Merge branch 'develop' into svyat/feat/user_permissions

This commit is contained in:
SvyatoslavArtymovych 2023-05-29 12:31:56 +03:00
commit 1554015e31
59 changed files with 740 additions and 306 deletions

View File

@ -22,7 +22,6 @@ def create_app(environment="development"):
user_blueprint,
book_blueprint,
home_blueprint,
section_blueprint,
vote_blueprint,
approve_blueprint,
star_blueprint,
@ -55,7 +54,6 @@ def create_app(environment="development"):
app.register_blueprint(user_blueprint)
app.register_blueprint(book_blueprint)
app.register_blueprint(home_blueprint)
app.register_blueprint(section_blueprint)
app.register_blueprint(vote_blueprint)
app.register_blueprint(approve_blueprint)
app.register_blueprint(star_blueprint)

View File

@ -39,7 +39,7 @@ def create_breadcrumbs(
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.AuthorBookList,
url="#",
url="",
label=book.owner.username + "'s books",
)
]
@ -60,11 +60,7 @@ def create_breadcrumbs(
crumples += [
s.BreadCrumb(
type=s.BreadCrumbType.Collection,
url=url_for(
"book.sub_collection_view",
book_id=book_id,
collection_id=collection_id,
),
url="",
label=collection.label,
)
]
@ -72,12 +68,7 @@ def create_breadcrumbs(
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],
),
url="",
label=collection.label,
)
]

View File

@ -28,6 +28,8 @@ def delete_nested_version_entities(book_version: m.BookVersion):
def delete_nested_collection_entities(collection: m.Collection):
for sub_collection in collection.children:
delete_nested_collection_entities(sub_collection)
for section in collection.sections:
section: m.Section
section.is_deleted = True

View File

@ -25,7 +25,7 @@ def set_book_tags(book: m.Book, tags: str):
for book_tag in book_tags:
db.session.delete(book_tag)
tags_names = [tag.title() for tag in tags.split(",") if len(tag)]
tags_names = [tag.lower() for tag in tags.split(",") if len(tag)]
for tag_name in tags_names:
try:

View File

@ -1,7 +1,10 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms import StringField, SubmitField, ValidationError
from wtforms.validators import DataRequired
from app.controllers import clean_html
from app.logger import log
class BaseInterpretationForm(FlaskForm):
about = StringField("About")
@ -12,6 +15,14 @@ class CreateInterpretationForm(BaseInterpretationForm):
section_id = StringField("Interpretation ID", [DataRequired()])
submit = SubmitField("Create")
def validate_text(self, field):
text = clean_html(field.data)
text = text.replace(" ", "")
text = text.strip()
if len(text) < 1:
log(log.WARNING, "Can't submit empty interpretation")
raise ValidationError("You can't create interpretation with no text")
class EditInterpretationForm(BaseInterpretationForm):
interpretation_id = StringField("Interpretation ID", [DataRequired()])

View File

@ -1,3 +1,5 @@
import base64
from flask_wtf import FlaskForm
from wtforms import (
StringField,
@ -58,7 +60,7 @@ class NewUserForm(FlaskForm):
class EditUserForm(FlaskForm):
name = StringField("Name", [DataRequired()])
avatar_img = FileField("Avatar file (max 200x200px)")
avatar_img = FileField("Avatar file (max 1mb, formats: jpg,jpeg,png)")
submit = SubmitField("Save")
def validate_username(self, field):
@ -69,6 +71,16 @@ class EditUserForm(FlaskForm):
):
raise ValidationError("This username is taken.")
def validate_avatar_img(self, field):
if field.data:
img_data = field.data.read()
img_data = base64.b64encode(img_data)
img_data = img_data.decode("utf-8")
field.data = img_data
size = len(img_data) / 1000000
if size > 1:
raise ValidationError("Avatar file size too large")
class ReactivateUserForm(FlaskForm):
submit = SubmitField("Save")

View File

@ -36,4 +36,8 @@ class BookVersion(BaseModel):
@property
def children_collections(self):
return self.root_collection.children
return [
collection
for collection in self.root_collection.children
if not collection.is_deleted
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,10 @@
{% extends "base.html" %}
<!-- prettier-ignore -->
{% block body %}
{% block title %}Login{% endblock %}
{% block body %}
<!-- component -->
<div class="bg-gray-50 dark:bg-gray-900 h-screen pt-20">
<section>

View File

@ -2,13 +2,21 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{ config.APP_NAME }}</title>
<title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
<!-- meta -->
<meta name="description" content="OpenLaw Flask App" />
<meta name="author" content="Simple2B" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
{% block meta %}{% endblock %}
<!-- favicon -->
<link
rel="icon"
type="image/x-icon"
href="{{ url_for('static', filename='img/logo.svg') }}"
/>
<!-- styles -->
<!-- prettier-ignore -->
@ -35,7 +43,7 @@
{% endblock %}
</head>
<body class="bg-white dark:bg-gray-800">
<body class="bg-white dark:bg-gray-800" >
<!-- Header -->
<!-- prettier-ignore -->
{% include 'header.html' %}

View File

@ -18,7 +18,7 @@
<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 />
<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="Section label" required />
</div>
</div>
</div>

View File

@ -1,7 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block content %}
{% block title %}Books{% endblock %}
{% block content %}
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
<!-- prettier-ignore -->
<div class="p-5 flex border-b-2 border-gray-200 border-solid dark:border-gray-700 text-gray-900 dark:text-white dark:divide-gray-700">

View File

@ -12,6 +12,8 @@
{% include 'book/delete_section_modal.html' %}
{% endif %}
{% block title %}{{book.label[:32]}}{% endblock %}
{% block content %}
<div class="flex overflow-hidden">
@ -29,7 +31,7 @@
<div class="flex text-black dark:text-white">
<!-- prettier-ignore -->
<div>
{% if not book.versions[-1].children_collections %}
{% if not book.versions[-1].children_collections and current_user.is_authenticated %}
<button type="button" data-modal-target="add-collection-modal" data-modal-toggle="add-collection-modal" ><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 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" /> </svg> </button>
{% endif %}
<a href="{{ url_for("book.settings", book_id=book.id) }}" type="button" class="ml-2" >
@ -262,7 +264,7 @@
<div class="gap-1 flex flex-wrap">
{% for tag in book.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>
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag.name)}}"><div class="cursor-pointer multiple-input-word bg-sky-300 hover:bg-sky-400 dark:bg-blue-600 dark:hover:bg-blue-700 dark:text-white rounded text-center py-1/2 px-2">{{tag.name}}</div></a>
{% endfor %}
</div>

