diff --git a/src/registrar/admin.py b/src/registrar/admin.py index bd6666c8b..ee25a3a73 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -15,9 +15,8 @@ from django.contrib.contenttypes.models import ContentType from django.urls import reverse from dateutil.relativedelta import relativedelta # type: ignore from epplibwrapper.errors import ErrorCode, RegistryError -from waffle.admin import FlagAdmin +from waffle.admin import FlagAdmin, SampleAdmin, SwitchAdmin from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website -from waffle.models import Sample, Switch from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes from registrar.views.utility.mixins import OrderableFieldsMixin from django.contrib.admin.views.main import ORDER_VAR @@ -2168,6 +2167,22 @@ class WaffleFlagAdmin(FlagAdmin): fields = "__all__" +class WaffleSwitchAdmin(SwitchAdmin): + class Meta: + """Contains meta information about this class""" + + model = models.WaffleSwitch + fields = "__all__" + + +class WaffleSampleAdmin(SampleAdmin): + class Meta: + """Contains meta information about this class""" + + model = models.WaffleSample + fields = "__all__" + + admin.site.unregister(LogEntry) # Unregister the default registration admin.site.register(LogEntry, CustomLogEntryAdmin) @@ -2192,12 +2207,7 @@ admin.site.register(models.DomainRequest, DomainRequestAdmin) admin.site.register(models.TransitionDomain, TransitionDomainAdmin) admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin) -# Register our custom waffle flag implementation +# Register our custom waffle implementations admin.site.register(models.WaffleFlag, WaffleFlagAdmin) - -# Unregister samples and switches from django-waffle, as we currently don't use these. -# Django admin sorts different "sites" alphabetically, and offers little customization for them. -# If we do need to use these, we should also consider using this library: -# https://pypi.org/project/django-reorder-admin/ -admin.site.unregister(Sample) -admin.site.unregister(Switch) +admin.site.register(models.WaffleSample, WaffleSampleAdmin) +admin.site.register(models.WaffleSwitch, WaffleSwitchAdmin) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index eec184704..b14ea231b 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -328,8 +328,17 @@ SERVER_EMAIL = "root@get.gov" WAFFLE_CREATE_MISSING_FLAGS = False # The model that will be used to keep track of flags. Extends AbstractUserFlag. +# Used to replace the default flag class (for customization purposes). WAFFLE_FLAG_MODEL = "registrar.WaffleFlag" +# The model that will be used to keep track of switches. Extends AbstractBaseSwitch. +# Used to replace the default switch class (for customization purposes). +WAFFLE_SWITCH_MODEL = "registrar.WaffleSwitch" + +# The model that will be used to keep track of samples. Extends AbstractBaseSample. +# Used to replace the default sample class (for customization purposes). +WAFFLE_SAMPLE_MODEL = "registrar.WaffleSample" + # endregion # region: Headers-----------------------------------------------------------### diff --git a/src/registrar/migrations/0090_waffleflag.py b/src/registrar/migrations/0090_wafflesample_waffleswitch_waffleflag.py similarity index 57% rename from src/registrar/migrations/0090_waffleflag.py rename to src/registrar/migrations/0090_wafflesample_waffleswitch_waffleflag.py index 08ea178c6..66c2a22e5 100644 --- a/src/registrar/migrations/0090_waffleflag.py +++ b/src/registrar/migrations/0090_wafflesample_waffleswitch_waffleflag.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-04-30 16:56 +# Generated by Django 4.2.10 on 2024-05-01 15:10 from django.conf import settings from django.db import migrations, models @@ -13,6 +13,93 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name="WaffleSample", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "name", + models.CharField( + help_text="The human/computer readable name.", max_length=100, unique=True, verbose_name="Name" + ), + ), + ( + "percent", + models.DecimalField( + decimal_places=1, + help_text="A number between 0.0 and 100.0 to indicate a percentage of the time this sample will be active.", + max_digits=4, + verbose_name="Percent", + ), + ), + ( + "note", + models.TextField(blank=True, help_text="Note where this Sample is used.", verbose_name="Note"), + ), + ( + "created", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + help_text="Date when this Sample was created.", + verbose_name="Created", + ), + ), + ( + "modified", + models.DateTimeField( + default=django.utils.timezone.now, + help_text="Date when this Sample was last modified.", + verbose_name="Modified", + ), + ), + ], + options={ + "verbose_name": "waffle sample", + "verbose_name_plural": "Waffle samples", + }, + ), + migrations.CreateModel( + name="WaffleSwitch", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ( + "name", + models.CharField( + help_text="The human/computer readable name.", max_length=100, unique=True, verbose_name="Name" + ), + ), + ( + "active", + models.BooleanField(default=False, help_text="Is this switch active?", verbose_name="Active"), + ), + ( + "note", + models.TextField(blank=True, help_text="Note where this Switch is used.", verbose_name="Note"), + ), + ( + "created", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + help_text="Date when this Switch was created.", + verbose_name="Created", + ), + ), + ( + "modified", + models.DateTimeField( + default=django.utils.timezone.now, + help_text="Date when this Switch was last modified.", + verbose_name="Modified", + ), + ), + ], + options={ + "verbose_name": "waffle switch", + "verbose_name_plural": "Waffle switches", + }, + ), migrations.CreateModel( name="WaffleFlag", fields=[ diff --git a/src/registrar/migrations/0091_create_waffle_flags_v01.py b/src/registrar/migrations/0091_create_waffle_flags_v01.py index c643eb12c..6b1bd678d 100644 --- a/src/registrar/migrations/0091_create_waffle_flags_v01.py +++ b/src/registrar/migrations/0091_create_waffle_flags_v01.py @@ -25,6 +25,7 @@ def delete_flags(apps, schema_editor): Deletes all prexisting flags. Does not delete flags not defined in this scope (user generated). """ + # This is a bit of a hack to get around "apps" not knowing what the concept of a constant is default_flags = WaffleFlag.get_default_waffle_flags() WaffleFlag.delete_waffle_flags_for_migrations(apps, default_flags) @@ -32,7 +33,7 @@ def delete_flags(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ("registrar", "0090_waffleflag"), + ("registrar", "0090_wafflesample_waffleswitch_waffleflag"), ] operations = [ diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 14ae25df4..75abb03e9 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -16,6 +16,8 @@ from .website import Website from .transition_domain import TransitionDomain from .verified_by_staff import VerifiedByStaff from .waffle_flag import WaffleFlag +from .waffle_sample import WaffleSample +from .waffle_switch import WaffleSwitch __all__ = [ "Contact", @@ -35,6 +37,8 @@ __all__ = [ "TransitionDomain", "VerifiedByStaff", "WaffleFlag", + "WaffleSwitch", + "WaffleSample", ] auditlog.register(Contact) @@ -54,3 +58,5 @@ auditlog.register(Website) auditlog.register(TransitionDomain) auditlog.register(VerifiedByStaff) auditlog.register(WaffleFlag) +auditlog.register(WaffleSample) +auditlog.register(WaffleSwitch) diff --git a/src/registrar/models/waffle_flag.py b/src/registrar/models/waffle_flag.py index ee6199677..044aa3d37 100644 --- a/src/registrar/models/waffle_flag.py +++ b/src/registrar/models/waffle_flag.py @@ -22,10 +22,10 @@ class WaffleFlag(AbstractUserFlag): def get_default_waffle_flags(): """ Defines which waffle flags should be created at startup. - Add to this list if you want to add another flag that is generated at startup. + + Add to this function if you want to add another flag that is generated at startup. When you do so, you will need to add a new instance of `0091_create_waffle_flags_v{version_number}` in registrar/migrations for that change to update automatically on migrate. - This has to exist here, as from the context of migrations, it cannot access constants """ default_flags = [ # flag_name, flag_note @@ -37,7 +37,7 @@ class WaffleFlag(AbstractUserFlag): @staticmethod def create_waffle_flags_for_migrations(apps, default_waffle_flags): """ - Creates a pre-defined list of flags for our migrations. + Creates a list of flags for our migrations. """ logger.info("Creating default waffle flags...") WaffleFlag = apps.get_model("registrar", "WaffleFlag") @@ -57,7 +57,7 @@ class WaffleFlag(AbstractUserFlag): @staticmethod def delete_waffle_flags_for_migrations(apps, default_waffle_flags): """ - Delete a pre-defined list of flags for our migrations (the reverse_code operation). + Delete a list of flags for our migrations (the reverse_code operation). """ logger.info("Deleting default waffle flags...") WaffleFlag = apps.get_model("registrar", "WaffleFlag") diff --git a/src/registrar/models/waffle_sample.py b/src/registrar/models/waffle_sample.py new file mode 100644 index 000000000..6b2ae59c6 --- /dev/null +++ b/src/registrar/models/waffle_sample.py @@ -0,0 +1,19 @@ +from waffle.models import AbstractBaseSample +import logging + +logger = logging.getLogger(__name__) + + +class WaffleSample(AbstractBaseSample): + """ + Custom implementation of django-waffles 'sample' object. + Read more here: https://waffle.readthedocs.io/en/stable/types/sample.html + + Use this class when dealing with samples. + """ + + class Meta: + """Contains meta information about this class""" + + verbose_name = "waffle sample" + verbose_name_plural = "Waffle samples" diff --git a/src/registrar/models/waffle_switch.py b/src/registrar/models/waffle_switch.py new file mode 100644 index 000000000..640b2b767 --- /dev/null +++ b/src/registrar/models/waffle_switch.py @@ -0,0 +1,19 @@ +from waffle.models import AbstractBaseSwitch +import logging + +logger = logging.getLogger(__name__) + + +class WaffleSwitch(AbstractBaseSwitch): + """ + Custom implementation of django-waffles 'switch' object. + Read more here: https://waffle.readthedocs.io/en/stable/types/switch.html + + Use this class when dealing with switches. + """ + + class Meta: + """Contains meta information about this class""" + + verbose_name = "waffle switch" + verbose_name_plural = "Waffle switches"