diff --git a/src/backend/apps/flags/migrations/0001_initial.py b/src/backend/apps/flags/migrations/0001_initial.py index d0266be..cbb0619 100644 --- a/src/backend/apps/flags/migrations/0001_initial.py +++ b/src/backend/apps/flags/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.11 on 2026-02-13 07:57 +# Generated by Django 5.2.11 on 2026-02-14 09:55 import django.core.validators import uuid diff --git a/src/backend/apps/flags/models.py b/src/backend/apps/flags/models.py index 29388f6..ad5e434 100644 --- a/src/backend/apps/flags/models.py +++ b/src/backend/apps/flags/models.py @@ -7,7 +7,7 @@ from django.utils.translation import gettext_lazy as _ from apps.core.models import BaseModel -KEY_PATTERN = r"^[a-z][a-z0-9_]*$" +FLAG_KEY_PATTERN = r"^[A-Za-z][A-Za-z0-9_]*$" class FeatureFlagType(models.TextChoices): @@ -60,7 +60,7 @@ class FeatureFlag(BaseModel): help_text=_("Unique identifier for the feature flag"), validators=[ RegexValidator( - regex=KEY_PATTERN, + regex=FLAG_KEY_PATTERN, message=( "Key must start with a lowercase letter and contain only " "lowercase letters, digits, and underscores." diff --git a/src/backend/apps/reviews/migrations/0001_initial.py b/src/backend/apps/reviews/migrations/0001_initial.py index 3176ad3..2869599 100644 --- a/src/backend/apps/reviews/migrations/0001_initial.py +++ b/src/backend/apps/reviews/migrations/0001_initial.py @@ -1,9 +1,7 @@ -# Generated by Django 5.2.11 on 2026-02-12 10:14 +# Generated by Django 5.2.11 on 2026-02-14 09:55 import django.core.validators -import django.db.models.deletion import uuid -from django.conf import settings from django.db import migrations, models @@ -12,23 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ - migrations.CreateModel( - name='ReviewSettings', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('default_min_approvals', models.PositiveIntegerField(default=1, help_text='Fallback number of approvals required when no approver group is explicitly assigned to an experimenter.', validators=[django.core.validators.MinValueValidator(1)], verbose_name='default minimum approvals')), - ('allow_any_approver', models.BooleanField(default=True, help_text='When True, any user with the Approver role can approve experiments that have no explicit approver group. When False, experiments without an approver group cannot proceed to review.', verbose_name='allow any approver')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), - ], - options={ - 'verbose_name': 'review settings', - 'verbose_name_plural': 'review settings', - }, - ), migrations.CreateModel( name='ApproverGroup', fields=[ @@ -36,12 +20,23 @@ class Migration(migrations.Migration): ('min_approvals', models.PositiveIntegerField(default=1, help_text="Number of distinct approvals required before an experiment can transition to 'approved'.", validators=[django.core.validators.MinValueValidator(1)], verbose_name='minimum approvals')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), - ('approvers', models.ManyToManyField(blank=True, help_text="Approver-role users who may approve this experimenter's experiments.", limit_choices_to={'role': 'approver'}, related_name='approvable_groups', to=settings.AUTH_USER_MODEL, verbose_name='approvers')), - ('experimenter', models.OneToOneField(help_text='The experimenter whose experiments this group governs.', limit_choices_to={'role': 'experimenter'}, on_delete=django.db.models.deletion.CASCADE, related_name='approver_group', to=settings.AUTH_USER_MODEL, verbose_name='experimenter')), ], options={ 'verbose_name': 'approver group', 'verbose_name_plural': 'approver groups', }, ), + migrations.CreateModel( + name='ReviewSettings', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('default_min_approvals', models.PositiveIntegerField(default=1, help_text='Fallback number of approvals required when no approver group is explicitly assigned to an experimenter.', validators=[django.core.validators.MinValueValidator(1)], verbose_name='default minimum approvals')), + ('allow_any_approver', models.BooleanField(default=False, help_text='When True, any user with the Approver role can approve experiments that have no explicit approver group. When False, experiments without an approver group cannot proceed to review.', verbose_name='allow any approver')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), + ], + options={ + 'verbose_name': 'review settings', + 'verbose_name_plural': 'review settings', + }, + ), ] diff --git a/src/backend/apps/reviews/migrations/0002_alter_reviewsettings_allow_any_approver.py b/src/backend/apps/reviews/migrations/0002_alter_reviewsettings_allow_any_approver.py deleted file mode 100644 index b25406d..0000000 --- a/src/backend/apps/reviews/migrations/0002_alter_reviewsettings_allow_any_approver.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.11 on 2026-02-12 18:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('reviews', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='reviewsettings', - name='allow_any_approver', - field=models.BooleanField(default=False, help_text='When True, any user with the Approver role can approve experiments that have no explicit approver group. When False, experiments without an approver group cannot proceed to review.', verbose_name='allow any approver'), - ), - ] diff --git a/src/backend/apps/reviews/migrations/0002_initial.py b/src/backend/apps/reviews/migrations/0002_initial.py new file mode 100644 index 0000000..830326a --- /dev/null +++ b/src/backend/apps/reviews/migrations/0002_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.11 on 2026-02-14 09:55 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('reviews', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='approvergroup', + name='approvers', + field=models.ManyToManyField(blank=True, help_text="Approver-role users who may approve this experimenter's experiments.", limit_choices_to={'role': 'approver'}, related_name='approvable_groups', to=settings.AUTH_USER_MODEL, verbose_name='approvers'), + ), + migrations.AddField( + model_name='approvergroup', + name='experimenter', + field=models.OneToOneField(help_text='The experimenter whose experiments this group governs.', limit_choices_to={'role': 'experimenter'}, on_delete=django.db.models.deletion.CASCADE, related_name='approver_group', to=settings.AUTH_USER_MODEL, verbose_name='experimenter'), + ), + ] diff --git a/src/backend/apps/users/migrations/0001_initial.py b/src/backend/apps/users/migrations/0001_initial.py index ae8d719..f702f56 100644 --- a/src/backend/apps/users/migrations/0001_initial.py +++ b/src/backend/apps/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.11 on 2026-02-12 13:05 +# Generated by Django 5.2.11 on 2026-02-14 09:55 import django.contrib.auth.models import django.contrib.auth.validators @@ -30,7 +30,7 @@ class Migration(migrations.Migration): ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('role', models.CharField(choices=[('admin', 'Admin'), ('experimenter', 'Experimenter'), ('approver', 'Approver'), ('viewer', 'Viewer')], db_index=True, default='viewer', help_text='Platform role that defines user permissions.', max_length=20, verbose_name='role')), + ('role', models.CharField(choices=[('admin', 'Admin'), ('experimenter', 'Experimenter'), ('approver', 'Approver'), ('viewer', 'Viewer')], db_index=True, default='viewer', help_text='Platform role that defines user permissions', max_length=20, verbose_name='role')), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), ], diff --git a/src/backend/apps/users/migrations/0002_alter_user_role.py b/src/backend/apps/users/migrations/0002_alter_user_role.py deleted file mode 100644 index fd812a4..0000000 --- a/src/backend/apps/users/migrations/0002_alter_user_role.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.11 on 2026-02-12 18:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('users', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='role', - field=models.CharField(choices=[('admin', 'Admin'), ('experimenter', 'Experimenter'), ('approver', 'Approver'), ('viewer', 'Viewer')], db_index=True, default='viewer', help_text='Platform role that defines user permissions', max_length=20, verbose_name='role'), - ), - ]