diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 016df0da513c5d9c7376f3a9c193103222c10b28..4ef4447536ba9422b44e1e91b34223da0aa80b28 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -28,6 +28,7 @@ Fixed
 * PWA theme colour defaulted to red
 * Form for editing group type displayed irrelevant fields
 * Permission groups could get outdated if re-assigning a user account to a different person
+* User preferences didn't work correctly sometimes due to race conditions.
 
 `2.7`_ - 2022-01-24
 -------------------
diff --git a/aleksis/core/migrations/0035_preference_model_unique.py b/aleksis/core/migrations/0035_preference_model_unique.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c82528eff8655f356cddb5705623fb5cd4a6120
--- /dev/null
+++ b/aleksis/core/migrations/0035_preference_model_unique.py
@@ -0,0 +1,55 @@
+# Generated by Django 3.2.11 on 2022-01-27 18:52
+
+from email.headerregistry import Group
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('sites', '0002_alter_domain_unique'),
+        ('core', '0034_invite_permission'),
+    ]
+
+    def _migrate_preference_models_to_unique(apps, schema_editor):
+        GroupPreferenceModel = apps.get_model("core", "GroupPreferenceModel")
+        PersonPreferenceModel = apps.get_model("core", "PersonPreferenceModel")
+        SitePreferenceModel = apps.get_model("core", "SitePreferenceModel")
+
+        from dynamic_preferences import BasePreferenceModel
+
+        models = [GroupPreferenceModel, PersonPreferenceModel, SitePreferenceModel]
+
+        for model in models:
+            duplicates = {}
+            db_alias = schema_editor.connection.alias
+            for obj in model.objects.using(db_alias).all():
+                key = f"{obj.instance.pk}__{obj.section}__{obj.name}"
+                duplicates.setdefault(key, [])
+                duplicates[key].append(obj)
+
+            for key, objs in duplicates.items():
+                if len(objs) > 1:
+                    found = False
+                    for obj in objs:
+                        if BasePreferenceModel.get_value(obj) == BasePreferenceModel.preference(obj).default or found:
+                            obj.delete()                    
+                        else:
+                            found = True
+
+
+    operations = [
+        migrations.RunPython(_migrate_preference_models_to_unique),
+        migrations.AlterUniqueTogether(
+            name='grouppreferencemodel',
+            unique_together={('instance', 'section', 'name')},
+        ),
+        migrations.AlterUniqueTogether(
+            name='personpreferencemodel',
+            unique_together={('instance', 'section', 'name')},
+        ),
+        migrations.AlterUniqueTogether(
+            name='sitepreferencemodel',
+            unique_together={('instance', 'section', 'name')},
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index d5dc7d7befe1395b222ff492f1a9f8eed3994f69..edc92c0fc68a7b495e844cf3f24821eafe2bfc95 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1082,7 +1082,7 @@ class SitePreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
 
     instance = models.ForeignKey(Site, on_delete=models.CASCADE)
 
-    class Meta:
+    class Meta(PerInstancePreferenceModel.Meta):
         app_label = "core"
 
 
@@ -1091,7 +1091,7 @@ class PersonPreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
 
     instance = models.ForeignKey(Person, on_delete=models.CASCADE)
 
-    class Meta:
+    class Meta(PerInstancePreferenceModel.Meta):
         app_label = "core"
 
 
@@ -1100,7 +1100,7 @@ class GroupPreferenceModel(PerInstancePreferenceModel, PureDjangoModel):
 
     instance = models.ForeignKey(Group, on_delete=models.CASCADE)
 
-    class Meta:
+    class Meta(PerInstancePreferenceModel.Meta):
         app_label = "core"