mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-20 01:36:00 +02:00
Merge branch 'main' into dk/2593-domain-request-search-bar
This commit is contained in:
commit
bda1041ecd
6 changed files with 101 additions and 17 deletions
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Cert:
|
class Cert:
|
||||||
|
@ -12,7 +12,7 @@ class Cert:
|
||||||
variable but Python's ssl library requires a file.
|
variable but Python's ssl library requires a file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, data=settings.SECRET_REGISTRY_CERT) -> None:
|
def __init__(self, data=settings.SECRET_REGISTRY_CERT) -> None: # type: ignore
|
||||||
self.filename = self._write(data)
|
self.filename = self._write(data)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
@ -31,4 +31,4 @@ class Key(Cert):
|
||||||
"""Location of private key as written to disk."""
|
"""Location of private key as written to disk."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(data=settings.SECRET_REGISTRY_KEY)
|
super().__init__(data=settings.SECRET_REGISTRY_KEY) # type: ignore
|
||||||
|
|
|
@ -963,7 +963,9 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
|
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
|
||||||
domains = Domain.objects.filter(id__in=domain_ids).exclude(state=Domain.State.DELETED)
|
domains = Domain.objects.filter(id__in=domain_ids).exclude(state=Domain.State.DELETED)
|
||||||
|
|
||||||
extra_context = {"domain_requests": domain_requests, "domains": domains}
|
portfolio_ids = obj.get_portfolios().values_list("portfolio", flat=True)
|
||||||
|
portfolios = models.Portfolio.objects.filter(id__in=portfolio_ids)
|
||||||
|
extra_context = {"domain_requests": domain_requests, "domains": domains, "portfolios": portfolios}
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ from cfenv import AppEnv # type: ignore
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Final
|
from typing import Final
|
||||||
from botocore.config import Config
|
from botocore.config import Config
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from django.utils.log import ServerFormatter
|
||||||
|
|
||||||
# # # ###
|
# # # ###
|
||||||
# Setup code goes here #
|
# Setup code goes here #
|
||||||
|
@ -57,7 +60,7 @@ env_db_url = env.dj_db_url("DATABASE_URL")
|
||||||
env_debug = env.bool("DJANGO_DEBUG", default=False)
|
env_debug = env.bool("DJANGO_DEBUG", default=False)
|
||||||
env_is_production = env.bool("IS_PRODUCTION", default=False)
|
env_is_production = env.bool("IS_PRODUCTION", default=False)
|
||||||
env_log_level = env.str("DJANGO_LOG_LEVEL", "DEBUG")
|
env_log_level = env.str("DJANGO_LOG_LEVEL", "DEBUG")
|
||||||
env_base_url = env.str("DJANGO_BASE_URL")
|
env_base_url: str = env.str("DJANGO_BASE_URL")
|
||||||
env_getgov_public_site_url = env.str("GETGOV_PUBLIC_SITE_URL", "")
|
env_getgov_public_site_url = env.str("GETGOV_PUBLIC_SITE_URL", "")
|
||||||
env_oidc_active_provider = env.str("OIDC_ACTIVE_PROVIDER", "identity sandbox")
|
env_oidc_active_provider = env.str("OIDC_ACTIVE_PROVIDER", "identity sandbox")
|
||||||
|
|
||||||
|
@ -192,7 +195,7 @@ MIDDLEWARE = [
|
||||||
"registrar.registrar_middleware.CheckPortfolioMiddleware",
|
"registrar.registrar_middleware.CheckPortfolioMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
# application object used by Django’s built-in servers (e.g. `runserver`)
|
# application object used by Django's built-in servers (e.g. `runserver`)
|
||||||
WSGI_APPLICATION = "registrar.config.wsgi.application"
|
WSGI_APPLICATION = "registrar.config.wsgi.application"
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -415,7 +418,7 @@ LANGUAGE_COOKIE_SECURE = True
|
||||||
# and to interpret datetimes entered in forms
|
# and to interpret datetimes entered in forms
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
# enable Django’s translation system
|
# enable Django's translation system
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
# enable localized formatting of numbers and dates
|
# enable localized formatting of numbers and dates
|
||||||
|
@ -450,6 +453,40 @@ PHONENUMBER_DEFAULT_REGION = "US"
|
||||||
# logger.error("Can't do this important task. Something is very wrong.")
|
# logger.error("Can't do this important task. Something is very wrong.")
|
||||||
# logger.critical("Going to crash now.")
|
# logger.critical("Going to crash now.")
|
||||||
|
|
||||||
|
|
||||||
|
class JsonFormatter(logging.Formatter):
|
||||||
|
"""Formats logs into JSON for better parsing"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(datefmt="%d/%b/%Y %H:%M:%S")
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
log_record = {
|
||||||
|
"timestamp": self.formatTime(record, self.datefmt),
|
||||||
|
"level": record.levelname,
|
||||||
|
"name": record.name,
|
||||||
|
"lineno": record.lineno,
|
||||||
|
"message": record.getMessage(),
|
||||||
|
}
|
||||||
|
return json.dumps(log_record)
|
||||||
|
|
||||||
|
|
||||||
|
class JsonServerFormatter(ServerFormatter):
|
||||||
|
"""Formats server logs into JSON for better parsing"""
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
formatted_record = super().format(record)
|
||||||
|
log_entry = {"server_time": record.server_time, "level": record.levelname, "message": formatted_record}
|
||||||
|
return json.dumps(log_entry)
|
||||||
|
|
||||||
|
|
||||||
|
# default to json formatted logs
|
||||||
|
server_formatter, console_formatter = "json.server", "json"
|
||||||
|
|
||||||
|
# don't use json format locally, it makes logs hard to read in console
|
||||||
|
if "localhost" in env_base_url:
|
||||||
|
server_formatter, console_formatter = "django.server", "verbose"
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
# Don't import Django's existing loggers
|
# Don't import Django's existing loggers
|
||||||
|
@ -469,6 +506,12 @@ LOGGING = {
|
||||||
"format": "[{server_time}] {message}",
|
"format": "[{server_time}] {message}",
|
||||||
"style": "{",
|
"style": "{",
|
||||||
},
|
},
|
||||||
|
"json.server": {
|
||||||
|
"()": JsonServerFormatter,
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"()": JsonFormatter,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
# define where log messages will be sent;
|
# define where log messages will be sent;
|
||||||
# each logger can have one or more handlers
|
# each logger can have one or more handlers
|
||||||
|
@ -476,12 +519,12 @@ LOGGING = {
|
||||||
"console": {
|
"console": {
|
||||||
"level": env_log_level,
|
"level": env_log_level,
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "verbose",
|
"formatter": console_formatter,
|
||||||
},
|
},
|
||||||
"django.server": {
|
"django.server": {
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"class": "logging.StreamHandler",
|
"class": "logging.StreamHandler",
|
||||||
"formatter": "django.server",
|
"formatter": server_formatter,
|
||||||
},
|
},
|
||||||
# No file logger is configured,
|
# No file logger is configured,
|
||||||
# because containerized apps
|
# because containerized apps
|
||||||
|
|
|
@ -306,6 +306,9 @@ class User(AbstractUser):
|
||||||
|
|
||||||
return roles
|
return roles
|
||||||
|
|
||||||
|
def get_portfolios(self):
|
||||||
|
return self.portfolio_permissions.all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def needs_identity_verification(cls, email, uuid):
|
def needs_identity_verification(cls, email, uuid):
|
||||||
"""A method used by our oidc classes to test whether a user needs email/uuid verification
|
"""A method used by our oidc classes to test whether a user needs email/uuid verification
|
||||||
|
|
|
@ -17,6 +17,26 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block after_related_objects %}
|
{% block after_related_objects %}
|
||||||
|
{% if portfolios %}
|
||||||
|
<div class="module aligned padding-3">
|
||||||
|
<h2>Portfolio information</h2>
|
||||||
|
<div class="grid-row grid-gap mobile:padding-x-1 desktop:padding-x-4">
|
||||||
|
<div class="mobile:grid-col-12 tablet:grid-col-6 desktop:grid-col-4">
|
||||||
|
<h3>Portfolios</h3>
|
||||||
|
<ul class="margin-0 padding-0">
|
||||||
|
{% for portfolio in portfolios %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'admin:registrar_portfolio_change' portfolio.pk %}">
|
||||||
|
{{ portfolio }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="module aligned padding-3">
|
<div class="module aligned padding-3">
|
||||||
<h2>Associated requests and domains</h2>
|
<h2>Associated requests and domains</h2>
|
||||||
<div class="grid-row grid-gap mobile:padding-x-1 desktop:padding-x-4">
|
<div class="grid-row grid-gap mobile:padding-x-1 desktop:padding-x-4">
|
||||||
|
|
|
@ -2,6 +2,7 @@ from datetime import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.test import TestCase, RequestFactory, Client
|
from django.test import TestCase, RequestFactory, Client
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
from django_webtest import WebTest # type: ignore
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from registrar.admin import (
|
from registrar.admin import (
|
||||||
|
@ -41,13 +42,12 @@ from registrar.models import (
|
||||||
TransitionDomain,
|
TransitionDomain,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
Suborganization,
|
Suborganization,
|
||||||
|
UserPortfolioPermission,
|
||||||
|
UserDomainRole,
|
||||||
|
SeniorOfficial,
|
||||||
|
PortfolioInvitation,
|
||||||
|
VerifiedByStaff,
|
||||||
)
|
)
|
||||||
from registrar.models.portfolio_invitation import PortfolioInvitation
|
|
||||||
from registrar.models.senior_official import SeniorOfficial
|
|
||||||
from registrar.models.user_domain_role import UserDomainRole
|
|
||||||
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
|
||||||
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
|
||||||
from registrar.models.verified_by_staff import VerifiedByStaff
|
|
||||||
from .common import (
|
from .common import (
|
||||||
MockDbForSharedTests,
|
MockDbForSharedTests,
|
||||||
AuditedAdminMockData,
|
AuditedAdminMockData,
|
||||||
|
@ -60,10 +60,11 @@ from .common import (
|
||||||
multiple_unalphabetical_domain_objects,
|
multiple_unalphabetical_domain_objects,
|
||||||
GenericTestHelper,
|
GenericTestHelper,
|
||||||
)
|
)
|
||||||
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||||
from django.contrib.sessions.backends.db import SessionStore
|
from django.contrib.sessions.backends.db import SessionStore
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from unittest.mock import ANY, patch, Mock
|
from unittest.mock import ANY, patch, Mock
|
||||||
from django_webtest import WebTest # type: ignore
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -973,7 +974,7 @@ class TestListHeaderAdmin(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestMyUserAdmin(MockDbForSharedTests):
|
class TestMyUserAdmin(MockDbForSharedTests, WebTest):
|
||||||
"""Tests for the MyUserAdmin class as super or staff user
|
"""Tests for the MyUserAdmin class as super or staff user
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
@ -993,6 +994,7 @@ class TestMyUserAdmin(MockDbForSharedTests):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
self.app.set_user(self.superuser.username)
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -1227,6 +1229,20 @@ class TestMyUserAdmin(MockDbForSharedTests):
|
||||||
self.assertNotContains(response, "Portfolio roles:")
|
self.assertNotContains(response, "Portfolio roles:")
|
||||||
self.assertNotContains(response, "Portfolio additional permissions:")
|
self.assertNotContains(response, "Portfolio additional permissions:")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_user_can_see_related_portfolios(self):
|
||||||
|
"""Tests if a user can see the portfolios they are associated with on the user page"""
|
||||||
|
portfolio, _ = Portfolio.objects.get_or_create(organization_name="test", creator=self.superuser)
|
||||||
|
permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.superuser, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
response = self.app.get(reverse("admin:registrar_user_change", args=[self.superuser.pk]))
|
||||||
|
expected_href = reverse("admin:registrar_portfolio_change", args=[portfolio.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
self.assertContains(response, str(portfolio))
|
||||||
|
permission.delete()
|
||||||
|
portfolio.delete()
|
||||||
|
|
||||||
|
|
||||||
class AuditedAdminTest(TestCase):
|
class AuditedAdminTest(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue