Merge pull request #53 from Simple2B/svyat/feat/book_stats

Svyat/feat/book_stats
This commit is contained in:
Костя Столярский 2023-05-17 16:38:50 +03:00 committed by GitHub
commit dd05759b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 182 additions and 15 deletions

View File

@ -1,4 +1,5 @@
from flask_login import current_user
from sqlalchemy import and_
from app import db, models as m
from app.models.utils import BaseModel
@ -17,7 +18,7 @@ class Book(BaseModel):
owner = db.relationship("User", viewonly=True)
stars = db.relationship("User", secondary="books_stars", back_populates="stars")
contributors = db.relationship("BookContributor")
versions = db.relationship("BookVersion")
versions = db.relationship("BookVersion", order_by="asc(BookVersion.id)")
def __repr__(self):
return f"<{self.id}: {self.label}>"
@ -34,3 +35,55 @@ class Book(BaseModel):
).first()
if book_star:
return True
@property
def approved_comments(self):
comments = (
db.session.query(
m.Comment,
)
.filter(
and_(
m.BookVersion.id == self.last_version.id,
m.Section.version_id == m.BookVersion.id,
m.Collection.id == m.Section.collection_id,
m.Interpretation.section_id == m.Section.id,
m.Comment.interpretation_id == m.Interpretation.id,
m.Comment.approved.is_(True),
m.Comment.is_deleted.is_(False),
m.BookVersion.is_deleted.is_(False),
m.Interpretation.is_deleted.is_(False),
m.Section.is_deleted.is_(False),
m.Collection.is_deleted.is_(False),
),
)
.order_by(m.Comment.created_at.desc())
.all()
)
return comments
@property
def approved_interpretations(self):
interpretations = (
db.session.query(
m.Interpretation,
)
.filter(
and_(
m.BookVersion.id == self.last_version.id,
m.Section.version_id == m.BookVersion.id,
m.Collection.id == m.Section.collection_id,
m.Interpretation.section_id == m.Section.id,
m.Interpretation.approved.is_(True),
m.BookVersion.is_deleted.is_(False),
m.Interpretation.is_deleted.is_(False),
m.Section.is_deleted.is_(False),
m.Collection.is_deleted.is_(False),
),
)
.order_by(m.Interpretation.created_at.desc())
.all()
)
return interpretations

View File

@ -1,5 +1,3 @@
from datetime import datetime
from flask_login import current_user
from app import db, models as m
@ -13,7 +11,6 @@ class Interpretation(BaseModel):
text = db.Column(db.Text, unique=False, nullable=False)
approved = db.Column(db.Boolean, default=False)
marked = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.now)
# Foreign keys
user_id = db.Column(db.ForeignKey("users.id"))

View File

