From 5f58f6cd2b5fe0b199592b711fd37fbe73c2f39e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ji=C5=99=C3=AD=20Setni=C4=8Dka?= <setnicka@seznam.cz>
Date: Thu, 31 Dec 2020 16:59:17 +0100
Subject: [PATCH] =?UTF-8?q?P=C5=99esuny=20m=C3=ADst?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Dvojkrokově, nejdříve vyhledání a pak přesun. Lze přesunout jen pod místa,
která neporuší constrain na level daného typu a která mohu editovat.
---
 mo/db.py                      | 10 +++++
 mo/web/org.py                 | 76 +++++++++++++++++++++++++++++++++++
 templates/org_place.html      |  1 +
 templates/org_place_edit.html | 12 ------
 templates/org_place_move.html | 42 +++++++++++++++++++
 5 files changed, 129 insertions(+), 12 deletions(-)
 create mode 100644 templates/org_place_move.html

diff --git a/mo/db.py b/mo/db.py
index d531c51b..d8b2a9ae 100644
--- a/mo/db.py
+++ b/mo/db.py
@@ -101,6 +101,16 @@ class Place(Base):
         return len(PlaceType.choices(level=self.level + 1)) > 0
 
 
+def place_by_code(code: str) -> Optional[Place]:
+    if code.startswith("#"):
+        try:
+            id = int(code[1:])
+            return get_session().query(Place).get(id)
+        except ValueError:
+            return None
+    return get_session().query(Place).filter_by(code=code).first()
+
+
 class School(Base):
     __tablename__ = 'schools'
 
diff --git a/mo/web/org.py b/mo/web/org.py
index 77b1d875..7fd69de5 100644
--- a/mo/web/org.py
+++ b/mo/web/org.py
@@ -157,6 +157,82 @@ def org_place_edit(id: int):
     )
 
 
+class PlaceMoveForm(FlaskForm):
+    code = wtforms.StringField(validators=[validators.DataRequired()])
+    submit = wtforms.SubmitField('Najít místo')
+    reset = wtforms.HiddenField()
+    move = wtforms.HiddenField()
+
+
+class PlaceMoveConfirmForm(FlaskForm):
+    code = wtforms.HiddenField()
+    reset = wtforms.SubmitField('Zrušit')
+    move = wtforms.SubmitField('Přesunout')
+
+
+@app.route('/org/place/<int:id>/move', methods=('GET', 'POST'))
+def org_place_move(id: int):
+    sess = db.get_session()
+
+    # Tests: can move only existing places that we can edit
+    place = sess.query(db.Place).get(id)
+    if not place:
+        raise werkzeug.exceptions.NotFound()
+    rr = mo.rights.Rights(g.user)
+    rr.get_for(place)
+    if not rr.can_edit_place(place):
+        raise werkzeug.exceptions.Forbidden()
+
+    parents = reversed(db.get_place_parents(place)[1:])  # without place itself and from the top
+    new_parents = None
+    search_error = None
+
+    form = PlaceMoveForm()
+    form_confirm = None
+    if form.validate_on_submit():
+        if form.reset.data:
+            return redirect(url_for('org_place_move', id=id))
+
+        new_parent = db.place_by_code(form.code.data)
+        if not new_parent:
+            search_error = 'Místo s tímto kódem se nepovedlo nalézt'
+        else:
+            new_parents = reversed(db.get_place_parents(new_parent))
+            (_, levels) = db.place_type_names_and_levels[place.type]
+
+            rr.get_for(new_parent)
+            if not rr.can_edit_place(new_parent):
+                search_error = 'Nemáte právo k editaci vybraného nadřazeného místa, přesun nelze uskutečnit'
+            elif (new_parent.level + 1) not in levels:
+                search_error = f'Toto místo ({place.type_name()}) nelze přemístit pod vybrané místo ({new_parent.type_name()}), dostalo by se na nepovolený level'
+            elif new_parent.place_id == place.parent:
+                search_error = 'Žádná změna, místo je zde již umístěno'
+            elif form.move.data:
+                # Everything is OK, if submitted with 'move' do the move
+                place.parent = new_parent.place_id
+                place.level = new_parent.level + 1
+                changes = db.get_object_changes(place)
+                mo.util.log(
+                    type=db.LogType.place,
+                    what=id,
+                    details={'action': 'move', 'changes': changes},
+                )
+                app.logger.info(f"Place {id} moved, changes: {changes}")
+                db.get_session().commit()
+                flash('Místo úspěšně přesunuto', 'success')
+                return redirect(url_for('org_place', id=id))
+            else:
+                # OK but not confirmed yet, display the confirm form
+                form_confirm = PlaceMoveConfirmForm()
+                form_confirm.code.data = form.code.data
+
+    return render_template(
+        'org_place_move.html',
+        place=place, form=form, form_confirm=form_confirm, search_error=search_error,
+        parents=parents, new_parents=new_parents
+    )
+
+
 @app.route('/org/place/<int:id>/delete', methods=('POST',))
 def org_place_delete(id: int):
     sess = db.get_session()