View File

@ -1,8 +1,10 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% set selected_tab='favorite_books' %}
{% block content %}
{% block title %}Favorite Books{% endblock %}
{% block content %}
<div
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
{% if not current_user.is_authenticated %}

View File

@ -2,22 +2,16 @@
{% extends 'base.html' %}
{% if current_user.is_authenticated %}
{% include 'book/delete_section_modal.html' %}
{% include 'book/edit_section_modal.html' %}
{% include 'book/approve_interpretation_modal.html' %}
<!-- prettier-ignore -->
{% set show_delete_section = True %}
<!-- prettier-ignore -->
{% set show_edit_section = True %}
{% block right_sidebar %}
{% include 'book/right_sidebar.html' %}
{% endblock %}
{% endif %}
{% block title %}{{section.label}}{% endblock %}
{% block content %}
{% include 'book/breadcrumbs_navigation.html'%}
<div class="overflow-x-auto shadow-md sm:rounded-lg md:mr-64">
<div class="overflow-x-auto shadow-md sm:rounded-lg">
<!-- prettier-ignore -->
<div class="fixed z-30 w-full top-44 pt-6 bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<!-- prettier-ignore -->

View File

@ -1,8 +1,10 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% set selected_tab='my_contributions' %}
{% block content %}
{% block title %}My Contributions{% endblock %}
{% block content %}
<div
class="md:mr-64 pt-1 relative overflow-x-auto shadow-md sm:rounded-lg mt-1 h-box w-box flex">
{% if not current_user.is_authenticated %}

View File

@ -1,6 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% set selected_tab='my_library' %}
{% block title %}My Library{% endblock %}
{% block content %}
{% if current_user.is_authenticated %}
@ -110,7 +113,7 @@
</div>
{% endif %}
</div>
</div>
</div>
<!-- prettier-ignore -->
{% endblock %}
<!-- prettier-ignore -->

View File

@ -4,21 +4,17 @@
{% if current_user.is_authenticated %}
{% include 'book/delete_interpretation_modal.html' %}
{% include 'book/delete_comment_modal.html' %}
{% include 'book/edit_comment_modal.html' %}
{% include 'book/edit_interpretation_modal.html' %}
{% set show_edit_interpretation = True %}
{% set show_delete_interpretation = True %}
{% block right_sidebar %}
{% include 'book/right_sidebar.html' %}
{% endblock %}
{% endif %}
{% block title %}{{ section.label[:32] }}{% endblock %}
{% block content %}
{% include 'book/breadcrumbs_navigation.html'%}
<div class="shadow-md mt-5 md:mr-64 h-auto overflow-x-hidden">
<div class="shadow-md mt-5 h-auto overflow-x-hidden">
<div class="ql-snow mt-20">
<h1 class="text-l font-extrabold dark:text-white ml-4 truncate">
{{ section.label }}
@ -207,7 +203,7 @@
</div>
<div class="p-5 m-3">
{% for child in comment.children %}
<div class="p-5 mb-2 flex justify-between items-end bg-slate-600 rounded-lg">
<div class="p-5 mb-2 flex justify-between items-end bg-slate-100 dark:bg-slate-600 rounded-lg">
<div class="ql-snow">
<div class="inline-block mb-4 ql-editor-readonly !p-0">
{{display_tags(child.text)|safe}}

View File

@ -4,6 +4,9 @@
{% include 'book/delete_book_modal.html' %}
{% include 'book/access_level_modal.html' %}
{% block title %}Book Settings{% endblock %}
{% block content %}
<!-- Hide right_sidebar -->
<!-- prettier-ignore -->

View File

@ -1,5 +1,8 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Statistics{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->

View File

@ -14,6 +14,7 @@
{% include 'book/add_section_modal.html' %}
{% endif %}
{% block title %}{{book.label[:32]}}{% endblock %}
{% block right_sidebar %}
{% include 'book/right_sidebar.html' %}

View File

@ -0,0 +1,119 @@
<!-- prettier-ignore -->
<div id="quickSearchModal" tabindex="-1" aria-hidden="true" class="absolute w-96 top-14 left-[250px] z-50 hidden h-[calc(100%-1rem)] max-h-full">
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<button type="button" class="hidden text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide="quickSearchModal"> <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>
<span class="sr-only">Close modal</span>
</button>
<!-- Modal body -->
<div class="p-1 space-y-1">
<div id="emptyQuickSearchDiv" class="hidden h-32">
<table>
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
<!-- prettier-ignore -->
Nothing found
</th>
</tr>
</thead>
</table>
</div>
<div id="quickSearchBlock-interpretations">
<table>
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
<!-- prettier-ignore -->
Interpretations
</th>
</tr>
</thead>
<tbody>
<tr class="interpretationsText-0">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<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-2"> <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>
<a href="" id="interpretationsText-0"></a></th>
</tr>
<tr class="interpretationsText-1">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<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-2"> <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>
<a href="" id="interpretationsText-1"></a></th>
</tr>
</tbody>
</table>
</div>
<div id="quickSearchBlock-books">
<table>
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
<!-- prettier-ignore -->
Books
</th>
</tr>
</thead>
<tbody>
<tr class="booksText-0">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" /> </svg>
<a href="" id="booksText-0"></a></th>
</tr>
<tr class="booksText-1">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0120.25 6v12A2.25 2.25 0 0118 20.25H6A2.25 2.25 0 013.75 18V6A2.25 2.25 0 016 3.75h1.5m9 0h-9" /> </svg>
<a href="" id="booksText-1"></a></th>
</tr>
</tbody>
</table>
</div>
<div id="quickSearchBlock-users">
<table>
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
<!-- prettier-ignore -->
Users
</th>
</tr>
</thead>
<tbody>
<tr class="usersText-0">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
<a href="" id="usersText-0"></a></th>
</tr>
<tr class="usersText-1">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <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>
<a href="" id="usersText-1"></a></th>
</tr>
</tbody>
</table>
</div>
<div id="quickSearchBlock-tags" class="hidden">
<table>
<thead class="text-xs text-gray-900 uppercase dark:text-gray-400">
<tr><th scope="col" class="flex items-center justify-start px-1 py-1">
<!-- prettier-ignore -->
Tags
</th>
</tr>
</thead>
<tbody>
<tr class="tagsText-0">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" /> </svg>
<a href="" id="tagsText-0"></a></th>
</tr>
<tr class="tagsText-1">
<th scope="row" class="flex items-center px-2 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white">
<svg aria-hidden="true" class="w-5 h-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" /> </svg>
<a href="" id="tagsText-1"></a></th>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Modal footer -->
</div>
</div>
</div>

View File

@ -1,5 +1,8 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
@ -10,9 +13,9 @@
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
Interpretations
</a>

View File

@ -1,5 +1,8 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
@ -10,9 +13,9 @@
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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>
Interpretations
</a>

View File

@ -1,5 +1,8 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
@ -10,9 +13,9 @@
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
Interpretations
</a>

View File

@ -1,5 +1,8 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
@ -10,9 +13,9 @@
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<a href="{{url_for('search.search_interpretations',q=query)}}" 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 group" >
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
Interpretations
</a>
@ -53,7 +56,7 @@
<div class="pl-3">
<div class="text-base font-semibold">{{user.username}}</div>
<div class="font-normal text-gray-500">{{user.wallet_id}}</div>
</div>
</div>
</th>
<td class="px-6 py-4">
<a href="{{ url_for('user.profile',user_id=user.id) }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Go to {{user.username}}'s library</a>

View File

@ -1,18 +1,21 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Tag search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Search results</h1>
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Tag search results</h1>
<!-- prettier-ignore -->
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Search result for {{tag_name}} </p>
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400">Tag search result for {{tag_name}} </p>
<!-- prettier-ignore -->
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag_name)}}" 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 group" >
<a href="{{url_for('search.tag_search_interpretations',tag_name=tag_name)}}" 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 group" >
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500 dark:text-gray-500 dark:group-hover:text-gray-300" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" > <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>
Interpretations
</a>

