Merge branch 'develop' into svyat/feat/user_menu_on_header

This commit is contained in:
SvyatoslavArtymovych 2023-05-02 10:59:20 +03:00
commit dcbabf8fbc
20 changed files with 517 additions and 203 deletions

View File

@ -1,2 +1,3 @@
# flake8: noqa F401
from .pagination import create_pagination
from .breadcrumbs import create_breadcrumbs

View File

@ -0,0 +1,131 @@
from flask import url_for
from flask_login import current_user
from app import models as m, db
from app import schema as s
def create_breadcrumbs(
book_id: int,
collection_path: tuple[int],
section_id: int = 0,
interpretation_id: int = 0,
) -> list[s.BreadCrumb]:
"""
How breadcrumbs look like:
Book List -> Book Name -> Top Level Collection -> SubCollection -> Section -> Interpretation
- If i am not owner of a book
John's books -> Book Name -> Top Level Collection -> SubCollection -> Section -> Interpretation
- If i am owner
My Books -> Book Title -> Part I -> Chapter X -> Paragraph 1.7 -> By John
"""
crumples: list[s.BreadCrumb] = []
book: m.Book = db.session.get(m.Book, book_id)
if current_user.is_authenticated and book.user_id == current_user.id:
# My Book
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.MyBookList,
url=url_for("book.my_books"),
label="My Books",
)
]
else:
# Not mine book
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.AuthorBookList,
url="#",
label=book.owner.username + "'s books",
)
]
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Collection,
url=url_for("book.collection_view", book_id=book_id),
label=book.label,
)
]
for collection_id in collection_path:
if collection_id is None:
continue
collection: m.Collection = db.session.get(m.Collection, collection_id)
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Collection,
url=url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
),
label=collection.label,
)
]
if section_id and collection_path:
section: m.Section = db.session.get(m.Section, section_id)
if len(collection_path) == 2:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Section,
url=url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[-1],
),
label=section.label,
)
]
else:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Section,
url=url_for(
"book.section_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[0],
),
label=section.label,
)
]
if interpretation_id:
interpretation: m.Interpretation = db.session.get(
m.Interpretation, interpretation_id
)
if len(collection_path) == 2:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Interpretation,
url=url_for(
"book.interpretation_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[-1],
section_id=section_id,
),
label=interpretation.label,
)
]
else:
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Interpretation,
url=url_for(
"book.interpretation_view",
book_id=book_id,
collection_id=collection_path[0],
sub_collection_id=collection_path[0],
section_id=section_id,
),
label=interpretation.label,
)
]
return crumples

View File

