Merge branch 'main' into za/1753-improve-admin-load-performance

This commit is contained in:
zandercymatics 2024-02-15 13:54:33 -07:00
commit 227083462d
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
37 changed files with 671 additions and 660 deletions

View file

@ -37,3 +37,11 @@ jobs:
cf_org: cisa-dotgov
cf_space: stable
cf_manifest: "ops/manifests/manifest-stable.yaml"
- name: Run Django migrations
uses: cloud-gov/cg-cli-tools@main
with:
cf_username: ${{ secrets.CF_STABLE_USERNAME }}
cf_password: ${{ secrets.CF_STABLE_PASSWORD }}
cf_org: cisa-dotgov
cf_space: stable
cf_command: "run-task getgov-stable --command 'python manage.py migrate' --name migrate"

View file

@ -37,3 +37,11 @@ jobs:
cf_org: cisa-dotgov
cf_space: staging
cf_manifest: "ops/manifests/manifest-staging.yaml"
- name: Run Django migrations
uses: cloud-gov/cg-cli-tools@main
with:
cf_username: ${{ secrets.CF_STAGING_USERNAME }}
cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
cf_org: cisa-dotgov
cf_space: staging
cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate"

View file

@ -4,7 +4,7 @@ verify_ssl = true
name = "pypi"
[packages]
django = "*"
django = "4.2.10"
cfenv = "*"
django-cors-headers = "*"
pycryptodomex = "*"

1137
src/Pipfile.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,7 +19,6 @@ API_BASE_PATH = "/api/v1/available/?domain="
class AvailableViewTest(MockEppLib):
"""Test that the view function works as expected."""
def setUp(self):
@ -123,7 +122,6 @@ class AvailableViewTest(MockEppLib):
class AvailableAPITest(MockEppLib):
"""Test that the API can be called as expected."""
def setUp(self):

View file

@ -1,4 +1,5 @@
"""Internal API views"""
from django.apps import apps
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse

View file