View File

@ -1,18 +1,21 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}Tag search results{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
<!-- prettier-ignore -->
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Search results</h1>
<h1 class="hidden md:inline font-extrabold text-lg dark:text-white ml-4 mt-5">Tag search results</h1>
<!-- prettier-ignore -->
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Search result for {{tag_name}} </p>
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Tag search result for {{tag_name}} </p>
<!-- prettier-ignore -->
<p class="hidden md:block text-sm ml-4 w-1/2 text-gray-500 text-center md:text-left dark:text-gray-400"> Showing {{count}} results </p>
<!-- prettier-ignore -->
<div class="border-b border-gray-200 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<ul class="flex flex-wrap -mb-px text-sm font-medium text-center text-gray-500 dark:text-gray-400">
<li class="mr-2">
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
<a class="inline-flex p-4 text-blue-600 border-b-2 border-blue-600 rounded-t-lg active dark:text-blue-500 dark:border-blue-500 group" aria-current="page">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-500"> <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>
Interpretations
</a>

View File

@ -7,8 +7,9 @@
<svg class="w-5 h-5 text-gray-500 dark:text-gray-400" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path></svg>
</div>
<!-- prettier-ignore -->
<input id="mainSearchInput" required minlength="1" name="search_query" {% if search_query %}value={{ search_query }}{% endif %} type="text" class="block p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg w-80 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 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">
<input required minlength="1" autocomplete="off" name="search_query" {% if search_query %}value={{ search_query }}{% endif %} type="text" id="mainSearchInput" class="block p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg w-80 bg-gray-50 focus:ring-blue-500 focus:border-blue-500 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" />
</div>
<!-- prettier-ignore -->
<button type="button" id="global-search-button" class="md:flex px-3 py-2 text-xs text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 ml-2 font-medium rounded-lg text-center inline-flex items-center mr-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"><svg class="w-5 h-5 text-white-500" 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 ml-0 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75l-2.489-2.489m0 0a3.375 3.375 0 10-4.773-4.773 3.375 3.375 0 004.774 4.774zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>Search</button>
<button type="submit" id="global-search-button" class="md:flex px-3 py-2 text-xs text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 ml-2 font-medium rounded-lg text-center inline-flex items-center mr-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"><svg class="w-5 h-5 text-white-500" 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 ml-0 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75l-2.489-2.489m0 0a3.375 3.375 0 10-4.773-4.773 3.375 3.375 0 004.774 4.774zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>Search</button>
</div>
{% include 'search/quick_search_window.html' %}

View File

@ -1,7 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block content %}
{% block title %}Sections{% endblock %}
{% block content %}
<div class="md:mr-64 relative overflow-x-auto shadow-md sm:rounded-lg mt-1">
<!-- prettier-ignore -->
<div class="p-5 flex border-b-2 border-gray-200 border-solid dark:border-gray-700 text-gray-900 dark:text-white dark:divide-gray-700">

View File