@ -1,9 +1,7 @@
# flake8: noqa F401
from .auth import LoginForm
from .user import UserForm, NewUserForm
from .book import (
CreateBookForm,
)
from .book import CreateBookForm, EditBookForm
from .contributor import (
AddContributorForm,
DeleteContributorForm,

View File

@ -1,8 +1,40 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms import StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired, Length
from app import models as m, db
from app.logger import log
class CreateBookForm(FlaskForm):
class BaseBookForm(FlaskForm):
label = StringField("Label", [DataRequired(), Length(6, 256)])
class CreateBookForm(BaseBookForm):
submit = SubmitField("Add new book")
class EditBookForm(BaseBookForm):
book_id = StringField("User ID", [DataRequired()])
submit = SubmitField("Edit book")
def validate_book_id(self, field):
book_id = field.data
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
raise ValidationError("Book not found")
def validate_label(self, field):
label = field.data
book_id = self.book_id.data
existing_book: m.Book = m.Book.query.filter_by(
is_deleted=False,
label=label,
).first()
if existing_book:
log(
log.WARNING, "Book with label [%s] already exists: [%s]", label, book_id
)
raise ValidationError("Book label must be unique!")

View File

@ -1,3 +1,4 @@
# flake8: noqa F401
from .pagination import Pagination
from .user import User
from .breadcrumbs import BreadCrumbType, BreadCrumb

21
app/schema/breadcrumbs.py Normal file
View File

@ -0,0 +1,21 @@
import enum
from pydantic import BaseModel
class BreadCrumbType(enum.StrEnum):
"""Bread Crumb Type"""
MyBookList = "MyBookList"
AuthorBookList = "AuthorBookList"
Collection = "Collection"
Section = "Section"
Interpretation = "Interpretation"
class BreadCrumb(BaseModel):
"""Bread Crumb for navigation"""
label: str
url: str
type: BreadCrumbType

File diff suppressed because one or more lines are too long

View File

@ -22,9 +22,6 @@
<button id="search-btn" class="cursor-pointer select-none text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Search</button>
</div>
</div>
<div class="col-span-6 sm:col-span-3 overflow-x-auto shadow-md sm:rounded-lg" id="search-results">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400">
<tbody id="search-results-tbody">
@ -38,9 +35,7 @@
</tbody>
</table>
</div>
</div>
<div>
<label for="role" class="mb-2 block text-sm font-medium text-gray-900 dark:text-white">Select an option</label >
<select id="role" name="role" class="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">

View File

@ -0,0 +1,21 @@
<!-- prettier-ignore -->
<nav class="flex mt-5 mb-3" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 md:space-x-3 ml-5">
{% for breadcrumb in breadcrumbs %}
<li class="inline-flex items-center">
<a href="{{ breadcrumb.url }}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white">
<!-- prettier-ignore -->
<!--svg for all types of breadcrumb-->
{% if breadcrumb.type == "MyBookList" or breadcrumb.type == "AuthorBookList" %}
<svg aria-hidden="true" class="flex-shrink-0 w-4 h-4 mr-2 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path></svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex-shrink-0 w-4 h-4 mr-2 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25" /> </svg>
{% endif %}
<!-- prettier-ignore -->
{{ breadcrumb.label }}
</a>
<svg aria-hidden="true" class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path> </svg>
</li>
{% endfor %}
</ol>
</nav>

View File

@ -18,13 +18,8 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{ book.label }}</h1>
</div>
</div>
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">Collections page</h1>
<!-- prettier-ignore -->
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
@ -43,16 +38,11 @@
</ul>
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<dl
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for collection in book.versions[-1].children_collections if not collection.is_root and not collection.is_deleted %}
<!-- prettier-ignore -->
<a href="{{url_for('book.sub_collection_view',book_id=book.id,collection_id=collection.id)}}" >
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 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">
@ -74,7 +64,6 @@
</div>
</dl>
</a >
{% endfor %}
</dl>
</div>

View File

