mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-18 18:39:21 +02:00
Refactor groups and permissions: divide fixtures in 2 files, one for users and one for data, load groups in migrations (using methods defined in user_groups model), use hasperm in admin to test for 'superuser'
This commit is contained in:
parent
fd860998fb
commit
cd14eb2584
15 changed files with 667 additions and 559 deletions
|
@ -80,7 +80,7 @@ The endpoint /admin can be used to view and manage site content, including but n
|
||||||
1. Login via login.gov
|
1. Login via login.gov
|
||||||
2. Go to the home page and make sure you can see the part where you can submit an application
|
2. Go to the home page and make sure you can see the part where you can submit an application
|
||||||
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
|
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
|
||||||
4. in src/registrar/fixtures.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
|
4. in src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
|
||||||
|
|
||||||
```
|
```
|
||||||
ADMINS = [
|
ADMINS = [
|
||||||
|
@ -102,7 +102,7 @@ Analysts are a variant of the admin role with limited permissions. The process f
|
||||||
1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
|
1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
|
||||||
2. Go to the home page and make sure you can see the part where you can submit an application
|
2. Go to the home page and make sure you can see the part where you can submit an application
|
||||||
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
|
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
|
||||||
4. in src/registrar/fixtures.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
|
4. in src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
|
||||||
|
|
||||||
```
|
```
|
||||||
STAFF = [
|
STAFF = [
|
||||||
|
@ -145,7 +145,7 @@ You can change the logging verbosity, if needed. Do a web search for "django log
|
||||||
|
|
||||||
## Mock data
|
## Mock data
|
||||||
|
|
||||||
There is a `post_migrate` signal in [signals.py](../../src/registrar/signals.py) that will load the fixtures from [fixtures.py](../../src/registrar/fixtures.py), giving you some test data to play with while developing.
|
There is a `post_migrate` signal in [signals.py](../../src/registrar/signals.py) that will load the fixtures from [fixtures_user.py](../../src/registrar/fixtures_users.py) and [fixtures_applications.py](../../src/registrar/fixtures_applications.py), giving you some test data to play with while developing.
|
||||||
|
|
||||||
See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures.
|
See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures.
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,7 @@ future, as we add additional roles that our product vision calls for
|
||||||
(read-only? editing only some information?), we need to add conditional
|
(read-only? editing only some information?), we need to add conditional
|
||||||
behavior in the permission mixin, or additional mixins that more clearly
|
behavior in the permission mixin, or additional mixins that more clearly
|
||||||
express what is allowed for those new roles.
|
express what is allowed for those new roles.
|
||||||
|
|
||||||
|
# Admin User Permissions
|
||||||
|
|
||||||
|
Refre to [Django Admin Roles](../django-admin/roles.md)
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
# Django admin user roles
|
# Django admin user roles
|
||||||
|
|
||||||
Roles other than superuser should be defined in authentication and authorization groups in django admin
|
For our MVP, we create and maintain 2 admin roles:
|
||||||
|
Full access and CISA analyst. Both have the role `staff`.
|
||||||
|
Permissions on these roles are set through groups:
|
||||||
|
`full_access_group` and `cisa_analysts_group`. These
|
||||||
|
groups and the methods to create them are defined in
|
||||||
|
our `user_group` model and run in a migration.
|
||||||
|
|
||||||
## Superuser
|
## Editing group permissions through code
|
||||||
|
|
||||||
Full access
|
We can edit and deploy new group permissions by
|
||||||
|
editing `user_group` then:
|
||||||
|
|
||||||
## CISA analyst
|
- Duplicating migration `0036_create_groups`
|
||||||
|
and running migrations (RECOMMENDED METHOD), or
|
||||||
|
|
||||||
### Basic permission level
|
- Fake the previous migration to run an existing create groups migration:
|
||||||
|
- step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
Staff
|
- step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||||
|
- step 3: fake run the latest migration in the migrations list
|
||||||
### Additional group permissions
|
|
||||||
|
|
||||||
auditlog | log entry | can view log entry
|
|
||||||
registrar | contact | can view contact
|
|
||||||
registrar | domain application | can change domain application
|
|
||||||
registrar | domain | can view domain
|
|
||||||
registrar | user | can view user
|
|
|
@ -89,7 +89,8 @@ command in the running Cloud.gov container. For example, to run our Django
|
||||||
admin command that loads test fixture data:
|
admin command that loads test fixture data:
|
||||||
|
|
||||||
```
|
```
|
||||||
cf run-task getgov-{environment} --command "./manage.py load" --name fixtures
|
cf run-task getgov-{environment} --command "./manage.py load" --name fixtures--users
|
||||||
|
cf run-task getgov-{environment} --command "./manage.py load" --name fixtures--applications
|
||||||
```
|
```
|
||||||
|
|
||||||
However, this task runs asynchronously in the background without any command
|
However, this task runs asynchronously in the background without any command
|
||||||
|
|
|
@ -161,6 +161,9 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Hide Username (uuid), Groups and Permissions
|
||||||
|
# Q: Now that we're using Groups and Permissions,
|
||||||
|
# do we expose those to analysts to view?
|
||||||
analyst_fieldsets = (
|
analyst_fieldsets = (
|
||||||
(
|
(
|
||||||
None,
|
None,
|
||||||
|
@ -180,6 +183,8 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# NOT all fields are readonly for admin, otherwise we would have
|
||||||
|
# set this at the permissions level. The exception is 'status'
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"password",
|
"password",
|
||||||
"Personal Info",
|
"Personal Info",
|
||||||
|
@ -196,8 +201,15 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_list_display(self, request):
|
def get_list_display(self, request):
|
||||||
if request.user.groups.filter(name='cisa_analysts_group').exists():
|
# The full_access_permission perm will load onto the full_access_group
|
||||||
# Customize the list display for staff users
|
# which is equivalent to superuser. The other group we use to manage
|
||||||
|
# perms is cisa_analysts_group. cisa_analysts_group will never contain
|
||||||
|
# full_access_permission
|
||||||
|
if request.user.has_perm('registrar.full_access_permission'):
|
||||||
|
# Use the default list display for all access users
|
||||||
|
return super().get_list_display(request)
|
||||||
|
|
||||||
|
# Customize the list display for analysts
|
||||||
return (
|
return (
|
||||||
"email",
|
"email",
|
||||||
"first_name",
|
"first_name",
|
||||||
|
@ -207,22 +219,18 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
"status",
|
"status",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use the default list display for non-staff users
|
|
||||||
return super().get_list_display(request)
|
|
||||||
|
|
||||||
def get_fieldsets(self, request, obj=None):
|
def get_fieldsets(self, request, obj=None):
|
||||||
if request.user.groups.filter(name='cisa_analysts_group').exists():
|
if request.user.has_perm('registrar.full_access_permission'):
|
||||||
# If the user doesn't have permission to change the model,
|
# Show all fields for all access users
|
||||||
# show a read-only fieldset
|
|
||||||
return self.analyst_fieldsets
|
|
||||||
|
|
||||||
# If the user has permission to change the model, show all fields
|
|
||||||
return super().get_fieldsets(request, obj)
|
return super().get_fieldsets(request, obj)
|
||||||
|
|
||||||
|
# show analyst_fieldsets for analysts
|
||||||
|
return self.analyst_fieldsets
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
if request.user.groups.filter(name='cisa_analysts_group').exists():
|
if request.user.has_perm('registrar.full_access_permission'):
|
||||||
|
return () # No read-only fields for all access users
|
||||||
return self.analyst_readonly_fields # Read-only fields for analysts
|
return self.analyst_readonly_fields # Read-only fields for analysts
|
||||||
return () # No read-only fields for other users
|
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
|
@ -401,7 +409,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
readonly_fields = list(self.readonly_fields)
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
if request.user.groups.filter(name='full_access_group').exists():
|
if request.user.has_perm('registrar.full_access_permission'):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
else:
|
else:
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
@ -619,7 +627,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
["current_websites", "other_contacts", "alternative_domains"]
|
["current_websites", "other_contacts", "alternative_domains"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.user.groups.filter(name='full_access_group').exists():
|
if request.user.has_perm('registrar.full_access_permission'):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
else:
|
else:
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
|
|
|
@ -1,511 +0,0 @@
|
||||||
import logging
|
|
||||||
import random
|
|
||||||
from faker import Faker
|
|
||||||
|
|
||||||
from registrar.models import (
|
|
||||||
User,
|
|
||||||
UserGroup,
|
|
||||||
DomainApplication,
|
|
||||||
DraftDomain,
|
|
||||||
Contact,
|
|
||||||
Website,
|
|
||||||
)
|
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
fake = Faker()
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class UserFixture:
|
|
||||||
"""
|
|
||||||
Load users into the database.
|
|
||||||
|
|
||||||
Make sure this class' `load` method is called from `handle`
|
|
||||||
in management/commands/load.py, then use `./manage.py load`
|
|
||||||
to run this code.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ADMINS = [
|
|
||||||
{
|
|
||||||
"username": "5f283494-31bd-49b5-b024-a7e7cae00848",
|
|
||||||
"first_name": "Rachid",
|
|
||||||
"last_name": "Mrad",
|
|
||||||
},
|
|
||||||
# {
|
|
||||||
# "username": "eb2214cd-fc0c-48c0-9dbd-bc4cd6820c74",
|
|
||||||
# "first_name": "Alysia",
|
|
||||||
# "last_name": "Broddrick",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "8f8e7293-17f7-4716-889b-1990241cbd39",
|
|
||||||
# "first_name": "Katherine",
|
|
||||||
# "last_name": "Osos",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "70488e0a-e937-4894-a28c-16f5949effd4",
|
|
||||||
# "first_name": "Gaby",
|
|
||||||
# "last_name": "DiSarli",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "83c2b6dd-20a2-4cac-bb40-e22a72d2955c",
|
|
||||||
# "first_name": "Cameron",
|
|
||||||
# "last_name": "Dixon",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "0353607a-cbba-47d2-98d7-e83dcd5b90ea",
|
|
||||||
# "first_name": "Ryan",
|
|
||||||
# "last_name": "Brooks",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "30001ee7-0467-4df2-8db2-786e79606060",
|
|
||||||
# "first_name": "Zander",
|
|
||||||
# "last_name": "Adkinson",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "2bf518c2-485a-4c42-ab1a-f5a8b0a08484",
|
|
||||||
# "first_name": "Paul",
|
|
||||||
# "last_name": "Kuykendall",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "2a88a97b-be96-4aad-b99e-0b605b492c78",
|
|
||||||
# "first_name": "Rebecca",
|
|
||||||
# "last_name": "Hsieh",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "fa69c8e8-da83-4798-a4f2-263c9ce93f52",
|
|
||||||
# "first_name": "David",
|
|
||||||
# "last_name": "Kennedy",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "f14433d8-f0e9-41bf-9c72-b99b110e665d",
|
|
||||||
# "first_name": "Nicolle",
|
|
||||||
# "last_name": "LeClair",
|
|
||||||
# },
|
|
||||||
]
|
|
||||||
|
|
||||||
STAFF = [
|
|
||||||
{
|
|
||||||
"username": "319c490d-453b-43d9-bc4d-7d6cd8ff6844",
|
|
||||||
"first_name": "Rachid-Analyst",
|
|
||||||
"last_name": "Mrad-Analyst",
|
|
||||||
"email": "rachid.mrad@gmail.com",
|
|
||||||
},
|
|
||||||
# {
|
|
||||||
# "username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd",
|
|
||||||
# "first_name": "Alysia-Analyst",
|
|
||||||
# "last_name": "Alysia-Analyst",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "91a9b97c-bd0a-458d-9823-babfde7ebf44",
|
|
||||||
# "first_name": "Katherine-Analyst",
|
|
||||||
# "last_name": "Osos-Analyst",
|
|
||||||
# "email": "kosos@truss.works",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
|
|
||||||
# "first_name": "Zander-Analyst",
|
|
||||||
# "last_name": "Adkinson-Analyst",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "57ab5847-7789-49fe-a2f9-21d38076d699",
|
|
||||||
# "first_name": "Paul-Analyst",
|
|
||||||
# "last_name": "Kuykendall-Analyst",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "e474e7a9-71ca-449d-833c-8a6e094dd117",
|
|
||||||
# "first_name": "Rebecca-Analyst",
|
|
||||||
# "last_name": "Hsieh-Analyst",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "5dc6c9a6-61d9-42b4-ba54-4beff28bac3c",
|
|
||||||
# "first_name": "David-Analyst",
|
|
||||||
# "last_name": "Kennedy-Analyst",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "0eb6f326-a3d4-410f-a521-aa4c1fad4e47",
|
|
||||||
# "first_name": "Gaby-Analyst",
|
|
||||||
# "last_name": "DiSarli-Analyst",
|
|
||||||
# "email": "gaby@truss.works",
|
|
||||||
# },
|
|
||||||
# {
|
|
||||||
# "username": "cfe7c2fc-e24a-480e-8b78-28645a1459b3",
|
|
||||||
# "first_name": "Nicolle-Analyst",
|
|
||||||
# "last_name": "LeClair-Analyst",
|
|
||||||
# "email": "nicolle.leclair@ecstech.com",
|
|
||||||
# },
|
|
||||||
]
|
|
||||||
|
|
||||||
CISA_ANALYST_GROUP_PERMISSIONS = [
|
|
||||||
{
|
|
||||||
"app_label": "auditlog",
|
|
||||||
"model": "logentry",
|
|
||||||
"permissions": ["view_logentry"],
|
|
||||||
},
|
|
||||||
{"app_label": "registrar", "model": "contact", "permissions": ["view_contact"]},
|
|
||||||
{
|
|
||||||
"app_label": "registrar",
|
|
||||||
"model": "domaininformation",
|
|
||||||
"permissions": ["change_domaininformation"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"app_label": "registrar",
|
|
||||||
"model": "domainapplication",
|
|
||||||
"permissions": ["change_domainapplication"],
|
|
||||||
},
|
|
||||||
{"app_label": "registrar", "model": "domain", "permissions": ["view_domain"]},
|
|
||||||
{
|
|
||||||
"app_label": "registrar",
|
|
||||||
"model": "draftdomain",
|
|
||||||
"permissions": ["change_draftdomain"],
|
|
||||||
},
|
|
||||||
{"app_label": "registrar", "model": "user", "permissions": ["change_user"]},
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls):
|
|
||||||
logger.info("Going to load %s groups" % str(len(cls.ADMINS)))
|
|
||||||
try:
|
|
||||||
cisa_analysts_group, cisa_analysts_group_created = UserGroup.objects.get_or_create(
|
|
||||||
name="cisa_analysts_group",
|
|
||||||
)
|
|
||||||
full_access_group, full_access_group_created = UserGroup.objects.get_or_create(
|
|
||||||
name="full_access_group",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
|
|
||||||
if cisa_analysts_group_created:
|
|
||||||
for permission in cls.CISA_ANALYST_GROUP_PERMISSIONS:
|
|
||||||
try:
|
|
||||||
app_label = permission["app_label"]
|
|
||||||
model_name = permission["model"]
|
|
||||||
permissions = permission["permissions"]
|
|
||||||
|
|
||||||
# Retrieve the content type for the app and model
|
|
||||||
content_type = ContentType.objects.get(
|
|
||||||
app_label=app_label, model=model_name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Retrieve the permissions based on their codenames
|
|
||||||
permissions = Permission.objects.filter(
|
|
||||||
content_type=content_type, codename__in=permissions
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assign the permissions to the group
|
|
||||||
cisa_analysts_group.permissions.add(*permissions)
|
|
||||||
|
|
||||||
# Convert the permissions QuerySet to a list of codenames
|
|
||||||
permission_list = list(
|
|
||||||
permissions.values_list("codename", flat=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
app_label
|
|
||||||
+ " | "
|
|
||||||
+ model_name
|
|
||||||
+ " | "
|
|
||||||
+ ", ".join(permission_list)
|
|
||||||
+ " added to group "
|
|
||||||
+ cisa_analysts_group.name
|
|
||||||
)
|
|
||||||
|
|
||||||
cisa_analysts_group.save()
|
|
||||||
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
else:
|
|
||||||
logger.warning(cisa_analysts_group.name + " was not created successfully.")
|
|
||||||
|
|
||||||
if full_access_group_created:
|
|
||||||
try:
|
|
||||||
# Get all available permissions
|
|
||||||
all_permissions = Permission.objects.all()
|
|
||||||
|
|
||||||
# Assign all permissions to the group
|
|
||||||
full_access_group.permissions.add(*all_permissions)
|
|
||||||
|
|
||||||
full_access_group.save()
|
|
||||||
logger.debug("All permissions added to group " + full_access_group.name)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
else:
|
|
||||||
logger.warning(full_access_group.name + " was not created successfully.")
|
|
||||||
logger.info("%s groups loaded." % str(len(cls.ADMINS)))
|
|
||||||
|
|
||||||
logger.info("Going to load %s superusers" % str(len(cls.ADMINS)))
|
|
||||||
for admin in cls.ADMINS:
|
|
||||||
try:
|
|
||||||
user, _ = User.objects.get_or_create(
|
|
||||||
username=admin["username"],
|
|
||||||
)
|
|
||||||
user.is_superuser = False
|
|
||||||
user.first_name = admin["first_name"]
|
|
||||||
user.last_name = admin["last_name"]
|
|
||||||
if "email" in admin.keys():
|
|
||||||
user.email = admin["email"]
|
|
||||||
user.is_staff = True
|
|
||||||
user.is_active = True
|
|
||||||
user.groups.add(full_access_group)
|
|
||||||
user.save()
|
|
||||||
logger.debug("User object created for %s" % admin["first_name"])
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
logger.info("All superusers loaded.")
|
|
||||||
|
|
||||||
logger.info("Going to load %s CISA analysts (staff)" % str(len(cls.STAFF)))
|
|
||||||
for staff in cls.STAFF:
|
|
||||||
try:
|
|
||||||
user, _ = User.objects.get_or_create(
|
|
||||||
username=staff["username"],
|
|
||||||
)
|
|
||||||
user.is_superuser = False
|
|
||||||
user.first_name = staff["first_name"]
|
|
||||||
user.last_name = staff["last_name"]
|
|
||||||
if "email" in admin.keys():
|
|
||||||
user.email = admin["email"]
|
|
||||||
user.is_staff = True
|
|
||||||
user.is_active = True
|
|
||||||
user.groups.add(cisa_analysts_group)
|
|
||||||
user.save()
|
|
||||||
logger.debug("User object created for %s" % staff["first_name"])
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
logger.info("All CISA analysts (staff) loaded.")
|
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationFixture:
|
|
||||||
"""
|
|
||||||
Load domain applications into the database.
|
|
||||||
|
|
||||||
Make sure this class' `load` method is called from `handle`
|
|
||||||
in management/commands/load.py, then use `./manage.py load`
|
|
||||||
to run this code.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# any fields not specified here will be filled in with fake data or defaults
|
|
||||||
# NOTE BENE: each fixture must have `organization_name` for uniqueness!
|
|
||||||
# Here is a more complete example as a template:
|
|
||||||
# {
|
|
||||||
# "status": "started",
|
|
||||||
# "organization_name": "Example - Just started",
|
|
||||||
# "organization_type": "federal",
|
|
||||||
# "federal_agency": None,
|
|
||||||
# "federal_type": None,
|
|
||||||
# "address_line1": None,
|
|
||||||
# "address_line2": None,
|
|
||||||
# "city": None,
|
|
||||||
# "state_territory": None,
|
|
||||||
# "zipcode": None,
|
|
||||||
# "urbanization": None,
|
|
||||||
# "purpose": None,
|
|
||||||
# "anything_else": None,
|
|
||||||
# "is_policy_acknowledged": None,
|
|
||||||
# "authorizing_official": None,
|
|
||||||
# "submitter": None,
|
|
||||||
# "other_contacts": [],
|
|
||||||
# "current_websites": [],
|
|
||||||
# "alternative_domains": [],
|
|
||||||
# },
|
|
||||||
DA = [
|
|
||||||
{
|
|
||||||
"status": "started",
|
|
||||||
"organization_name": "Example - Finished but not Submitted",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "submitted",
|
|
||||||
"organization_name": "Example - Submitted but pending Investigation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "in review",
|
|
||||||
"organization_name": "Example - In Investigation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "in review",
|
|
||||||
"organization_name": "Example - Approved",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "withdrawn",
|
|
||||||
"organization_name": "Example - Withdrawn",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "action needed",
|
|
||||||
"organization_name": "Example - Action Needed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"status": "rejected",
|
|
||||||
"organization_name": "Example - Rejected",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fake_contact(cls):
|
|
||||||
return {
|
|
||||||
"first_name": fake.first_name(),
|
|
||||||
"middle_name": None,
|
|
||||||
"last_name": fake.last_name(),
|
|
||||||
"title": fake.job(),
|
|
||||||
"email": fake.ascii_safe_email(),
|
|
||||||
"phone": "201-555-5555",
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fake_dot_gov(cls):
|
|
||||||
return f"{fake.slug()}.gov"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
|
|
||||||
"""Helper method used by `load`."""
|
|
||||||
da.status = app["status"] if "status" in app else "started"
|
|
||||||
da.organization_type = (
|
|
||||||
app["organization_type"] if "organization_type" in app else "federal"
|
|
||||||
)
|
|
||||||
da.federal_agency = (
|
|
||||||
app["federal_agency"]
|
|
||||||
if "federal_agency" in app
|
|
||||||
# Random choice of agency for selects, used as placeholders for testing.
|
|
||||||
else random.choice(DomainApplication.AGENCIES) # nosec
|
|
||||||
)
|
|
||||||
|
|
||||||
da.federal_type = (
|
|
||||||
app["federal_type"]
|
|
||||||
if "federal_type" in app
|
|
||||||
else random.choice(["executive", "judicial", "legislative"]) # nosec
|
|
||||||
)
|
|
||||||
da.address_line1 = (
|
|
||||||
app["address_line1"] if "address_line1" in app else fake.street_address()
|
|
||||||
)
|
|
||||||
da.address_line2 = app["address_line2"] if "address_line2" in app else None
|
|
||||||
da.city = app["city"] if "city" in app else fake.city()
|
|
||||||
da.state_territory = (
|
|
||||||
app["state_territory"] if "state_territory" in app else fake.state_abbr()
|
|
||||||
)
|
|
||||||
da.zipcode = app["zipcode"] if "zipcode" in app else fake.postalcode()
|
|
||||||
da.urbanization = app["urbanization"] if "urbanization" in app else None
|
|
||||||
da.purpose = app["purpose"] if "purpose" in app else fake.paragraph()
|
|
||||||
da.anything_else = app["anything_else"] if "anything_else" in app else None
|
|
||||||
da.is_policy_acknowledged = (
|
|
||||||
app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _set_foreign_key_fields(cls, da: DomainApplication, app: dict, user: User):
|
|
||||||
"""Helper method used by `load`."""
|
|
||||||
if not da.investigator:
|
|
||||||
da.investigator = (
|
|
||||||
User.objects.get(username=user.username)
|
|
||||||
if "investigator" in app
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
if not da.authorizing_official:
|
|
||||||
if (
|
|
||||||
"authorizing_official" in app
|
|
||||||
and app["authorizing_official"] is not None
|
|
||||||
):
|
|
||||||
da.authorizing_official, _ = Contact.objects.get_or_create(
|
|
||||||
**app["authorizing_official"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
da.authorizing_official = Contact.objects.create(**cls.fake_contact())
|
|
||||||
|
|
||||||
if not da.submitter:
|
|
||||||
if "submitter" in app and app["submitter"] is not None:
|
|
||||||
da.submitter, _ = Contact.objects.get_or_create(**app["submitter"])
|
|
||||||
else:
|
|
||||||
da.submitter = Contact.objects.create(**cls.fake_contact())
|
|
||||||
|
|
||||||
if not da.requested_domain:
|
|
||||||
if "requested_domain" in app and app["requested_domain"] is not None:
|
|
||||||
da.requested_domain, _ = DraftDomain.objects.get_or_create(
|
|
||||||
name=app["requested_domain"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
da.requested_domain = DraftDomain.objects.create(
|
|
||||||
name=cls.fake_dot_gov()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
|
||||||
"""Helper method used by `load`."""
|
|
||||||
if "other_contacts" in app:
|
|
||||||
for contact in app["other_contacts"]:
|
|
||||||
da.other_contacts.add(Contact.objects.get_or_create(**contact)[0])
|
|
||||||
elif not da.other_contacts.exists():
|
|
||||||
other_contacts = [
|
|
||||||
Contact.objects.create(**cls.fake_contact())
|
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
|
||||||
da.other_contacts.add(*other_contacts)
|
|
||||||
|
|
||||||
if "current_websites" in app:
|
|
||||||
for website in app["current_websites"]:
|
|
||||||
da.current_websites.add(
|
|
||||||
Website.objects.get_or_create(website=website)[0]
|
|
||||||
)
|
|
||||||
elif not da.current_websites.exists():
|
|
||||||
current_websites = [
|
|
||||||
Website.objects.create(website=fake.uri())
|
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
|
||||||
da.current_websites.add(*current_websites)
|
|
||||||
|
|
||||||
if "alternative_domains" in app:
|
|
||||||
for domain in app["alternative_domains"]:
|
|
||||||
da.alternative_domains.add(
|
|
||||||
Website.objects.get_or_create(website=domain)[0]
|
|
||||||
)
|
|
||||||
elif not da.alternative_domains.exists():
|
|
||||||
alternative_domains = [
|
|
||||||
Website.objects.create(website=cls.fake_dot_gov())
|
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
|
||||||
da.alternative_domains.add(*alternative_domains)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls):
|
|
||||||
"""Creates domain applications for each user in the database."""
|
|
||||||
logger.info("Going to load %s domain applications" % len(cls.DA))
|
|
||||||
try:
|
|
||||||
users = list(User.objects.all()) # force evaluation to catch db errors
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
logger.debug("Loading domain applications for %s" % user)
|
|
||||||
for app in cls.DA:
|
|
||||||
try:
|
|
||||||
da, _ = DomainApplication.objects.get_or_create(
|
|
||||||
creator=user,
|
|
||||||
organization_name=app["organization_name"],
|
|
||||||
)
|
|
||||||
cls._set_non_foreign_key_fields(da, app)
|
|
||||||
cls._set_foreign_key_fields(da, app, user)
|
|
||||||
da.save()
|
|
||||||
cls._set_many_to_many_relations(da, app)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
|
|
||||||
|
|
||||||
class DomainFixture(DomainApplicationFixture):
|
|
||||||
|
|
||||||
"""Create one domain and permissions on it for each user."""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls):
|
|
||||||
try:
|
|
||||||
users = list(User.objects.all()) # force evaluation to catch db errors
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
return
|
|
||||||
|
|
||||||
for user in users:
|
|
||||||
# approve one of each users in review status domains
|
|
||||||
application = DomainApplication.objects.filter(
|
|
||||||
creator=user, status=DomainApplication.IN_REVIEW
|
|
||||||
).last()
|
|
||||||
logger.debug(f"Approving {application} for {user}")
|
|
||||||
application.approve()
|
|
||||||
application.save()
|
|
253
src/registrar/fixtures_applications.py
Normal file
253
src/registrar/fixtures_applications.py
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
from registrar.models import (
|
||||||
|
User,
|
||||||
|
DomainApplication,
|
||||||
|
DraftDomain,
|
||||||
|
Contact,
|
||||||
|
Website,
|
||||||
|
)
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
fake = Faker()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainApplicationFixture:
|
||||||
|
"""
|
||||||
|
Load domain applications into the database.
|
||||||
|
|
||||||
|
Make sure this class' `load` method is called from `handle`
|
||||||
|
in management/commands/load.py, then use `./manage.py load`
|
||||||
|
to run this code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# any fields not specified here will be filled in with fake data or defaults
|
||||||
|
# NOTE BENE: each fixture must have `organization_name` for uniqueness!
|
||||||
|
# Here is a more complete example as a template:
|
||||||
|
# {
|
||||||
|
# "status": "started",
|
||||||
|
# "organization_name": "Example - Just started",
|
||||||
|
# "organization_type": "federal",
|
||||||
|
# "federal_agency": None,
|
||||||
|
# "federal_type": None,
|
||||||
|
# "address_line1": None,
|
||||||
|
# "address_line2": None,
|
||||||
|
# "city": None,
|
||||||
|
# "state_territory": None,
|
||||||
|
# "zipcode": None,
|
||||||
|
# "urbanization": None,
|
||||||
|
# "purpose": None,
|
||||||
|
# "anything_else": None,
|
||||||
|
# "is_policy_acknowledged": None,
|
||||||
|
# "authorizing_official": None,
|
||||||
|
# "submitter": None,
|
||||||
|
# "other_contacts": [],
|
||||||
|
# "current_websites": [],
|
||||||
|
# "alternative_domains": [],
|
||||||
|
# },
|
||||||
|
DA = [
|
||||||
|
{
|
||||||
|
"status": "started",
|
||||||
|
"organization_name": "Example - Finished but not Submitted",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "submitted",
|
||||||
|
"organization_name": "Example - Submitted but pending Investigation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "in review",
|
||||||
|
"organization_name": "Example - In Investigation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "in review",
|
||||||
|
"organization_name": "Example - Approved",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "withdrawn",
|
||||||
|
"organization_name": "Example - Withdrawn",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "action needed",
|
||||||
|
"organization_name": "Example - Action Needed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"status": "rejected",
|
||||||
|
"organization_name": "Example - Rejected",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fake_contact(cls):
|
||||||
|
return {
|
||||||
|
"first_name": fake.first_name(),
|
||||||
|
"middle_name": None,
|
||||||
|
"last_name": fake.last_name(),
|
||||||
|
"title": fake.job(),
|
||||||
|
"email": fake.ascii_safe_email(),
|
||||||
|
"phone": "201-555-5555",
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fake_dot_gov(cls):
|
||||||
|
return f"{fake.slug()}.gov"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
|
||||||
|
"""Helper method used by `load`."""
|
||||||
|
da.status = app["status"] if "status" in app else "started"
|
||||||
|
da.organization_type = (
|
||||||
|
app["organization_type"] if "organization_type" in app else "federal"
|
||||||
|
)
|
||||||
|
da.federal_agency = (
|
||||||
|
app["federal_agency"]
|
||||||
|
if "federal_agency" in app
|
||||||
|
# Random choice of agency for selects, used as placeholders for testing.
|
||||||
|
else random.choice(DomainApplication.AGENCIES) # nosec
|
||||||
|
)
|
||||||
|
|
||||||
|
da.federal_type = (
|
||||||
|
app["federal_type"]
|
||||||
|
if "federal_type" in app
|
||||||
|
else random.choice(["executive", "judicial", "legislative"]) # nosec
|
||||||
|
)
|
||||||
|
da.address_line1 = (
|
||||||
|
app["address_line1"] if "address_line1" in app else fake.street_address()
|
||||||
|
)
|
||||||
|
da.address_line2 = app["address_line2"] if "address_line2" in app else None
|
||||||
|
da.city = app["city"] if "city" in app else fake.city()
|
||||||
|
da.state_territory = (
|
||||||
|
app["state_territory"] if "state_territory" in app else fake.state_abbr()
|
||||||
|
)
|
||||||
|
da.zipcode = app["zipcode"] if "zipcode" in app else fake.postalcode()
|
||||||
|
da.urbanization = app["urbanization"] if "urbanization" in app else None
|
||||||
|
da.purpose = app["purpose"] if "purpose" in app else fake.paragraph()
|
||||||
|
da.anything_else = app["anything_else"] if "anything_else" in app else None
|
||||||
|
da.is_policy_acknowledged = (
|
||||||
|
app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _set_foreign_key_fields(cls, da: DomainApplication, app: dict, user: User):
|
||||||
|
"""Helper method used by `load`."""
|
||||||
|
if not da.investigator:
|
||||||
|
da.investigator = (
|
||||||
|
User.objects.get(username=user.username)
|
||||||
|
if "investigator" in app
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not da.authorizing_official:
|
||||||
|
if (
|
||||||
|
"authorizing_official" in app
|
||||||
|
and app["authorizing_official"] is not None
|
||||||
|
):
|
||||||
|
da.authorizing_official, _ = Contact.objects.get_or_create(
|
||||||
|
**app["authorizing_official"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
da.authorizing_official = Contact.objects.create(**cls.fake_contact())
|
||||||
|
|
||||||
|
if not da.submitter:
|
||||||
|
if "submitter" in app and app["submitter"] is not None:
|
||||||
|
da.submitter, _ = Contact.objects.get_or_create(**app["submitter"])
|
||||||
|
else:
|
||||||
|
da.submitter = Contact.objects.create(**cls.fake_contact())
|
||||||
|
|
||||||
|
if not da.requested_domain:
|
||||||
|
if "requested_domain" in app and app["requested_domain"] is not None:
|
||||||
|
da.requested_domain, _ = DraftDomain.objects.get_or_create(
|
||||||
|
name=app["requested_domain"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
da.requested_domain = DraftDomain.objects.create(
|
||||||
|
name=cls.fake_dot_gov()
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
||||||
|
"""Helper method used by `load`."""
|
||||||
|
if "other_contacts" in app:
|
||||||
|
for contact in app["other_contacts"]:
|
||||||
|
da.other_contacts.add(Contact.objects.get_or_create(**contact)[0])
|
||||||
|
elif not da.other_contacts.exists():
|
||||||
|
other_contacts = [
|
||||||
|
Contact.objects.create(**cls.fake_contact())
|
||||||
|
for _ in range(random.randint(0, 3)) # nosec
|
||||||
|
]
|
||||||
|
da.other_contacts.add(*other_contacts)
|
||||||
|
|
||||||
|
if "current_websites" in app:
|
||||||
|
for website in app["current_websites"]:
|
||||||
|
da.current_websites.add(
|
||||||
|
Website.objects.get_or_create(website=website)[0]
|
||||||
|
)
|
||||||
|
elif not da.current_websites.exists():
|
||||||
|
current_websites = [
|
||||||
|
Website.objects.create(website=fake.uri())
|
||||||
|
for _ in range(random.randint(0, 3)) # nosec
|
||||||
|
]
|
||||||
|
da.current_websites.add(*current_websites)
|
||||||
|
|
||||||
|
if "alternative_domains" in app:
|
||||||
|
for domain in app["alternative_domains"]:
|
||||||
|
da.alternative_domains.add(
|
||||||
|
Website.objects.get_or_create(website=domain)[0]
|
||||||
|
)
|
||||||
|
elif not da.alternative_domains.exists():
|
||||||
|
alternative_domains = [
|
||||||
|
Website.objects.create(website=cls.fake_dot_gov())
|
||||||
|
for _ in range(random.randint(0, 3)) # nosec
|
||||||
|
]
|
||||||
|
da.alternative_domains.add(*alternative_domains)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
"""Creates domain applications for each user in the database."""
|
||||||
|
logger.info("Going to load %s domain applications" % len(cls.DA))
|
||||||
|
try:
|
||||||
|
users = list(User.objects.all()) # force evaluation to catch db errors
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
logger.debug("Loading domain applications for %s" % user)
|
||||||
|
for app in cls.DA:
|
||||||
|
try:
|
||||||
|
da, _ = DomainApplication.objects.get_or_create(
|
||||||
|
creator=user,
|
||||||
|
organization_name=app["organization_name"],
|
||||||
|
)
|
||||||
|
cls._set_non_foreign_key_fields(da, app)
|
||||||
|
cls._set_foreign_key_fields(da, app, user)
|
||||||
|
da.save()
|
||||||
|
cls._set_many_to_many_relations(da, app)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainFixture(DomainApplicationFixture):
|
||||||
|
|
||||||
|
"""Create one domain and permissions on it for each user."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
try:
|
||||||
|
users = list(User.objects.all()) # force evaluation to catch db errors
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
# approve one of each users in review status domains
|
||||||
|
application = DomainApplication.objects.filter(
|
||||||
|
creator=user, status=DomainApplication.IN_REVIEW
|
||||||
|
).last()
|
||||||
|
logger.debug(f"Approving {application} for {user}")
|
||||||
|
application.approve()
|
||||||
|
application.save()
|
156
src/registrar/fixtures_users.py
Normal file
156
src/registrar/fixtures_users.py
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import logging
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
from registrar.models import (
|
||||||
|
User,
|
||||||
|
UserGroup,
|
||||||
|
)
|
||||||
|
|
||||||
|
fake = Faker()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UserFixture:
|
||||||
|
"""
|
||||||
|
Load users into the database.
|
||||||
|
|
||||||
|
Make sure this class' `load` method is called from `handle`
|
||||||
|
in management/commands/load.py, then use `./manage.py load`
|
||||||
|
to run this code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ADMINS = [
|
||||||
|
{
|
||||||
|
"username": "5f283494-31bd-49b5-b024-a7e7cae00848",
|
||||||
|
"first_name": "Rachid",
|
||||||
|
"last_name": "Mrad",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "eb2214cd-fc0c-48c0-9dbd-bc4cd6820c74",
|
||||||
|
"first_name": "Alysia",
|
||||||
|
"last_name": "Broddrick",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "8f8e7293-17f7-4716-889b-1990241cbd39",
|
||||||
|
"first_name": "Katherine",
|
||||||
|
"last_name": "Osos",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "70488e0a-e937-4894-a28c-16f5949effd4",
|
||||||
|
"first_name": "Gaby",
|
||||||
|
"last_name": "DiSarli",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "83c2b6dd-20a2-4cac-bb40-e22a72d2955c",
|
||||||
|
"first_name": "Cameron",
|
||||||
|
"last_name": "Dixon",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "0353607a-cbba-47d2-98d7-e83dcd5b90ea",
|
||||||
|
"first_name": "Ryan",
|
||||||
|
"last_name": "Brooks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "30001ee7-0467-4df2-8db2-786e79606060",
|
||||||
|
"first_name": "Zander",
|
||||||
|
"last_name": "Adkinson",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "2bf518c2-485a-4c42-ab1a-f5a8b0a08484",
|
||||||
|
"first_name": "Paul",
|
||||||
|
"last_name": "Kuykendall",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "2a88a97b-be96-4aad-b99e-0b605b492c78",
|
||||||
|
"first_name": "Rebecca",
|
||||||
|
"last_name": "Hsieh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "fa69c8e8-da83-4798-a4f2-263c9ce93f52",
|
||||||
|
"first_name": "David",
|
||||||
|
"last_name": "Kennedy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "f14433d8-f0e9-41bf-9c72-b99b110e665d",
|
||||||
|
"first_name": "Nicolle",
|
||||||
|
"last_name": "LeClair",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
STAFF = [
|
||||||
|
{
|
||||||
|
"username": "319c490d-453b-43d9-bc4d-7d6cd8ff6844",
|
||||||
|
"first_name": "Rachid-Analyst",
|
||||||
|
"last_name": "Mrad-Analyst",
|
||||||
|
"email": "rachid.mrad@gmail.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd",
|
||||||
|
"first_name": "Alysia-Analyst",
|
||||||
|
"last_name": "Alysia-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "91a9b97c-bd0a-458d-9823-babfde7ebf44",
|
||||||
|
"first_name": "Katherine-Analyst",
|
||||||
|
"last_name": "Osos-Analyst",
|
||||||
|
"email": "kosos@truss.works",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
|
||||||
|
"first_name": "Zander-Analyst",
|
||||||
|
"last_name": "Adkinson-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "57ab5847-7789-49fe-a2f9-21d38076d699",
|
||||||
|
"first_name": "Paul-Analyst",
|
||||||
|
"last_name": "Kuykendall-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "e474e7a9-71ca-449d-833c-8a6e094dd117",
|
||||||
|
"first_name": "Rebecca-Analyst",
|
||||||
|
"last_name": "Hsieh-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "5dc6c9a6-61d9-42b4-ba54-4beff28bac3c",
|
||||||
|
"first_name": "David-Analyst",
|
||||||
|
"last_name": "Kennedy-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "0eb6f326-a3d4-410f-a521-aa4c1fad4e47",
|
||||||
|
"first_name": "Gaby-Analyst",
|
||||||
|
"last_name": "DiSarli-Analyst",
|
||||||
|
"email": "gaby@truss.works",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "cfe7c2fc-e24a-480e-8b78-28645a1459b3",
|
||||||
|
"first_name": "Nicolle-Analyst",
|
||||||
|
"last_name": "LeClair-Analyst",
|
||||||
|
"email": "nicolle.leclair@ecstech.com",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def load_users(cls, users, group_name):
|
||||||
|
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.first_name = user_data["first_name"]
|
||||||
|
user.last_name = user_data["last_name"]
|
||||||
|
if "email" in user_data:
|
||||||
|
user.email = user_data["email"]
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_active = True
|
||||||
|
group = UserGroup.objects.get(name=group_name)
|
||||||
|
user.groups.add(group)
|
||||||
|
user.save()
|
||||||
|
logger.debug(f"User object created for {user_data['first_name']}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
logger.info(f"All users in group {group_name} loaded.")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load(cls):
|
||||||
|
cls.load_users(cls, cls.ADMINS, "full_access_group")
|
||||||
|
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")
|
||||||
|
|
|
@ -4,7 +4,8 @@ from django.core.management.base import BaseCommand
|
||||||
from auditlog.context import disable_auditlog # type: ignore
|
from auditlog.context import disable_auditlog # type: ignore
|
||||||
|
|
||||||
|
|
||||||
from registrar.fixtures import UserFixture, DomainApplicationFixture, DomainFixture
|
from registrar.fixtures_users import UserFixture
|
||||||
|
from registrar.fixtures_applications import DomainApplicationFixture, DomainFixture
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import django.db.models.deletion
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
("registrar", "0031_transitiondomain_and_more"),
|
("registrar", "0032_alter_transitiondomain_status"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
20
src/registrar/migrations/0034_alter_user_options.py
Normal file
20
src/registrar/migrations/0034_alter_user_options.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.1 on 2023-09-27 18:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0033_usergroup"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="user",
|
||||||
|
options={
|
||||||
|
"permissions": [
|
||||||
|
("full_access_permission", "Full Access Permission"),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
40
src/registrar/migrations/0035_contenttypes_permissions.py
Normal file
40
src/registrar/migrations/0035_contenttypes_permissions.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# From mithuntnt's answer on:
|
||||||
|
# https://stackoverflow.com/questions/26464838/getting-model-contenttype-in-migration-django-1-7
|
||||||
|
# The problem is that ContentType and Permission objects are not already created
|
||||||
|
# while we're still running migrations, so we'll go ahead and speen up that process
|
||||||
|
# a bit before we attempt to create groups which require Permissions and ContentType.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
def create_all_contenttypes(**kwargs):
|
||||||
|
from django.apps import apps
|
||||||
|
from django.contrib.contenttypes.management import create_contenttypes
|
||||||
|
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
create_contenttypes(app_config, **kwargs)
|
||||||
|
|
||||||
|
def create_all_permissions(**kwargs):
|
||||||
|
from django.contrib.auth.management import create_permissions
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
for app_config in apps.get_app_configs():
|
||||||
|
create_permissions(app_config, **kwargs)
|
||||||
|
|
||||||
|
def forward(apps, schema_editor):
|
||||||
|
create_all_contenttypes()
|
||||||
|
create_all_permissions()
|
||||||
|
|
||||||
|
def backward(apps, schema_editor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
("registrar", "0034_alter_user_options"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(forward, backward)
|
||||||
|
]
|
22
src/registrar/migrations/0036_create_groups.py
Normal file
22
src/registrar/migrations/0036_create_groups.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||||
|
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||||
|
# in the user_group model then:
|
||||||
|
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||||
|
# step 3: fake run the latest migration in the migrations list
|
||||||
|
# Alternatively:
|
||||||
|
# Only step: duplicate the migtation that loads data and run: docker-compose exec app ./manage.py migrate
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from registrar.models import UserGroup
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0035_contenttypes_permissions"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(UserGroup.create_cisa_analyst_group, reverse_code=migrations.RunPython.noop, atomic=True),
|
||||||
|
migrations.RunPython(UserGroup.create_full_access_group, reverse_code=migrations.RunPython.noop, atomic=True),
|
||||||
|
]
|
||||||
|
|
|
@ -81,3 +81,8 @@ class User(AbstractUser):
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Failed to retrieve invitation %s", invitation, exc_info=True
|
"Failed to retrieve invitation %s", invitation, exc_info=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
permissions = [
|
||||||
|
("full_access_permission", "Full Access Permission"),
|
||||||
|
]
|
||||||
|
|
|
@ -1,8 +1,117 @@
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class UserGroup(Group):
|
class UserGroup(Group):
|
||||||
# Add custom fields or methods specific to your group model here
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "User group"
|
verbose_name = "User group"
|
||||||
verbose_name_plural = "User groups"
|
verbose_name_plural = "User groups"
|
||||||
|
|
||||||
|
def create_cisa_analyst_group(apps, schema_editor):
|
||||||
|
|
||||||
|
# Hard to pass self to these methods as the calls from migrations
|
||||||
|
# are only expecting apps and schema_editor, so we'll just define
|
||||||
|
# apps, schema_editor in the local scope instead
|
||||||
|
CISA_ANALYST_GROUP_PERMISSIONS = [
|
||||||
|
{
|
||||||
|
"app_label": "auditlog",
|
||||||
|
"model": "logentry",
|
||||||
|
"permissions": ["view_logentry"],
|
||||||
|
},
|
||||||
|
{"app_label": "registrar", "model": "contact", "permissions": ["view_contact"]},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domaininformation",
|
||||||
|
"permissions": ["change_domaininformation"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domainapplication",
|
||||||
|
"permissions": ["change_domainapplication"],
|
||||||
|
},
|
||||||
|
{"app_label": "registrar", "model": "domain", "permissions": ["view_domain"]},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "draftdomain",
|
||||||
|
"permissions": ["change_draftdomain"],
|
||||||
|
},
|
||||||
|
{"app_label": "registrar", "model": "user", "permissions": ["change_user"]},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Avoid error: You can't execute queries until the end
|
||||||
|
# of the 'atomic' block.
|
||||||
|
# From django docs:
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/migrations/#data-migrations
|
||||||
|
# We can’t import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
Permission = apps.get_model("auth", "Permission")
|
||||||
|
UserGroup = apps.get_model("registrar", "UserGroup")
|
||||||
|
|
||||||
|
logger.info("Going to create the Analyst Group")
|
||||||
|
try:
|
||||||
|
cisa_analysts_group, _ = UserGroup.objects.get_or_create(
|
||||||
|
name="cisa_analysts_group",
|
||||||
|
)
|
||||||
|
|
||||||
|
cisa_analysts_group.permissions.clear()
|
||||||
|
|
||||||
|
for permission in CISA_ANALYST_GROUP_PERMISSIONS:
|
||||||
|
app_label = permission["app_label"]
|
||||||
|
model_name = permission["model"]
|
||||||
|
permissions = permission["permissions"]
|
||||||
|
|
||||||
|
# Retrieve the content type for the app and model
|
||||||
|
content_type = ContentType.objects.get(
|
||||||
|
app_label=app_label, model=model_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Retrieve the permissions based on their codenames
|
||||||
|
permissions = Permission.objects.filter(
|
||||||
|
content_type=content_type, codename__in=permissions
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assign the permissions to the group
|
||||||
|
cisa_analysts_group.permissions.add(*permissions)
|
||||||
|
|
||||||
|
# Convert the permissions QuerySet to a list of codenames
|
||||||
|
permission_list = list(
|
||||||
|
permissions.values_list("codename", flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
app_label
|
||||||
|
+ " | "
|
||||||
|
+ model_name
|
||||||
|
+ " | "
|
||||||
|
+ ", ".join(permission_list)
|
||||||
|
+ " added to group "
|
||||||
|
+ cisa_analysts_group.name
|
||||||
|
)
|
||||||
|
|
||||||
|
cisa_analysts_group.save()
|
||||||
|
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating analyst permissions group: {e}")
|
||||||
|
|
||||||
|
def create_full_access_group(apps, schema_editor):
|
||||||
|
Permission = apps.get_model("auth", "Permission")
|
||||||
|
UserGroup = apps.get_model("registrar", "UserGroup")
|
||||||
|
|
||||||
|
logger.info("Going to create the Full Access Group")
|
||||||
|
try:
|
||||||
|
full_access_group, _ = UserGroup.objects.get_or_create(
|
||||||
|
name="full_access_group",
|
||||||
|
)
|
||||||
|
# Get all available permissions
|
||||||
|
all_permissions = Permission.objects.all()
|
||||||
|
|
||||||
|
# Assign all permissions to the group
|
||||||
|
full_access_group.permissions.add(*all_permissions)
|
||||||
|
|
||||||
|
full_access_group.save()
|
||||||
|
logger.debug("All permissions added to group " + full_access_group.name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating full access group: {e}")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue