Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2934: Django Admin - Dynamic Domain Request edit page [DK] #3040

Merged
merged 29 commits into from
Nov 18, 2024

Conversation

dave-kennedy-ecs
Copy link
Contributor

@dave-kennedy-ecs dave-kennedy-ecs commented Nov 7, 2024

Ticket

Resolves #2934

Changes

  • Domain request page - dynamically display/hide fields depending upon whether portfolio is selected
  • Domain request page - dynamically update fields when a portfolio is selected/deselected
  • Domain request page - suborganization dropdown options are dynamically limited to suborganizations associated with the selected portfolio
  • Reorganize the fieldsets on the domain request page according to specs in ticket/miro
  • Domain request page - certain fields display as links to portfolio rather than as text (organization type, organization name, federal agency, federal type)
  • Domain request page - conditional display of certain fields based on presence/value of other fields
    • Urbanization display depends on value of state/territory
    • Display of organization name, federal agency and federal type depend upon whether organization type is 'Federal'

Context for reviewers

Setup

Code Review Verification Steps

As the original developer, I have

Satisfied acceptance criteria and met development standards

  • Met the acceptance criteria, or will meet them in a subsequent PR
  • Created/modified automated tests
  • Update documentation in READMEs and/or onboarding guide

Ensured code standards are met (Original Developer)

  • If any updated dependencies on Pipfile, also update dependencies in requirements.txt.
  • Interactions with external systems are wrapped in try/except
  • Error handling exists for unusual or missing values

Validated user-facing changes (if applicable)

  • Tag @dotgov-designers in this PR's Reviewers for design review. If code is not user-facing, delete design reviewer checklist
  • Verify new pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
  • Checked keyboard navigability
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)

As a code reviewer, I have

Reviewed, tested, and left feedback about the changes

  • Pulled this branch locally and tested it
  • Verified code meets all checks above. Address any checks that are not satisfied
  • Reviewed this code and left comments. Indicate if comments must be addressed before code is merged
  • Checked that all code is adequately covered by tests
  • Verify migrations are valid and do not conflict with existing migrations

Validated user-facing changes as a developer

Note: Multiple code reviewers can share the checklists above, a second reviewer should not make a duplicate checklist. All checks should be checked before approving, even those labeled N/A.

  • New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
  • Checked keyboard navigability
  • Meets all designs and user flows provided by design/product
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
  • (Rarely needed) Tested as both an analyst and applicant user

As a designer reviewer, I have

Verified that the changes match the design intention

  • Checked that the design translated visually
  • Checked behavior. Comment any found issues or broken flows.
  • Checked different states (empty, one, some, error)
  • Checked for landmarks, page heading structure, and links

Validated user-facing changes as a designer

  • Checked keyboard navigability
  • Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
  • Tested with multiple browsers (check off which ones were used)
    • Chrome
    • Microsoft Edge
    • FireFox
    • Safari
  • (Rarely needed) Tested as both an analyst and applicant user

References

Screenshots

Copy link

github-actions bot commented Nov 7, 2024

🥳 Successfully deployed to developer sandbox dk.

Copy link

github-actions bot commented Nov 7, 2024

🥳 Successfully deployed to developer sandbox dk.

Copy link

github-actions bot commented Nov 7, 2024

🥳 Successfully deployed to developer sandbox dk.

Copy link

github-actions bot commented Nov 7, 2024

🥳 Successfully deployed to developer sandbox dk.

Copy link

github-actions bot commented Nov 7, 2024

🥳 Successfully deployed to developer sandbox dk.

@dave-kennedy-ecs dave-kennedy-ecs changed the title [DRAFT] #2934: Django Admin - Dynamic Domain Request edit page [DK] #2934: Django Admin - Dynamic Domain Request edit page [DK] Nov 7, 2024
@rachidatecs rachidatecs removed the request for review from abroddrick November 8, 2024 17:03
Copy link

github-actions bot commented Nov 8, 2024

🥳 Successfully deployed to developer sandbox dk.

Copy link

github-actions bot commented Nov 8, 2024

🥳 Successfully deployed to developer sandbox dk.

@zandercymatics zandercymatics self-assigned this Nov 12, 2024
src/registrar/views/utility/api_views.py Show resolved Hide resolved