@ -15,14 +15,10 @@
{% for book in books %}
<!-- prettier-ignore -->
<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">
<a
class="flex flex-col pb-2"
href="{{url_for('book.collection_view',book_id=book.id)}}">
<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">
<a class="flex flex-col pb-2" href="{{url_for('book.collection_view',book_id=book.id)}}">
<dt class="mb-2">{{book.owner.username}}/{{book.label}}</dt>
<dd
class="flex flex-col md:flex-row text-lg font-semibold text-gray-500 md:text-lg dark:text-gray-400">
<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>
Last updated on {{book.versions[-1].updated_at.strftime('%B %d, %Y')}}
@ -30,48 +26,15 @@
{% endif %}
<div class="flex ml-auto align-center justify-center space-x-3">
<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="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>
<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="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>
<p>{{ book.stars|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="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
</svg>
<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>
</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>
<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>
</span>
</div>
@ -79,6 +42,60 @@
</a>
</dl>
{% endfor %}
<!-- prettier-ignore -->
{% if page.pages > 1 %}
<div class="container content-center mt-3 flex bg-white dark:bg-gray-800">
<nav aria-label="Page navigation example" class="mx-auto">
<ul class="inline-flex items-center -space-x-px">
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page=1&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">First</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M15.79 14.77a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L11.832 10l3.938 3.71a.75.75 0 01.02 1.06zm-6 0a.75.75 0 01-1.06.02l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 111.04 1.08L5.832 10l3.938 3.71a.75.75 0 01.02 1.06z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.page-1 if page.page > 1 else 1}}&q={{page.query}}" class="block px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<span class="sr-only">Previous</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<!-- prettier-ignore -->
{% for p in page.pages_for_links %}
<li>
<!-- prettier-ignore -->
{% if p == page.page %}
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{p}}&q={{page.query}}" aria-current="page" class="z-10 px-3 py-2 leading-tight text-blue-600 border border-blue-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white">{{p}}</a>
{% else %}
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{p}}&q={{page.query}}" class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{{p}}</a>
{% endif %}
</li>
{% endfor %}
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.page+1 if page.page < page.pages else page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Next</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
<li>
<!-- prettier-ignore -->
<a href="{{ url_for('book.my_books') }}?page={{page.pages}}&q={{page.query}}" class="block px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">
<!-- prettier-ignore -->
<span class="sr-only">Last</span>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path fill-rule="evenodd" d="M10.21 14.77a.75.75 0 01.02-1.06L14.168 10 10.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M4.21 14.77a.75.75 0 01.02-1.06L8.168 10 4.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> </svg>
</a>
</li>
</ul>
</nav>
</div>
{% endif %}
</div>
{% include 'user/add.html' %}
<!-- prettier-ignore -->

View File

@ -2,19 +2,11 @@
{% extends 'base.html' %}
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">
Interpretations page
</h1>
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4">
{{ book.owner.username }}/{{ book.label }}
</h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">
{{collection.label}}/{{sub_collection.label}}/{{section.label}}
</h1>
</div>
</div>
<!-- prettier-ignore -->
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
@ -32,7 +24,11 @@
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for interpretation in section.interpretations %}
<a href="{{url_for('book.interpretation_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id, section_id=section.id)}}" >
{% if sub_collection %}
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id)}}" >
{% else %}
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, section_id=section.id)}}" >
{% endif %}
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 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">
<div class="flex flex-col pb-3 p-3 w-full">
<!-- prettier-ignore -->

View File

@ -1,6 +1,5 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% if book.owner.id == current_user.id %}
<!-- show edit collection btn on rightside bar -->
{% set show_edit_collection = True %}
@ -19,24 +18,21 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }}/{{ book.label }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{collection.label}}/{{sub_collection.label}}</h1>
</div>
</div>
<!-- prettier-ignore -->
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">Sections page</h1>
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<!-- prettier-ignore -->
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="files-tab" data-tabs-target="#files" type="button" role="tab" aria-controls="files" aria-selected="false">
<!-- 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"> <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.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Files</span>
</button>
</li>
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="about-tab" data-tabs-target="#about" type="button" role="tab" aria-controls="about" 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"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" /> </svg>
<span>About</span>
@ -46,18 +42,18 @@
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<dl
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for section in sub_collection.sections %}
{% for section in sections %}
<!-- prettier-ignore -->
<a href="{{url_for('book.interpretation_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id, section_id=section.id)}}">
{% if sub_collection %}
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, sub_collection_id=sub_collection.id, section_id=section.id)}}">
{% else %}
<a href="{{url_for('book.interpretation_view', book_id=book.id, collection_id=collection.id, section_id=section.id)}}">
{% endif %}
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 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">
<div class="flex flex-col pb-3 p-3 w-full">
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
@ -68,7 +64,6 @@
<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>
</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>
@ -78,18 +73,11 @@
</div>
</dl>
</a>
{% endfor %}
</dl>
</div>
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="about"
role="tabpanel"
aria-labelledby="about-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">
This is about of {{sub_collection.label}}
</p>
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="about" role="tabpanel" aria-labelledby="about-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">This is about</p>
</div>
</div>

View File