@ -327,6 +327,27 @@ class ViewsTest(TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(actual, expected)
def test_logout_redirect_url_with_no_session_state(self, mock_client):
"""Test that logout redirects to the configured post_logout_redirect_uris."""
with less_console_noise():
# MOCK
mock_client.callback.side_effect = self.user_info
mock_client.registration_response = {"post_logout_redirect_uris": ["http://example.com/back"]}
mock_client.provider_info = {"end_session_endpoint": "http://example.com/log_me_out"}
mock_client.client_id = "TEST"
# TEST
with less_console_noise():
response = self.client.get(reverse("logout"))
# ASSERTIONS
# Assert redirect code and url are accurate
expected = (
"http://example.com/log_me_out?client_id=TEST"
"&post_logout_redirect_uri=http%3A%2F%2Fexample.com%2Fback"
)
actual = response.url
self.assertEqual(response.status_code, 302)
self.assertEqual(actual, expected)
@patch("djangooidc.views.auth_logout")
def test_logout_always_logs_out(self, mock_logout, _):
"""Without additional mocking, logout will always fail.

View file

@ -145,8 +145,12 @@ def logout(request, next_page=None):
user = request.user
request_args = {
"client_id": CLIENT.client_id,
"state": request.session["state"],
}
# if state is not in request session, still redirect to the identity
# provider's logout url, but don't include the state in the url; this
# will successfully log out of the identity provider
if "state" in request.session:
request_args["state"] = request.session["state"]
if (
"post_logout_redirect_uris" in CLIENT.registration_response.keys()
and len(CLIENT.registration_response["post_logout_redirect_uris"]) > 0

View file

@ -809,7 +809,6 @@ class DomainApplicationAdminForm(forms.ModelForm):
class DomainApplicationAdmin(ListHeaderAdmin):
"""Custom domain applications admin class."""
class InvestigatorFilter(admin.SimpleListFilter):

View file

@ -2,7 +2,6 @@ from django.apps import AppConfig
class RegistrarConfig(AppConfig):
"""Configure signal handling for our registrar Django application."""
name = "registrar"

View file

@ -16,6 +16,7 @@ $ docker-compose exec app python manage.py shell
```
"""
import environs
from base64 import b64decode
from cfenv import AppEnv # type: ignore

View file

@ -201,7 +201,6 @@ class DomainApplicationFixture:
class DomainFixture(DomainApplicationFixture):
"""Create one domain and permissions on it for each user."""
@classmethod

View file

@ -1,4 +1,5 @@
"""Loads files from /tmp into our sandboxes"""
import glob
import logging

View file

@ -1,4 +1,5 @@
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
import logging
import os

View file

@ -1,4 +1,5 @@
"""Generates current-full.csv and current-federal.csv then uploads them to the desired URL."""
import logging
import os

View file

@ -1,4 +1,5 @@
"""Loops through each valid DomainInformation object and updates its agency value"""
import argparse
import csv
import logging

View file

@ -5,6 +5,7 @@ Regarding our dataclasses:
Not intended to be used as models but rather as an alternative to storing as a dictionary.
By keeping it as a dataclass instead of a dictionary, we can maintain data consistency.
""" # noqa
from dataclasses import dataclass, field
from datetime import date
from enum import Enum

View file

@ -1,4 +1,5 @@
""""""
import csv
from dataclasses import dataclass
from datetime import datetime

View file

@ -0,0 +1,45 @@
# Generated by Django 4.2.7 on 2024-02-14 21:45
from django.db import migrations, models
import phonenumber_field.modelfields
class Migration(migrations.Migration):
dependencies = [
("registrar", "0068_domainapplication_notes_domaininformation_notes"),
]
operations = [
migrations.AlterField(
model_name="contact",
name="email",
field=models.EmailField(blank=True, db_index=True, max_length=254, null=True),
),
migrations.AlterField(
model_name="contact",
name="first_name",
field=models.TextField(blank=True, db_index=True, null=True, verbose_name="first name / given name"),
),
migrations.AlterField(
model_name="contact",
name="last_name",
field=models.TextField(blank=True, db_index=True, null=True, verbose_name="last name / family name"),
),
migrations.AlterField(
model_name="contact",
name="middle_name",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="contact",
name="phone",
field=phonenumber_field.modelfields.PhoneNumberField(
blank=True, db_index=True, max_length=128, null=True, region=None
),
),
migrations.AlterField(
model_name="contact",
name="title",
field=models.TextField(blank=True, null=True, verbose_name="title or role in your organization"),
),
]

View file

@ -6,7 +6,6 @@ from .utility.time_stamped_model import TimeStampedModel
class Contact(TimeStampedModel):
"""Contact information follows a similar pattern for each contact."""
user = models.OneToOneField(
@ -19,38 +18,32 @@ class Contact(TimeStampedModel):
first_name = models.TextField(
null=True,
blank=True,
help_text="First name",
verbose_name="first name / given name",
db_index=True,
)
middle_name = models.TextField(
null=True,
blank=True,
help_text="Middle name (optional)",
)
last_name = models.TextField(
null=True,
blank=True,
help_text="Last name",
verbose_name="last name / family name",
db_index=True,
)
title = models.TextField(
null=True,
blank=True,
help_text="Title",
verbose_name="title or role in your organization",
)
email = models.EmailField(
null=True,
blank=True,
help_text="Email",
db_index=True,
)
phone = PhoneNumberField(
null=True,
blank=True,
help_text="Phone",
db_index=True,
)

View file

@ -17,7 +17,6 @@ logger = logging.getLogger(__name__)
class DomainApplication(TimeStampedModel):
"""A registrant's application for a new domain."""
# Constants for choice fields
@ -97,7 +96,6 @@ class DomainApplication(TimeStampedModel):
ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)"
class OrganizationChoices(models.TextChoices):
"""
Primary organization choices:
For use in django admin
@ -114,7 +112,6 @@ class DomainApplication(TimeStampedModel):
SCHOOL_DISTRICT = "school_district", "School district"
class OrganizationChoicesVerbose(models.TextChoices):
"""
Secondary organization choices
For use in the application form and on the templates

