diff --git a/mo/db.py b/mo/db.py index d531c51b62f883f877b6116f4f985839694977bf..d8b2a9ae47379a1fe9b307a4c6b44f3c7837187c 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 77b1d875af8c8a3c622921981f35c70c997b23ff..7fd69de53780c9ac48ead9bfe7ac7ca22d064722 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 b42dc5b6f7b292abcef9fa4c5e12e1e7ecf49493..5f403b16ad589d8c1400b6c60a11f4419970ccd9 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 b3f4a40a7af22d3c6596836aa778e631360cb568..d0fca3eaf05a184d0364e07a9f9e0b5e66b2c56e 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> → -{% 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 0000000000000000000000000000000000000000..935e480d8f49440b202811121690f334d276a080 --- /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 %}→{% 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 %}→{% 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 %}