@ -7,6 +7,24 @@
<!-- prettier-ignore -->
{% block right_sidebar %} {% endblock %}
<div class="p-5">
<div class="flex justify-between ml-4 mb-3">
<h1 class="text-2xl font-extrabold dark:text-white">Settings</h1>
</div>
<form action="{{ url_for('book.edit', book_id=book.id) }}" method="post" class="mb-0 flex flex-col space-y-2 w-1/2">
{{ form_hidden_tag() }}
<input value="{{book.id}}" type="text" name="book_id" id="book_id" class="hidden" placeholder="Book id" required>
<div>
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Label</label>
<input value="{{book.label}}" type="text" name="label" id="label" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="My Book" required>
</div>
<div>
<button type="submit" class="text-center 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-4 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
</div>
</form>
</div>
<div class="p-5">
<div class="flex justify-between ml-4 mb-2">
<h1 class="text-2xl font-extrabold dark:text-white">Contributors</h1>

View File

@ -22,24 +22,23 @@
{% block content %}
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
<!-- prettier-ignore -->
<div class="flex p-2">
<div>
<h1 class="text-l font-extrabold dark:text-white ml-4"> {{ book.owner.username }}/{{ book.label }} </h1>
<h1 class="text-2xl font-extrabold dark:text-white ml-4">{{collection.label}}</h1>
</div>
</div>
<!-- prettier-ignore -->
{% include 'book/breadcrumbs_navigation.html'%}
<h1 class="text-l font-extrabold dark:text-white ml-4">
Sub collections page
</h1>
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
<!-- prettier-ignore -->
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center" id="myTab" data-tabs-toggle="#myTabContent" role="tablist">
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 rounded-t-lg" id="files-tab" data-tabs-target="#files" type="button" role="tab" aria-controls="files" 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"> <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.25m5.231 13.481L15 17.25m-4.5-15H5.625c-.621 0-1.125.504-1.125 1.125v16.5c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9zm3.75 11.625a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" /> </svg>
<span>Files</span>
</button>
</li>
<li class="mr-2" role="presentation">
<!-- prettier-ignore -->
<button class="flex items-center space-x-2 p-4 border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300" id="about-tab" data-tabs-target="#about" type="button" role="tab" aria-controls="about" 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"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z" /> </svg>
<span>About</span>
@ -49,22 +48,17 @@
</div>
<div id="myTabContent">
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="files"
role="tabpanel"
aria-labelledby="files-tab">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="files" role="tabpanel" aria-labelledby="files-tab">
<!-- prettier-ignore -->
<dl class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
<!-- prettier-ignore -->
{% for sub_collection in collection.children %}
<!-- prettier-ignore -->
<a href="{{url_for('book.section_view',book_id=book.id,collection_id=collection.id,sub_collection_id=sub_collection.id)}}">
<dl class="bg-white dark:bg-gray-900 max-w-full p-3 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">
<div class="flex flex-col pb-3 p-3 w-full">
<dt
class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<dt class="flex w-full mb-1 text-gray-500 md:text-lg dark:text-gray-400 flex-col">
<p>{{ sub_collection.label }}</p>
<div class="ml-auto">
<!-- prettier-ignore -->
@ -80,17 +74,13 @@
{% endfor %}
</dl>
</div>
<div
class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800"
id="about"
role="tabpanel"
aria-labelledby="about-tab">
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="about" role="tabpanel" aria-labelledby="about-tab">
<p class="text-sm text-gray-500 dark:text-gray-400">
This is about of {{collection.about}}
</p>
</div>
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->

View File