View file

@ -14,7 +14,6 @@ logger = logging.getLogger(__name__)
class DomainInformation(TimeStampedModel):
"""A registrant's domain information for that domain, exported from
DomainApplication. We use these field from DomainApplication with few exceptions
which are 'removed' via pop at the bottom of this file. Most of design for domain

View file

@ -4,11 +4,9 @@ from .utility.time_stamped_model import TimeStampedModel
class UserDomainRole(TimeStampedModel):
"""This is a linking table that connects a user with a role on a domain."""
class Roles(models.TextChoices):
"""The possible roles are listed here.
Implementation of the named roles for allowing particular operations happens

View file

@ -4,7 +4,6 @@ from .utility.time_stamped_model import TimeStampedModel
class VerifiedByStaff(TimeStampedModel):
"""emails that get added to this table will bypass ial2 on login."""
email = models.EmailField(

View file

@ -4,7 +4,6 @@ from .utility.time_stamped_model import TimeStampedModel
class Website(TimeStampedModel):
"""Keep domain names in their own table so that applications can refer to
many of them."""

View file

@ -6,7 +6,6 @@ better caching responses.
class NoCacheMiddleware:
"""Middleware to add a single header to every response."""
def __init__(self, get_response):

View file

@ -18,7 +18,7 @@
<button
type="submit"
class="usa-button"
>Add a domain manager</button>
>Add domain manager</button>
</form>
{% endblock %} {# domain_content #}

View file

@ -1,4 +1,5 @@
"""Custom field helpers for our inputs."""
import re
from django import template

View file

@ -630,7 +630,6 @@ class TestPermissions(TestCase):
class TestDomainInformation(TestCase):
"""Test the DomainInformation model, when approved or otherwise"""
def setUp(self):
@ -683,7 +682,6 @@ class TestDomainInformation(TestCase):
class TestInvitations(TestCase):
"""Test the retrieval of invitations."""
def setUp(self):

View file

@ -3,6 +3,7 @@ Feature being tested: Registry Integration
This file tests the various ways in which the registrar interacts with the registry.
"""
from django.test import TestCase
from django.db.utils import IntegrityError
from unittest.mock import MagicMock, patch, call

View file

@ -30,7 +30,6 @@ logger = logging.getLogger(__name__)
class DomainApplicationTests(TestWithUser, WebTest):
"""Webtests for domain application to test filling and submitting."""
# Doesn't work with CSRF checking

View file

@ -1236,7 +1236,6 @@ class TestDomainSecurityEmail(TestDomainOverview):
class TestDomainDNSSEC(TestDomainOverview):
"""MockEPPLib is already inherited."""
def test_dnssec_page_refreshes_enable_button(self):

View file

@ -10,7 +10,6 @@ logger = logging.getLogger(__name__)
class EmailSendingError(RuntimeError):
"""Local error for handling all failures when sending email."""
pass

View file

@ -135,7 +135,6 @@ class DomainFormBaseView(DomainBaseView, FormMixin):
class DomainView(DomainBaseView):
"""Domain detail overview page."""
template_name = "domain_detail.html"
@ -787,14 +786,17 @@ class DomainAddUserView(DomainFormBaseView):
return redirect(self.get_success_url())
class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMessageMixin):
# The order of the superclasses matters here. BaseDeleteView has a bug where the
# "form_valid" function does not call super, so it cannot use SuccessMessageMixin.
# The workaround is to use SuccessMessageMixin first.
class DomainInvitationDeleteView(SuccessMessageMixin, DomainInvitationPermissionDeleteView):
object: DomainInvitation # workaround for type mismatch in DeleteView
def get_success_url(self):
return reverse("domain-users", kwargs={"pk": self.object.domain.id})
def get_success_message(self, cleaned_data):
return f"Successfully canceled invitation for {self.object.email}."
return f"Canceled invitation to {self.object.email}."
class DomainDeleteUserView(UserDomainRolePermissionDeleteView):

View file

@ -146,7 +146,6 @@ class OrderableFieldsMixin:
class PermissionsLoginMixin(PermissionRequiredMixin):
"""Mixin that redirects to login page if not logged in, otherwise 403."""
def handle_no_permission(self):
@ -155,7 +154,6 @@ class PermissionsLoginMixin(PermissionRequiredMixin):
class DomainPermission(PermissionsLoginMixin):
"""Permission mixin that redirects to domain if user has access,
otherwise 403"""
@ -264,7 +262,6 @@ class DomainPermission(PermissionsLoginMixin):
class DomainApplicationPermission(PermissionsLoginMixin):
"""Permission mixin that redirects to domain application if user
has access, otherwise 403"""
@ -287,7 +284,6 @@ class DomainApplicationPermission(PermissionsLoginMixin):
class UserDeleteDomainRolePermission(PermissionsLoginMixin):
"""Permission mixin for UserDomainRole if user
has access, otherwise 403"""
@ -324,7 +320,6 @@ class UserDeleteDomainRolePermission(PermissionsLoginMixin):
class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
"""Permission mixin that redirects to withdraw action on domain application
if user has access, otherwise 403"""
@ -347,7 +342,6 @@ class DomainApplicationPermissionWithdraw(PermissionsLoginMixin):
class ApplicationWizardPermission(PermissionsLoginMixin):
"""Permission mixin that redirects to start or edit domain application if
user has access, otherwise 403"""
@ -365,7 +359,6 @@ class ApplicationWizardPermission(PermissionsLoginMixin):
class DomainInvitationPermission(PermissionsLoginMixin):
"""Permission mixin that redirects to domain invitation if user has
access, otherwise 403"

View file

@ -20,7 +20,6 @@ logger = logging.getLogger(__name__)
class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
"""Abstract base view for domains that enforces permissions.
This abstract view cannot be instantiated. Actual views must specify
@ -58,7 +57,6 @@ class DomainPermissionView(DomainPermission, DetailView, abc.ABC):
class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, abc.ABC):
"""Abstract base view for domain applications that enforces permissions
This abstract view cannot be instantiated. Actual views must specify
@ -78,7 +76,6 @@ class DomainApplicationPermissionView(DomainApplicationPermission, DetailView, a
class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdraw, DetailView, abc.ABC):
"""Abstract base view for domain application withdraw function
This abstract view cannot be instantiated. Actual views must specify
@ -98,7 +95,6 @@ class DomainApplicationPermissionWithdrawView(DomainApplicationPermissionWithdra
class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView, abc.ABC):
"""Abstract base view for the application form that enforces permissions
This abstract view cannot be instantiated. Actual views must specify
@ -113,7 +109,6 @@ class ApplicationWizardPermissionView(ApplicationWizardPermission, TemplateView,
class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteView, abc.ABC):
"""Abstract view for deleting a domain invitation.
This one is fairly specialized, but this is the only thing that we do
@ -127,7 +122,6 @@ class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteVie
class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteView, abc.ABC):
"""Abstract view for deleting a DomainApplication."""
model = DomainApplication
@ -135,7 +129,6 @@ class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteV
class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC):
"""Abstract base view for deleting a UserDomainRole.
This abstract view cannot be instantiated. Actual views must specify

View file

@ -1,18 +1,18 @@
-i https://pypi.python.org/simple
annotated-types==0.6.0; python_version >= '3.8'
asgiref==3.7.2; python_version >= '3.7'
boto3==1.33.7; python_version >= '3.7'
botocore==1.33.7; python_version >= '3.7'
boto3==1.34.37; python_version >= '3.8'
botocore==1.34.37; python_version >= '3.8'
cachetools==5.3.2; python_version >= '3.7'
certifi==2023.11.17; python_version >= '3.6'
certifi==2024.2.2; python_version >= '3.6'
cfenv==0.5.3
cffi==1.16.0; python_version >= '3.8'
cffi==1.16.0; platform_python_implementation != 'PyPy'
charset-normalizer==3.3.2; python_full_version >= '3.7.0'
cryptography==41.0.7; python_version >= '3.7'
cryptography==42.0.2; python_version >= '3.7'
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
dj-database-url==2.1.0
dj-email-url==1.0.6
django==4.2.7; python_version >= '3.8'
django==4.2.10; python_version >= '3.8'
django-allow-cidr==0.7.1
django-auditlog==2.3.0; python_version >= '3.7'
django-cache-url==3.4.5
@ -20,42 +20,42 @@ django-cors-headers==4.3.1; python_version >= '3.8'
django-csp==3.7
django-fsm==2.8.1
django-login-required-middleware==0.9.0
django-phonenumber-field[phonenumberslite]==7.2.0; python_version >= '3.8'
django-phonenumber-field[phonenumberslite]==7.3.0; python_version >= '3.8'
django-widget-tweaks==1.5.0; python_version >= '3.8'
environs[django]==9.5.0; python_version >= '3.6'
faker==20.1.0; python_version >= '3.8'
environs[django]==10.3.0; python_version >= '3.8'
faker==23.1.0; python_version >= '3.8'
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
furl==2.1.3
future==0.18.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
gevent==23.9.1; python_version >= '3.8'
geventconnpool@ git+https://github.com/rasky/geventconnpool.git@1bbb93a714a331a069adf27265fe582d9ba7ecd4
greenlet==3.0.1; python_version >= '3.7'
greenlet==3.0.3; python_version >= '3.7'
gunicorn==21.2.0; python_version >= '3.5'
idna==3.6; python_version >= '3.5'
jmespath==1.0.1; python_version >= '3.7'
lxml==4.9.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
mako==1.3.0; python_version >= '3.8'
markupsafe==2.1.3; python_version >= '3.7'
marshmallow==3.20.1; python_version >= '3.8'
lxml==5.1.0; python_version >= '3.6'
mako==1.3.2; python_version >= '3.8'
markupsafe==2.1.5; python_version >= '3.7'
marshmallow==3.20.2; python_version >= '3.8'
oic==1.6.1; python_version ~= '3.7'
orderedmultidict==1.0.1
packaging==23.2; python_version >= '3.7'
phonenumberslite==8.13.26
phonenumberslite==8.13.29
psycopg2-binary==2.9.9; python_version >= '3.7'
pycparser==2.21
pycryptodomex==3.19.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
pydantic==2.5.2; python_version >= '3.7'
pydantic-core==2.14.5; python_version >= '3.7'
pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
pydantic==2.6.1; python_version >= '3.8'
pydantic-core==2.16.2; python_version >= '3.8'
pydantic-settings==2.1.0; python_version >= '3.8'
pyjwkest==1.4.2
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
python-dotenv==1.0.0; python_version >= '3.8'
python-dotenv==1.0.1; python_version >= '3.8'
requests==2.31.0; python_version >= '3.7'
s3transfer==0.8.2; python_version >= '3.7'
setuptools==69.0.2; python_version >= '3.8'
s3transfer==0.10.0; python_version >= '3.8'
setuptools==69.0.3; python_version >= '3.8'
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sqlparse==0.4.4; python_version >= '3.5'
typing-extensions==4.8.0; python_version >= '3.8'
typing-extensions==4.9.0; python_version >= '3.8'
urllib3==2.0.7; python_version >= '3.7'
whitenoise==6.6.0; python_version >= '3.8'
zope.event==5.0; python_version >= '3.7'