Race conditions in tree operations causing Internal Server Errors
### Describe the Bug
<!-- A clear and concise description of what the bug is. -->
When content forms are submitted several times in succession too quickly, the same translation version can be created multiple times.

### Steps to Reproduce

Send edit requests multiple times after another (for me, it didn't work to push the "save" button multiple times, but I could simulate this beaviour and we know that it already happened in the production system, so somehow this problem definitely exists)

Maybe, this is a combination of autosave and the real saving process?

### Expected Behavior
<!-- A clear and concise description of what you expected to happen. -->
There should only exist exactly one database object for each translation version.

### Actual Behavior
<!-- A clear and concise description of what actually happened. -->
In some cases, a version can be created multiple times, which breaks the CMS functionality in multiple places.

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f88c15941..3ca1e67b8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ UNRELEASED
 * [ [#1578](https://github.com/digitalfabrik/integreat-cms/issues/1578) ] Add option to hide files in global media library
 * [ [#1752](https://github.com/digitalfabrik/integreat-cms/issues/1752) ] Show chapter for internal link suggestions
 * [ [#1965](https://github.com/digitalfabrik/integreat-cms/issues/1965) ] Add ical rrule for recurring events
+* [ [#1518](https://github.com/digitalfabrik/integreat-cms/issues/1518) ] Prevent database corruption when moving 2 pages at the same time
 
 
 2023.2.0
diff --git a/Pipfile.lock b/Pipfile.lock
index 3fd74b420..eefd1425c 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -506,6 +506,13 @@
             "markers": "python_version >= '3.7'",
             "version": "==3.13.0"
         },
+        "django-db-mutex": {
+            "hashes": [
+                "sha256:a2c882ec677b80b931a33ae902fe347a634de64a43b5ac3da4e5d64230ae2afe",
+                "sha256:dadbd6fdae070749ddbb76a86b9fb68a5e45228b366f707868267468f1aad774"
+            ],
+            "version": "==3.0.0"
+        },
         "django-debug-toolbar": {
             "hashes": [
                 "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27",
@@ -1036,45 +1043,45 @@
         },
         "pydantic": {
             "hashes": [
-                "sha256:05a81b006be15655b2a1bae5faa4280cf7c81d0e09fcb49b342ebf826abe5a72",
-                "sha256:0b53e1d41e97063d51a02821b80538053ee4608b9a181c1005441f1673c55423",
-                "sha256:2b3ce5f16deb45c472dde1a0ee05619298c864a20cded09c4edd820e1454129f",
-                "sha256:2e82a6d37a95e0b1b42b82ab340ada3963aea1317fd7f888bb6b9dfbf4fff57c",
-                "sha256:301d626a59edbe5dfb48fcae245896379a450d04baeed50ef40d8199f2733b06",
-                "sha256:39f4a73e5342b25c2959529f07f026ef58147249f9b7431e1ba8414a36761f53",
-                "sha256:4948f264678c703f3877d1c8877c4e3b2e12e549c57795107f08cf70c6ec7774",
-                "sha256:4b05697738e7d2040696b0a66d9f0a10bec0efa1883ca75ee9e55baf511909d6",
-                "sha256:51bdeb10d2db0f288e71d49c9cefa609bca271720ecd0c58009bd7504a0c464c",
-                "sha256:55b1625899acd33229c4352ce0ae54038529b412bd51c4915349b49ca575258f",
-                "sha256:572066051eeac73d23f95ba9a71349c42a3e05999d0ee1572b7860235b850cc6",
-                "sha256:6a05a9db1ef5be0fe63e988f9617ca2551013f55000289c671f71ec16f4985e3",
-                "sha256:6dc1cc241440ed7ca9ab59d9929075445da6b7c94ced281b3dd4cfe6c8cff817",
-                "sha256:6e7124d6855b2780611d9f5e1e145e86667eaa3bd9459192c8dc1a097f5e9903",
-                "sha256:75d52162fe6b2b55964fbb0af2ee58e99791a3138588c482572bb6087953113a",
-                "sha256:78cec42b95dbb500a1f7120bdf95c401f6abb616bbe8785ef09887306792e66e",
-                "sha256:7feb6a2d401f4d6863050f58325b8d99c1e56f4512d98b11ac64ad1751dc647d",
-                "sha256:8775d4ef5e7299a2f4699501077a0defdaac5b6c4321173bcb0f3c496fbadf85",
-                "sha256:887ca463c3bc47103c123bc06919c86720e80e1214aab79e9b779cda0ff92a00",
-                "sha256:9193d4f4ee8feca58bc56c8306bcb820f5c7905fd919e0750acdeeeef0615b28",
-                "sha256:983e720704431a6573d626b00662eb78a07148c9115129f9b4351091ec95ecc3",
-                "sha256:990406d226dea0e8f25f643b370224771878142155b879784ce89f633541a024",
-                "sha256:9cbdc268a62d9a98c56e2452d6c41c0263d64a2009aac69246486f01b4f594c4",
-                "sha256:a48f1953c4a1d9bd0b5167ac50da9a79f6072c63c4cef4cf2a3736994903583e",
-                "sha256:a9a6747cac06c2beb466064dda999a13176b23535e4c496c9d48e6406f92d42d",
-                "sha256:a9f2de23bec87ff306aef658384b02aa7c32389766af3c5dee9ce33e80222dfa",
-                "sha256:b5635de53e6686fe7a44b5cf25fcc419a0d5e5c1a1efe73d49d48fe7586db854",
-                "sha256:b6f9d649892a6f54a39ed56b8dfd5e08b5f3be5f893da430bed76975f3735d15",
-                "sha256:b9a3859f24eb4e097502a3be1fb4b2abb79b6103dd9e2e0edb70613a4459a648",
-                "sha256:cd8702c5142afda03dc2b1ee6bc358b62b3735b2cce53fc77b31ca9f728e4bc8",
-                "sha256:d7b5a3821225f5c43496c324b0d6875fde910a1c2933d726a743ce328fbb2a8c",
-                "sha256:d88c4c0e5c5dfd05092a4b271282ef0588e5f4aaf345778056fc5259ba098857",
-                "sha256:eb992a1ef739cc7b543576337bebfc62c0e6567434e522e97291b251a41dad7f",
-                "sha256:f2f7eb6273dd12472d7f218e1fef6f7c7c2f00ac2e1ecde4db8824c457300416",
-                "sha256:fdf88ab63c3ee282c76d652fc86518aacb737ff35796023fae56a65ced1a5978",
-                "sha256:fdf8d759ef326962b4678d89e275ffc55b7ce59d917d9f72233762061fd04a2d"
+                "sha256:1fd326aff5d6c36f05735c7c9b3d5b0e933b4ca52ad0b6e4b38038d82703d35b",
+                "sha256:2185a3b3d98ab4506a3f6707569802d2d92c3a7ba3a9a35683a7709ea6c2aaa2",
+                "sha256:261f357f0aecda005934e413dfd7aa4077004a174dafe414a8325e6098a8e419",
+                "sha256:305d0376c516b0dfa1dbefeae8c21042b57b496892d721905a6ec6b79494a66d",
+                "sha256:3257bd714de9db2102b742570a56bf7978e90441193acac109b1f500290f5718",
+                "sha256:3353072625ea2a9a6c81ad01b91e5c07fa70deb06368c71307529abf70d23325",
+                "sha256:36e44a4de37b8aecffa81c081dbfe42c4d2bf9f6dff34d03dce157ec65eb0f15",
+                "sha256:3bb99cf9655b377db1a9e47fa4479e3330ea96f4123c6c8200e482704bf1eda2",
+                "sha256:3f9d9b2be177c3cb6027cd67fbf323586417868c06c3c85d0d101703136e6b31",
+                "sha256:45edea10b75d3da43cfda12f3792833a3fa70b6eee4db1ed6aed528cef17c74e",
+                "sha256:51782fd81f09edcf265823c3bf43ff36d00db246eca39ee765ef58dc8421a642",
+                "sha256:532e97c35719f137ee5405bd3eeddc5c06eb91a032bc755a44e34a712420daf3",
+                "sha256:58e41dd1e977531ac6073b11baac8c013f3cd8706a01d3dc74e86955be8b2c0c",
+                "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb",
+                "sha256:5f3bc8f103b56a8c88021d481410874b1f13edf6e838da607dcb57ecff9b4594",
+                "sha256:63200cd8af1af2c07964546b7bc8f217e8bda9d0a2ef0ee0c797b36353914984",
+                "sha256:663d2dd78596c5fa3eb996bc3f34b8c2a592648ad10008f98d1348be7ae212fb",
+                "sha256:6a4b0aab29061262065bbdede617ef99cc5914d1bf0ddc8bcd8e3d7928d85bd6",
+                "sha256:6bb0452d7b8516178c969d305d9630a3c9b8cf16fcf4713261c9ebd465af0d73",
+                "sha256:72ef3783be8cbdef6bca034606a5de3862be6b72415dc5cb1fb8ddbac110049a",
+                "sha256:76c930ad0746c70f0368c4596020b736ab65b473c1f9b3872310a835d852eb19",
+                "sha256:7c5b94d598c90f2f46b3a983ffb46ab806a67099d118ae0da7ef21a2a4033b28",
+                "sha256:7ce1612e98c6326f10888df951a26ec1a577d8df49ddcaea87773bfbe23ba5cc",
+                "sha256:8481dca324e1c7b715ce091a698b181054d22072e848b6fc7895cd86f79b4449",
+                "sha256:87f831e81ea0589cd18257f84386bf30154c5f4bed373b7b75e5cb0b5d53ea87",
+                "sha256:9a9d9155e2a9f38b2eb9374c88f02fd4d6851ae17b65ee786a87d032f87008f8",
+                "sha256:9e337ac83686645a46db0e825acceea8e02fca4062483f40e9ae178e8bd1103a",
+                "sha256:b429f7c457aebb7fbe7cd69c418d1cd7c6fdc4d3c8697f45af78b8d5a7955760",
+                "sha256:b473d00ccd5c2061fd896ac127b7755baad233f8d996ea288af14ae09f8e0d1e",
+                "sha256:bd46a0e6296346c477e59a954da57beaf9c538da37b9df482e50f836e4a7d4bb",
+                "sha256:c428c0f64a86661fb4873495c4fac430ec7a7cef2b8c1c28f3d1a7277f9ea5ab",
+                "sha256:c9e5b778b6842f135902e2d82624008c6a79710207e28e86966cd136c621bfee",
+                "sha256:ca9075ab3de9e48b75fa8ccb897c34ccc1519177ad8841d99f7fd74cf43be5bf",
+                "sha256:f582cac9d11c227c652d3ce8ee223d94eb06f4228b52a8adaafa9fa62e73d5c9",
+                "sha256:f5bee6c523d13944a1fdc6f0525bc86dbbd94372f17b83fa6331aabacc8fd08e",
+                "sha256:f836444b4c5ece128b23ec36a446c9ab7f9b0f7981d0d27e13a7c366ee163f8a"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==1.10.4"
+            "version": "==1.10.5"
         },
         "pyhanko": {
             "hashes": [
@@ -1365,11 +1372,11 @@
         },
         "typing-extensions": {
             "hashes": [
-                "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
-                "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
+                "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+                "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==4.4.0"
+            "version": "==4.5.0"
         },
         "tzdata": {
             "hashes": [
@@ -1405,10 +1412,10 @@
         },
         "webauthn": {
             "hashes": [
-                "sha256:d5f5c257a6a18c6bd5a67169e62c1a2cd7a7350a6f9cd1ced02919706cef3074",
-                "sha256:f5122b9d988fe20116b3712c874a97183d18c0f2d62287c979edf1892d198b57"
+                "sha256:4153dd245dcc44f948f2543a726cbc852353d7c5bc04e7e35c6e05dc744b9a11",
+                "sha256:d4821ce47d9870a94bb55c23002c4e1969fb1c08ad35e878f4b8cec71e507a1d"
             ],
-            "version": "==1.7.0"
+            "version": "==1.7.2"
         },
         "webencodings": {
             "hashes": [
@@ -1987,7 +1994,7 @@
                 "sha256:04924aca48770f36b5a41c27e4d917062507be05118acb0ba869c97389084297",
                 "sha256:eeb69ef21078486b615241f0393a72b41352c5219ee648e7c61f5632d26f0420"
             ],
-            "markers": "python_version >= '3.7' and python_version < '4.0'",
+            "markers": "python_version >= '3.7' and python_version < '4'",
             "version": "==0.1.2"
         },
         "html-void-elements": {
@@ -1995,7 +2002,7 @@
                 "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2",
                 "sha256:931b88f84cd606fee0b582c28fcd00e41d7149421fb673e1e1abd2f0c4f231f0"
             ],
-            "markers": "python_version >= '3.7' and python_version < '4.0'",
+            "markers": "python_version >= '3.7' and python_version < '4'",
             "version": "==0.1.0"
         },
         "identify": {
@@ -2027,7 +2034,7 @@
                 "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad",
                 "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"
             ],
-            "markers": "python_version < '3.10'",
+            "markers": "python_version >= '3.7'",
             "version": "==6.0.0"
         },
         "iniconfig": {
@@ -2051,7 +2058,7 @@
                 "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
                 "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
             ],
-            "markers": "python_full_version >= '3.8.0'",
+            "markers": "python_version >= '3.8'",
             "version": "==5.12.0"
         },
         "jaraco.classes": {
@@ -2662,7 +2669,7 @@
                 "sha256:125d96d20c92b946b983d0d392b84ff945461e5a06d3867e9f9e575f8697b67f",
                 "sha256:8aa57747f3fc3e977684f0176a88e789be314a99f99b43b75d1e9cb5dc6db9e9"
             ],
-            "markers": "python_full_version >= '3.7.0'",
+            "markers": "python_version >= '3.7'",
             "version": "==13.3.1"
         },
         "secretstorage": {
@@ -2675,11 +2682,11 @@
         },
         "setuptools": {
             "hashes": [
-                "sha256:23c86b4e44432bfd8899384afc08872ec166a24f48a3f99f293b0a557e6a6b5d",
-                "sha256:daec07fd848d80676694d6bf69c009d28910aeece68a38dbe88b7e1bb6dba12e"
+                "sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012",
+                "sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==67.3.1"
+            "version": "==67.3.2"
         },
         "shellcheck-py": {
             "hashes": [
@@ -2859,11 +2866,11 @@
         },
         "typing-extensions": {
             "hashes": [
-                "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
-                "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
+                "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+                "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==4.4.0"
+            "version": "==4.5.0"
         },
         "urllib3": {
             "hashes": [
@@ -2967,11 +2974,11 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:23f70e964bc11a34cef175bc90ba2914e1e4545ea1e3e2f67c079671883f9cb6",
-                "sha256:e8b2a36ea17df80ffe9e2c4fda3f693c3dad6df1697d3cd3af232db680950b0b"
+                "sha256:188834565033387710d046e3fe96acfc9b5e86cbca7f39ff69cf21a4128198b7",
+                "sha256:9e5421e176ef5ab4c0ad896624e87a7b2f07aca746c9b2aa305952800cb8eecb"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==3.13.0"
+            "version": "==3.14.0"
         }
     }
 }
diff --git a/integreat_cms/cms/models/abstract_tree_node.py b/integreat_cms/cms/models/abstract_tree_node.py
index 8240491be..7df2c208d 100644
--- a/integreat_cms/cms/models/abstract_tree_node.py
+++ b/integreat_cms/cms/models/abstract_tree_node.py
@@ -8,6 +8,9 @@ from cacheops import invalidate_model
 from treebeard.exceptions import InvalidPosition
 from treebeard.ns_tree import NS_Node
 
+from db_mutex import DBMutexError, DBMutexTimeoutError
+from db_mutex.db_mutex import db_mutex
+
 from .abstract_base_model import AbstractBaseModel
 from ..constants import position
 
@@ -259,30 +262,42 @@ class AbstractTreeNode(NS_Node, AbstractBaseModel):
         :type pos: str
 
         :raises ~treebeard.exceptions.InvalidPosition: If the node is moved to another region
+        :raises ~db_mutex.exceptions.DBMutexError: If the DB mutex could not be retrieved
+        :raises ~db_mutex.exceptions.DBMutexTimeoutError: If waiting for the DB mutex timed out
         """
         logger.debug("Moving %r to position %r of %r", self, pos, target)
-        # Do not allow to move a node outside its region
-        if self.region != target.region:
-            # Allow moving as siblings of root nodes (because it's a separate tree)
-            if not (
-                target.is_root()
-                and pos
-                in [
-                    position.LEFT,
-                    position.RIGHT,
-                    position.FIRST_SIBLING,
-                    position.LAST_SIBLING,
-                ]
-            ):
-                raise InvalidPosition(
-                    _('The node "{}" in region "{}" cannot be moved to "{}".').format(
-                        self, self.region, target.region
-                    )
-                )
-        # Moving a node can modify all other nodes via raw sql queries (which are not recognized by cachalot),
-        # so we have to invalidate the whole model manually.
-        invalidate_model(self.__class__)
-        super().move(target=target, pos=pos)
+        try:
+            with db_mutex(self.__class__.__name__):
+                # Do not allow to move a node outside its region
+                if self.region != target.region:
+                    # Allow moving as siblings of root nodes (because it's a separate tree)
+                    if not (
+                        target.is_root()
+                        and pos
+                        in [
+                            position.LEFT,
+                            position.RIGHT,
+                            position.FIRST_SIBLING,
+                            position.LAST_SIBLING,
+                        ]
+                    ):
+                        raise InvalidPosition(
+                            _(
+                                'The node "{}" in region "{}" cannot be moved to "{}".'
+                            ).format(self, self.region, target.region)
+                        )
+                # Moving a node can modify all other nodes via raw sql queries (which are not recognized by cachalot),
+                # so we have to invalidate the whole model manually.
+                invalidate_model(self.__class__)
+                super().move(target, pos)
+        except DBMutexError as e:
+            raise DBMutexError(
+                _('Could not change position in tree of "{}".').format(self)
+            ) from e
+        except DBMutexTimeoutError as e:
+            raise DBMutexTimeoutError(
+                _('Could not change position in tree of "{}".').format(self)
+            ) from e
         invalidate_model(self.__class__)
 
         # Reload 'self' because lft/rgt may have changed
diff --git a/integreat_cms/cms/views/pages/page_actions.py b/integreat_cms/cms/views/pages/page_actions.py
index f54a54a05..3f9e8b6de 100644
--- a/integreat_cms/cms/views/pages/page_actions.py
+++ b/integreat_cms/cms/views/pages/page_actions.py
@@ -16,6 +16,7 @@ from django.utils.translation import gettext as _
 from django.views.decorators.http import require_POST
 from django.db import transaction
 
+from db_mutex import DBMutexError, DBMutexTimeoutError
 from treebeard.exceptions import InvalidPosition, InvalidMoveToDescendant
 
 from ....api.decorators import json_response
@@ -491,7 +492,13 @@ def move_page(request, region_slug, language_slug, page_id, target_id, position)
                 page=page.best_translation.title
             ),
         )
-    except (ValueError, InvalidPosition, InvalidMoveToDescendant) as e:
+    except (
+        ValueError,
+        InvalidPosition,
+        InvalidMoveToDescendant,
+        DBMutexTimeoutError,
+        DBMutexError,
+    ) as e:
         messages.error(request, e)
         logger.exception(e)
 
diff --git a/integreat_cms/core/settings.py b/integreat_cms/core/settings.py
index 8ac97e680..f3c2ce20c 100644
--- a/integreat_cms/core/settings.py
+++ b/integreat_cms/core/settings.py
@@ -247,6 +247,7 @@ INSTALLED_APPS = [
     "django.contrib.staticfiles",
     # Installed third-party-apps
     "corsheaders",
+    "db_mutex",
     "linkcheck",
     "polymorphic",
     "rules.apps.AutodiscoverRulesConfig",
@@ -1012,3 +1013,11 @@ XLIFF_DOWNLOAD_DIR = os.path.join(XLIFF_ROOT, "download")
 
 #: The URL path where XLIFF files are served for download
 XLIFF_URL = "/xliff/"
+
+
+############
+# DB Mutex #
+############
+
+# Our database operations should never exceed 60 seconds
+DB_MUTEX_TTL_SECONDS = 60
diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po
index f15c1f8b4..a14369c5c 100644
--- a/integreat_cms/locale/de/LC_MESSAGES/django.po
+++ b/integreat_cms/locale/de/LC_MESSAGES/django.po
@@ -2340,6 +2340,10 @@ msgid "The node \"{}\" in region \"{}\" cannot be moved to \"{}\"."
 msgstr ""
 "Der Knoten \"{}\" in Region \"{}\" kann nicht nach \"{}\" verschoben werden."
 
+#: cms/models/abstract_tree_node.py
+msgid "Could not change position in tree of \"{}\"."
+msgstr "Position von \"{}\" im Baum konnte nicht geändert werden."
+
 #: cms/models/chat/chat_message.py
 msgid "sender"
 msgstr "Absender:in"
diff --git a/setup.cfg b/setup.cfg
index babbdb439..9aca16c00 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,6 +49,7 @@ install_requires =
 	Django>=3.2,<4.0
 	django-cacheops
 	django-cors-headers
+	django-db-mutex
 	django-debug-toolbar
 	django-linkcheck
 	django-polymorphic
diff --git a/sphinx/conf.py b/sphinx/conf.py
index 7e9ab2bb1..a7d1e80f7 100644
--- a/sphinx/conf.py
+++ b/sphinx/conf.py
@@ -94,6 +94,7 @@ intersphinx_mapping = {
         "https://sphinx-rtd-tutorial.readthedocs.io/en/latest/",
         None,
     ),
+    "db_mutex": ("https://django-db-mutex.readthedocs.io/en/latest/", None),
     "django": (
         f"https://docs.djangoproject.com/en/{django_version}/",
         f"https://docs.djangoproject.com/en/{django_version}/_objects/",
['integreat_cms/locale/de/LC_MESSAGES/django.po', 'Pipfile.lock', 'setup.cfg', 'sphinx/conf.py', 'integreat_cms/core/settings.py', 'CHANGELOG.md', 'integreat_cms/cms/models/abstract_tree_node.py', 'integreat_cms/cms/views/pages/page_actions.py']