@ -65,11 +65,11 @@
</div>
<!-- prettier-ignore -->
<div class="hidden p-4 rounded-lg bg-gray-50 dark:bg-gray-800" id="all-sections" role="tabpanel" aria-labelledby="all-sections-tab">
{% for section in sections %}
<!-- prettier-ignore -->
<h1 class="text-l font-extrabold dark:text-white my-2">{{section.path}}</h1>
{% for section in sections %}
<!-- prettier-ignore -->
<h1 class="text-l font-extrabold dark:text-white my-2">{{section.path}}</h1>
{% endfor %}
<a type="button" href="{{ url_for('section.get_all') }}" class="mt-4 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 inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> Explore all sections... <svg aria-hidden="true" class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> </a>
<a type="button" href="#" class="mt-4 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 inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> Explore all sections... <svg aria-hidden="true" class="w-5 h-5 ml-2 -mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> </a>
</div>
</div>
{% endblock %}

View File

@ -35,17 +35,8 @@
<a
href="{{ url_for('user.get_all') }}"
class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">
<svg
aria-hidden="true"
class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
<!-- prettier-ignore -->
<svg aria-hidden="true" class="flex-shrink-0 w-6 h-6 text-gray-500 transition duration-75 dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg>
<span class="flex-1 ml-3 whitespace-nowrap">Users</span>
</a>
</li>

View File

@ -8,7 +8,7 @@ from flask import (
)
from flask_login import login_required, current_user
from app.controllers import create_pagination
from app.controllers import create_pagination, create_breadcrumbs
from app import models as m, db, forms as f
from app.logger import log
@ -76,81 +76,75 @@ def create():
return redirect(url_for("book.my_books"))
@bp.route("/<int:book_id>", methods=["GET"])
@bp.route("/<int:book_id>/edit", methods=["POST"])
@login_required
def edit(book_id: int):
form = f.EditBookForm()
if form.validate_on_submit():
book: m.Book = db.session.get(m.Book, book_id)
label = form.label.data
book.label = label
log(log.INFO, "Update Book: [%s]", book)
book.save()
flash("Success!", "success")
return redirect(url_for("book.collection_view", book_id=book_id))
else:
log(log.ERROR, "Book create errors: [%s]", form.errors)
for field, errors in form.errors.items():
field_label = form._fields[field].label.text
for error in errors:
flash(error.replace("Field", field_label), "danger")
return redirect(url_for("book.settings", book_id=book_id))
@bp.route("/<int:book_id>/collections", methods=["GET"])
def collection_view(book_id: int):
book = db.session.get(m.Book, book_id)
breadcrumbs = create_breadcrumbs(book_id=book_id, collection_path=())
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
else:
return render_template("book/collection_view.html", book=book)
return render_template(
"book/collection_view.html", book=book, breadcrumbs=breadcrumbs
)
@bp.route("/<int:book_id>/<int:collection_id>", methods=["GET"])
@bp.route("/<int:book_id>/<int:collection_id>/subcollections", methods=["GET"])
def sub_collection_view(book_id: int, collection_id: int):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
breadcrumbs = create_breadcrumbs(book_id=book_id, collection_path=(collection.id,))
if collection.is_leaf:
return render_template(
"book/section_view.html",
book=book,
collection=collection,
sub_collection=collection,
)
else:
return render_template(
"book/sub_collection_view.html", book=book, collection=collection
)
@bp.route("/<int:book_id>/<int:collection_id>/<int:sub_collection_id>", methods=["GET"])
def section_view(book_id: int, collection_id: int, sub_collection_id: int):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("Sub_collection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view", book_id=book_id, collection_id=collection_id
)
url_for("book.section_view", book_id=book.id, collection_id=collection.id)
)
else:
return render_template(
"book/section_view.html",
"book/sub_collection_view.html",
book=book,
collection=collection,
sub_collection=sub_collection,
breadcrumbs=breadcrumbs,
)
@bp.route("/<int:book_id>/<int:collection_id>/sections", methods=["GET"])
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>",
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/sections",
methods=["GET"],
)
def interpretation_view(
book_id: int, collection_id: int, sub_collection_id: int, section_id: int
def section_view(
book_id: int, collection_id: int, sub_collection_id: int | None = None
):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
@ -164,15 +158,81 @@ def interpretation_view(
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("Sub_collection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view", book_id=book_id, collection_id=collection_id
sub_collection = None
if sub_collection_id:
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("Sub_collection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
)
)
if sub_collection:
sections = sub_collection.sections
else:
sections = collection.sections
breadcrumbs = create_breadcrumbs(
book_id=book_id,
collection_path=(
collection_id,
sub_collection_id,
),
)
return render_template(
"book/section_view.html",
book=book,
collection=collection,
sections=sections,
sub_collection=sub_collection,
breadcrumbs=breadcrumbs,
)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:section_id>/interpretations",
methods=["GET"],
)
@bp.route(
"/<int:book_id>/<int:collection_id>/<int:sub_collection_id>/<int:section_id>/interpretations",
methods=["GET"],
)
def interpretation_view(
book_id: int,
collection_id: int,
section_id: int,
sub_collection_id: int | None = None,
):
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.WARNING, "Book with id [%s] not found", book_id)
flash("Book not found", "danger")
return redirect(url_for("book.my_books"))
collection: m.Collection = db.session.get(m.Collection, collection_id)
if not collection or collection.is_deleted:
log(log.WARNING, "Collection with id [%s] not found", collection_id)
flash("Collection not found", "danger")
return redirect(url_for("book.collection_view", book_id=book_id))
if sub_collection_id:
sub_collection: m.Collection = db.session.get(m.Collection, sub_collection_id)
if not sub_collection or sub_collection.is_deleted:
log(log.WARNING, "Sub_collection with id [%s] not found", sub_collection_id)
flash("Sub_collection not found", "danger")
return redirect(
url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
)
)
)
section: m.Section = db.session.get(m.Section, section_id)
if not section:
@ -187,12 +247,21 @@ def interpretation_view(
)
)
else:
breadcrumbs = create_breadcrumbs(
book_id=book_id,
collection_path=(
collection_id,
sub_collection_id,
),
section_id=section_id,
)
return render_template(
"book/interpretation_view.html",
book=book,
collection=collection,
sub_collection=sub_collection,
sub_collection=sub_collection if sub_collection_id else None,
section=section,
breadcrumbs=breadcrumbs,
)

