Merge pull request #2100 from cisagov/za/2050-waffle-feature-flags

(on getgov-za) Ticket #2054: Add waffle feature flags
This commit is contained in:
zandercymatics 2024-05-06 13:06:32 -06:00 committed by GitHub
commit d71814faa9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 319 additions and 106 deletions

View file

@ -0,0 +1,23 @@
# 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.
### Enabling the profile_feature flag
1. On the app, navigate to `\admin`.
2. Under models, click `Waffle flags`.
3. Click the `profile_feature` record. This should exist by default, if not - create one with that name.
4. (Important) Set the field `Everyone` to `Unknown`. This field overrides all other settings when set to anything else.
5. Configure the settings as you see fit.
## 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.

View file

@ -32,6 +32,7 @@ fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
pyzipper="*"
tblib = "*"
django-admin-multiple-choice-list-filter = "*"
django-waffle = "*"
[dev-packages]
django-debug-toolbar = "*"

190
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "16a0db98015509322cf1d27f06fced5b7635057c4eb98921a9419d63d51925ab"
"sha256": "9095c4f98f58a9502444584067a63f329d5a5fc4b49454c4e129bda09552d19d"
},
"pipfile-spec": 6,
"requires": {},
@ -32,20 +32,20 @@
},
"boto3": {
"hashes": [
"sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d",
"sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422"
"sha256:decf52f8d5d8a1b10c9ff2a0e96ee207ed79e33d2e53fdf0880a5cbef70785e0",
"sha256:e836b71d79671270fccac0a4d4c8ec239a6b82ea47c399b64675aa597d0ee63b"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.34.90"
"version": "==1.34.95"
},
"botocore": {
"hashes": [
"sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133",
"sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392"
"sha256:6bd76a2eadb42b91fa3528392e981ad5b4dfdee3968fa5b904278acf6cbf15ff",
"sha256:ead5823e0dd6751ece5498cb979fd9abf190e691c8833bcac6876fd6ca261fa7"
],
"markers": "python_version >= '3.8'",
"version": "==1.34.90"
"version": "==1.34.95"
},
"cachetools": {
"hashes": [
@ -370,6 +370,15 @@
"markers": "python_version >= '3.8'",
"version": "==7.3.0"
},
"django-waffle": {
"hashes": [
"sha256:5979a2f3dd674ef7086480525b39651fc2045427f6d8e6a614192656d3402c5b",
"sha256:e49d7d461d89f3bd8e53f20efe39310acca8f275c9888495e68e195345bf18b1"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==4.1.0"
},
"django-widget-tweaks": {
"hashes": [
"sha256:1c2180681ebb994e922c754804c7ffebbe1245014777ac47897a81f57cc629c7",
@ -392,12 +401,12 @@
},
"faker": {
"hashes": [
"sha256:34b947581c2bced340c39b35f89dbfac4f356932cfff8fe893bde854903f0e6e",
"sha256:adb98e771073a06bdc5d2d6710d8af07ac5da64c8dc2ae3b17bb32319e66fd82"
"sha256:87ef41e24b39a5be66ecd874af86f77eebd26782a2681200e86c5326340a95d3",
"sha256:e23a2b74888885c3d23a9237bacb823041291c03d609a39acb9ebe6c123f3986"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==24.11.0"
"version": "==25.0.0"
},
"fred-epplib": {
"git": "https://github.com/cisagov/epplib.git",
@ -588,7 +597,6 @@
"sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b",
"sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6",
"sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8",
"sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5",
"sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306",
"sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5",
"sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f",
@ -801,12 +809,12 @@
},
"oic": {
"hashes": [
"sha256:385a1f64bb59519df1e23840530921bf416740240f505ea6d161e331d3d39fad",
"sha256:fcbf948a22e4d4df66f6bf57d327933f32a7b539640d9b42883457634360ba78"
"sha256:b74bd06c7de1ab4f8e798f714062e6a68f68ad9cdbed1f1c30a7fb887602f321",
"sha256:e51705d0c14c97e9ca594374bfb54269a72c9b489e0e979598344c0189bfcb64"
],
"index": "pypi",
"markers": "python_version ~= '3.7'",
"version": "==1.6.1"
"markers": "python_version ~= '3.8'",
"version": "==1.7.0"
},
"orderedmultidict": {
"hashes": [
@ -1244,49 +1252,49 @@
},
"black": {
"hashes": [
"sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d",
"sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd",
"sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33",
"sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965",
"sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070",
"sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397",
"sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745",
"sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1",
"sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665",
"sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436",
"sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb",
"sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e",
"sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6",
"sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702",
"sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8",
"sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8",
"sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3",
"sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad",
"sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf",
"sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e",
"sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641",
"sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"
"sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474",
"sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1",
"sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0",
"sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8",
"sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96",
"sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1",
"sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04",
"sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021",
"sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94",
"sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d",
"sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c",
"sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7",
"sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c",
"sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc",
"sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7",
"sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d",
"sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c",
"sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741",
"sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce",
"sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb",
"sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063",
"sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==24.4.0"
"version": "==24.4.2"
},
"blinker": {
"hashes": [
"sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9",
"sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"
"sha256:5f1cdeff423b77c31b89de0565cd03e5275a03028f44b2b15f912632a58cced6",
"sha256:da44ec748222dcd0105ef975eed946da197d5bdf8bafb6aa92f5bc89da63fa25"
],
"markers": "python_version >= '3.8'",
"version": "==1.7.0"
"version": "==1.8.1"
},
"boto3": {
"hashes": [
"sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d",
"sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422"
"sha256:decf52f8d5d8a1b10c9ff2a0e96ee207ed79e33d2e53fdf0880a5cbef70785e0",
"sha256:e836b71d79671270fccac0a4d4c8ec239a6b82ea47c399b64675aa597d0ee63b"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.34.90"
"version": "==1.34.95"
},
"boto3-mocking": {
"hashes": [
@ -1299,28 +1307,28 @@
},
"boto3-stubs": {
"hashes": [
"sha256:7361f162523168ddcfb3e0cc70e5208e78f95b9f1f2553032036a2b67ab33355",
"sha256:c82f3db8558e28f766361ba1eea7c77dff735f72fef2a0b9dffaa9c0d9ae76a3"
"sha256:412006b27ee707e9b51a084b02ac92b143af8a3b56727582afec2a76ce93c3b6",
"sha256:4fb5830626de42446c238ca72ca1a53e461281396007fb900edf50ceeb044a10"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.34.90"
"version": "==1.34.95"
},
"botocore": {
"hashes": [
"sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133",
"sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392"
"sha256:6bd76a2eadb42b91fa3528392e981ad5b4dfdee3968fa5b904278acf6cbf15ff",
"sha256:ead5823e0dd6751ece5498cb979fd9abf190e691c8833bcac6876fd6ca261fa7"
],
"markers": "python_version >= '3.8'",
"version": "==1.34.90"
"version": "==1.34.95"
},
"botocore-stubs": {
"hashes": [
"sha256:b2d7416b524bce7325aa5fe09bb5e0b6bc9531d4136f4407fa39b6bc58507f34",
"sha256:d9b66542cbb8fbe28eef3c22caf941ae22d36cc1ef55b93fc0b52239457cab57"
"sha256:64d80a3467e3b19939e9c2750af33328b3087f8f524998dbdf7ed168227f507d",
"sha256:b0345f55babd8b901c53804fc5c326a4a0bd2e23e3b71f9ea5d9f7663466e6ba"
],
"markers": "python_version >= '3.8' and python_version < '4.0'",
"version": "==1.34.89"
"version": "==1.34.94"
},
"click": {
"hashes": [
@ -1357,20 +1365,20 @@
},
"django-stubs": {
"hashes": [
"sha256:4cf4de258fa71adc6f2799e983091b9d46cfc67c6eebc68fe111218c9a62b3b8",
"sha256:8ccd2ff4ee5adf22b9e3b7b1a516d2e1c2191e9d94e672c35cc2bc3dd61e0f6b"
"sha256:084484cbe16a6d388e80ec687e46f529d67a232f3befaf55c936b3b476be289d",
"sha256:b8a792bee526d6cab31e197cb414ee7fa218abd931a50948c66a80b3a2548621"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==4.2.7"
"version": "==5.0.0"
},
"django-stubs-ext": {
"hashes": [
"sha256:45a5d102417a412e3606e3c358adb4744988a92b7b58ccf3fd64bddd5d04d14c",
"sha256:519342ac0849cda1559746c9a563f03ff99f636b0ebe7c14b75e816a00dfddc3"
"sha256:5bacfbb498a206d5938454222b843d81da79ea8b6fcd1a59003f529e775bc115",
"sha256:8e1334fdf0c8bff87e25d593b33d4247487338aaed943037826244ff788b56a8"
],
"markers": "python_version >= '3.8'",
"version": "==4.2.7"
"version": "==5.0.0"
},
"django-webtest": {
"hashes": [
@ -1423,37 +1431,37 @@
},
"mypy": {
"hashes": [
"sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6",
"sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913",
"sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129",
"sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc",
"sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974",
"sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374",
"sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150",
"sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03",
"sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9",
"sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02",
"sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89",
"sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2",
"sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d",
"sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3",
"sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612",
"sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e",
"sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3",
"sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e",
"sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd",
"sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04",
"sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed",
"sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185",
"sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf",
"sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b",
"sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4",
"sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f",
"sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"
"sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061",
"sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99",
"sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de",
"sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a",
"sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9",
"sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec",
"sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1",
"sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131",
"sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f",
"sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821",
"sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5",
"sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee",
"sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e",
"sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746",
"sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2",
"sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0",
"sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b",
"sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53",
"sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30",
"sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda",
"sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051",
"sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2",
"sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7",
"sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee",
"sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727",
"sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976",
"sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"
],
"index": "pypi",
"markers": "python_version >= '3.8'",
"version": "==1.9.0"
"version": "==1.10.0"
},
"mypy-extensions": {
"hashes": [
@ -1665,14 +1673,6 @@
"markers": "python_version >= '3.7'",
"version": "==5.3.0.7"
},
"types-pytz": {
"hashes": [
"sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981",
"sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"
],
"markers": "python_version >= '3.8'",
"version": "==2024.1.0.20240417"
},
"types-pyyaml": {
"hashes": [
"sha256:a9e0f0f88dc835739b0c1ca51ee90d04ca2a897a71af79de9aec5f38cb0a5342",

View file

@ -15,6 +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.models import Sample, Switch
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from registrar.views.utility.mixins import OrderableFieldsMixin
@ -2158,7 +2160,16 @@ class UserGroupAdmin(AuditedAdmin):
return obj.name
class WaffleFlagAdmin(FlagAdmin):
class Meta:
"""Contains meta information about this class"""
model = models.WaffleFlag
fields = "__all__"
admin.site.unregister(LogEntry) # Unregister the default registration
admin.site.register(LogEntry, CustomLogEntryAdmin)
admin.site.register(models.User, MyUserAdmin)
# Unregister the built-in Group model
@ -2180,3 +2191,10 @@ admin.site.register(models.PublicContact, PublicContactAdmin)
admin.site.register(models.DomainRequest, DomainRequestAdmin)
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
# Register our custom waffle implementations
admin.site.register(models.WaffleFlag, WaffleFlagAdmin)
# Unregister Sample and Switch from the waffle library
admin.site.unregister(Sample)
admin.site.unregister(Switch)

View file

@ -22,7 +22,6 @@ from base64 import b64decode
from cfenv import AppEnv # type: ignore
from pathlib import Path
from typing import Final
from botocore.config import Config
# # # ###
@ -148,6 +147,8 @@ INSTALLED_APPS = [
"corsheaders",
# library for multiple choice filters in django admin
"django_admin_multiple_choice_list_filter",
# Waffle feature flags
"waffle",
]
# Middleware are routines for processing web requests.
@ -183,6 +184,8 @@ MIDDLEWARE = [
"csp.middleware.CSPMiddleware",
# django-auditlog: obtain the request User for use in logging
"auditlog.middleware.AuditlogMiddleware",
# Used for waffle feature flags
"waffle.middleware.WaffleMiddleware",
]
# application object used by Djangos built-in servers (e.g. `runserver`)
@ -319,6 +322,17 @@ EMAIL_TIMEOUT = 30
SERVER_EMAIL = "root@get.gov"
# endregion
# region: Waffle feature flags-----------------------------------------------------------###
# If Waffle encounters a reference to a flag that is not in the database, should Waffle create the flag?
WAFFLE_CREATE_MISSING_FLAGS = True
# 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"
# endregion
# region: Headers-----------------------------------------------------------###
# Content-Security-Policy configuration

View file

@ -196,12 +196,12 @@ class UserFixture:
},
]
def load_users(cls, users, group_name):
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 = False
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")
cls.load_users(cls, cls.ADMINS, "full_access_group", are_superusers=True)
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")

View file

@ -0,0 +1,127 @@
# Generated by Django 4.2.10 on 2024-05-02 17:47
from django.conf import settings
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("registrar", "0089_user_verification_type"),
]
operations = [
migrations.CreateModel(
name="WaffleFlag",
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"
),
),
(
"everyone",
models.BooleanField(
blank=True,
help_text="Flip this flag on (Yes) or off (No) for everyone, overriding all other settings. Leave as Unknown to use normally.",
null=True,
verbose_name="Everyone",
),
),
(
"percent",
models.DecimalField(
blank=True,
decimal_places=1,
help_text="A number between 0.0 and 99.9 to indicate a percentage of users for whom this flag will be active.",
max_digits=3,
null=True,
verbose_name="Percent",
),
),
(
"testing",
models.BooleanField(
default=False,
help_text="Allow this flag to be set for a session for user testing",
verbose_name="Testing",
),
),
(
"superusers",
models.BooleanField(
default=True, help_text="Flag always active for superusers?", verbose_name="Superusers"
),
),
(
"staff",
models.BooleanField(default=False, help_text="Flag always active for staff?", verbose_name="Staff"),
),
(
"authenticated",
models.BooleanField(
default=False,
help_text="Flag always active for authenticated users?",
verbose_name="Authenticated",
),
),
(
"languages",
models.TextField(
blank=True,
default="",
help_text="Activate this flag for users with one of these languages (comma-separated list)",
verbose_name="Languages",
),
),
(
"rollout",
models.BooleanField(default=False, help_text="Activate roll-out mode?", verbose_name="Rollout"),
),
("note", models.TextField(blank=True, help_text="Note where this Flag is used.", verbose_name="Note")),
(
"created",
models.DateTimeField(
db_index=True,
default=django.utils.timezone.now,
help_text="Date when this Flag was created.",
verbose_name="Created",
),
),
(
"modified",
models.DateTimeField(
default=django.utils.timezone.now,
help_text="Date when this Flag was last modified.",
verbose_name="Modified",
),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="Activate this flag for these user groups.",
to="auth.group",
verbose_name="Groups",
),
),
(
"users",
models.ManyToManyField(
blank=True,
help_text="Activate this flag for these users.",
to=settings.AUTH_USER_MODEL,
verbose_name="Users",
),
),
],
options={
"verbose_name": "waffle flag",
"verbose_name_plural": "Waffle flags",
},
),
]

View file

@ -15,6 +15,8 @@ from .user_group import UserGroup
from .website import Website
from .transition_domain import TransitionDomain
from .verified_by_staff import VerifiedByStaff
from .waffle_flag import WaffleFlag
__all__ = [
"Contact",
@ -33,6 +35,7 @@ __all__ = [
"Website",
"TransitionDomain",
"VerifiedByStaff",
"WaffleFlag",
]
auditlog.register(Contact)
@ -51,3 +54,4 @@ auditlog.register(UserGroup, m2m_fields=["permissions"])
auditlog.register(Website)
auditlog.register(TransitionDomain)
auditlog.register(VerifiedByStaff)
auditlog.register(WaffleFlag)

View file

@ -0,0 +1,19 @@
from waffle.models import AbstractUserFlag
import logging
logger = logging.getLogger(__name__)
class WaffleFlag(AbstractUserFlag):
"""
Custom implementation of django-waffles 'Flag' object.
Read more here: https://waffle.readthedocs.io/en/stable/types/flag.html
Use this class when dealing with feature flags, such as profile_feature.
"""
class Meta:
"""Contains meta information about this class"""
verbose_name = "waffle flag"
verbose_name_plural = "Waffle flags"

View file

@ -1,8 +1,10 @@
<div class="usa-site-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert"
>
<div class="usa-site-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
<div class="usa-alert">
<div class="usa-alert__body {% if add_body_class %}{{ add_body_class }}{% endif %}">
<b>Attention:</b> You are on a test site.
{% if has_profile_feature_flag %}
The profile_feature flag is active.
{% endif %}
</div>
</div>
</div>

View file

@ -1,6 +1,7 @@
from django.shortcuts import render
from registrar.models import DomainRequest, Domain, UserDomainRole
from waffle.decorators import flag_is_active
def index(request):
@ -20,6 +21,9 @@ def index(request):
has_deletable_domain_requests = deletable_domain_requests.exists()
context["has_deletable_domain_requests"] = has_deletable_domain_requests
# This is a django waffle flag which toggles features based off of the "flag" table
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
# If they can delete domain requests, add the delete button to the context
if has_deletable_domain_requests:
# Add the delete modal button to the context

View file

@ -1,8 +1,8 @@
-i https://pypi.python.org/simple
annotated-types==0.6.0; python_version >= '3.8'
asgiref==3.8.1; python_version >= '3.8'
boto3==1.34.90; python_version >= '3.8'
botocore==1.34.90; python_version >= '3.8'
boto3==1.34.95; python_version >= '3.8'
botocore==1.34.95; python_version >= '3.8'
cachetools==5.3.3; python_version >= '3.7'
certifi==2024.2.2; python_version >= '3.6'
cfenv==0.5.3
@ -22,9 +22,10 @@ django-csp==3.8
django-fsm==2.8.1
django-login-required-middleware==0.9.0
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
django-waffle==4.1.0; python_version >= '3.8'
django-widget-tweaks==1.5.0; python_version >= '3.8'
environs[django]==11.0.0; python_version >= '3.8'
faker==24.11.0; python_version >= '3.8'
faker==25.0.0; python_version >= '3.8'
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
furl==2.1.3
future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
@ -37,7 +38,7 @@ lxml==5.2.1; python_version >= '3.6'
mako==1.3.3; python_version >= '3.8'
markupsafe==2.1.5; python_version >= '3.7'
marshmallow==3.21.1; python_version >= '3.8'
oic==1.6.1; python_version ~= '3.7'
oic==1.7.0; python_version ~= '3.8'
orderedmultidict==1.0.1
packaging==24.0; python_version >= '3.7'
phonenumberslite==8.13.35