# Add suborganizations related to this portfolio
suborganizations = portfolio.portfolio_suborganizations.all().values("id", "name")
results = [{"id": sub["id"], "text": sub["name"]} for sub in suborganizations]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Q) Just looking at the pattern of use, can't you use model to dict here for each item? I know we wouldn't be encoding "text" => "name" though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW not blocking or necessarily a suggestion (I'm neutral on it), just curious as to if you can

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly certain that the select2 expects responses with "id" and "text" defined. And to get this to work properly, we are updating the data-ajax--url of the select2 dom object so that the javascript which we don't have control over calls this view directly and processes the response. So, for this one, I think we need to leave in the format it is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I gotcha, thanks for the context on this one. Did not know that. That makes sense

Comment on lines +346 to +350
{% comment %}fields_always_present=True will shortcut the contact_detail_list template when
1. Senior official field should be hidden on domain request because no portfoloio is selected, which is desirable
2. A portfolio is selected but there is no senior official on the portfolio, where the shortcut is not desirable
To solve 2, we use an else No additional contact information found on field.field.name == "portfolio_senior_official"
and we hide the placeholders from detail_table_fieldset in JS{% endcomment %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice comment, this was helpful to have. Thanks.

Comment on lines +69 to +76
{% elif field.field.name == "portfolio_senior_official" %}
<div class="readonly">
{% if original_object.portfolio.senior_official %}
<a href="{% url 'admin:registrar_seniorofficial_change' original_object.portfolio.senior_official.id %}">{{ field.contents }}</a>
{% else %}
No senior official found.<br>
{% endif %}
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is totally fine, per our past convo about it. I do have a helper though which you can use to do this exact thing without having to define it manually, I'll give an example in a follow-on comment

Comment on lines +1740 to +1741
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
# Queryset is the list that gets iterated on.
# model_name is what model the link will, well, link to: `<a href={{reverse_of_model_name}}>`
# attribute_name is what is displayed between the `<a>` tag: `<a>{{obj.attribute_name}}</a>`
# separator does a join on each `<a>` element that gets generated and returns the resu;t
# msg_for_none is what is returned if the given queryset is empty
queryset = Portfolio.objects.filter(id=obj.portfolio.id, senior_official__isnull=False)
return get_field_links_as_list(queryset, model_name="seniorofficial", attribute_name="senior_official", separator=",", msg_for_none="No senior official found.")

This is the shortcut as mentioned earlier. The separator and queryset is a minor hack: a current limitation of get_field_links is that it expects a list of items (as it was designed for that), hence the separator / queryset dependency. However with a list size of either one or zero, it will look as you'd expect. What is needed is basically the same exact function but it doesn't operate on a queryset list.

No need to use this, but it may be helpful to know in any event

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Zander. I think I will keep this as is in this case. I think it makes most sense for this logic to be in the template, if it is simple enough to define there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough!

Copy link
Contributor

@zandercymatics zandercymatics left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks great. Doing some testing locally, and if all is well I will approve shortly

function updatePortfolioSeniorOfficial(senior_official) {
if (senior_official) {
let seniorOfficialName = [senior_official.first_name, senior_official.last_name].join(' ');
let seniorOfficialLink = `<a href=/admin/registrar/seniorofficial/${senior_official.id}/change/ class='test'>${seniorOfficialName}</a>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let seniorOfficialLink = `<a href=/admin/registrar/seniorofficial/${senior_official.id}/change/ class='test'>${seniorOfficialName}</a>`
let seniorOfficialLink = `<a href="/admin/registrar/seniorofficial/${senior_official.id}/change">${seniorOfficialName}</a>`

(Nice to have but optional) - May also be a nice addition to have this reference a div/input element with the reverse to this url path. Aside from that, just minor cleanup stuff

Comment on lines +417 to +419
if (suborganizationDropdown.data('select2')) {
suborganizationDropdown.select2('destroy');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Comment - no change) I've messed with this too and I wonder if there is a way around destroying the picker and recreating it. I wouldn't worry about doing it, but just speaking theoretically I think it may be possible to change the url this is bound to on page load, and where it pulls data for search params, when initializing it via python

// Initial handling of these groups.
updateFormGroupVisibility(isStatus);

// Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like an old comment?

@@ -33,17 +33,24 @@

{# Phone #}
{% if user.phone %}
<span id="contact_info_phone">{{ user.phone }}</span>
<span class="contact_info_phone">{{ user.phone }}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice changes, thanks

<br>
{% else %}
None<br>
{% endif %}

{% elif fields_always_present %}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Question - No change needed) This contact_detail_list file has been getting complex as it has grown. Did you have any trouble using this as the ticket was made?

response = self.client.get(self.api_url, {"id": self.portfolio.id})
self.assertEqual(response.status_code, 200)
portfolio = response.json()
self.assertEqual(portfolio["id"], self.portfolio.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Optional) I've never used it, but you may be able to use assertJSONEqual (link) instead. Would minorly benefit readability but this is readable as is

self.assertJSONEqual(
    response.content.decode(),  # or response.json() -- not sure
    {.....some data}
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE - this is on the test that is testing the entire API response, it looks like the rest of the content got cut off

Copy link
Contributor

@zandercymatics zandercymatics left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Discussed on a meeting) Found a bug where if a suborg is selected and the portfolio is changed, the detail fields don't reappear (as suborg gets cleared). Feel free to merge after that is fixed

Copy link

🥳 Successfully deployed to developer sandbox dk.

@dave-kennedy-ecs dave-kennedy-ecs merged commit 68dc494 into main Nov 18, 2024
10 checks passed
@dave-kennedy-ecs dave-kennedy-ecs deleted the dk/2934-dja-domain-request branch November 18, 2024 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update Domain Requests Django admin view
3 participants