Merge branch 'svyat/feat/book_version' into svyat/feat/fork

This commit is contained in:
SvyatoslavArtymovych 2023-06-14 14:17:52 +03:00
commit be37971143
12 changed files with 162 additions and 152776 deletions

View File

@ -1,3 +1,5 @@
from flask_login import current_user
from app import models as m
from app.logger import log
@ -70,7 +72,10 @@ def create_new_version(book: m.Book, semver: str):
book_root_collection: m.Collection = book_active_version.root_collection
version: m.BookVersion = m.BookVersion(
semver=semver, derivative_id=book.active_version.id, book_id=book.id
semver=semver,
derivative_id=book.active_version.id,
book_id=book.id,
user_id=current_user.id,
)
log(log.INFO, "Create new version for book [%s]", book)
version.save()

View File

@ -52,7 +52,9 @@ class Book(BaseModel):
@property
def actual_versions(self):
versions = (
m.BookVersion.query.filter_by(is_deleted=False, book_id=self.id)
m.BookVersion.query.filter_by(
is_deleted=False, book_id=self.id, is_active=False
)
.order_by(m.BookVersion.created_at.asc())
.all()
)

View File

@ -16,8 +16,10 @@ class BookVersion(BaseModel):
# Foreign keys
derivative_id = db.Column(db.Integer, db.ForeignKey("book_versions.id"))
book_id = db.Column(db.Integer, db.ForeignKey("books.id"))
user_id = db.Column(db.ForeignKey("users.id"))
# Relationships
user = db.relationship("User", viewonly=True)
book = db.relationship("Book", viewonly=True)
derivative = db.relationship("BookVersion", remote_side=[id])
sections = db.relationship("Section", viewonly=True, order_by="desc(Section.id)")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -115,62 +115,92 @@
{% endfor %}
</div>
<!-- Here start book-preview on right side-->
<div class="p-3 px-6 w-4/6 dark:text-white overflow-y-scroll h-box ">
<p class="text-xs mb-3">Created by <a href="{{url_for('user.profile',user_id=book.owner.id)}}" class=" text-blue-500 {% if book.owner.is_deleted %}line-through{% endif %}">{{book.owner.username}}</a> on {{book.created_at.strftime('%B %d, %Y')}}. Last updated on {{book.created_at.strftime('%B %d, %Y')}}</p>
<div class="p-3 px-6 w-4/6 dark:text-white overflow-y-scroll h-box">
<p class="text-xs mb-3">
Created by
<a
href="{{url_for('user.profile',user_id=book.owner.id)}}"
class="text-blue-500 {% if book.owner.is_deleted %}line-through{% endif %}"
>{{book.owner.username}}</a
>
on {{book.created_at.strftime('%B %d, %Y')}}. Last updated on
{{book.created_at.strftime('%B %d, %Y')}}
</p>
{% if version %}
<div class="my-2 select-none bg-sky-300 dark:bg-blue-600 dark:text-white rounded text-center py-1/2 px-2">
{% if version %}
<p class="flex text-s font-bold mb-3">
<!-- prettier-ignore -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-3"> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /> </svg>
Version: {{ version.semver.title() }}
</div>
{% endif %}
</p>
{% endif %}
<div class="flex justify-between item-center">
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white">{{book.label}}</h1>
<div class="ml-auto flex">
<div class="space-x-0.5 mr-3">
<a href="{{ url_for('book.statistic_view', book_id=book.id ) }}" class="flex items-center">
<svg class="w-4 h-4 inline-flex mr-1 {% if book.current_user_has_star %}fill-yellow-300{% endif %}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" /> </svg>
{{ book.stars|length }}</a>
</div>
<div class="space-x-0.5 mr-3">
<a href="{{ url_for('book.statistic_view', book_id=book.id,active_tab='contributors' ) }}" class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
{{ book.contributors|length }}</a>
</div>
<div class="space-x-0.5 mr-3">
<a href="{{ url_for('book.statistic_view', book_id=book.id,active_tab='forks' ) }}" class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" /> </svg>
{{ book.versions|length }}</a>
<div class="flex justify-between item-center">
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white">
{{book.label}}
</h1>
<div class="ml-auto flex">
<div class="space-x-0.5 mr-3">
<!-- prettier-ignore -->
<a href="{{ url_for('book.statistic_view', book_id=book.id ) }}" class="flex items-center"> <svg class="w-4 h-4 inline-flex mr-1 {% if book.current_user_has_star %}fill-yellow-300{% endif %}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" /> </svg>
{{ book.stars|length }}</a
>
</div>
<div class="space-x-0.5 mr-3">
<!-- prettier-ignore -->
<a href="{{ url_for('book.statistic_view', book_id=book.id,active_tab='contributors' ) }}" class="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
{{ book.contributors|length }}</a
>
</div>
<div class="space-x-0.5 mr-3">
<!-- prettier-ignore -->
<a href="{{ url_for('book.statistic_view', book_id=book.id,active_tab='version' ) }}" class="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /> </svg>
{{ book.actual_versions|length }}</a
>
</div>
<div class="space-x-0.5 mr-3">
<!-- prettier-ignore -->
<a href="{{ url_for('book.statistic_view', book_id=book.id,active_tab='forks' ) }}" class="flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" /> </svg>
{{ book.forks|length }}</a
>
</div>
</div>
</div>
</div>
<div class="gap-1 flex flex-wrap">
{% for tag in book.tags %}
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag.name)}}"><div class="cursor-pointer multiple-input-word bg-sky-300 hover:bg-sky-400 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white rounded text-center py-1/2 px-2">{{tag.name}}</div></a>
{% endfor %}
</div>
<div class="gap-1 flex flex-wrap">
{% for tag in book.tags %}
<a
href="{{url_for('search.tag_search_interpretations',tag_name=tag.name)}}"
><div
class="cursor-pointer multiple-input-word bg-sky-300 hover:bg-sky-400 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white rounded text-center py-1/2 px-2">
{{tag.name}}
</div></a
>
{% endfor %}
</div>
<p class=" text-sm mb-3">{% if book.about==None %}About text{% else %}{{book.about}}{% endif %}</p>
{% if version %}
{% set children_collections = version.children_collections %}
{% else %}
<p class="text-sm mb-3">
{% if book.about==None %}About text{% else %}{{book.about}}{% endif %}
</p>
<!-- prettier-ignore -->
{% if version %}
{% set children_collections = version.children_collections%}
{% else %}
{% set children_collections = book.active_version.children_collections %}
{% endif %}
{% for collection in children_collections if not collection.is_root and not collection.is_deleted %}
<p class="my-3 underline" id="collection-{{collection.label}}">#{{collection.label}}</p>
{% endif %} {% for collection in children_collections if not collection.is_root and not collection.is_deleted %}
<p class="my-3 underline" id="collection-{{collection.label}}">
#{{collection.label}}
</p>
{% if not collection.active_sections and not collection.active_children %}
<p class="ml-3 my-3 italic text-sm">Collection is empty</p>
<p class="ml-3 my-3 italic text-sm">Collection is empty</p>
{% endif %}
{{recursive_render("book/components/sub_collection_preview_content.html",collection,book,version)|safe}}
{% endfor %}
{% endfor %}
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->
{% block scripts %}
{% endblock %}
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->
{% block scripts %}
{% endblock %}

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">{{book.label}}</h1>
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 my-2">{{book.label}}</h1>
<!-- prettier-ignore -->
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> {{book.about}} </p>
<!-- prettier-ignore -->
@ -22,6 +22,12 @@
<button class="inline-flex p-4 mrounded-t-lg hover:text-gray-600 dark:hover:text-gray-300" id="contributors-tab" data-tabs-target="#contributors" type="button" role="tab" aria-controls="contributors" {% if active_tab=='contributors' %}aria-selected="true"{% endif %}><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-3"> <path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
Contributors
</button>
</li>
<li class="mr-2 w-full md:w-auto" role="presentation">
<!-- prettier-ignore -->
<button class="inline-flex p-4 mrounded-t-lg hover:text-gray-600 dark:hover:text-gray-300" id="version-tab" data-tabs-target="#version" type="button" role="tab" aria-controls="version" {% if active_tab=='version' %}aria-selected="true"{% endif %}><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 mr-3"> <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z" /> </svg>
Released versions
</button>
</li>
<li class="mr-2 w-full md:w-auto" role="presentation">
<!-- prettier-ignore -->
@ -64,10 +70,37 @@
</tbody>
</table>
</div>
</div>
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="version" role="tabpanel" aria-labelledby="version-tab">
{% for version in book.actual_versions %}
<dl class=" bg-white dark:bg-gray-900 max-w-full p-5 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<dt class="mb-2"><a class="flex flex-col pb-4" href="{{url_for('book.collection_view',book_id=book.id,version_index=loop.index)}}">{{version.semver}}</a></dt>
<dd class="flex flex-col md:flex-row text-lg font-semibold text-gray-500 md:text-lg dark:text-gray-400">
{% if book.versions %}
<p> Created by <a href="{{url_for('user.profile',user_id=version.user.id)}}" class=" text-blue-500 {% if book.owner.is_deleted %}line-through{% endif %}">{{version.book.owner.username}}</a> on {{version.created_at.strftime('%B %d, %Y')}} </p>
{% endif %}
<div class="flex ml-auto align-center justify-center space-x-3">
<span class="book-star-block space-x-0.5 flex items-center">
<svg class="star-btn cursor-pointer w-4 h-4 inline-flex mr-1 {% if book.current_user_has_star %}fill-yellow-300{% endif %}" data-book-id={{ book.id }} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z" /> </svg>
<span class="total-stars">{{ book.stars|length }}</span>
</span>
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /> </svg>
<p>{{ book.interpretations|length }}</p>
</span>
<span class="space-x-0.5 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22" stroke-width="1" stroke="currentColor" class="w-4 h-4 inline-flex mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /> </svg>
<p>{{ book.approved_comments|length }}</p>
</span>
</div>
</dd>
</dl>
{% endfor %}
</div>
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="fork" role="tabpanel" aria-labelledby="fork-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">Fork sections</p>
<button type="button" class="text-blue-700 hover:text-white border border-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800">Create a new fork</button>
{#{% EMPTY FOR FOR FORKS %}#}
</div>
</div>
{% endblock %}

View File

@ -24,7 +24,7 @@ def collection_view(book_id: int, version_index: int = None):
actual_versions = book.actual_versions
if version_index == 0 or len(actual_versions) < version_index:
return redirect(url_for("book.collection_view", book_id=book_id))
version = actual_versions[version_index]
version = actual_versions[version_index - 1]
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")

View File

@ -0,0 +1,34 @@
"""version_user_id
Revision ID: a8184a265190
Revises: ef2254f9bc92
Create Date: 2023-06-14 14:04:38.690202
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a8184a265190'
down_revision = 'ef2254f9bc92'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('book_versions', schema=None) as batch_op:
batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True))
batch_op.create_foreign_key(None, 'users', ['user_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('book_versions', schema=None) as batch_op:
batch_op.drop_constraint(None, type_='foreignkey')
batch_op.drop_column('user_id')
# ### end Alembic commands ###

View File

@ -5,7 +5,6 @@ from app import models as m
from tests.utils import (
login,
logout,
create_test_book,
create_book,
create_collection,
create_section,

View File

@ -1,7 +1,7 @@
from flask import current_app as Response
from flask.testing import FlaskClient
from app import models as m, db
from app import models as m
from tests.utils import (
create_book,
create_collection,
@ -9,7 +9,6 @@ from tests.utils import (
create_interpretation,
create_section,
login,
create_test_book,
)

View File

@ -221,7 +221,6 @@ def test_create_version(client):
login(client)
book: m.Book = create_book(client)
book_2: m.Book = create_book(client)
logout(client)
login(client, "test_2")