section tagging

This commit is contained in:
SvyatoslavArtymovych 2023-05-17 18:39:37 +03:00
parent 5d9cde449e
commit c09bc4ee41
11 changed files with 212 additions and 17 deletions

View File

@ -98,3 +98,28 @@ def set_interpretation_tags(interpretation: m.InterpretationTag, tags: str):
log(log.INFO, "Create InterpretationTag: [%s]", interpretation_tag)
interpretation_tag.save(False)
db.session.commit()
def set_section_tags(section: m.SectionTag, tags: str):
section_tags = m.SectionTag.query.filter_by(section_id=section.id).all()
for tag in section_tags:
db.session.delete(tag)
tags_names = [tag.title() for tag in tags.split(",") if len(tag)]
for tag_name in tags_names:
try:
tag = get_or_create_tag(tag_name)
except ValueError as e:
if str(e) == "Exceeded name length":
continue
log(
log.CRITICAL,
"Unexpected error [%s]",
str(e),
)
raise e
section_tag = m.SectionTag(tag_id=tag.id, section_id=section.id)
log(log.INFO, "Create SectionTag: [%s]", section_tag)
section_tag.save(False)
db.session.commit()

View File

@ -9,6 +9,7 @@ from app.logger import log
class BaseSectionForm(FlaskForm):
label = StringField("Label", [DataRequired(), Length(3, 256)])
about = StringField("About")
tags = StringField("Tags")
class CreateSectionForm(BaseSectionForm):
@ -49,7 +50,12 @@ class EditSectionForm(BaseSectionForm):
label = field.data
section_id = self.section_id.data
collection_id = db.session.get(m.Section, section_id).collection_id
session = db.session.get(m.Section, section_id)
if not session:
log(log.WARNING, "Session with id [%s] not found", section_id)
raise ValidationError("Invalid session id")
collection_id = session.collection_id
section: m.Section = (
m.Section.query.filter_by(
is_deleted=False, label=label, collection_id=collection_id

View File

@ -14,3 +14,4 @@ from .tag import Tag
from .interpretation_tag import InterpretationTag
from .comment_tag import CommentTags
from .book_tag import BookTags
from .section_tag import SectionTag

View File

@ -22,6 +22,11 @@ class Section(BaseModel):
interpretations = db.relationship(
"Interpretation", viewonly=True, order_by="desc(Interpretation.id)"
)
tags = db.relationship(
"Tag",
secondary="section_tags",
back_populates="sections",
)
@property
def path(self):

13
app/models/section_tag.py Normal file
View File

@ -0,0 +1,13 @@
from app import db
from app.models.utils import BaseModel
class SectionTag(BaseModel):
__tablename__ = "section_tags"
# Foreign keys
tag_id = db.Column(db.Integer, db.ForeignKey("tags.id"))
section_id = db.Column(db.Integer, db.ForeignKey("sections.id"))
def __repr__(self):
return f"<t:{self.tag_id} to i:{self.section_id}"

View File

@ -8,6 +8,9 @@ class Tag(BaseModel):
name = db.Column(db.String(32), unique=True, nullable=False)
# Relationships
sections = db.relationship(
"Section", secondary="section_tags", back_populates="tags"
)
interpretations = db.relationship(
"Interpretation", secondary="interpretation_tags", back_populates="tags"
)

View File

@ -1,6 +1,6 @@
<!-- Add collection modal -->
<!-- prettier-ignore-->
<div id="add-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div id="add-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
@ -9,7 +9,9 @@
{% else %}
action="{{ url_for('book.section_create', book_id=book.id, collection_id=collection.id) }}"
{% endif %}
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
method="post"
class="prevent-submit-on-enter relative bg-white rounded-lg shadow dark:bg-gray-700"
>
{{ form_hidden_tag() }}
<input type="hidden" name="collection_id" id="collection_id" value="{{sub_collection.id if sub_collection else collection.id}}" />
<input type="hidden" name="about" id="new-section-input" />
@ -30,10 +32,25 @@
<div class="p-6 pt-0 space-y-6">
<div class="w-full max-w-6xl mx-auto rounded-xl bg-gray-50 dark:bg-gray-600 shadow-lg text-white-900">
<div class="overflow-hidden rounded-md bg-gray-50 [&>*]:dark:bg-gray-600 text-black [&>*]:!border-none [&>*]:!stroke-black dark:text-white dark:[&>*]:!stroke-white">
<div id="new-section" class="quill-editor dark:text-white h-80"></div>
<div id="new-section" class="quill-editor dark:text-white h-40"></div>
</div>
</div>
</div>
<div class="multiple-input-block mb-6 px-6 ">
<label for="tags-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Tags
</label>
<input type="text" name="tags" class="hidden tags-to-submit">
<input
type="text"
id="tags-input"
class="multiple-input mb-3 shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="e.g. Law (press 'Enter' or Comma to add tag. Click on tag to edit it)"
data-save-results-to="tags-to-submit"
>
<div class="multiple-input-items gap-1 flex flex-wrap">
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button name="submit" type="submit" class="text-white bg-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 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Create</button>

View File

@ -1,5 +1,5 @@
<!-- prettier-ignore-->
<div id="edit-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-50 hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div id="edit-section-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form
@ -8,7 +8,9 @@
{% else %}
action="{{ url_for('book.section_edit', book_id=book.id, collection_id=collection.id, section_id=section.id) }}"
{% endif %}
method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
method="post"
class="relative bg-white rounded-lg shadow dark:bg-gray-700"
>
{{ form_hidden_tag() }}
<input type="hidden" name="section_id" id="section_id" value="{{section.id}}" />
<input type="hidden" name="about" id="new-section-about-input" />
@ -30,12 +32,30 @@
<div class="p-6 pt-0 space-y-6">
<div class="w-full max-w-6xl mx-auto rounded-xl bg-gray-50 dark:bg-gray-600 shadow-lg text-white-900">
<div class="overflow-hidden rounded-md bg-gray-50 [&>*]:dark:bg-gray-600 text-black [&>*]:!border-none [&>*]:!stroke-black dark:text-white dark:[&>*]:!stroke-white">
<div id="new-section-about" class="quill-editor dark:text-white h-80">
<div id="new-section-about" class="quill-editor dark:text-white h-40">
{{ section.about|safe }}
</div>
</div>
</div>
</div>
<div class="multiple-input-block mb-6 px-6 ">
<label for="tags-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
Tags
</label>
<input type="text" name="tags" class="hidden tags-to-submit">
<input
type="text"
id="tags-input"
class="multiple-input mb-3 shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="e.g. Law (press 'Enter' or Comma to add tag. Click on tag to edit it)"
data-save-results-to="tags-to-submit"
>
<div class="multiple-input-items gap-1 flex flex-wrap">
{% for tag in section.tags %}
<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>
{% endfor %}
</div>
</div>
<!-- Modal footer -->
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button name="submit" type="submit" class="text-white bg-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 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>

View File

@ -18,6 +18,7 @@ from app.controllers.tags import (
set_book_tags,
set_comment_tags,
set_interpretation_tags,
set_section_tags,
)
from app import models as m, db, forms as f
from app.logger import log
@ -675,6 +676,10 @@ def section_create(
log(log.INFO, "Create section [%s]. Collection: [%s]", section, collection_id)
section.save()
tags = form.tags.data
if tags:
set_section_tags(section, tags)
flash("Success!", "success")
return redirect(redirect_url)
else:
@ -721,6 +726,10 @@ def section_edit(
if about:
section.about = about
tags = form.tags.data
if tags:
set_section_tags(section, tags)
log(log.INFO, "Edit section [%s]", section.id)
section.save()

View File

@ -0,0 +1,37 @@
"""section-tags
Revision ID: 7baa732e01c6
Revises: 0961578f302a
Create Date: 2023-05-17 18:34:29.178354
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7baa732e01c6'
down_revision = '0961578f302a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('section_tags',
sa.Column('tag_id', sa.Integer(), nullable=True),
sa.Column('section_id', sa.Integer(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('is_deleted', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['section_id'], ['sections.id'], ),
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('section_tags')
# ### end Alembic commands ###

View File

@ -5,7 +5,7 @@ from app import models as m, db
from tests.utils import login, create_test_book
def test_create_tags_on_book_create(client: FlaskClient):
def test_create_tags_on_book_create_and_edit(client: FlaskClient):
login(client)
BOOK_NAME = "Test Book"
@ -32,15 +32,6 @@ def test_create_tags_on_book_create(client: FlaskClient):
tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 3
def test_create_tags_on_book_edit(client: FlaskClient):
_, user = login(client)
book: m.Book = m.Book(label="Test book", user_id=user.id).save()
m.BookVersion(semver="1.0.0", book_id=book.id).save()
assert not book.tags
tags = "tag1,tag2,tag3"
client.post(
@ -201,3 +192,71 @@ def test_create_tags_on_interpretation_create_and_edit(client: FlaskClient):
tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 5
def test_create_tags_on_section_create_and_edit(client: FlaskClient):
_, user = login(client)
create_test_book(user.id, 1)
book: m.Book = db.session.get(m.Book, 1)
collection: m.Collection = db.session.get(m.Collection, 1)
section: m.Section = db.session.get(m.Section, 1)
tags = "tag1,tag2,tag3"
label_1 = "Test Interpretation #1 Label"
text_1 = "Test Interpretation #1 Text"
response: Response = client.post(
f"/book/{book.id}/{collection.id}/create_section",
data=dict(
collection_id=collection.id,
label=label_1,
about=text_1,
tags=tags,
),
follow_redirects=True,
)
assert response.status_code == 200
assert response.status_code == 200
section: m.Section = m.Section.query.filter_by(label=label_1).first()
assert section
assert section.tags
splitted_tags = [tag.title() for tag in tags.split(",")]
assert len(section.tags) == 3
for tag in section.tags:
tag: m.Tag
assert tag.name in splitted_tags
tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 3
tags = "tag-4,tag5,tag3"
response: Response = client.post(
f"/book/{book.id}/{collection.id}/{section.id}/edit_section",
data=dict(
section_id=section.id,
label=label_1,
about=text_1,
tags=tags,
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
assert response.status_code == 200
section: m.Section = m.Section.query.filter_by(label=label_1).first()
assert section
assert section.tags
splitted_tags = [tag.title() for tag in tags.split(",")]
assert len(section.tags) == 3
for tag in section.tags:
tag: m.Tag
assert tag.name in splitted_tags
tags_from_db: m.Tag = m.Tag.query.all()
assert len(tags_from_db) == 5