diff --git a/templates/org_place.html b/templates/org_place.html
index b42dc5b6..5f403b16 100644
--- a/templates/org_place.html
+++ b/templates/org_place.html
@@ -18,6 +18,7 @@
 {% if can_edit %}
 	<div class="btn-group" role="group" style="margin: 10px 0px;">
 		<a class="btn btn-primary" href="{{ url_for('org_place_edit', id=place.place_id) }}">Editovat</a>
+		<a class="btn btn-primary" href="{{ url_for('org_place_move', id=place.place_id) }}">Přesunout</a>
 		<form class="btn-group" method="POST" onsubmit="return confirm('Opravdu nenávratně smazat?')" action='{{ url_for('org_place_delete', id=place.place_id) }}'>
 			<input class="btn btn-danger" type=submit value='Smazat'>
 		</form>
diff --git a/templates/org_place_edit.html b/templates/org_place_edit.html
index b3f4a40a..d0fca3ea 100644
--- a/templates/org_place_edit.html
+++ b/templates/org_place_edit.html
@@ -11,16 +11,4 @@
 
 	{{ wtf.quick_form(form, form_type='horizontal') }}
 
-<hr>
-
-<h3>Přesun místa</h3>
-
-<p><b>Nadřazená místa:</b>
-{% for parent in parents %}
-	<a href='{{ url_for('org_place', id=parent.place_id) }}'>{{ parent.name }}</a> &rightarrow;
-{% endfor %}
-<i>{{ place.name }}</i></p>
-
-<p>TODO: přesun pod jiné místo</p>
-
 {% endblock %}
diff --git a/templates/org_place_move.html b/templates/org_place_move.html
new file mode 100644
index 00000000..935e480d
--- /dev/null
+++ b/templates/org_place_move.html
@@ -0,0 +1,42 @@
+{% extends "base.html" %}
+{% import "bootstrap/wtf.html" as wtf %}
+{% block body %}
+<h2>{{ place.type_name().title() }}: {{ place.name }}</h2>
+
+<a href='{{ url_for('org_place', id=place.place_id) }}'>Zpět na detail místa</a>
+
+<hr>
+
+<h3>Přesun místa</h3>
+
+<p><b>Současné nadřazené místo:</b>
+{% for parent in parents %}
+	{% if loop.index != 1 %}&rightarrow;{% endif %}<a href='{{ url_for('org_place', id=parent.place_id) }}'>{{ parent.name }}</a>
+{% endfor %}
+</p>
+
+<h4>Krok 1: Vyhledání místa</h4>
+<p>Vyhledejte místo podle jeho kódu. Můžete zadat i ID ve formátu <code>#123</code>.</p>
+
+{{ wtf.quick_form(form, form_type='inline') }}
+
+{% if new_parents %}
+<p><b>Vyhledané nadřazené místo:</b>
+{% for parent in new_parents %}
+	{% if loop.index != 1 %}&rightarrow;{% endif %} <a href='{{ url_for('org_place', id=parent.place_id) }}'>{{ parent.name }}</a>
+{% endfor %}
+</p>
+{% endif %}
+
+{% if search_error %}
+<div class="alert alert-danger" role="alert">
+	{{ search_error }}
+</div>
+{% endif %}
+
+{% if form_confirm %}
+<h4>Krok 2: Potvrdit přesun</h4>
+{{ wtf.quick_form(form_confirm, form_type='inline') }}
+{% endif %}
+
+{% endblock %}
-- 
GitLab