Merge remote-tracking branch 'origin/main' into rjm/795-admin-css

This commit is contained in:
rachidatecs 2023-08-09 17:52:24 -04:00
commit 7bc8cbcafb
No known key found for this signature in database
GPG key ID: 3CEBBFA7325E5525
19 changed files with 249 additions and 17 deletions

View file

@ -3,7 +3,7 @@ name: Developer Onboarding
about: Onboarding steps for developers. about: Onboarding steps for developers.
title: 'Developer Onboarding: GH_HANDLE' title: 'Developer Onboarding: GH_HANDLE'
labels: dev, onboarding labels: dev, onboarding
assignees: loganmeetsworld assignees: abroddrick
--- ---
@ -16,7 +16,7 @@ assignees: loganmeetsworld
There are several tools we use locally that you will need to have. There are several tools we use locally that you will need to have.
- [ ] [Install the cf CLI v7](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html#pkg-mac) for the ability to deploy - [ ] [Install the cf CLI v7](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html#pkg-mac) for the ability to deploy
- [ ] Make sure you have `gpg` >2.1.7. Run `gpg --version` to check. - [ ] Make sure you have `gpg` >2.1.7. Run `gpg --version` to check. If not, [install gnupg](https://formulae.brew.sh/formula/gnupg)
- [ ] Install the [Github CLI](https://cli.github.com/) - [ ] Install the [Github CLI](https://cli.github.com/)
## Access ## Access
@ -80,6 +80,15 @@ You may need to add these two lines to your shell's rc file (e.g. `.bashrc` or `
GPG_TTY=$(tty) GPG_TTY=$(tty)
export GPG_TTY export GPG_TTY
``` ```
and then
```bash
source ~/.bashrc
```
or
```bash
source ~/.zshrc
```
## Setting up developer sandbox ## Setting up developer sandbox

View file

@ -15,6 +15,9 @@ jobs:
|| startsWith(github.head_ref, 'rb/') || startsWith(github.head_ref, 'rb/')
|| startsWith(github.head_ref, 'ko/') || startsWith(github.head_ref, 'ko/')
|| startsWith(github.head_ref, 'gd/') || startsWith(github.head_ref, 'gd/')
|| startsWith(github.head_ref, 'za/')
|| startsWith(github.head_ref, 'rh/')
|| startsWith(github.head_ref, 'nl/')
outputs: outputs:
environment: ${{ steps.var.outputs.environment}} environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"

View file

@ -15,6 +15,9 @@ on:
options: options:
- stable - stable
- staging - staging
- nl
- rh
- za
- gd - gd
- rb - rb
- ko - ko

View file

@ -16,6 +16,9 @@ on:
options: options:
- stable - stable
- staging - staging
- nl
- rh
- za
- gd - gd
- rb - rb
- ko - ko

View file

@ -18,6 +18,10 @@ If you're new to Django, see [Getting Started with Django](https://www.djangopro
Visit the running application at [http://localhost:8080](http://localhost:8080). Visit the running application at [http://localhost:8080](http://localhost:8080).
Note: If you are using Windows, you may need to change your [line endings](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings). If not, you may not be able to run manage.py.
* Unix based operating systems (like macOS or Linux) handle line separators [differently than Windows does](https://superuser.com/questions/374028/how-are-n-and-r-handled-differently-on-linux-and-windows). This can break bash scripts in particular. In the case of manage.py, it uses *#!/usr/bin/env python* to access the Python executable. Since the script is still thinking in terms of unix line seperators, it may look for the executable *python\r* rather than *python* (since Windows cannot read the carriage return on its own) - thus leading to the error `usr/bin/env: 'python\r' no such file or directory`
* If you'd rather not change this globally, add a `.gitattributes` file in the project root with `* text eol=lf` as the text content, and [refresh the repo](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings#refreshing-a-repository-after-changing-line-endings)
## Branch Conventions ## Branch Conventions
We use the branch convention of `initials/branch-topic` (ex: `lmm/fix-footer`). This allows for automated deployment to a developer sandbox namespaced to the initials. We use the branch convention of `initials/branch-topic` (ex: `lmm/fix-footer`). This allows for automated deployment to a developer sandbox namespaced to the initials.
@ -66,7 +70,7 @@ The endpoint /admin can be used to view and manage site content, including but n
1. Login via login.gov 1. Login via login.gov
2. Go to the home page and make sure you can see the part where you can submit an application 2. Go to the home page and make sure you can see the part where you can submit an application
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
4. in src/registrar/fixtures.py add to the ADMINS list in that file by adding your UUID as your username along with your first and last name. See below: 4. in src/registrar/fixtures.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
``` ```
ADMINS = [ ADMINS = [
@ -79,8 +83,30 @@ The endpoint /admin can be used to view and manage site content, including but n
] ]
``` ```
5. In the browser, navigate to /admins. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses. 5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses.
### Adding an Analyst to /admin
Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin:
1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
2. Go to the home page and make sure you can see the part where you can submit an application
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
4. in src/registrar/fixtures.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
```
STAFF = [
{
"username": "<UUID here>",
"first_name": "",
"last_name": "",
},
...
]
```
5. In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only.
Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst`
## Adding to CODEOWNERS (optional) ## Adding to CODEOWNERS (optional)
The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of. The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of.

View file

@ -0,0 +1,29 @@
---
applications:
- name: getgov-nl
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-nl.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
routes:
- route: getgov-nl.app.cloud.gov
services:
- getgov-credentials
- getgov-nl-database

View file

@ -0,0 +1,29 @@
---
applications:
- name: getgov-rh
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-rh.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
routes:
- route: getgov-rh.app.cloud.gov
services:
- getgov-credentials
- getgov-rh-database

View file

@ -0,0 +1,29 @@
---
applications:
- name: getgov-za
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-za.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
routes:
- route: getgov-za.app.cloud.gov
services:
- getgov-credentials
- getgov-za-database

View file

@ -571,6 +571,9 @@ SECURE_SSL_REDIRECT = True
ALLOWED_HOSTS = [ ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov", "getgov-stable.app.cloud.gov",
"getgov-staging.app.cloud.gov", "getgov-staging.app.cloud.gov",
"getgov-nl.app.cloud.gov",
"getgov-rh.app.cloud.gov",
"getgov-za.app.cloud.gov",
"getgov-gd.app.cloud.gov", "getgov-gd.app.cloud.gov",
"getgov-rb.app.cloud.gov", "getgov-rb.app.cloud.gov",
"getgov-ko.app.cloud.gov", "getgov-ko.app.cloud.gov",

View file

@ -57,6 +57,16 @@ class UserFixture:
"first_name": "Ryan", "first_name": "Ryan",
"last_name": "Brooks", "last_name": "Brooks",
}, },
{
"username": "30001ee7-0467-4df2-8db2-786e79606060",
"first_name": "Zander",
"last_name": "Adkinson",
},
{
"username": "bb21f687-c773-4df3-9243-111cfd4c0be4",
"first_name": "Paul",
"last_name": "Kuykendall",
},
] ]
STAFF = [ STAFF = [
@ -70,6 +80,16 @@ class UserFixture:
"first_name": "Alysia-Analyst", "first_name": "Alysia-Analyst",
"last_name": "Alysia-Analyst", "last_name": "Alysia-Analyst",
}, },
{
"username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
"first_name": "Zander-Analyst",
"last_name": "Adkinson-Analyst",
},
{
"username": "57ab5847-7789-49fe-a2f9-21d38076d699",
"first_name": "Paul-Analyst",
"last_name": "Kuykendall-Analyst",
},
] ]
STAFF_PERMISSIONS = [ STAFF_PERMISSIONS = [

View file

@ -3,7 +3,17 @@
{% block form_fields %} {% block form_fields %}
{% with sublabel_text="Please include the entire name of your tribe as recognized by the Bureau of Indian Affairs." %}
{% with link_text="Bureau of Indian Affairs" %}
{% with link_href="https://www.federalregister.gov/documents/2023/01/12/2023-00504/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" %}
{% with target_blank="true" %}
{% input_with_errors forms.0.tribe_name %} {% input_with_errors forms.0.tribe_name %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
<fieldset class="usa-fieldset"> <fieldset class="usa-fieldset">
<legend class="usa-legend"> <legend class="usa-legend">
<p>Is your organization a federally-recognized tribe or a state-recognized tribe? Check all that apply. <p>Is your organization a federally-recognized tribe or a state-recognized tribe? Check all that apply.

View file

@ -33,6 +33,8 @@
</thead> </thead>
<tbody> <tbody>
{% for domain in domains %} {% for domain in domains %}
{% comment %} ticket 796
{% if domain.application_status == "approved" or (domain.application does not exist) %} {% endcomment %}
<tr> <tr>
<th th scope="row" role="rowheader" data-label="Domain name"> <th th scope="row" role="rowheader" data-label="Domain name">
{{ domain.name }} {{ domain.name }}

View file

@ -3,6 +3,8 @@ Template include for form fields with classes and their corresponding
error messages, if necessary. error messages, if necessary.
{% endcomment %} {% endcomment %}
{% load custom_filters %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% if widget.attrs.maxlength %} {% if widget.attrs.maxlength %}
@ -29,7 +31,18 @@ error messages, if necessary.
{% endif %} {% endif %}
{% if sublabel_text %} {% if sublabel_text %}
<p id="{{ widget.attrs.id }}__sublabel" class="text-base margin-top-2px margin-bottom-1">{{ sublabel_text }}</p> <p id="{{ widget.attrs.id }}__sublabel" class="text-base margin-top-2px margin-bottom-1">
{% comment %} If the link_text appears more than once, the first instance will be a link and the other instances will be ignored {% endcomment %}
{% if link_text and link_text in sublabel_text %}
{% with link_index=sublabel_text|find_index:link_text %}
{{ sublabel_text|slice:link_index }}
{% comment %} HTML will convert a new line into a space, resulting with a space before the fullstop in case link_text is at the end of sublabel_text, hence the unfortunate line below {% endcomment %}
<a {% if target_blank == "true" %}target="_blank" {% endif %}href="{{ link_href }}">{{ link_text }}</a>{% with sublabel_part_after=sublabel_text|slice_after:link_text %}{{ sublabel_part_after }}{% endwith %}
{% endwith %}
{% else %}
{{ sublabel_text }}
{% endif %}
</p>
{% endif %} {% endif %}
{% if field.errors %} {% if field.errors %}

View file

@ -23,3 +23,20 @@ def extract_a_text(value):
extracted_text = "" extracted_text = ""
return extracted_text return extracted_text
@register.filter
def find_index(haystack, needle):
try:
return haystack.index(needle)
except ValueError:
return -1
@register.filter
def slice_after(value, substring):
index = value.find(substring)
if index != -1:
result = value[index + len(substring) :]
return result
return value

View file

@ -3,6 +3,12 @@
from django.conf import settings from django.conf import settings
from django.test import TestCase from django.test import TestCase
from django.template import Context, Template from django.template import Context, Template
from registrar.templatetags.custom_filters import (
extract_value,
extract_a_text,
find_index,
slice_after,
)
class TestTemplateTags(TestCase): class TestTemplateTags(TestCase):
@ -33,8 +39,6 @@ class TestTemplateTags(TestCase):
class CustomFiltersTestCase(TestCase): class CustomFiltersTestCase(TestCase):
def test_extract_value_filter(self): def test_extract_value_filter(self):
from registrar.templatetags.custom_filters import extract_value
html_input = ( html_input = (
'<input type="checkbox" name="_selected_action" value="123" ' '<input type="checkbox" name="_selected_action" value="123" '
'id="label_123" class="action-select">' 'id="label_123" class="action-select">'
@ -50,8 +54,6 @@ class CustomFiltersTestCase(TestCase):
self.assertEqual(result, "abc") self.assertEqual(result, "abc")
def test_extract_a_text_filter(self): def test_extract_a_text_filter(self):
from registrar.templatetags.custom_filters import extract_a_text
input_text = '<a href="#">Link Text</a>' input_text = '<a href="#">Link Text</a>'
result = extract_a_text(input_text) result = extract_a_text(input_text)
self.assertEqual(result, "Link Text") self.assertEqual(result, "Link Text")
@ -59,3 +61,25 @@ class CustomFiltersTestCase(TestCase):
input_text = '<a href="/example">Another Link</a>' input_text = '<a href="/example">Another Link</a>'
result = extract_a_text(input_text) result = extract_a_text(input_text)
self.assertEqual(result, "Another Link") self.assertEqual(result, "Another Link")
def test_find_index(self):
haystack = "Hello, World!"
needle = "lo"
result = find_index(haystack, needle)
self.assertEqual(result, 3)
needle = "XYZ"
result = find_index(haystack, needle)
self.assertEqual(result, -1)
def test_slice_after(self):
value = "Hello, World!"
substring = "lo"
result = slice_after(value, substring)
self.assertEqual(result, ", World!")
substring = "XYZ"
result = slice_after(value, substring)
self.assertEqual(
result, value
) # Should return the original value if substring not found

View file

@ -145,7 +145,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
type_form = type_page.form type_form = type_page.form
type_form["organization_type-organization_type"] = "federal" type_form["organization_type-organization_type"] = "federal"
# test next button and validate data # test next button and validate data
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit() type_result = type_page.form.submit()
@ -161,6 +160,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- FEDERAL BRANCH PAGE ---- # ---- FEDERAL BRANCH PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_page = type_result.follow() federal_page = type_result.follow()
federal_form = federal_page.form federal_form = federal_page.form
federal_form["organization_federal-federal_type"] = "executive" federal_form["organization_federal-federal_type"] = "executive"

View file

@ -67,7 +67,7 @@ class ApplicationWizard(TemplateView):
URL_NAMESPACE = "application" URL_NAMESPACE = "application"
# name for accessing /application/<id>/edit # name for accessing /application/<id>/edit
EDIT_URL_NAME = "edit-application" EDIT_URL_NAME = "edit-application"
NEW_URL_NAME = "/register/"
# We need to pass our human-readable step titles as context to the templates. # We need to pass our human-readable step titles as context to the templates.
TITLES = { TITLES = {
Step.ORGANIZATION_TYPE: _("Type of organization"), Step.ORGANIZATION_TYPE: _("Type of organization"),
@ -144,6 +144,7 @@ class ApplicationWizard(TemplateView):
self._application = DomainApplication.objects.create( self._application = DomainApplication.objects.create(
creator=self.request.user, # type: ignore creator=self.request.user, # type: ignore
) )
self.storage["application_id"] = self._application.id self.storage["application_id"] = self._application.id
return self._application return self._application
@ -195,7 +196,6 @@ class ApplicationWizard(TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""This method handles GET requests.""" """This method handles GET requests."""
current_url = resolve(request.path_info).url_name current_url = resolve(request.path_info).url_name
# if user visited via an "edit" url, associate the id of the # if user visited via an "edit" url, associate the id of the
@ -213,12 +213,15 @@ class ApplicationWizard(TemplateView):
# send users "to the application wizard" without needing to # send users "to the application wizard" without needing to
# know which view is first in the list of steps. # know which view is first in the list of steps.
if self.__class__ == ApplicationWizard: if self.__class__ == ApplicationWizard:
# if starting a new application, clear the storage
if request.path_info == self.NEW_URL_NAME:
del self.storage
return self.goto(self.steps.first) return self.goto(self.steps.first)
self.steps.current = current_url self.steps.current = current_url
context = self.get_context_data() context = self.get_context_data()
context["forms"] = self.get_forms() context["forms"] = self.get_forms()
return render(request, self.template_name, context) return render(request, self.template_name, context)
def get_all_forms(self, **kwargs) -> list: def get_all_forms(self, **kwargs) -> list:
@ -242,7 +245,6 @@ class ApplicationWizard(TemplateView):
and from the database if `use_db` is True (provided that record exists). and from the database if `use_db` is True (provided that record exists).
An empty form will be provided if neither of those are true. An empty form will be provided if neither of those are true.
""" """
kwargs = { kwargs = {
"files": files, "files": files,
"prefix": self.steps.current, "prefix": self.steps.current,

View file

@ -24,6 +24,12 @@ class DomainPermission(PermissionsLoginMixin):
The user is in self.request.user and the domain needs to be looked The user is in self.request.user and the domain needs to be looked
up from the domain's primary key in self.kwargs["pk"] up from the domain's primary key in self.kwargs["pk"]
""" """
# ticket 806
# if self.request.user is staff or admin and
# domain.application__status = 'approved' or 'rejected' or 'action needed'
# return True
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return False return False
@ -33,6 +39,10 @@ class DomainPermission(PermissionsLoginMixin):
).exists(): ).exists():
return False return False
# ticket 796
# if domain.application__status != 'approved'
# return false
# if we need to check more about the nature of role, do it here. # if we need to check more about the nature of role, do it here.
return True return True

View file

@ -3,7 +3,7 @@ asgiref==3.7.2 ; python_version >= '3.7'
boto3==1.26.145 boto3==1.26.145
botocore==1.29.145 ; python_version >= '3.7' botocore==1.29.145 ; python_version >= '3.7'
cachetools==5.3.1 cachetools==5.3.1
certifi==2023.5.7 ; python_version >= '3.6' certifi==2023.7.22 ; python_version >= '3.6'
cfenv==0.5.3 cfenv==0.5.3
cffi==1.15.1 cffi==1.15.1
charset-normalizer==3.1.0 ; python_full_version >= '3.7.0' charset-normalizer==3.1.0 ; python_full_version >= '3.7.0'