mirror of https://github.com/logos-co/open-law.git
version CUD
This commit is contained in:
parent
62c42b7144
commit
c8b7878d61
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
@ -110,8 +111,6 @@ def create_app(environment="development"):
|
||||||
index_view=CustomAdminIndexView(),
|
index_view=CustomAdminIndexView(),
|
||||||
)
|
)
|
||||||
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.filterwarnings("ignore", "Fields missing from ruleset", UserWarning)
|
warnings.filterwarnings("ignore", "Fields missing from ruleset", UserWarning)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
from app import models as m
|
||||||
|
from app.logger import log
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_copy_collection(
|
||||||
|
collection: m.Collection, parent_id: int, version_id: int
|
||||||
|
):
|
||||||
|
collection_copy = m.Collection(
|
||||||
|
label=collection.label,
|
||||||
|
about=collection.about,
|
||||||
|
is_root=collection.is_root,
|
||||||
|
is_leaf=collection.is_leaf,
|
||||||
|
position=collection.position,
|
||||||
|
parent_id=parent_id,
|
||||||
|
version_id=version_id,
|
||||||
|
copy_of=collection.id,
|
||||||
|
)
|
||||||
|
log(log.INFO, "Create copy of collection [%s]", collection)
|
||||||
|
collection_copy.save()
|
||||||
|
|
||||||
|
if collection.active_sections:
|
||||||
|
for section in collection.active_sections:
|
||||||
|
section: m.Section
|
||||||
|
section_copy = m.Section(
|
||||||
|
label=section.label,
|
||||||
|
collection_id=collection_copy.id,
|
||||||
|
user_id=section.user_id,
|
||||||
|
version_id=version_id,
|
||||||
|
position=section.position,
|
||||||
|
copy_of=section.id,
|
||||||
|
)
|
||||||
|
log(log.INFO, "Create copy of section [%s]", section)
|
||||||
|
section_copy.save()
|
||||||
|
|
||||||
|
interpretation: m.Interpretation = section.approved_interpretation
|
||||||
|
if not interpretation:
|
||||||
|
continue
|
||||||
|
|
||||||
|
interpretation_copy = m.Interpretation(
|
||||||
|
text=interpretation.text,
|
||||||
|
plain_text=interpretation.plain_text,
|
||||||
|
approved=interpretation.approved,
|
||||||
|
user_id=interpretation.user_id,
|
||||||
|
section_id=section_copy.id,
|
||||||
|
copy_of=interpretation.id,
|
||||||
|
)
|
||||||
|
log(log.INFO, "Create copy of interpretation [%s]", interpretation_copy)
|
||||||
|
interpretation_copy.save()
|
||||||
|
|
||||||
|
comments: list[m.Comment] = section.approved_comments
|
||||||
|
for comment in comments:
|
||||||
|
comment_copy = m.Comment(
|
||||||
|
text=comment.text,
|
||||||
|
approved=comment.approved,
|
||||||
|
edited=comment.edited,
|
||||||
|
user_id=comment.user_id,
|
||||||
|
interpretation_id=interpretation_copy.id,
|
||||||
|
copy_of=comment.id,
|
||||||
|
)
|
||||||
|
log(log.INFO, "Create copy of comment [%s]", comment)
|
||||||
|
comment_copy.save()
|
||||||
|
|
||||||
|
elif collection.active_children:
|
||||||
|
for child in collection.active_children:
|
||||||
|
recursive_copy_collection(child, collection_copy.id, version_id)
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_version(book: m.Book, semver: str):
|
||||||
|
book_active_version: m.BookVersion = book.active_version
|
||||||
|
book_root_collection: m.Collection = book_active_version.root_collection
|
||||||
|
|
||||||
|
version: m.BookVersion = m.BookVersion(
|
||||||
|
semver=semver, derivative_id=book.active_version.id, book_id=book.id
|
||||||
|
)
|
||||||
|
log(log.INFO, "Create new version for book [%s]", book)
|
||||||
|
version.save()
|
||||||
|
|
||||||
|
root_collection = m.Collection(
|
||||||
|
label="Root Collection",
|
||||||
|
version_id=version.id,
|
||||||
|
is_root=True,
|
||||||
|
copy_of=book_root_collection.id,
|
||||||
|
).save()
|
||||||
|
|
||||||
|
for collection in book_root_collection.active_children:
|
||||||
|
recursive_copy_collection(collection, root_collection.id, version.id)
|
||||||
|
|
||||||
|
return version
|
|
@ -18,4 +18,4 @@ from .comment import CreateCommentForm
|
||||||
from .vote import VoteForm
|
from .vote import VoteForm
|
||||||
from .comment import CreateCommentForm, DeleteCommentForm, EditCommentForm
|
from .comment import CreateCommentForm, DeleteCommentForm, EditCommentForm
|
||||||
from .permission import EditPermissionForm
|
from .permission import EditPermissionForm
|
||||||
from .version import EditVersionForm, DeleteVersionForm
|
from .version import EditVersionForm, DeleteVersionForm, CreateVersionForm
|
||||||
|
|
|
@ -2,8 +2,6 @@ from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, SubmitField, IntegerField, ValidationError
|
from wtforms import StringField, SubmitField, IntegerField, ValidationError
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
from app.controllers import clean_html
|
|
||||||
from app.logger import log
|
|
||||||
from app import models as m, db
|
from app import models as m, db
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,3 +29,8 @@ class EditVersionForm(BaseVersionForm):
|
||||||
|
|
||||||
class DeleteVersionForm(BaseVersionForm):
|
class DeleteVersionForm(BaseVersionForm):
|
||||||
submit = SubmitField("Delete")
|
submit = SubmitField("Delete")
|
||||||
|
|
||||||
|
|
||||||
|
class CreateVersionForm(FlaskForm):
|
||||||
|
semver = StringField("Semver", validators=[DataRequired()])
|
||||||
|
submit = SubmitField("Delete")
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Collection(BaseModel):
|
||||||
is_root = db.Column(db.Boolean, default=False)
|
is_root = db.Column(db.Boolean, default=False)
|
||||||
is_leaf = db.Column(db.Boolean, default=False)
|
is_leaf = db.Column(db.Boolean, default=False)
|
||||||
position = db.Column(db.Integer, default=-1, nullable=True)
|
position = db.Column(db.Integer, default=-1, nullable=True)
|
||||||
|
copy_of = db.Column(db.Integer, default=0, nullable=True)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
version_id = db.Column(db.ForeignKey("book_versions.id"))
|
version_id = db.Column(db.ForeignKey("book_versions.id"))
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Comment(BaseModel):
|
||||||
text = db.Column(db.Text, unique=False, nullable=False)
|
text = db.Column(db.Text, unique=False, nullable=False)
|
||||||
approved = db.Column(db.Boolean, default=False)
|
approved = db.Column(db.Boolean, default=False)
|
||||||
edited = db.Column(db.Boolean, default=False)
|
edited = db.Column(db.Boolean, default=False)
|
||||||
|
copy_of = db.Column(db.Integer, default=0, nullable=True)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
user_id = db.Column(db.ForeignKey("users.id"))
|
user_id = db.Column(db.ForeignKey("users.id"))
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Interpretation(BaseModel):
|
||||||
text = db.Column(db.Text, unique=False, nullable=False)
|
text = db.Column(db.Text, unique=False, nullable=False)
|
||||||
plain_text = db.Column(db.Text, unique=False)
|
plain_text = db.Column(db.Text, unique=False)
|
||||||
approved = db.Column(db.Boolean, default=False)
|
approved = db.Column(db.Boolean, default=False)
|
||||||
|
copy_of = db.Column(db.Integer, default=0, nullable=True)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
user_id = db.Column(db.ForeignKey("users.id"))
|
user_id = db.Column(db.ForeignKey("users.id"))
|
||||||
|
|
|
@ -13,12 +13,13 @@ class Section(BaseModel):
|
||||||
__tablename__ = "sections"
|
__tablename__ = "sections"
|
||||||
|
|
||||||
label = db.Column(db.String(256), unique=False, nullable=False)
|
label = db.Column(db.String(256), unique=False, nullable=False)
|
||||||
|
position = db.Column(db.Integer, default=-1, nullable=True)
|
||||||
|
copy_of = db.Column(db.Integer, default=0, nullable=True)
|
||||||
|
|
||||||
# Foreign keys
|
# Foreign keys
|
||||||
collection_id = db.Column(db.ForeignKey("collections.id"))
|
collection_id = db.Column(db.ForeignKey("collections.id"))
|
||||||
user_id = db.Column(db.ForeignKey("users.id"))
|
user_id = db.Column(db.ForeignKey("users.id"))
|
||||||
version_id = db.Column(db.ForeignKey("book_versions.id"))
|
version_id = db.Column(db.ForeignKey("book_versions.id"))
|
||||||
position = db.Column(db.Integer, default=-1, nullable=True)
|
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
collection = db.relationship("Collection", viewonly=True)
|
collection = db.relationship("Collection", viewonly=True)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- Add contributor modal -->
|
||||||
|
<!-- prettier-ignore-->
|
||||||
|
<div id="edit-version-label-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<div class="relative w-full max-w-2xl max-h-full">
|
||||||
|
<!-- Modal content -->
|
||||||
|
<form action="{{ url_for('book.edit_version', book_id=book.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
|
{{ form_hidden_tag() }}
|
||||||
|
<!-- Modal header -->
|
||||||
|
<input type="hidden" name="version_id" id="version_id" value="0" class="version-id-input"/>
|
||||||
|
|
||||||
|
<div class="p-6 space-y-6">
|
||||||
|
<div>
|
||||||
|
<label for="semver-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
|
||||||
|
<input type="text" name="semver" id="semver-input" class="version-semver-input bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="1.0.0" required>
|
||||||
|
</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>
|
|
@ -2,7 +2,7 @@
|
||||||
<div id="delete_version_modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
<div id="delete_version_modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
<div class="relative w-full max-w-2xl max-h-full">
|
<div class="relative w-full max-w-2xl max-h-full">
|
||||||
<!-- Modal content -->
|
<!-- Modal content -->
|
||||||
<form action="{{ url_for('book.delete_contributor', book_id=book.id) }}" id="delete_version_modal_form" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<form action="{{ url_for('book.delete_version', book_id=book.id) }}" id="delete_version_modal_form" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
{{ form_hidden_tag() }}
|
{{ form_hidden_tag() }}
|
||||||
<!-- Modal header -->
|
<!-- Modal header -->
|
||||||
<input type="hidden" name="version_id" id="version_id" value="0" class="delete-version-id-input"/>
|
<input type="hidden" name="version_id" id="version_id" value="0" class="delete-version-id-input"/>
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
<!-- Add contributor modal -->
|
<!-- Add contributor modal -->
|
||||||
<!-- prettier-ignore-->
|
<!-- prettier-ignore-->
|
||||||
<div id="edit-version-label-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
<div id="add-version-modal" tabindex="-1" aria-hidden="true" class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
<div class="relative w-full max-w-2xl max-h-full">
|
<div class="relative w-full max-w-2xl max-h-full">
|
||||||
<!-- Modal content -->
|
<!-- Modal content -->
|
||||||
<form action="{{ url_for('book.edit_version', book_id=book.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<form action="{{ url_for('book.create_version', book_id=book.id) }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
{{ form_hidden_tag() }}
|
{{ form_hidden_tag() }}
|
||||||
<!-- Modal header -->
|
<!-- Modal header -->
|
||||||
<input type="hidden" name="version_id" id="version_id" value="0" class="version-id-input"/>
|
|
||||||
|
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label for="semver-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
|
<label for="semver-input" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
{% include 'book/modals/delete_contributor_modal.html' %}
|
{% include 'book/modals/delete_contributor_modal.html' %}
|
||||||
{% include 'book/modals/edit_version_label_modal.html' %}
|
{% include 'book/modals/edit_version_label_modal.html' %}
|
||||||
{% include 'book/modals/delete_version_modal.html' %}
|
{% include 'book/modals/delete_version_modal.html' %}
|
||||||
|
{% include 'book/modals/edit_version_label_modal.html' %}
|
||||||
|
|
||||||
<!-- Hide right_sidebar -->
|
<!-- Hide right_sidebar -->
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
|
@ -188,7 +189,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for version in book.versions %}
|
{% for version in book.versions if not version.is_deleted and not version.is_active %}
|
||||||
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
|
<tr class="bg-white border-b dark:bg-gray-900 dark:border-gray-700">
|
||||||
<td class="px-6 truncate max-w-[280]">{{ version.semver }}</td>
|
<td class="px-6 truncate max-w-[280]">{{ version.semver }}</td>
|
||||||
<td class="px-6"> {{ version.created_at }} </td>
|
<td class="px-6"> {{ version.created_at }} </td>
|
||||||
|
@ -198,7 +199,7 @@
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-4">
|
<td class="px-4 py-4">
|
||||||
<button type="button" data-modal-target="delete_version_modal" data-modal-toggle="delete_version_modal" class="delete-version-btn 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">
|
<button type="button" data-version-id="{{ version.id }}" data-modal-target="delete_version_modal" data-modal-toggle="delete_version_modal" class="delete-version-btn 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
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -207,7 +208,7 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" data-modal-target="add-version-modal" data-modal-toggle="add-versions-modal" class="add-versions-btn text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
<button type="button" data-modal-target="add-version-modal" data-modal-toggle="add-version-modal" class="add-versions-btn text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 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 mr-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> </svg>
|
<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-1"> <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> </svg>
|
||||||
New Version
|
New Version
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,50 +1,38 @@
|
||||||
from flask import render_template, flash, redirect, url_for, request
|
from flask import flash, redirect, url_for
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from sqlalchemy import and_, or_
|
|
||||||
|
|
||||||
from app.controllers import (
|
from app.controllers import (
|
||||||
create_pagination,
|
|
||||||
register_book_verify_route,
|
register_book_verify_route,
|
||||||
)
|
)
|
||||||
from app.controllers.tags import (
|
|
||||||
set_book_tags,
|
|
||||||
)
|
|
||||||
from app.controllers.delete_nested_book_entities import (
|
|
||||||
delete_nested_book_entities,
|
|
||||||
)
|
|
||||||
from app.controllers.create_access_groups import (
|
|
||||||
create_editor_group,
|
|
||||||
create_moderator_group,
|
|
||||||
)
|
|
||||||
from app.controllers.require_permission import require_permission
|
|
||||||
from app import models as m, db, forms as f
|
from app import models as m, db, forms as f
|
||||||
|
from app.controllers.version import create_new_version
|
||||||
|
from app.controllers.delete_nested_book_entities import delete_nested_version_entities
|
||||||
from app.logger import log
|
from app.logger import log
|
||||||
from .bp import bp
|
from .bp import bp
|
||||||
|
|
||||||
|
|
||||||
# @bp.route("/all", methods=["GET"])
|
@bp.route("/<int:book_id>/create_version", methods=["POST"])
|
||||||
# def get_all():
|
|
||||||
# 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,
|
|
||||||
# all_books=True,
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/create_version", methods=["POST"])
|
|
||||||
@login_required
|
@login_required
|
||||||
def create_version():
|
def create_version(book_id):
|
||||||
raise NotImplementedError
|
form: f.CreateVersionForm = f.CreateVersionForm()
|
||||||
|
|
||||||
|
redirect_url = url_for("book.settings", selected_tab="versions", book_id=book_id)
|
||||||
|
if form.validate_on_submit():
|
||||||
|
book = db.session.get(m.Book, book_id)
|
||||||
|
if book.user_id != current_user.id:
|
||||||
|
flash("You are not owner of this book", "warning")
|
||||||
|
return redirect(redirect_url)
|
||||||
|
create_new_version(book, form.semver.data)
|
||||||
|
flash("Success!", "success")
|
||||||
|
return redirect(redirect_url)
|
||||||
|
else:
|
||||||
|
log(log.ERROR, "Create version 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>/edit_version", methods=["POST"])
|
@bp.route("/<int:book_id>/edit_version", methods=["POST"])
|
||||||
|
@ -75,7 +63,7 @@ def edit_version(book_id: int):
|
||||||
flash("Success!", "success")
|
flash("Success!", "success")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
else:
|
else:
|
||||||
log(log.ERROR, "Section edit errors: [%s]", form.errors)
|
log(log.ERROR, "Edit version errors: [%s]", form.errors)
|
||||||
for field, errors in form.errors.items():
|
for field, errors in form.errors.items():
|
||||||
field_label = form._fields[field].label.text
|
field_label = form._fields[field].label.text
|
||||||
for error in errors:
|
for error in errors:
|
||||||
|
@ -105,14 +93,15 @@ def delete_version(book_id: int):
|
||||||
flash("You cant delete active version", "warning")
|
flash("You cant delete active version", "warning")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
# TODO delete nested items
|
version.is_deleted = True
|
||||||
# log(log.INFO, "Edit version [%s]", version)
|
delete_nested_version_entities(version)
|
||||||
# version.save()
|
log(log.INFO, "Delete version [%s]", version)
|
||||||
|
version.save()
|
||||||
|
|
||||||
flash("Success!", "success")
|
flash("Success!", "success")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
else:
|
else:
|
||||||
log(log.ERROR, "Section edit errors: [%s]", form.errors)
|
log(log.ERROR, "Delete version errors: [%s]", form.errors)
|
||||||
for field, errors in form.errors.items():
|
for field, errors in form.errors.items():
|
||||||
field_label = form._fields[field].label.text
|
field_label = form._fields[field].label.text
|
||||||
for error in errors:
|
for error in errors:
|
||||||
|
|
|
@ -10,25 +10,27 @@ import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '03e8384a23e1'
|
revision = "03e8384a23e1"
|
||||||
down_revision = 'a41f004cad1a'
|
down_revision = "a41f004cad1a"
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('book_versions', schema=None) as batch_op:
|
with op.batch_alter_table("book_versions", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('is_active', sa.Boolean(), nullable=True))
|
batch_op.add_column(sa.Column("is_active", sa.Boolean(), nullable=True))
|
||||||
batch_op.drop_column('exported')
|
batch_op.drop_column("exported")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('book_versions', schema=None) as batch_op:
|
with op.batch_alter_table("book_versions", schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('exported', sa.BOOLEAN(), autoincrement=False, nullable=True))
|
batch_op.add_column(
|
||||||
batch_op.drop_column('is_active')
|
sa.Column("exported", sa.BOOLEAN(), autoincrement=False, nullable=True)
|
||||||
|
)
|
||||||
|
batch_op.drop_column("is_active")
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
"""copy of fields
|
||||||
|
|
||||||
|
Revision ID: ef2254f9bc92
|
||||||
|
Revises: 03e8384a23e1
|
||||||
|
Create Date: 2023-06-13 09:43:26.575245
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "ef2254f9bc92"
|
||||||
|
down_revision = "03e8384a23e1"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table("collections", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("copy_of", sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("copy_of", sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("copy_of", sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
with op.batch_alter_table("sections", schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("copy_of", sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
# ### 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.drop_column("copy_of")
|
||||||
|
|
||||||
|
with op.batch_alter_table("interpretations", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("copy_of")
|
||||||
|
|
||||||
|
with op.batch_alter_table("comments", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("copy_of")
|
||||||
|
|
||||||
|
with op.batch_alter_table("collections", schema=None) as batch_op:
|
||||||
|
batch_op.drop_column("copy_of")
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
|
@ -6,9 +6,10 @@ export function initDeleteVersion() {
|
||||||
if (versionIdInput) {
|
if (versionIdInput) {
|
||||||
const deleteBtns = document.querySelectorAll('.delete-version-btn');
|
const deleteBtns = document.querySelectorAll('.delete-version-btn');
|
||||||
deleteBtns.forEach(el => {
|
deleteBtns.forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
const versionId = el.getAttribute('data-version-id');
|
const versionId = el.getAttribute('data-version-id');
|
||||||
|
|
||||||
versionIdInput.value = versionId;
|
versionIdInput.value = versionId;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@ export function initEditVersion() {
|
||||||
if (versionIdInput && versionSemverInput) {
|
if (versionIdInput && versionSemverInput) {
|
||||||
const editBtns = document.querySelectorAll('.edit-version-label-btns');
|
const editBtns = document.querySelectorAll('.edit-version-label-btns');
|
||||||
editBtns.forEach(el => {
|
editBtns.forEach(el => {
|
||||||
|
el.addEventListener('click', () => {
|
||||||
const versionId = el.getAttribute('data-version-id');
|
const versionId = el.getAttribute('data-version-id');
|
||||||
const versionSemver = el.getAttribute('data-version-semver');
|
const versionSemver = el.getAttribute('data-version-semver');
|
||||||
|
|
||||||
versionIdInput.value = versionId;
|
versionIdInput.value = versionId;
|
||||||
versionSemverInput.value = versionSemver;
|
versionSemverInput.value = versionSemver;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from flask import current_app as Response
|
from flask import current_app as Response
|
||||||
from flask.testing import FlaskClient, FlaskCliRunner
|
from flask.testing import FlaskClient
|
||||||
|
|
||||||
from app import models as m, db
|
from app import models as m, db
|
||||||
from app.controllers.create_access_groups import create_moderator_group
|
|
||||||
from tests.utils import (
|
from tests.utils import (
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
create_book,
|
create_book,
|
||||||
|
check_if_nested_version_entities_is_deleted,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,17 +128,34 @@ def test_delete_version(client: FlaskClient):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert b"Invalid version id" in response.data
|
assert b"Invalid version id" in response.data
|
||||||
|
|
||||||
# TODO improve test to check if nested items are deleted
|
response: Response = client.post(
|
||||||
# response: Response = client.post(
|
f"/book/{book_2.id}/create_version",
|
||||||
# f"/book/{book.id}/delete_version",
|
data=dict(
|
||||||
# data=dict(
|
semver="MyVer",
|
||||||
# version_id=book_2.versions[0].id,
|
),
|
||||||
# ),
|
follow_redirects=True,
|
||||||
# follow_redirects=True,
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
# assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
# assert b"Success" in response.data
|
assert b"Success" in response.data
|
||||||
|
assert len(book_2.versions) == 2
|
||||||
|
|
||||||
|
new_version: m.BookVersion = book_2.versions[-1]
|
||||||
|
|
||||||
|
for collection in new_version.root_collection.active_children:
|
||||||
|
recursive_copy_collection(collection)
|
||||||
|
|
||||||
|
response: Response = client.post(
|
||||||
|
f"/book/{book_2.id}/delete_version",
|
||||||
|
data=dict(
|
||||||
|
version_id=new_version.id,
|
||||||
|
),
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"Success" in response.data
|
||||||
|
check_if_nested_version_entities_is_deleted(new_version)
|
||||||
|
|
||||||
logout(client)
|
logout(client)
|
||||||
login(client, username="test_user")
|
login(client, username="test_user")
|
||||||
|
@ -153,3 +170,89 @@ def test_delete_version(client: FlaskClient):
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert b"You are not owner of this book" in response.data
|
assert b"You are not owner of this book" in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def recursive_copy_collection(collection: m.Collection):
|
||||||
|
collection: m.Collection
|
||||||
|
assert collection.copy_of
|
||||||
|
|
||||||
|
copy_of: m.Collection = db.session.get(m.Collection, collection.copy_of)
|
||||||
|
assert collection.label == copy_of.label
|
||||||
|
assert collection.about == copy_of.about
|
||||||
|
assert collection.is_root == copy_of.is_root
|
||||||
|
assert collection.is_leaf == copy_of.is_leaf
|
||||||
|
assert collection.position == copy_of.position
|
||||||
|
|
||||||
|
if collection.active_sections:
|
||||||
|
for section in collection.active_sections:
|
||||||
|
section: m.Section
|
||||||
|
copy_of = db.session.get(m.Section, section.copy_of)
|
||||||
|
assert section.label == copy_of.label
|
||||||
|
assert section.position == copy_of.position
|
||||||
|
|
||||||
|
interpretations: list[m.Interpretation] = section.approved_interpretation
|
||||||
|
for interpretation in interpretations:
|
||||||
|
interpretation: m.Interpretation
|
||||||
|
assert interpretation.copy_of
|
||||||
|
|
||||||
|
copy_of: m.Interpretation = db.session.get(
|
||||||
|
m.Interpretation, interpretation.copy_of
|
||||||
|
)
|
||||||
|
assert interpretation.text == copy_of.text
|
||||||
|
assert interpretation.plain_text == copy_of.plain_text
|
||||||
|
assert interpretation.approved == copy_of.approved
|
||||||
|
|
||||||
|
comments: list[m.Comment] = section.approved_comments
|
||||||
|
for comment in comments:
|
||||||
|
comment: m.Comment
|
||||||
|
assert comment.copy_of
|
||||||
|
|
||||||
|
copy_of: m.Comment = db.session.get(m.Comment, comment.copy_of)
|
||||||
|
assert comment.text == copy_of.text
|
||||||
|
assert comment.approved == copy_of.approved
|
||||||
|
assert comment.edited == copy_of.edited
|
||||||
|
|
||||||
|
elif collection.active_children:
|
||||||
|
for child in collection.active_children:
|
||||||
|
recursive_copy_collection(child)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_version(client):
|
||||||
|
login(client)
|
||||||
|
|
||||||
|
book: m.Book = create_book(client)
|
||||||
|
book_2: m.Book = create_book(client)
|
||||||
|
|
||||||
|
logout(client)
|
||||||
|
login(client, "test_2")
|
||||||
|
|
||||||
|
response: Response = client.post(
|
||||||
|
f"/book/{book.id}/create_version",
|
||||||
|
data=dict(
|
||||||
|
semver="MyVer",
|
||||||
|
),
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"You are not owner of this book" in response.data
|
||||||
|
|
||||||
|
logout(client)
|
||||||
|
login(client)
|
||||||
|
|
||||||
|
response: Response = client.post(
|
||||||
|
f"/book/{book.id}/create_version",
|
||||||
|
data=dict(
|
||||||
|
semver="MyVer",
|
||||||
|
),
|
||||||
|
follow_redirects=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b"Success" in response.data
|
||||||
|
assert len(book.versions) == 2
|
||||||
|
|
||||||
|
new_version: m.BookVersion = book.versions[-1]
|
||||||
|
|
||||||
|
for collection in new_version.root_collection.active_children:
|
||||||
|
recursive_copy_collection(collection)
|
||||||
|
|
Loading…
Reference in New Issue