View File

@ -5,7 +5,7 @@ from app import models as m, db
from tests.utils import login
def test_create_book(client: FlaskClient):
def test_create_edit_book(client: FlaskClient):
login(client)
BOOK_NAME = "Test Book"
@ -61,6 +61,44 @@ def test_create_book(client: FlaskClient):
assert book.versions
assert len(book.versions) == 1
response: Response = client.post(
"/book/999/edit",
data=dict(
book_id=999,
label="Book 1",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Book not found" in response.data
response: Response = client.post(
f"/book/{book.id}/edit",
data=dict(
book_id=book.id,
label=BOOK_NAME,
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Book label must be unique!" in response.data
response: Response = client.post(
f"/book/{book.id}/edit",
data=dict(
book_id=book.id,
label=BOOK_NAME + " EDITED",
),
follow_redirects=True,
)
assert response.status_code == 200
assert b"Success!" in response.data
book = db.session.get(m.Book, book.id)
assert book.label != BOOK_NAME
def test_add_contributor(client: FlaskClient):
_, user = login(client)

18
tests/test_breadcrumbs.py Normal file
View File

@ -0,0 +1,18 @@
from flask.testing import FlaskCliRunner
from app.controllers import create_breadcrumbs
from app import models as m, db
def test_breadcrumbs(runner: FlaskCliRunner, app):
runner.invoke(args=["db-populate"])
with app.app_context(), app.test_request_context():
res = create_breadcrumbs(1, (1,), 1)
assert len(res) == 4
book: m.Book = db.session.get(m.Book, 1)
assert book
assert book.owner.username in res[0].label
assert res[1].label == book.label
with app.app_context(), app.test_request_context():
res = create_breadcrumbs(1, (), 1)
assert res
assert len(res) == 2