mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 02:36:02 +02:00
Merge pull request #760 from cisagov/rjm/680-admin-workshop
Django admin MVP implementation: views and permissions
This commit is contained in:
commit
bf93ea7a28
7 changed files with 411 additions and 13 deletions
20
docs/django-admin/roles.md
Normal file
20
docs/django-admin/roles.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Django admin user roles
|
||||||
|
|
||||||
|
Roles other than superuser should be defined in authentication and authorization groups in django admin
|
||||||
|
|
||||||
|
## Superuser
|
||||||
|
|
||||||
|
Full access
|
||||||
|
|
||||||
|
## CISA analyst
|
||||||
|
|
||||||
|
### Basic permission level
|
||||||
|
|
||||||
|
Staff
|
||||||
|
|
||||||
|
### 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
|
|
@ -24,6 +24,68 @@ class AuditedAdmin(admin.ModelAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ListHeaderAdmin(AuditedAdmin):
|
||||||
|
|
||||||
|
"""Custom admin to add a descriptive subheader to list views."""
|
||||||
|
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
# Get the filtered values
|
||||||
|
filters = self.get_filters(request)
|
||||||
|
# Pass the filtered values to the template context
|
||||||
|
extra_context["filters"] = filters
|
||||||
|
extra_context["search_query"] = request.GET.get(
|
||||||
|
"q", ""
|
||||||
|
) # Assuming the search query parameter is 'q'
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
def get_filters(self, request):
|
||||||
|
"""Retrieve the current set of parameters being used to filter the table
|
||||||
|
Returns:
|
||||||
|
dictionary objects in the format {parameter_name: string,
|
||||||
|
parameter_value: string}
|
||||||
|
TODO: convert investigator id to investigator username
|
||||||
|
"""
|
||||||
|
|
||||||
|
filters = []
|
||||||
|
# Retrieve the filter parameters
|
||||||
|
for param in request.GET.keys():
|
||||||
|
# Exclude the default search parameter 'q'
|
||||||
|
if param != "q" and param != "o":
|
||||||
|
parameter_name = (
|
||||||
|
param.replace("__exact", "")
|
||||||
|
.replace("_type", "")
|
||||||
|
.replace("__id", " id")
|
||||||
|
)
|
||||||
|
|
||||||
|
if parameter_name == "investigator id":
|
||||||
|
# Retrieves the corresponding contact from Users
|
||||||
|
id_value = request.GET.get(param)
|
||||||
|
try:
|
||||||
|
contact = models.User.objects.get(id=id_value)
|
||||||
|
investigator_name = contact.first_name + " " + contact.last_name
|
||||||
|
|
||||||
|
filters.append(
|
||||||
|
{
|
||||||
|
"parameter_name": "investigator",
|
||||||
|
"parameter_value": investigator_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except models.User.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# For other parameter names, append a dictionary with the original
|
||||||
|
# parameter_name and the corresponding parameter_value
|
||||||
|
filters.append(
|
||||||
|
{
|
||||||
|
"parameter_name": parameter_name,
|
||||||
|
"parameter_value": request.GET.get(param),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class UserContactInline(admin.StackedInline):
|
class UserContactInline(admin.StackedInline):
|
||||||
|
|
||||||
"""Edit a user's profile on the user page."""
|
"""Edit a user's profile on the user page."""
|
||||||
|
@ -52,10 +114,12 @@ class MyHostAdmin(AuditedAdmin):
|
||||||
inlines = [HostIPInline]
|
inlines = [HostIPInline]
|
||||||
|
|
||||||
|
|
||||||
class DomainAdmin(AuditedAdmin):
|
class DomainAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Custom domain admin class to add extra buttons."""
|
"""Custom domain admin class to add extra buttons."""
|
||||||
|
|
||||||
|
search_fields = ["name"]
|
||||||
|
search_help_text = "Search by domain name."
|
||||||
change_form_template = "django/admin/domain_change_form.html"
|
change_form_template = "django/admin/domain_change_form.html"
|
||||||
readonly_fields = ["state"]
|
readonly_fields = ["state"]
|
||||||
|
|
||||||
|
@ -80,10 +144,107 @@ class DomainAdmin(AuditedAdmin):
|
||||||
return super().response_change(request, obj)
|
return super().response_change(request, obj)
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdmin(AuditedAdmin):
|
class ContactAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
|
"""Custom contact admin class to add search."""
|
||||||
|
|
||||||
|
search_fields = ["email", "first_name", "last_name"]
|
||||||
|
search_help_text = "Search by firstname, lastname or email."
|
||||||
|
|
||||||
|
|
||||||
|
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Customize the applications listing view."""
|
"""Customize the applications listing view."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"requested_domain",
|
||||||
|
"status",
|
||||||
|
"organization_type",
|
||||||
|
"created_at",
|
||||||
|
"submitter",
|
||||||
|
"investigator",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filters
|
||||||
|
list_filter = ("status", "organization_type", "investigator")
|
||||||
|
|
||||||
|
# Search
|
||||||
|
search_fields = [
|
||||||
|
"requested_domain__name",
|
||||||
|
"submitter__email",
|
||||||
|
"submitter__first_name",
|
||||||
|
"submitter__last_name",
|
||||||
|
]
|
||||||
|
search_help_text = "Search by domain or submitter."
|
||||||
|
|
||||||
|
# Detail view
|
||||||
|
fieldsets = [
|
||||||
|
(None, {"fields": ["status", "investigator", "creator"]}),
|
||||||
|
(
|
||||||
|
"Type of organization",
|
||||||
|
{
|
||||||
|
"fields": [
|
||||||
|
"organization_type",
|
||||||
|
"federally_recognized_tribe",
|
||||||
|
"state_recognized_tribe",
|
||||||
|
"tribe_name",
|
||||||
|
"federal_agency",
|
||||||
|
"federal_type",
|
||||||
|
"is_election_board",
|
||||||
|
"type_of_work",
|
||||||
|
"more_organization_information",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Organization name and mailing address",
|
||||||
|
{
|
||||||
|
"fields": [
|
||||||
|
"organization_name",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"city",
|
||||||
|
"state_territory",
|
||||||
|
"zipcode",
|
||||||
|
"urbanization",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("Authorizing official", {"fields": ["authorizing_official"]}),
|
||||||
|
("Current websites", {"fields": ["current_websites"]}),
|
||||||
|
(".gov domain", {"fields": ["requested_domain", "alternative_domains"]}),
|
||||||
|
("Purpose of your domain", {"fields": ["purpose"]}),
|
||||||
|
("Your contact information", {"fields": ["submitter"]}),
|
||||||
|
("Other employees from your organization?", {"fields": ["other_contacts"]}),
|
||||||
|
(
|
||||||
|
"No other employees from your organization?",
|
||||||
|
{"fields": ["no_other_contacts_rationale"]},
|
||||||
|
),
|
||||||
|
("Anything else we should know?", {"fields": ["anything_else"]}),
|
||||||
|
(
|
||||||
|
"Requirements for operating .gov domains",
|
||||||
|
{"fields": ["is_policy_acknowledged"]},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Read only that we'll leverage for CISA Analysts
|
||||||
|
readonly_fields = [
|
||||||
|
"creator",
|
||||||
|
"type_of_work",
|
||||||
|
"more_organization_information",
|
||||||
|
"address_line1",
|
||||||
|
"address_line2",
|
||||||
|
"zipcode",
|
||||||
|
"requested_domain",
|
||||||
|
"alternative_domains",
|
||||||
|
"purpose",
|
||||||
|
"submitter",
|
||||||
|
"no_other_contacts_rationale",
|
||||||
|
"anything_else",
|
||||||
|
"is_policy_acknowledged",
|
||||||
|
]
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
if change: # Check if the application is being edited
|
if change: # Check if the application is being edited
|
||||||
|
@ -113,10 +274,18 @@ class DomainApplicationAdmin(AuditedAdmin):
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
if request.user.is_superuser:
|
||||||
|
# Superusers have full access, no fields are read-only
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
# Regular users can only view the specified fields
|
||||||
|
return self.readonly_fields
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.User, MyUserAdmin)
|
admin.site.register(models.User, MyUserAdmin)
|
||||||
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
||||||
admin.site.register(models.Contact, AuditedAdmin)
|
admin.site.register(models.Contact, ContactAdmin)
|
||||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
||||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
admin.site.register(models.DomainInformation, AuditedAdmin)
|
||||||
admin.site.register(models.Domain, DomainAdmin)
|
admin.site.register(models.Domain, DomainAdmin)
|
||||||
|
|
|
@ -78,6 +78,12 @@ DEBUG = env_debug
|
||||||
# Installing them here makes them available for execution.
|
# Installing them here makes them available for execution.
|
||||||
# Do not access INSTALLED_APPS directly. Use `django.apps.apps` instead.
|
# Do not access INSTALLED_APPS directly. Use `django.apps.apps` instead.
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
# let's be sure to install our own application!
|
||||||
|
# it needs to be listed before django.contrib.admin
|
||||||
|
# otherwise Django would find the default template
|
||||||
|
# provided by django.contrib.admin first and use
|
||||||
|
# that instead of our custom templates.
|
||||||
|
"registrar",
|
||||||
# Django automatic admin interface reads metadata
|
# Django automatic admin interface reads metadata
|
||||||
# from database models to provide a quick, model-centric
|
# from database models to provide a quick, model-centric
|
||||||
# interface where trusted users can manage content
|
# interface where trusted users can manage content
|
||||||
|
@ -85,6 +91,10 @@ INSTALLED_APPS = [
|
||||||
# vv Required by django.contrib.admin vv
|
# vv Required by django.contrib.admin vv
|
||||||
# the "user" model! *\o/*
|
# the "user" model! *\o/*
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
|
# audit logging of changes to models
|
||||||
|
# it needs to be listed before django.contrib.contenttypes
|
||||||
|
# for a ContentType query in fixtures.py
|
||||||
|
"auditlog",
|
||||||
# generic interface for Django models
|
# generic interface for Django models
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
# required for CSRF protection and many other things
|
# required for CSRF protection and many other things
|
||||||
|
@ -98,16 +108,12 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
# application used for integrating with Login.gov
|
# application used for integrating with Login.gov
|
||||||
"djangooidc",
|
"djangooidc",
|
||||||
# audit logging of changes to models
|
|
||||||
"auditlog",
|
|
||||||
# library to simplify form templating
|
# library to simplify form templating
|
||||||
"widget_tweaks",
|
"widget_tweaks",
|
||||||
# library for Finite State Machine statuses
|
# library for Finite State Machine statuses
|
||||||
"django_fsm",
|
"django_fsm",
|
||||||
# library for phone numbers
|
# library for phone numbers
|
||||||
"phonenumber_field",
|
"phonenumber_field",
|
||||||
# let's be sure to install our own application!
|
|
||||||
"registrar",
|
|
||||||
# Our internal API application
|
# Our internal API application
|
||||||
"api",
|
"api",
|
||||||
# Only for generating documentation, uncomment to run manage.py generate_puml
|
# Only for generating documentation, uncomment to run manage.py generate_puml
|
||||||
|
|
|
@ -10,6 +10,9 @@ from registrar.models import (
|
||||||
Website,
|
Website,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
fake = Faker()
|
fake = Faker()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -56,9 +59,37 @@ class UserFixture:
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
STAFF = [
|
||||||
|
{
|
||||||
|
"username": "319c490d-453b-43d9-bc4d-7d6cd8ff6844",
|
||||||
|
"first_name": "Rachid-Analyst",
|
||||||
|
"last_name": "Mrad-Analyst",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd",
|
||||||
|
"first_name": "Alysia-Analyst",
|
||||||
|
"last_name": "Alysia-Analyst",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
STAFF_PERMISSIONS = [
|
||||||
|
{
|
||||||
|
"app_label": "auditlog",
|
||||||
|
"model": "logentry",
|
||||||
|
"permissions": ["view_logentry"],
|
||||||
|
},
|
||||||
|
{"app_label": "registrar", "model": "contact", "permissions": ["view_contact"]},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "domainapplication",
|
||||||
|
"permissions": ["change_domainapplication"],
|
||||||
|
},
|
||||||
|
{"app_label": "registrar", "model": "domain", "permissions": ["view_domain"]},
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
logger.info("Going to load %s users" % str(len(cls.ADMINS)))
|
logger.info("Going to load %s superusers" % str(len(cls.ADMINS)))
|
||||||
for admin in cls.ADMINS:
|
for admin in cls.ADMINS:
|
||||||
try:
|
try:
|
||||||
user, _ = User.objects.get_or_create(
|
user, _ = User.objects.get_or_create(
|
||||||
|
@ -73,7 +104,58 @@ class UserFixture:
|
||||||
logger.debug("User object created for %s" % admin["first_name"])
|
logger.debug("User object created for %s" % admin["first_name"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
logger.debug("All users loaded.")
|
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"]
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_active = True
|
||||||
|
|
||||||
|
for permission in cls.STAFF_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 user
|
||||||
|
user.user_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 for user "
|
||||||
|
+ staff["first_name"]
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
class DomainApplicationFixture:
|
||||||
|
|
26
src/registrar/templates/admin/change_list.html
Normal file
26
src/registrar/templates/admin/change_list.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
|
||||||
|
{% block content_title %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<h2>
|
||||||
|
{{ cl.result_count }}
|
||||||
|
{% if cl.get_ordering_field_columns %}
|
||||||
|
sorted
|
||||||
|
{% endif %}
|
||||||
|
{% if cl.result_count == 1 %}
|
||||||
|
result
|
||||||
|
{% else %}
|
||||||
|
results
|
||||||
|
{% endif %}
|
||||||
|
{% if filters %}
|
||||||
|
filtered by
|
||||||
|
{% for filter_param in filters %}
|
||||||
|
{{ filter_param.parameter_name }} = {{ filter_param.parameter_value }}
|
||||||
|
{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if search_query %}
|
||||||
|
for {{ search_query }}
|
||||||
|
{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% endblock %}
|
|
@ -8,7 +8,7 @@ from typing import List, Dict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model, login
|
from django.contrib.auth import get_user_model, login
|
||||||
|
|
||||||
from registrar.models import Contact, DraftDomain, Website, DomainApplication
|
from registrar.models import Contact, DraftDomain, Website, DomainApplication, User
|
||||||
|
|
||||||
|
|
||||||
def get_handlers():
|
def get_handlers():
|
||||||
|
@ -157,3 +157,16 @@ def completed_application(
|
||||||
application.alternative_domains.add(alt)
|
application.alternative_domains.add(alt)
|
||||||
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
|
def mock_user():
|
||||||
|
"""A simple user."""
|
||||||
|
user_kwargs = dict(
|
||||||
|
id=4,
|
||||||
|
first_name="Rachid",
|
||||||
|
last_name="Mrad",
|
||||||
|
)
|
||||||
|
|
||||||
|
user, _ = User.objects.get_or_create(**user_kwargs)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory, Client
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
from registrar.admin import DomainApplicationAdmin
|
from registrar.admin import DomainApplicationAdmin, ListHeaderAdmin
|
||||||
from registrar.models import DomainApplication, DomainInformation, User
|
from registrar.models import DomainApplication, DomainInformation, User
|
||||||
from .common import completed_application
|
from .common import completed_application, mock_user
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
@ -13,6 +14,21 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
self.admin = ListHeaderAdmin(model=DomainApplication, admin_site=None)
|
||||||
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
username = "admin"
|
||||||
|
first_name = "First"
|
||||||
|
last_name = "Last"
|
||||||
|
email = "info@example.com"
|
||||||
|
p = "adminpassword"
|
||||||
|
User = get_user_model()
|
||||||
|
self.superuser = User.objects.create_superuser(
|
||||||
|
username=username,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
email=email,
|
||||||
|
password=p,
|
||||||
|
)
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_save_model_sends_submitted_email(self):
|
def test_save_model_sends_submitted_email(self):
|
||||||
|
@ -162,3 +178,69 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
if DomainInformation.objects.get(id=application.pk) is not None:
|
if DomainInformation.objects.get(id=application.pk) is not None:
|
||||||
DomainInformation.objects.get(id=application.pk).delete()
|
DomainInformation.objects.get(id=application.pk).delete()
|
||||||
application.delete()
|
application.delete()
|
||||||
|
|
||||||
|
def test_changelist_view(self):
|
||||||
|
# Have to get creative to get past linter
|
||||||
|
p = "adminpassword"
|
||||||
|
self.client.login(username="admin", password=p)
|
||||||
|
|
||||||
|
# Mock a user
|
||||||
|
user = mock_user()
|
||||||
|
|
||||||
|
# Make the request using the Client class
|
||||||
|
# which handles CSRF
|
||||||
|
# Follow=True handles the redirect
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainapplication/",
|
||||||
|
{
|
||||||
|
"status__exact": "started",
|
||||||
|
"investigator__id__exact": user.id,
|
||||||
|
"q": "Hello",
|
||||||
|
},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert that the filters and search_query are added to the extra_context
|
||||||
|
self.assertIn("filters", response.context)
|
||||||
|
self.assertIn("search_query", response.context)
|
||||||
|
# Assert the content of filters and search_query
|
||||||
|
filters = response.context["filters"]
|
||||||
|
search_query = response.context["search_query"]
|
||||||
|
self.assertEqual(search_query, "Hello")
|
||||||
|
self.assertEqual(
|
||||||
|
filters,
|
||||||
|
[
|
||||||
|
{"parameter_name": "status", "parameter_value": "started"},
|
||||||
|
{
|
||||||
|
"parameter_name": "investigator",
|
||||||
|
"parameter_value": user.first_name + " " + user.last_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_filters(self):
|
||||||
|
# Create a mock request object
|
||||||
|
request = self.factory.get("/admin/yourmodel/")
|
||||||
|
# Set the GET parameters for testing
|
||||||
|
request.GET = {
|
||||||
|
"status": "started",
|
||||||
|
"investigator": "Rachid Mrad",
|
||||||
|
"q": "search_value",
|
||||||
|
}
|
||||||
|
# Call the get_filters method
|
||||||
|
filters = self.admin.get_filters(request)
|
||||||
|
|
||||||
|
# Assert the filters extracted from the request GET
|
||||||
|
self.assertEqual(
|
||||||
|
filters,
|
||||||
|
[
|
||||||
|
{"parameter_name": "status", "parameter_value": "started"},
|
||||||
|
{"parameter_name": "investigator", "parameter_value": "Rachid Mrad"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# delete any applications too
|
||||||
|
DomainApplication.objects.all().delete()
|
||||||
|
User.objects.all().delete()
|
||||||
|
self.superuser.delete()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue