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> &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 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 %}&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 %}