mirror of https://github.com/logos-co/open-law.git
Merge pull request #7 from Simple2B/svyat/feat/book-forms
Svyat/feat/book forms
This commit is contained in:
commit
f36f62140a
|
@ -3,7 +3,10 @@ from .auth import LoginForm
|
|||
from .user import UserForm, NewUserForm
|
||||
from .book import (
|
||||
CreateBookForm,
|
||||
)
|
||||
from .contributor import (
|
||||
AddContributorForm,
|
||||
DeleteContributorForm,
|
||||
EditContributorRoleForm,
|
||||
)
|
||||
from .collection import CreateCollectionForm, EditCollectionForm
|
||||
|
|
|
@ -1,42 +1,8 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, SelectField
|
||||
from wtforms import StringField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
|
||||
from app import models as m
|
||||
|
||||
|
||||
class CreateBookForm(FlaskForm):
|
||||
label = StringField("Label", [DataRequired(), Length(6, 1024)])
|
||||
submit = SubmitField("Add new book")
|
||||
|
||||
|
||||
class AddContributorForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
role = SelectField(
|
||||
"Role",
|
||||
choices=[
|
||||
(member.value, name.capitalize())
|
||||
for name, member in m.BookContributor.Roles.__members__.items()
|
||||
],
|
||||
)
|
||||
|
||||
submit = SubmitField("Add Contributor")
|
||||
|
||||
|
||||
class DeleteContributorForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
|
||||
submit = SubmitField("Delete Contributor")
|
||||
|
||||
|
||||
class EditContributorRoleForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
role = SelectField(
|
||||
"Role",
|
||||
choices=[
|
||||
(member.value, name.capitalize())
|
||||
for name, member in m.BookContributor.Roles.__members__.items()
|
||||
],
|
||||
)
|
||||
|
||||
submit = SubmitField("Edit Contributor")
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length
|
||||
|
||||
|
||||
class CreateCollectionForm(FlaskForm):
|
||||
label = StringField("Label", [DataRequired(), Length(6, 1024)])
|
||||
about = StringField("About")
|
||||
|
||||
submit = SubmitField("Create")
|
||||
|
||||
|
||||
class EditCollectionForm(FlaskForm):
|
||||
label = StringField("Label", [Length(6, 1024)])
|
||||
about = StringField("About")
|
||||
|
||||
submit = SubmitField("Edit")
|
|
@ -0,0 +1,37 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, SelectField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
from app import models as m
|
||||
|
||||
|
||||
class AddContributorForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
role = SelectField(
|
||||
"Role",
|
||||
choices=[
|
||||
(member.value, name.capitalize())
|
||||
for name, member in m.BookContributor.Roles.__members__.items()
|
||||
],
|
||||
)
|
||||
|
||||
submit = SubmitField("Add Contributor")
|
||||
|
||||
|
||||
class DeleteContributorForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
|
||||
submit = SubmitField("Delete Contributor")
|
||||
|
||||
|
||||
class EditContributorRoleForm(FlaskForm):
|
||||
user_id = StringField("User ID", [DataRequired()])
|
||||
role = SelectField(
|
||||
"Role",
|
||||
choices=[
|
||||
(member.value, name.capitalize())
|
||||
for name, member in m.BookContributor.Roles.__members__.items()
|
||||
],
|
||||
)
|
||||
|
||||
submit = SubmitField("Edit Contributor")
|
|
@ -8,7 +8,7 @@ class Collection(BaseModel):
|
|||
# need to redeclare id to use it in the parrent relationship
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
label = db.Column(db.String(1024), unique=False, nullable=False)
|
||||
about = db.Column(db.Text, unique=False, nullable=False)
|
||||
about = db.Column(db.Text, unique=False, nullable=True)
|
||||
is_root = db.Column(db.Boolean, default=False)
|
||||
is_leaf = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -81,6 +81,9 @@
|
|||
|
||||
{% include 'book/add_book_modal.html' %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<script src="{{ url_for('static', filename='js/flowbite.min.js') }}" defer></script>
|
||||
|
||||
<div class="p-0 mt-16 h-full overflow-x-scroll md:ml-64">
|
||||
<!-- Main Content -->
|
||||
{% block content %}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Edit user modal -->
|
||||
<!-- Add book modal -->
|
||||
<!-- prettier-ignore-->
|
||||
<div id="add-book-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 class="relative w-full max-w-2xl max-h-full">
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<!-- Add collection modal -->
|
||||
<!-- prettier-ignore-->
|
||||
<div id="add-collection-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 class="relative w-full max-w-2xl max-h-full">
|
||||
<!-- Modal content -->
|
||||
<form action="{{ url_for('book.collection_create', book_id=book.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Add Collection </h3>
|
||||
<button id="modalAddCloseButton" data-modal-hide="add-collection-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
<div class="p-6 space-y-6">
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >Label</label >
|
||||
<input type="text" name="label" id="label" 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" placeholder="Collection label" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 pt-0 space-y-6">
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="about-collection" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >About</label >
|
||||
<textarea name="about" id="about-collection" rows="4" 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" placeholder="About collection..."></textarea>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
<!-- Edit user modal -->
|
||||
<!-- Add contributor modal -->
|
||||
<!-- prettier-ignore-->
|
||||
<div id="add-contributor-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 class="relative w-full max-w-2xl max-h-full">
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends 'base.html' %}
|
||||
|
||||
<!-- show create collection btn on rightside bar -->
|
||||
{% set show_create_collection = True %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{% include 'book/add_collection_modal.html' %}
|
||||
|
||||
|
||||
{% block right_sidebar %}
|
||||
{% include 'book/right_sidebar.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
|
||||
<!-- prettier-ignore -->
|
||||
|
@ -8,9 +21,6 @@
|
|||
<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 class="ml-auto">
|
||||
{% include 'book/components/settings_btn.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
<div class="mb-1 border-b border-gray-200 dark:border-gray-700">
|
||||
|
@ -39,7 +49,7 @@
|
|||
class="w-md md:w-full text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
|
||||
<!-- prettier-ignore -->
|
||||
{% if book.versions %}
|
||||
{% for collection in book.versions[-1].collections if not collection.is_root %}
|
||||
{% for collection in book.versions[-1].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)}}" >
|
||||
|
@ -75,10 +85,9 @@
|
|||
<p class="text-sm text-gray-500 dark:text-gray-400"> This is about {{book.label}} </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{% endblock %}
|
||||
<!-- prettier-ignore -->
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
{% endblock %}
|
||||
<!-- prettier-ignore -->
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<a
|
||||
href="{{ url_for("book.settings", book_id=book.id) }}"
|
||||
type="button"
|
||||
class="text-white bg-[#24292F] hover:bg-[#24292F]/90 focus:ring-4 focus:outline-none focus:ring-[#24292F]/50 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:focus:ring-gray-500 dark:hover:bg-[#050708]/30 mr-2 mb-2 border-2 border-gray-200 dark:border-gray-700 cursor-pointer">
|
||||
<!-- 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="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> </svg>
|
||||
<span class="ml-1">Settings</span>
|
||||
</a>
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Edit Collection modal -->
|
||||
<!-- prettier-ignore-->
|
||||
<div id="delete-collection-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 class="relative w-full max-w-2xl max-h-full">
|
||||
<!-- Modal content -->
|
||||
<form action="{{ url_for('book.collection_delete', book_id=book.id, collection_id=collection.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Delete Collection </h3>
|
||||
<button id="modalAddCloseButton" data-modal-hide="delete-collection-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
|
||||
<!-- 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-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">Confirm Deletion</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,35 @@
|
|||
<!-- Edit Collection modal -->
|
||||
<!-- prettier-ignore-->
|
||||
<div id="edit-collection-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 class="relative w-full max-w-2xl max-h-full">
|
||||
<!-- Modal content -->
|
||||
<form action="{{ url_for('book.collection_edit', book_id=book.id, collection_id=collection.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white"> Edit Collection </h3>
|
||||
<button id="modalAddCloseButton" data-modal-hide="edit-collection-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path> </svg> </button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
<div class="p-6 space-y-6">
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="label" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >Label</label >
|
||||
<input value="{{collection.label}}" type="text" name="label" id="label" 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" placeholder="Collection label" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-6 pt-0 space-y-6">
|
||||
<div class="grid gap-6">
|
||||
<div class="col-span-6 sm:col-span-3">
|
||||
<label for="about-collection" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" >About</label >
|
||||
<textarea name="about" id="about-collection" rows="4" 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" placeholder="About collection...">{{collection.about}}</textarea>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -12,9 +12,6 @@
|
|||
{{collection.label}}/{{sub_collection.label}}/{{section.label}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
{% include 'book/components/settings_btn.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<!-- prettier-ignore -->
|
||||
<aside id="logo-right-sidebar" class="fixed top-0 right-0 left-auto z-40 w-64 h-screen pt-28 transition-transform translate-x-96 bg-white border-r border-gray-200 md:translate-x-0 dark:bg-gray-800 dark:border-gray-700" aria-label="Right-sidebar">
|
||||
<div class="h-full pb-4 overflow-y-auto bg-white dark:bg-gray-800">
|
||||
<ul class="space-y-4 mt-1 font-medium">
|
||||
<li>
|
||||
<!-- prettier-ignore -->
|
||||
<a href="{{ url_for("book.settings", book_id=book.id) }}" type="button" class="space-x-3 text-white ml-2 w-11/12 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 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" >
|
||||
<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="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> </svg>
|
||||
<p>Book Settings</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if show_create_collection %}
|
||||
<li>
|
||||
<!-- prettier-ignore -->
|
||||
<button type="button" data-modal-target="add-collection-modal" data-modal-toggle="add-collection-modal" class="space-x-3 text-white ml-2 w-11/12 bg-emerald-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-emerald-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-emerald-600 dark:hover:bg-emerald-700 dark:focus:ring-emerald-800">
|
||||
<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="M12 4.5v15m7.5-7.5h-15" /> </svg>
|
||||
<p>New Collection</p>
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if show_edit_collection %}
|
||||
<li>
|
||||
<!-- prettier-ignore -->
|
||||
<button type="button" data-modal-target="edit-collection-modal" data-modal-toggle="edit-collection-modal" class="space-x-3 text-white ml-2 w-11/12 bg-teal-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-teal-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-teal-600 dark:hover:bg-teal-700 dark:focus:ring-teal-800">
|
||||
<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="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" /> </svg>
|
||||
<p>Edit Collection</p>
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if show_delete_collection %}
|
||||
<li>
|
||||
<!-- prettier-ignore -->
|
||||
<button type="button" data-modal-target="delete-collection-modal" data-modal-toggle="delete-collection-modal" class="space-x-3 text-white ml-2 w-11/12 bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
|
||||
<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="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /> </svg>
|
||||
<p>Delete Collection</p>
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
|
@ -8,9 +8,6 @@
|
|||
<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 class="ml-auto">
|
||||
{% include 'book/components/settings_btn.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
|
||||
<td class="px-6 py-4">{{ contributor.user.username }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<form action="{{ url_for('book.edit_contributor_role', book_id=book.id) }}" method="post" class="mb-0 shadow flex space-x-2">
|
||||
<form action="{{ url_for('book.edit_contributor_role', book_id=book.id) }}" method="post" class="mb-0 flex space-x-2">
|
||||
<input type="hidden" name="user_id" id="user_id" value="{{ contributor.user_id }}" />
|
||||
<select
|
||||
id="role"
|
||||
|
@ -56,7 +56,7 @@
|
|||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<!-- prettier-ignore -->
|
||||
<form action="{{ url_for('book.delete_contributor', book_id=book.id) }}" method="post" class="mb-0 shadow">
|
||||
<form action="{{ url_for('book.delete_contributor', book_id=book.id) }}" method="post" class="mb-0">
|
||||
<input type="hidden" name="user_id" id="user_id" value="{{ contributor.user_id }}" />
|
||||
|
||||
<button type="submit" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-sm rounded-lg text-sm px-5 py-1.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800">Delete</button>
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
<!-- prettier-ignore -->
|
||||
{% extends 'base.html' %}
|
||||
|
||||
<!-- show edit collection btn on rightside bar -->
|
||||
{% set show_edit_collection = True %}
|
||||
<!-- show delete collection btn on rightside bar -->
|
||||
{% set show_delete_collection = True %}
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
{% include 'book/edit_collection_modal.html' %}
|
||||
{% include 'book/delete_collection_modal.html' %}
|
||||
|
||||
|
||||
{% block right_sidebar %}
|
||||
{% include 'book/right_sidebar.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg mt-5 md:mr-64">
|
||||
<!-- prettier-ignore -->
|
||||
|
@ -8,9 +23,6 @@
|
|||
<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 class="ml-auto">
|
||||
{% include 'book/components/settings_btn.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- prettier-ignore -->
|
||||
|
||||
|
@ -69,7 +81,7 @@
|
|||
role="tabpanel"
|
||||
aria-labelledby="about-tab">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
This is about of {{collection.label}}
|
||||
This is about of {{collection.about}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -330,3 +330,143 @@ def edit_contributor_role(book_id: int):
|
|||
for error in errors:
|
||||
flash(error.replace("Field", field_label), "danger")
|
||||
return redirect(url_for("book.settings", book_id=book_id))
|
||||
|
||||
|
||||
#################
|
||||
# Collection CRUD
|
||||
#################
|
||||
|
||||
|
||||
@bp.route("/<int:book_id>/create_collection", methods=["POST"])
|
||||
@login_required
|
||||
def collection_create(book_id: int):
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if not book or book.owner != current_user or book.is_deleted:
|
||||
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
|
||||
flash("You are not owner of this book!", "danger")
|
||||
return redirect(url_for("book.my_books"))
|
||||
|
||||
form = f.CreateCollectionForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
label = form.label.data
|
||||
collection: m.Collection = m.Collection.query.filter_by(
|
||||
is_deleted=False, label=label, version_id=book.versions[-1].id
|
||||
).first()
|
||||
|
||||
if collection:
|
||||
log(
|
||||
log.INFO,
|
||||
"Collection with similar label already exists. Book: [%s], Collection: [%s], Label: [%s]",
|
||||
book.id,
|
||||
collection.id,
|
||||
label,
|
||||
)
|
||||
flash("Collection label must be unique!", "danger")
|
||||
return redirect(url_for("book.collection_view", book_id=book_id))
|
||||
|
||||
collection: m.Collection = m.Collection(
|
||||
label=label, about=form.about.data, version_id=book.versions[-1].id
|
||||
)
|
||||
log(log.INFO, "Create collection [%s]. Book: [%s]", collection, book.id)
|
||||
collection.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>/<int:collection_id>/edit", methods=["POST"])
|
||||
@login_required
|
||||
def collection_edit(book_id: int, collection_id: int):
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if not book or book.owner != current_user or book.is_deleted:
|
||||
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
|
||||
flash("You are not owner of this book!", "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))
|
||||
|
||||
form = f.EditCollectionForm()
|
||||
redirect_url = url_for(
|
||||
"book.sub_collection_view",
|
||||
book_id=book_id,
|
||||
collection_id=collection_id,
|
||||
)
|
||||
|
||||
if form.validate_on_submit():
|
||||
label = form.label.data
|
||||
|
||||
if (
|
||||
m.Collection.query.filter_by(label=label, version_id=book.versions[-1].id)
|
||||
.filter(m.Collection.id != collection_id)
|
||||
.first()
|
||||
):
|
||||
log(
|
||||
log.INFO,
|
||||
"Collection with similar label already exists. Book: [%s], Collection: [%s], Label: [%s]",
|
||||
book.id,
|
||||
collection.id,
|
||||
label,
|
||||
)
|
||||
flash("Collection label must be unique!", "danger")
|
||||
return redirect(redirect_url)
|
||||
|
||||
if label:
|
||||
collection.label = label
|
||||
|
||||
about = form.about.data
|
||||
if about:
|
||||
collection.about = about
|
||||
|
||||
log(log.INFO, "Edit collection [%s]", collection.id)
|
||||
collection.save()
|
||||
|
||||
flash("Success!", "success")
|
||||
return redirect(redirect_url)
|
||||
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(redirect_url)
|
||||
|
||||
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
def collection_delete(book_id: int, collection_id: int):
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if not book or book.owner != current_user:
|
||||
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
|
||||
flash("You are not owner of this book!", "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))
|
||||
|
||||
collection.is_deleted = True
|
||||
|
||||
log(log.INFO, "Delete collection [%s]", collection.id)
|
||||
collection.save()
|
||||
|
||||
flash("Success!", "success")
|
||||
return redirect(
|
||||
url_for(
|
||||
"book.collection_view",
|
||||
book_id=book_id,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -185,11 +185,101 @@ def test_edit_contributor_role(client: FlaskClient, runner: FlaskCliRunner):
|
|||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# response: Response = client.post(
|
||||
# f"/book/{book.id}/delete_contributor",
|
||||
# data=dict(user_id=contributor_to_delete.user_id),
|
||||
# follow_redirects=True,
|
||||
# )
|
||||
|
||||
# assert response.status_code == 200
|
||||
# assert b"Does not exists!" in response.data
|
||||
def test_crud_collection(client: FlaskClient, runner: FlaskCliRunner):
|
||||
_, user = login(client)
|
||||
user: m.User
|
||||
|
||||
# add dummmy data
|
||||
runner.invoke(args=["db-populate"])
|
||||
|
||||
book = db.session.get(m.Book, 1)
|
||||
book.user_id = user.id
|
||||
book.save()
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/create_collection",
|
||||
data=dict(label="Test Collection #1 Label", about="Test Collection #1 About"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/create_collection",
|
||||
data=dict(label="Test Collection #1 Label", about="Test Collection #1 About"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Collection label must be unique!" in response.data
|
||||
|
||||
collection: m.Collection = m.Collection.query.filter_by(
|
||||
label="Test Collection #1 Label"
|
||||
).first()
|
||||
m.Collection(
|
||||
label="Test Collection #2 Label", version_id=collection.version_id
|
||||
).save()
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/edit",
|
||||
data=dict(
|
||||
label="Test Collection #2 Label",
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Collection label must be unique!" in response.data
|
||||
|
||||
new_label = "Test Collection #1 Label(edited)"
|
||||
new_about = "Test Collection #1 About(edited)"
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/edit",
|
||||
data=dict(
|
||||
label=new_label,
|
||||
about=new_about,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
|
||||
edited_collection: m.Collection = m.Collection.query.filter_by(
|
||||
label=new_label, about=new_about
|
||||
).first()
|
||||
assert edited_collection
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/0/edit",
|
||||
data=dict(
|
||||
label=new_label,
|
||||
about=new_about,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Collection not found" in response.data
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/delete",
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
|
||||
deleted_collection: m.Collection = db.session.get(m.Collection, collection.id)
|
||||
assert deleted_collection.is_deleted
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/delete",
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Collection not found" in response.data
|
||||
|
|
Loading…
Reference in New Issue