@ -1,6 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% include 'user/delete_profile_modal.html' %}
{% block title %}Edit Profile{% endblock %}
{% block content %}
<!-- component -->
<section>
@ -35,19 +38,24 @@
<div class="flex items-center mb-3">
<div>
{% if current_user.avatar_img %}
<img class=" w-14 h-14 rounded-full mr-3" src="data:image/jpeg;base64,{{ current_user.avatar_img }}" alt="user avatar">
<img class="w-14 h-14 rounded-full mr-3" src="data:image/jpeg;base64,{{ current_user.avatar_img }}" alt="user avatar">
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 56 56" stroke-width="1.5" stroke="currentColor" class="w-14 h-14"> <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>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-14 h-14 mr-2"> <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 %}
</div>
<div>
{{form.avatar_img(type='file', 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', id="avatar_img",
{{form.avatar_img(type='file', 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', id="avatar_img", accept=".jpg,.png,.jpeg",
value=current_user.avatar_img if current_user.avatar_img else "")}}</div>
</div>
<button type="submit" class="w-full px-5 py-3 text-base font-medium text-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 sm:w-auto dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save changes</button>
</form>
<button type="button" data-modal-target="delete_profile_modal" data-modal-toggle="delete_profile_modal" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete you profile</button>
<div class="flex">
<form action="{{ url_for('user.delete_avatar') }}" class="mb-0" method="POST">
<button type="submit" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete Avatar</button>
</form>
<button type="button" data-modal-target="delete_profile_modal" data-modal-toggle="delete_profile_modal" class="text-red-700 hover:text-white border border-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 mr-2 mb-2 dark:border-red-500 dark:text-red-500 dark:hover:text-white dark:hover:bg-red-600 dark:focus:ring-red-900">Delete you profile</button>
</div>
</div>
</div>
</div>

View File

@ -1,5 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% block title %}{{user.username.strip() + "'s profile" or user.wallet_id}}{% endblock %}
{% block content %}
<div class="border-b border-gray-200 dark:border-gray-700 md:mr-64">
{% if user.is_deleted %}

View File

@ -1,6 +1,9 @@
<!-- prettier-ignore -->
{% extends 'base.html' %}
{% include 'user/delete_profile_modal.html' %}
{% block title %}Reactivate Profile{% endblock %}
{% block content %}
<!-- component -->
<section>

View File

@ -4,7 +4,6 @@ from .main import main_blueprint
from .user import bp as user_blueprint
from .book import bp as book_blueprint
from .home import bp as home_blueprint
from .section import bp as section_blueprint
from .vote import bp as vote_blueprint
from .approve import bp as approve_blueprint
from .star import bp as star_blueprint

View File

@ -3,7 +3,6 @@ from flask import (
flash,
redirect,
url_for,
request,
)
from flask_login import login_required, current_user
from sqlalchemy import and_, or_
@ -29,18 +28,19 @@ from .bp import bp
@bp.route("/all", methods=["GET"])
def get_all():
q = request.args.get("q", type=str, default=None)
books: m.Book = m.Book.query.order_by(m.Book.id)
if q:
books = books.filter(m.Book.label.like(f"{q}"))
log(log.INFO, "Create query for books")
books: m.Book = m.Book.query.filter(m.Book.is_deleted is not False).order_by(
m.Book.id
)
log(log.INFO, "Create pagination for books")
pagination = create_pagination(total=books.count())
log(log.INFO, "Returning data for front end")
return render_template(
"book/all.html",
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
search_query=q,
all_books=True,
)
@ -48,15 +48,22 @@ def get_all():
@bp.route("/my_library", methods=["GET"])
def my_library():
if current_user.is_authenticated:
log(log.INFO, "Create query for my_library page for books")
books: m.Book = m.Book.query.order_by(m.Book.id)
books = books.filter_by(user_id=current_user.id, is_deleted=False)
log(log.INFO, "Create pagination for books")
pagination = create_pagination(total=books.count())
log(log.INFO, "Returns data for front end")
return render_template(
"book/my_library.html",
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
)
log(log.INFO, "Returns data for front end is user is anonym")
return render_template(
"book/my_library.html",
books=[],
@ -77,9 +84,8 @@ def create():
root_collection = m.Collection(
label="Root Collection", version_id=version.id, is_root=True
).save()
tags = form.tags.data
if tags:
set_book_tags(book, tags)
tags = form.tags.data or ""
set_book_tags(book, tags)
# access groups
editor_access_group = create_editor_group(book_id=book.id)
@ -113,9 +119,8 @@ def edit(book_id: int):
book: m.Book = db.session.get(m.Book, book_id)
label = form.label.data
about = form.about.data
tags = form.tags.data
if tags:
set_book_tags(book, tags)
tags = form.tags.data or ""
set_book_tags(book, tags)
book.label = label
book.about = about
@ -163,6 +168,8 @@ def statistic_view(book_id: int):
@bp.route("/favorite_books", methods=["GET"])
def favorite_books():
if current_user.is_authenticated:
log(log.INFO, "Creating query for books")
books = (
db.session.query(
m.Book,
@ -178,7 +185,10 @@ def favorite_books():
)
books = books.filter_by(is_deleted=False)
log(log.INFO, "Creating pagination for books")
pagination = create_pagination(total=books.count())
log(log.INFO, "Returns data for front end")
return render_template(
"book/favorite_books.html",
@ -190,34 +200,40 @@ def favorite_books():
@bp.route("/my_contributions", methods=["GET"])
def my_contributions():
interpretations = (
db.session.query(
m.Interpretation,
)
.filter(
or_(
and_(
m.Interpretation.id == m.Comment.interpretation_id,
m.Comment.user_id == current_user.id,
m.Comment.is_deleted.is_(False),
m.Interpretation.is_deleted.is_(False),
),
and_(
m.Interpretation.user_id == current_user.id,
m.Interpretation.is_deleted.is_(False),
),
if current_user.is_authenticated:
log(log.INFO, "Creating query for interpretations")
interpretations = (
db.session.query(
m.Interpretation,
)
.filter(
or_(
and_(
m.Interpretation.id == m.Comment.interpretation_id,
m.Comment.user_id == current_user.id,
m.Comment.is_deleted.is_(False),
m.Interpretation.is_deleted.is_(False),
),
and_(
m.Interpretation.user_id == current_user.id,
m.Interpretation.is_deleted.is_(False),
),
)
)
.group_by(m.Interpretation.id)
.order_by(m.Interpretation.created_at.desc())
)
.group_by(m.Interpretation.id)
.order_by(m.Interpretation.created_at.desc())
)
log(log.INFO, "Creating pagination for interpretations")
pagination = create_pagination(total=interpretations.count())
pagination = create_pagination(total=interpretations.count())
log(log.INFO, "Returns data for front end")
return render_template(
"book/my_contributions.html",
interpretations=interpretations.paginate(
page=pagination.page, per_page=pagination.per_page
),
page=pagination,
)
return render_template(
"book/my_contributions.html",
interpretations=interpretations.paginate(
page=pagination.page, per_page=pagination.per_page
),
page=pagination,
)
return render_template("book/my_contributions.html", interpretations=[])

View File

@ -102,8 +102,7 @@ def create_comment(
comment.save()
tags = current_app.config["TAG_REGEX"].findall(text)
if tags:
set_comment_tags(comment, tags)
set_comment_tags(comment, tags)
flash("Success!", "success")
return redirect(redirect_url)
@ -198,8 +197,7 @@ def comment_edit(
log(log.INFO, "Edit comment [%s]", comment)
tags = current_app.config["TAG_REGEX"].findall(text)
if tags:
set_comment_tags(comment, tags)
set_comment_tags(comment, tags)
comment.save()

View File

@ -143,8 +143,7 @@ def interpretation_create(
# -------------
tags = current_app.config["TAG_REGEX"].findall(text)
if tags:
set_interpretation_tags(interpretation, tags)
set_interpretation_tags(interpretation, tags)
flash("Success!", "success")
return redirect(redirect_url)
@ -201,8 +200,7 @@ def interpretation_edit(
interpretation.plain_text = plain_text
interpretation.text = text
tags = current_app.config["TAG_REGEX"].findall(text)
if tags:
set_interpretation_tags(interpretation, tags)
set_interpretation_tags(interpretation, tags)
log(log.INFO, "Edit interpretation [%s]", interpretation.id)
interpretation.save()

View File

@ -4,15 +4,21 @@ from flask import (
)
from sqlalchemy import and_
from app import models as m, db
from app.logger import log
bp = Blueprint("home", __name__, url_prefix="/home")
@bp.route("/", methods=["GET"])
def get_all():
log(log.INFO, "Create query for home page for books")
books: m.Book = (
m.Book.query.filter_by(is_deleted=False).order_by(m.Book.id).limit(5)
).all()
log(log.INFO, "Create query for home page for interpretations")
interpretations = (
db.session.query(
m.Interpretation,
@ -34,6 +40,7 @@ def get_all():
.limit(5)
.all()
)
log(log.INFO, "Returning data to front end")
return render_template(
"home/index.html",

View File

@ -1,8 +1,12 @@
from flask import Blueprint, render_template, request
from flask import Blueprint, render_template, request, jsonify, url_for
from sqlalchemy import func, and_, or_
from app import models as m, db
from app.controllers import create_pagination
from app.controllers.build_qa_url_using_interpretation import (
build_qa_url_using_interpretation,
)
from app.logger import log
bp = Blueprint("search", __name__)
@ -11,14 +15,18 @@ bp = Blueprint("search", __name__)
@bp.route("/search_interpretations", methods=["GET"])
def search_interpretations():
q = request.args.get("q", type=str, default="").lower()
log(log.INFO, "Starting to build query for interpretations")
interpretations = m.Interpretation.query.order_by(m.Interpretation.id).filter(
(func.lower(m.Interpretation.plain_text).like(f"%{q}%"))
)
log(log.INFO, "Get count of interpretations")
count = interpretations.count()
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=interpretations.count())
log(log.INFO, "Returning data to front")
return render_template(
"searchResultsInterpretations.html",
"search/search_results_interpretations.html",
query=q,
interpretations=interpretations.paginate(
page=pagination.page, per_page=pagination.per_page
@ -30,49 +38,47 @@ def search_interpretations():
@bp.route("/search_books", methods=["GET"])
def search_books():
q = request.args.get("q", type=str, default="")
q = request.args.get("q", type=str, default="").lower()
log(log.INFO, "Starting to build query for books")
books = (
db.session.query(m.Book)
m.Book.query.join(m.BookVersion, m.BookVersion.book_id == m.Book.id)
.join(m.Collection, m.BookVersion.id == m.Collection.version_id, full=True)
.join(m.Section, m.BookVersion.id == m.Section.version_id, full=True)
.join(m.Interpretation, m.Interpretation.section_id == m.Section.id, full=True)
.filter(
or_(
and_(
func.lower(m.Book.label).like(f"%{q}%"),
m.Book.is_deleted == False, # noqa: E712
),
and_(
func.lower(m.Collection.label).like(f"%{q}%"),
m.Collection.is_deleted == False, # noqa: E712
m.Collection.is_root == False, # noqa: E712
m.BookVersion.id == m.Collection.version_id,
m.Book.id == m.BookVersion.book_id,
m.Book.is_deleted == False, # noqa: E712
),
and_(
func.lower(m.Section.label).like(f"%{q}%"),
m.Section.is_deleted == False, # noqa: E712
m.BookVersion.id == m.Section.version_id,
m.Book.id == m.BookVersion.book_id,
m.Book.is_deleted == False, # noqa: E712
),
and_(
func.lower(m.Interpretation.plain_text).like(f"%{q}%"),
m.Interpretation.is_deleted == False, # noqa: E712
m.Interpretation.section_id == m.Section.id,
m.Section.is_deleted == False, # noqa: E712
m.BookVersion.id == m.Section.version_id,
m.Book.id == m.BookVersion.book_id,
m.Book.is_deleted == False, # noqa: E712
),
),
m.Book.is_deleted == False, # noqa: E712
)
.order_by(m.Book.created_at.asc())
.group_by(m.Book.id)
)
log(log.INFO, "Get count of books")
count = books.count()
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=books.count())
log(log.INFO, "Returning data to front")
return render_template(
"searchResultsBooks.html",
"search/search_results_books.html",
query=q,
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
@ -82,7 +88,8 @@ def search_books():
@bp.route("/search_users", methods=["GET"])
def search_users():
q = request.args.get("q", type=str, default="")
q = request.args.get("q", type=str, default="").lower()
log(log.INFO, "Starting to build query for users")
users = (
m.User.query.order_by(m.User.id)
.filter(
@ -94,12 +101,16 @@ def search_users():
.order_by(m.User.id.asc())
.group_by(m.User.id)
)
log(log.INFO, "Get count of users")
count = users.count()
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=users.count())
log(log.INFO, "Returning data to front")
return render_template(
"searchResultsUsers.html",
"search/search_results_users.html",
query=q,
users=users.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
@ -109,13 +120,20 @@ def search_users():
@bp.route("/search_tags", methods=["GET"])
def search_tags():
q = request.args.get("q", type=str, default="")
q = request.args.get("q", type=str, default="").lower()
log(log.INFO, "Starting to build query for tags")
tags = m.Tag.query.order_by(m.Tag.id).filter(func.lower(m.Tag.name).like(f"%{q}%"))
log(log.INFO, "Get count of tags")
count = tags.count()
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=tags.count())
log(log.INFO, "Returning data to front")
return render_template(
"searchResultsTags.html",
"search/search_results_tags.html",
query=q,
tags=tags.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
@ -125,7 +143,9 @@ def search_tags():
@bp.route("/tag_search_interpretations", methods=["GET"])
def tag_search_interpretations():
tag_name = request.args.get("tag_name", type=str, default="")
tag_name = request.args.get("tag_name", type=str, default="").lower()
log(log.INFO, "Starting to build query for interpretations")
interpretations = (
db.session.query(m.Interpretation)
.filter(
@ -139,11 +159,13 @@ def tag_search_interpretations():
.order_by(m.Interpretation.created_at.asc())
.group_by(m.Interpretation.id)
)
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=interpretations.count())
log(log.INFO, "Returning data to front")
return render_template(
"tagSearchResultsInterpretations.html",
"search/tag_search_results_interpretations.html",
tag_name=tag_name,
interpretations=interpretations.paginate(
page=pagination.page, per_page=pagination.per_page
@ -155,7 +177,9 @@ def tag_search_interpretations():
@bp.route("/tag_search_books", methods=["GET"])
def tag_search_books():
tag_name = request.args.get("tag_name", type=str, default="")
tag_name = request.args.get("tag_name", type=str, default="").lower()
log(log.INFO, "Starting to build query for books")
books = (
db.session.query(m.Book)
.filter(
@ -169,13 +193,89 @@ def tag_search_books():
.order_by(m.Book.created_at.asc())
.group_by(m.Book.id)
)
log(log.INFO, "Creating pagination")
pagination = create_pagination(total=books.count())
log(log.INFO, "Returning data to front")
return render_template(
"tagSearchResultsBooks.html",
"search/tag_search_results_books.html",
tag_name=tag_name,
books=books.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
count=books.count(),
)
@bp.route("/quick_search", methods=["GET"])
def quick_search():
search_query = request.args.get("search_query", type=str, default="").lower()
log(log.INFO, "Starting to build query for interpretations")
interpretations = (
m.Interpretation.query.order_by(m.Interpretation.id)
.filter(
(func.lower(m.Interpretation.plain_text).like(f"%{search_query}%")),
m.Interpretation.is_deleted == False, # noqa: E712,
)
.limit(2)
)
interpretations_res = []
for interpretation in interpretations:
url_for_interpretation = build_qa_url_using_interpretation(interpretation)
interpretations_res.append(
{"label": interpretation.section.label, "url": url_for_interpretation}
)
log(log.INFO, "Starting to build query for books")
books = (
m.Book.query.order_by(m.Book.id)
.filter(
(func.lower(m.Book.label).like(f"%{search_query}%")),
m.Book.is_deleted == False, # noqa: E712,
)
.limit(2)
)
books_res = []
for book in books:
url_for_book = url_for("book.collection_view", book_id=book.id)
books_res.append({"label": book.label, "url": url_for_book})
log(log.INFO, "Starting to build query for users")
users = (
m.User.query.order_by(m.User.id)
.filter(
or_(
func.lower(m.User.username).like(f"%{search_query}%"),
func.lower(m.User.wallet_id).like(f"%{search_query}%"),
)
)
.order_by(m.User.id.asc())
.group_by(m.User.id)
.limit(2)
)
users_res = []
for user in users:
url_for_user = url_for("user.profile", user_id=user.id)
users_res.append({"label": user.username, "url": url_for_user})
log(log.INFO, "Starting to build query for tags")
tags = (
m.Tag.query.order_by(m.Tag.id)
.filter(func.lower(m.Tag.name).like(f"%{search_query}%"))
.limit(2)
)
tags_res = []
for tag in tags:
url_for_tag = url_for("search.tag_search_interpretations", tag_name=tag.name)
tags_res.append({"label": tag.name, "url": url_for_tag})
log(log.INFO, "Returning data to front")
return jsonify(
{
"interpretations": interpretations_res,
"books": books_res,
"users": users_res,
"tags": tags_res,
}
)

View File

@ -1,29 +0,0 @@
from flask import (
Blueprint,
render_template,
request,
)
from app.controllers import create_pagination
from app import models as m
bp = Blueprint("section", __name__, url_prefix="/section")
@bp.route("/all", methods=["GET"])
def get_all():
q = request.args.get("q", type=str, default=None)
section: m.Section = m.Section.query.order_by(m.Section.id)
if q:
section = section.filter(m.Section.label.like(f"{q}"))
pagination = create_pagination(total=section.count())
return render_template(
"section/all.html",
sections=section.paginate(page=pagination.page, per_page=pagination.per_page),
page=pagination,
search_query=q,
all_books=True,
)

View File

@ -1,5 +1,3 @@
import base64
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from flask_login import login_required, current_user, logout_user
from app.controllers import create_pagination
@ -40,9 +38,9 @@ def edit_profile():
user: m.User = current_user
user.username = form.name.data
if form.avatar_img.data:
img_data = form.avatar_img.data.read()
img_data = base64.b64encode(img_data)
current_user.avatar_img = img_data.decode("utf-8")
current_user.avatar_img = (
form.avatar_img.data
) # form.avatar_img.data is changed in form validator
user.is_activated = True
user.save()
return redirect(url_for("main.index"))
@ -58,6 +56,17 @@ def edit_profile():
return render_template("user/edit_profile.html", form=form)
@bp.route("/delete_avatar", methods=["POST"])
@login_required
def delete_avatar():
user: m.User = current_user
current_user.avatar_img = None
log(log.ERROR, "Delete user [%s] avatar", user)
current_user.save()
return redirect(url_for("user.edit_profile"))
@bp.route("/<int:user_id>/profile")
def profile(user_id: int):
user: m.User = db.session.get(m.User, user_id)
@ -72,22 +81,6 @@ def profile(user_id: int):
)
@bp.route("/create", methods=["POST"])
@login_required
def create():
form = f.NewUserForm()
if form.validate_on_submit():
user = m.User(
username=form.username.data,
password=form.password.data,
activated=form.activated.data,
)
log(log.INFO, "Form submitted. User: [%s]", user)
flash("User added!", "success")
user.save()
return redirect(url_for("user.get_all"))
@bp.route("/profile_delete", methods=["POST"])
@login_required
def profile_delete():

View File

@ -14,17 +14,17 @@ config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
logger = logging.getLogger("alembic.env")
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
"sqlalchemy.url",
str(current_app.extensions["migrate"].db.get_engine().url).replace("%", "%%"),
)
target_metadata = current_app.extensions["migrate"].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
@ -45,9 +45,7 @@ def run_migrations_offline():
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
with context.begin_transaction():
context.run_migrations()
@ -65,20 +63,20 @@ def run_migrations_online():
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
if getattr(config.cmd_opts, "autogenerate", False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
logger.info("No changes in schema detected.")
connectable = current_app.extensions['migrate'].db.get_engine()
connectable = current_app.extensions["migrate"].db.get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
**current_app.extensions["migrate"].configure_args
)
with context.begin_transaction():

View File

@ -10,23 +10,23 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '1dfa1f2c208f'
down_revision = '2ec60080de3b'
revision = "1dfa1f2c208f"
down_revision = "2ec60080de3b"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('comments', schema=None) as batch_op:
batch_op.add_column(sa.Column('edited', sa.Boolean(), nullable=True))
with op.batch_alter_table("comments", schema=None) as batch_op:
batch_op.add_column(sa.Column("edited", sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('comments', schema=None) as batch_op:
batch_op.drop_column('edited')
with op.batch_alter_table("comments", schema=None) as batch_op:
batch_op.drop_column("edited")
# ### end Alembic commands ###

View File

@ -10,29 +10,33 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '2ec60080de3b'
down_revision = '4ce4bacc7c06'
revision = "2ec60080de3b"
down_revision = "4ce4bacc7c06"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.alter_column('wallet_id',
existing_type=sa.VARCHAR(length=256),
type_=sa.String(length=64),
existing_nullable=True)
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.alter_column(
"wallet_id",
existing_type=sa.VARCHAR(length=256),
type_=sa.String(length=64),
existing_nullable=True,
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.alter_column('wallet_id',
existing_type=sa.String(length=64),
type_=sa.VARCHAR(length=256),
existing_nullable=True)
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.alter_column(
"wallet_id",
existing_type=sa.String(length=64),
type_=sa.VARCHAR(length=256),
existing_nullable=True,
)
# ### end Alembic commands ###

View File

@ -10,29 +10,29 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '377fc0b7e4bb'
down_revision = 'a1345b416f81'
revision = "377fc0b7e4bb"
down_revision = "a1345b416f81"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('is_activated', sa.Boolean(), nullable=True))
batch_op.alter_column('username',
existing_type=sa.VARCHAR(length=60),
nullable=True)
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.add_column(sa.Column("is_activated", sa.Boolean(), nullable=True))
batch_op.alter_column(
"username", existing_type=sa.VARCHAR(length=60), nullable=True
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.alter_column('username',
existing_type=sa.VARCHAR(length=60),
nullable=False)
batch_op.drop_column('is_activated')
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.alter_column(
"username", existing_type=sa.VARCHAR(length=60), nullable=False
)
batch_op.drop_column("is_activated")
# ### end Alembic commands ###

View File

@ -10,45 +10,57 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4ce4bacc7c06'
down_revision = '377fc0b7e4bb'
revision = "4ce4bacc7c06"
down_revision = "377fc0b7e4bb"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.alter_column('username',
existing_type=sa.VARCHAR(length=60),
type_=sa.String(length=64),
existing_nullable=True)
batch_op.alter_column('password_hash',
existing_type=sa.VARCHAR(length=255),
type_=sa.String(length=256),
existing_nullable=True)
batch_op.alter_column('wallet_id',
existing_type=sa.VARCHAR(length=255),
type_=sa.String(length=256),
existing_nullable=True)
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.alter_column(
"username",
existing_type=sa.VARCHAR(length=60),
type_=sa.String(length=64),
existing_nullable=True,
)
batch_op.alter_column(
"password_hash",
existing_type=sa.VARCHAR(length=255),
type_=sa.String(length=256),
existing_nullable=True,
)
batch_op.alter_column(
"wallet_id",
existing_type=sa.VARCHAR(length=255),
type_=sa.String(length=256),
existing_nullable=True,
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.alter_column('wallet_id',
existing_type=sa.String(length=256),
type_=sa.VARCHAR(length=255),
existing_nullable=True)
batch_op.alter_column('password_hash',
existing_type=sa.String(length=256),
type_=sa.VARCHAR(length=255),
existing_nullable=True)
batch_op.alter_column('username',
existing_type=sa.String(length=64),
type_=sa.VARCHAR(length=60),
existing_nullable=True)
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.alter_column(
"wallet_id",
existing_type=sa.String(length=256),
type_=sa.VARCHAR(length=255),
existing_nullable=True,
)
batch_op.alter_column(
"password_hash",
existing_type=sa.String(length=256),
type_=sa.VARCHAR(length=255),
existing_nullable=True,
)
batch_op.alter_column(
"username",
existing_type=sa.String(length=64),
type_=sa.VARCHAR(length=60),
existing_nullable=True,
)
# ### end Alembic commands ###

View File

@ -10,31 +10,38 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '5df1fabbee7d'
down_revision = '1dfa1f2c208f'
revision = "5df1fabbee7d"
down_revision = "1dfa1f2c208f"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('comments', schema=None) as batch_op:
batch_op.add_column(sa.Column('approved', sa.Boolean(), nullable=True))
batch_op.drop_column('included_with_interpretation')
with op.batch_alter_table("comments", schema=None) as batch_op:
batch_op.add_column(sa.Column("approved", sa.Boolean(), nullable=True))
batch_op.drop_column("included_with_interpretation")
with op.batch_alter_table('interpretations', schema=None) as batch_op:
batch_op.add_column(sa.Column('approved', sa.Boolean(), nullable=True))
with op.batch_alter_table("interpretations", schema=None) as batch_op:
batch_op.add_column(sa.Column("approved", sa.Boolean(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('interpretations', schema=None) as batch_op:
batch_op.drop_column('approved')
with op.batch_alter_table("interpretations", schema=None) as batch_op:
batch_op.drop_column("approved")
with op.batch_alter_table('comments', schema=None) as batch_op:
batch_op.add_column(sa.Column('included_with_interpretation', sa.BOOLEAN(), autoincrement=False, nullable=True))
batch_op.drop_column('approved')
with op.batch_alter_table("comments", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"included_with_interpretation",
sa.BOOLEAN(),
autoincrement=False,
nullable=True,
)
)
batch_op.drop_column("approved")
# ### end Alembic commands ###

View File

@ -10,28 +10,35 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7baa732e01c6'
down_revision = '0961578f302a'
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')
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')
op.drop_table("section_tags")
# ### end Alembic commands ###

View File

@ -10,29 +10,35 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '883298018384'
down_revision = '5df1fabbee7d'
revision = "883298018384"
down_revision = "5df1fabbee7d"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('interpretations', schema=None) as batch_op:
batch_op.drop_column('label')
with op.batch_alter_table("interpretations", schema=None) as batch_op:
batch_op.drop_column("label")
with op.batch_alter_table('sections', schema=None) as batch_op:
batch_op.drop_column('about')
with op.batch_alter_table("sections", schema=None) as batch_op:
batch_op.drop_column("about")
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('sections', schema=None) as batch_op:
batch_op.add_column(sa.Column('about', sa.TEXT(), autoincrement=False, nullable=True))
with op.batch_alter_table("sections", schema=None) as batch_op:
batch_op.add_column(
sa.Column("about", sa.TEXT(), autoincrement=False, nullable=True)
)
with op.batch_alter_table('interpretations', schema=None) as batch_op:
batch_op.add_column(sa.Column('label', sa.VARCHAR(length=256), autoincrement=False, nullable=False))
with op.batch_alter_table("interpretations", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"label", sa.VARCHAR(length=256), autoincrement=False, nullable=False
)
)
# ### end Alembic commands ###

View File

@ -10,23 +10,23 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a1345b416f81'
down_revision = '067a10a531d7'
revision = "a1345b416f81"
down_revision = "067a10a531d7"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('avatar_img', sa.Text(), nullable=True))
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.add_column(sa.Column("avatar_img", sa.Text(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.drop_column('avatar_img')
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.drop_column("avatar_img")
# ### end Alembic commands ###

View File

@ -13,8 +13,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@types/lodash.debounce": "^4.0.7",
"ethers": "5.5.3",
"flowbite": "^1.6.4",
"lodash.debounce": "^4.0.8",
"siwe": "1.0.0",
"tailwindcss": "^3.2.7"
},

View File

@ -46,6 +46,10 @@ export function addSection() {
`${collectionId}/${subCollectionId}/create_section`,
);
}
if (newActionPath.includes('/0')) {
console.log('ALERT');
return;
}
addSectionForm.setAttribute('action', `${newActionPath}`);
sectionModal.show();

View File

@ -24,6 +24,7 @@ import {scroll} from './scroll';
import {initCheckBoxTree} from './checkBoxTree';
import {initPermissions} from './permissions';
import {copyLink} from './copyLink';
import {quickSearch} from './quickSearch';
import {flash} from './flash';
import {slashSearch} from './slashSearch';
@ -53,5 +54,6 @@ scroll();
initCheckBoxTree();
initPermissions();
copyLink();
quickSearch();
flash();
slashSearch();

View File

@ -63,7 +63,7 @@ const multipleInputJs = () => {
tagsToSubmitInput.value = addedWords.join();
multipleInput.addEventListener('input', () => {
let inputValue = multipleInput.value.trim();
let inputValue = multipleInput.value.trim().toLowerCase();
if (inputValue.length > 32) {
multipleInput.value = inputValue.slice(0, 32);
return;

92
src/quickSearch.ts Normal file
View File

@ -0,0 +1,92 @@
import {Modal} from 'flowbite';
import type {ModalOptions, ModalInterface} from 'flowbite';
import debounce = require('lodash.debounce');
const currentSearchInput: HTMLInputElement =
document.querySelector('#mainSearchInput');
const searchDiv: HTMLElement = document.querySelector('#quickSearchModal');
const modalOptions: ModalOptions = {
closable: true,
backdrop: 'static',
onHide: () => {},
onShow: () => {},
onToggle: () => {},
};
const quickSearchModal: ModalInterface = new Modal(searchDiv, modalOptions);
export function quickSearch() {
if (currentSearchInput && searchDiv) {
currentSearchInput.addEventListener('input', debounce(onInputChange, 500));
currentSearchInput.addEventListener('keypress', async e => {
if (e.key === 'Enter') {
const urlParams = new URLSearchParams({
q: currentSearchInput.value.toLowerCase(),
});
const res = await fetch('/search_interpretations?' + urlParams);
if (res.status === 200) {
window.location.replace(res.url);
} else {
return;
}
}
});
}
}
const onInputChange = async (e: any) => {
e.preventDefault();
if (currentSearchInput.value.length > 0) {
const urlParams = new URLSearchParams({
search_query: currentSearchInput.value.toLowerCase(),
});
const res = await fetch('/quick_search?' + urlParams);
const json = await res.json();
if (res.status === 200) {
let emptyEntityArr = [];
for (const entity in json) {
// iterate over json from back end
const el: HTMLDivElement = document.querySelector(
`#quickSearchBlock-${entity}`,
);
const secondUnusedLink = document.querySelector(`.${entity}Text-1`);
const emptySearchDiv = document.querySelector('#emptyQuickSearchDiv');
if (secondUnusedLink) {
secondUnusedLink.classList.remove('hidden');
}
if (json[entity].length < 1) {
emptyEntityArr.push(entity);
if (el) {
el.classList.add('hidden');
}
}
if (json[entity].length == 1) {
if (secondUnusedLink) {
secondUnusedLink.classList.add('hidden');
}
}
if (emptyEntityArr.length === 4) {
emptySearchDiv.classList.remove('hidden');
}
for (const obj in json[entity]) {
// iterate over every entity in json
el.classList.remove('hidden');
emptySearchDiv.classList.add('hidden');
const link = document.querySelector(`#${entity}Text-${obj}`);
// taking needed html element for markup
if (link) {
// setting needed values to element
link.textContent = json[entity][obj].label;
link.setAttribute('href', json[entity][obj].url);
}
}
}
quickSearchModal.show();
} else {
return;
}
}
};

View File

@ -23,7 +23,7 @@ def test_create_tags_on_book_create_and_edit(client: FlaskClient):
book = m.Book.query.filter_by(label=BOOK_NAME).first()
assert book.tags
splitted_tags = [tag.title() for tag in tags.split(",")]
splitted_tags = [tag.lower() for tag in tags.split(",")]
assert len(book.tags) == 3
for tag in book.tags:
tag: m.Tag
@ -42,7 +42,7 @@ def test_create_tags_on_book_create_and_edit(client: FlaskClient):
book: m.Book = m.Book.query.first()
splitted_tags = [tag.title() for tag in tags.split(",")]
splitted_tags = [tag.lower() for tag in tags.split(",")]
assert len(book.tags) == 3
for tag in book.tags:
tag: m.Tag

View File

@ -133,6 +133,15 @@ def test_profile(client):
assert res.status_code == 200
assert str.encode(new_name) in res.data
# delete_avatar
assert user.avatar_img
res = client.post(
"/user/delete_avatar",
follow_redirects=True,
)
assert res
assert not user.avatar_img
# delete_profile
res = client.post(
"/user/profile_delete",

View File

@ -1113,6 +1113,18 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/lodash.debounce@^4.0.7":
version "4.0.7"
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz#0285879defb7cdb156ae633cecd62d5680eded9f"
integrity sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.194"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76"
integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==
"@types/mime@*":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10"
@ -2606,6 +2618,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"