mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-01 23:42:17 +02:00
Merge branch 'main' into za/profile-page-update-label
This commit is contained in:
commit
3ca08ee01e
10 changed files with 165 additions and 4 deletions
4
.github/ISSUE_TEMPLATE/issue-default.yml
vendored
4
.github/ISSUE_TEMPLATE/issue-default.yml
vendored
|
@ -31,8 +31,8 @@ body:
|
|||
attributes:
|
||||
label: Links to other issues
|
||||
description: |
|
||||
"Add issue #numbers this relates to and how (e.g., 🚧 [construction] Blocks, ⛔️ [no_entry] Is blocked by, 🔄 [arrows_counterclockwise] Relates to)."
|
||||
placeholder: 🔄 Relates to...
|
||||
"With a `-` to start the line, add issue #numbers this relates to and how (e.g., 🚧 [construction] Blocks, ⛔️ [no_entry] Is blocked by, 🔄 [arrows_counterclockwise] Relates to)."
|
||||
placeholder: "- 🔄 Relates to..."
|
||||
- type: markdown
|
||||
id: note
|
||||
attributes:
|
||||
|
|
|
@ -1043,6 +1043,19 @@ class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
return super().changelist_view(request, extra_context=extra_context)
|
||||
|
||||
|
||||
class SeniorOfficialAdmin(ListHeaderAdmin):
|
||||
"""Custom Senior Official Admin class."""
|
||||
|
||||
# NOTE: these are just placeholders. Not part of ACs (haven't been defined yet). Update in future tickets.
|
||||
search_fields = ["first_name", "last_name", "email"]
|
||||
search_help_text = "Search by first name, last name or email."
|
||||
list_display = ["first_name", "last_name", "email"]
|
||||
|
||||
# this ordering effects the ordering of results
|
||||
# in autocomplete_fields for Senior Official
|
||||
ordering = ["first_name", "last_name"]
|
||||
|
||||
|
||||
class WebsiteResource(resources.ModelResource):
|
||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||
import/export file"""
|
||||
|
@ -2799,6 +2812,7 @@ admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
|
|||
admin.site.register(models.Portfolio, PortfolioAdmin)
|
||||
admin.site.register(models.DomainGroup, DomainGroupAdmin)
|
||||
admin.site.register(models.Suborganization, SuborganizationAdmin)
|
||||
admin.site.register(models.SeniorOfficial, SeniorOfficialAdmin)
|
||||
|
||||
# Register our custom waffle implementations
|
||||
admin.site.register(models.WaffleFlag, WaffleFlagAdmin)
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Generated by Django 4.2.10 on 2024-07-02 21:03
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import phonenumber_field.modelfields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("registrar", "0109_domaininformation_sub_organization_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="SeniorOfficial",
|
||||
fields=[
|
||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
("first_name", models.CharField(verbose_name="first name")),
|
||||
("last_name", models.CharField(verbose_name="last name")),
|
||||
("title", models.CharField(verbose_name="title / role")),
|
||||
(
|
||||
"phone",
|
||||
phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None),
|
||||
),
|
||||
("email", models.EmailField(blank=True, max_length=320, null=True)),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="portfolio",
|
||||
name="senior_official",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text="Associated senior official",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="registrar.seniorofficial",
|
||||
),
|
||||
),
|
||||
]
|
37
src/registrar/migrations/0111_create_groups_v15.py
Normal file
37
src/registrar/migrations/0111_create_groups_v15.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||
# It is dependent on 0079 (which populates federal agencies)
|
||||
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||
# in the user_group model then:
|
||||
# [NOT RECOMMENDED]
|
||||
# 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
|
||||
# [RECOMMENDED]
|
||||
# Alternatively:
|
||||
# step 1: duplicate the migration that loads data
|
||||
# step 2: docker-compose exec app ./manage.py migrate
|
||||
|
||||
from django.db import migrations
|
||||
from registrar.models import UserGroup
|
||||
from typing import Any
|
||||
|
||||
|
||||
# For linting: RunPython expects a function reference,
|
||||
# so let's give it one
|
||||
def create_groups(apps, schema_editor) -> Any:
|
||||
UserGroup.create_cisa_analyst_group(apps, schema_editor)
|
||||
UserGroup.create_full_access_group(apps, schema_editor)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0110_seniorofficial_portfolio_senior_official"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
create_groups,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
atomic=True,
|
||||
),
|
||||
]
|
|
@ -19,6 +19,7 @@ from .waffle_flag import WaffleFlag
|
|||
from .portfolio import Portfolio
|
||||
from .domain_group import DomainGroup
|
||||
from .suborganization import Suborganization
|
||||
from .senior_official import SeniorOfficial
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -42,6 +43,7 @@ __all__ = [
|
|||
"Portfolio",
|
||||
"DomainGroup",
|
||||
"Suborganization",
|
||||
"SeniorOfficial",
|
||||
]
|
||||
|
||||
auditlog.register(Contact)
|
||||
|
@ -64,3 +66,4 @@ auditlog.register(WaffleFlag)
|
|||
auditlog.register(Portfolio)
|
||||
auditlog.register(DomainGroup)
|
||||
auditlog.register(Suborganization)
|
||||
auditlog.register(SeniorOfficial)
|
||||
|
|
|
@ -38,6 +38,15 @@ class Portfolio(TimeStampedModel):
|
|||
default=FederalAgency.get_non_federal_agency,
|
||||
)
|
||||
|
||||
senior_official = models.ForeignKey(
|
||||
"registrar.SeniorOfficial",
|
||||
on_delete=models.PROTECT,
|
||||
help_text="Associated senior official",
|
||||
unique=False,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
organization_type = models.CharField(
|
||||
max_length=255,
|
||||
choices=OrganizationChoices.choices,
|
||||
|
|
50
src/registrar/models/senior_official.py
Normal file
50
src/registrar/models/senior_official.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from django.db import models
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||
|
||||
|
||||
class SeniorOfficial(TimeStampedModel):
|
||||
"""
|
||||
Senior Official is a distinct Contact-like entity (NOT to be inherited
|
||||
from Contacts) developed for the unique role these individuals have in
|
||||
managing Portfolios.
|
||||
"""
|
||||
|
||||
first_name = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name="first name",
|
||||
)
|
||||
last_name = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name="last name",
|
||||
)
|
||||
title = models.CharField(
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name="title / role",
|
||||
)
|
||||
phone = PhoneNumberField(
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
email = models.EmailField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=320,
|
||||
)
|
||||
|
||||
def get_formatted_name(self):
|
||||
"""Returns the contact's name in Western order."""
|
||||
names = [n for n in [self.first_name, self.last_name] if n]
|
||||
return " ".join(names) if names else "Unknown"
|
||||
|
||||
def __str__(self):
|
||||
if self.first_name or self.last_name:
|
||||
return self.get_formatted_name()
|
||||
elif self.pk:
|
||||
return str(self.pk)
|
||||
else:
|
||||
return ""
|
|
@ -18,7 +18,7 @@
|
|||
completing your domain request might take around 15 minutes.</p>
|
||||
{% if has_profile_feature_flag %}
|
||||
<h2>How we’ll reach you</h2>
|
||||
<p>While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:" class="usa-link">your profile</a> to make updates.</p>
|
||||
<p>While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:" class="usa-link">your profile</a> to make updates.</p>
|
||||
{% include "includes/profile_information.html" with user=user%}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -686,6 +686,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
|
||||
# Add a phone number
|
||||
finish_setup_form = finish_setup_page.form
|
||||
finish_setup_form["first_name"] = "firstname"
|
||||
finish_setup_form["phone"] = "(201) 555-0123"
|
||||
finish_setup_form["title"] = "CEO"
|
||||
finish_setup_form["last_name"] = "example"
|
||||
|
@ -814,6 +815,8 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
|
|||
self.assertContains(save_page, "Your profile has been updated.")
|
||||
|
||||
# We need to assert that logo is not clickable and links to manage your domain are not present
|
||||
# NOTE: "anage" is not a typo. It is to accomodate the fact that the "m" is uppercase in one
|
||||
# instance and lowercase in the other.
|
||||
self.assertContains(save_page, "anage your domains", count=2)
|
||||
self.assertNotContains(
|
||||
save_page, "Before you can manage your domains, we need you to add contact information"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Views for a User Profile.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
@ -109,6 +108,7 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
"""If the form is invalid, conditionally display an additional error."""
|
||||
if hasattr(self.user, "finished_setup") and not self.user.finished_setup:
|
||||
messages.error(self.request, "Before you can manage your domain, we need you to add contact information.")
|
||||
form.initial["redirect"] = form.data.get("redirect")
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue