diff --git a/src/olympia/users/migrations/0027_userrestrictionhistory_restriction_instance_and_version.py b/src/olympia/users/migrations/0027_userrestrictionhistory_restriction_instance_and_version.py new file mode 100644 index 000000000000..0398bd3b5949 --- /dev/null +++ b/src/olympia/users/migrations/0027_userrestrictionhistory_restriction_instance_and_version.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.17 on 2026-08-25 14:53 + +import django.db.models.deletion +import olympia.amo.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('users', '0026_alter_userrestrictionhistory_restriction_and_more'), + ('versions', '0053_auto_20260720_1345'), + ] + + operations = [ + migrations.AddField( + model_name='userrestrictionhistory', + name='restriction_content_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='userrestrictionhistory', + name='restriction_object_id', + field=models.PositiveIntegerField(null=True), + ), + migrations.AddField( + model_name='userrestrictionhistory', + name='version', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='restriction_history', to='versions.version'), + ), + migrations.AddIndex( + model_name='userrestrictionhistory', + index=olympia.amo.models.LongNameIndex(fields=['restriction_content_type', 'restriction_object_id'], name='users_userrestrictionhistory_restriction_content_type_object_id'), + ), + ] diff --git a/src/olympia/users/models.py b/src/olympia/users/models.py index e6a5ba017840..f8e50778f825 100644 --- a/src/olympia/users/models.py +++ b/src/olympia/users/models.py @@ -13,6 +13,8 @@ from django.conf import settings from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from django.contrib.auth.signals import user_logged_in +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from django.core import validators from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile @@ -1430,6 +1432,27 @@ class UserRestrictionHistory(ModelBase): ) ip_address = models.CharField(default='', max_length=45) last_login_ip = models.CharField(default='', max_length=45) + # The specific restriction row that matched, e.g. an EmailUserRestriction + # or IPNetworkUserRestriction instance. A generic foreign key because the + # restriction classes live in different tables. NULL on rows recorded + # before these fields existed, and always NULL for restrictions that + # aren't backed by the database (developer agreement, reputation). + restriction_content_type = models.ForeignKey( + ContentType, null=True, on_delete=models.SET_NULL + ) + restriction_object_id = models.PositiveIntegerField(null=True) + restriction_instance = GenericForeignKey( + 'restriction_content_type', 'restriction_object_id' + ) + # The version whose auto-approval was being checked. NULL on rows recorded + # before this field existed, and always NULL for checks other than + # auto-approval, which aren't tied to a version. + version = models.ForeignKey( + 'versions.Version', + related_name='restriction_history', + null=True, + on_delete=models.SET_NULL, + ) class Meta: verbose_name_plural = 'User Restriction History' @@ -1442,6 +1465,10 @@ class Meta: fields=('last_login_ip',), name='users_userrestrictionhistory_last_login_ip_d58d95ff', ), + LongNameIndex( + fields=('restriction_content_type', 'restriction_object_id'), + name='users_userrestrictionhistory_restriction_content_type_object_id', + ), ]