diff --git a/docs/developer/adding-feature-flags.md b/docs/developer/adding-feature-flags.md new file mode 100644 index 000000000..711b6b7b6 --- /dev/null +++ b/docs/developer/adding-feature-flags.md @@ -0,0 +1,30 @@ +# Adding feature flags +Feature flags are booleans (stored in our DB as the `WaffleFlag` object) that programmatically disable/enable "features" (such as DNS hosting) for a specified set of users. + +We use [django-waffle](https://waffle.readthedocs.io/en/stable/) for our feature flags. Waffle makes using flags fairly straight forward. + +## Adding feature flags through django admin +1. On the app, navigate to `\admin`. +2. Under models, click `Waffle flags`. +3. Click `Add waffle flag`. +4. Add the model as you would normally. Refer to waffle's documentation [regarding attributes](https://waffle.readthedocs.io/en/stable/types/flag.html#flag-attributes) for more information on them. + +## Adding feature flags when migrations are ran +Given that we store waffle flags as a predefined list, this means that we need to create a new migration file when we want to add a set of feature flags programatically this way. Note that if `WAFFLE_CREATE_MISSING_FLAGS` is set to True, you may not need this step. + +Follow these steps to achieve this: +1. Navigate to `registrar/models/waffle_flag.py`. +2. Modify the `get_default_waffle_flags` and add the desired name of your feature flag to the `default_flags` array. +3. Navigate to `registrar/migrationdata`. +4. Copy the migration named `0091_create_waffle_flags_v01`. +5. Rename the copied migration to match the increment. For instance, if `0091_create_waffle_flags_v01` exists, you will rename your migration to `0091_create_waffle_flags_v02`. +6. Modify the migration dependency to match the last migration in the stack. + +## Modifying an existing feature flag through the CLI +Waffle comes with built in management commands that you can use to update records remotely. [Read here](https://waffle.readthedocs.io/en/stable/usage/cli.html) for information on how to use them. + +## Using feature flags as boolean values +Waffle [provides a boolean](https://waffle.readthedocs.io/en/stable/usage/views.html) called `flag_is_active` that you can use as you otherwise would a boolean. This boolean requires a request object and the flag name. + +## Using feature flags to disable/enable views +Waffle [provides a decorator](https://waffle.readthedocs.io/en/stable/usage/decorators.html) that you can use to enable/disable views. When disabled, the view will return a 404 if said user tries to navigate to it. diff --git a/docs/developer/feature-flags.md b/docs/developer/feature-flags.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 5eb7a0981..bd6666c8b 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2170,11 +2170,6 @@ class WaffleFlagAdmin(FlagAdmin): admin.site.unregister(LogEntry) # Unregister the default registration -# Unregister samples and switches from django-waffle, as we currently don't use these. -# TODO - address this -admin.site.unregister(Sample) -admin.site.unregister(Switch) - admin.site.register(LogEntry, CustomLogEntryAdmin) admin.site.register(models.User, MyUserAdmin) # Unregister the built-in Group model @@ -2199,3 +2194,10 @@ admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin) # Register our custom waffle flag implementation 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) diff --git a/src/registrar/fixtures_users.py b/src/registrar/fixtures_users.py index ee1ca7e81..c31acacfd 100644 --- a/src/registrar/fixtures_users.py +++ b/src/registrar/fixtures_users.py @@ -196,12 +196,12 @@ class UserFixture: }, ] - def load_users(cls, users, group_name, set_users_superusers=False): + def load_users(cls, users, group_name, are_superusers=False): logger.info(f"Going to load {len(users)} users in group {group_name}") for user_data in users: try: user, _ = User.objects.get_or_create(username=user_data["username"]) - user.is_superuser = set_users_superusers + user.is_superuser = are_superusers user.first_name = user_data["first_name"] user.last_name = user_data["last_name"] if "email" in user_data: @@ -229,5 +229,5 @@ class UserFixture: # steps now do not need to close/reopen a db connection, # instead they share one. with transaction.atomic(): - cls.load_users(cls, cls.ADMINS, "full_access_group", set_users_superusers=True) + cls.load_users(cls, cls.ADMINS, "full_access_group", are_superusers=True) cls.load_users(cls, cls.STAFF, "cisa_analysts_group")