@ -25,11 +25,11 @@
</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>55</p>
<p>{{ book.approved_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>55</p>
<p>{{ book.approved_comments|length }}</p>
</span>
</div>
</dd>

View File

@ -36,11 +36,11 @@
</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>55</p>
<p>{{ book.approved_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>55</p>
<p>{{ book.approved_comments|length }}</p>
</span>
</div>
</dd>

View File

@ -114,11 +114,11 @@
</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>55</p>
<p>{{ book.approved_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>55</p>
<p>{{ book.approved_comments|length }}</p>
</span>
</div>
</dd>

View File

@ -14,6 +14,7 @@
<!-- 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-10 h-10"> <path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" /> </svg>
{% endif %}
<!-- prettier-ignore -->
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4">{{user.username}}</h1>
<!-- prettier-ignore -->
@ -33,7 +34,6 @@
<li class="mr-2 w-full md:w-auto" role="presentation">
<!-- prettier-ignore -->
<button class="inline-flex p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="contributions-tab" data-tabs-target="#contributions" type="button" role="tab" aria-controls="contributions" aria-selected="false"><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="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" /> </svg>
Contributions
</button>
</li>
@ -59,11 +59,11 @@
</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>55</p>
<p>{{ book.approved_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>55</p>
<p>{{ book.approved_comments|length }}</p>
</span>
</div>
</dd>

View File

@ -1071,8 +1071,11 @@ def test_crud_comment(client: FlaskClient, runner: FlaskCliRunner):
def test_access_to_settings_page(client: FlaskClient):
_, user = login(client)
book_1 = m.Book(label="test", about="test").save()
book_1 = m.Book(label="test", about="test", user_id=user.id).save()
m.BookVersion(semver="1.0.0", book_id=book_1.id).save()
book_2 = m.Book(label="test", about="test", user_id=user.id).save()
m.BookVersion(semver="1.0.0", book_id=book_2.id).save()
response: Response = client.get(
f"/book/{book_1.id}/settings",
@ -1080,7 +1083,6 @@ def test_access_to_settings_page(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
response: Response = client.get(
f"/book/{book_2.id}/settings",

View File

@ -0,0 +1,114 @@
from flask.testing import FlaskClient
from app import models as m
from tests.utils import login, create_test_book
def test_approved_interpretations(client: FlaskClient):
_, user = login(client)
create_test_book(user.id)
dummy_user = m.User(username="Bob").save()
create_test_book(dummy_user.id)
book: m.Book = m.Book.query.first()
assert len(book.approved_interpretations) == 0
for interpretation in m.Interpretation.query.all():
interpretation.approved = True
interpretation.save()
assert len(book.approved_interpretations) == 1
section: m.Section = m.Section.query.first()
assert section
interpretation: m.Interpretation = m.Interpretation(
section_id=section.id, label="231", text="123", approved=True
).save()
assert len(book.approved_interpretations) == 2
interpretation.is_deleted = True
interpretation.save()
assert len(book.approved_interpretations) == 1
collection: m.Collection = m.Collection.query.first()
sub_collection: m.Collection = m.Collection(
parent_id=collection.id,
label="123",
).save()
section: m.Section = m.Section(
label="123", collection_id=sub_collection.id, version_id=book.last_version.id
).save()
interpretation: m.Interpretation = m.Interpretation(
section_id=section.id, label="231", text="123", approved=True
).save()
assert len(book.approved_interpretations) == 2
sub_collection.is_deleted = True
sub_collection.save()
assert len(book.approved_interpretations) == 1
sub_collection.is_deleted = False
sub_collection.save()
assert len(book.approved_interpretations) == 2
# collection.is_deleted = True
# collection.save()
# assert len(book.approved_interpretations) == 0
def test_approved_comments(client: FlaskClient):
_, user = login(client)
create_test_book(user.id)
dummy_user = m.User(username="Bob").save()
create_test_book(dummy_user.id)
book: m.Book = m.Book.query.first()
assert len(book.approved_comments) == 0
for comment in m.Comment.query.all():
comment.approved = True
comment.save()
assert len(book.approved_comments) == 1
interpretation: m.Interpretation = m.Interpretation.query.first()
assert interpretation
comment: m.Comment = m.Comment(
text="231", approved=True, interpretation_id=interpretation.id
).save()
assert len(book.approved_comments) == 2
comment.is_deleted = True
comment.save()
assert len(book.approved_comments) == 1
comment: m.Comment = m.Comment(
text="456", approved=True, interpretation_id=interpretation.id
).save()
assert len(book.approved_comments) == 2
interpretation.is_deleted = True
interpretation.save()
assert len(book.approved_comments) == 0
interpretation.is_deleted = False
interpretation.save()
assert len(book.approved_comments) == 2
collection: m.Collection = m.Collection.query.first()
collection.is_deleted = True
collection.save()
interpretation.is_deleted = False
interpretation.save()
assert len(book.approved_comments) == 0

View File

@ -120,6 +120,7 @@ def test_profile(client):
user_id=user.id,
)
book.save()
m.BookVersion(semver="1.0.0", book_id=book.id).save()
assert book
# profile page