mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 18:56:15 +02:00
New Member form is assembled (just needs validation)
This commit is contained in:
parent
8b6d49ded2
commit
b61d3936b5
8 changed files with 446 additions and 22 deletions
|
@ -912,6 +912,15 @@ function setupUrbanizationToggle(stateTerritoryField) {
|
|||
HookupYesNoListener("additional_details-has_anything_else_text",'anything-else', null)
|
||||
})();
|
||||
|
||||
|
||||
/**
|
||||
* An IIFE that listens to the yes/no radio buttons on the anything else form and toggles form field visibility accordingly
|
||||
*
|
||||
*/
|
||||
(function newMemberFormListener() {
|
||||
HookupYesNoListener("new_member-permission_level",'new-member-admin-permissions', 'new-member-basic-permissions')
|
||||
})();
|
||||
|
||||
/**
|
||||
* An IIFE that disables the delete buttons on nameserver forms on page load if < 3 forms
|
||||
*
|
||||
|
|
|
@ -86,11 +86,11 @@ urlpatterns = [
|
|||
views.PortfolioMembersView.as_view(),
|
||||
name="members",
|
||||
),
|
||||
# path(
|
||||
# "no-organization-members/",
|
||||
# views.PortfolioNoMembersView.as_view(),
|
||||
# name="no-portfolio-members",
|
||||
# ),
|
||||
path(
|
||||
"members/new-member/",
|
||||
views.NewMemberView.as_view(),
|
||||
name="new-member",
|
||||
),
|
||||
path(
|
||||
"requests/",
|
||||
views.PortfolioDomainRequestsView.as_view(),
|
||||
|
|
|
@ -3,8 +3,11 @@
|
|||
import logging
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator
|
||||
from django.core.validators import MaxLengthValidator
|
||||
|
||||
from ..models import DomainInformation, Portfolio, SeniorOfficial
|
||||
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||
|
||||
from ..models import DomainInformation, Portfolio, SeniorOfficial, User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -99,3 +102,69 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
|
|||
cleaned_data = super().clean()
|
||||
cleaned_data.pop("full_name", None)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class NewMemberForm(forms.ModelForm):
|
||||
admin_org_domain_request_permissions = forms.ChoiceField(
|
||||
label="Select permission",
|
||||
choices=[('view_only', 'View all requests'), ('view_and_create', 'View all requests plus create requests')],
|
||||
widget=forms.RadioSelect,
|
||||
required=True)
|
||||
admin_org_members_permissions = forms.ChoiceField(
|
||||
label="Select permission", choices=[('view_only', 'View all members'), ('view_and_create', 'View all members plus manage members')], widget=forms.RadioSelect, required=True)
|
||||
basic_org_domain_request_permissions = forms.ChoiceField(
|
||||
label="Select permission", choices=[('view_only', 'View all requests'), ('view_and_create', 'View all requests plus create requests'),('no_access', 'No access')], widget=forms.RadioSelect, required=True)
|
||||
|
||||
email = forms.EmailField(
|
||||
label="Enter the email of the member you'd like to invite",
|
||||
max_length=None,
|
||||
error_messages={
|
||||
"invalid": ("Enter an email address in the required format, like name@example.com."),
|
||||
"required": ("Enter an email address in the required format, like name@example.com."),
|
||||
},
|
||||
validators=[
|
||||
MaxLengthValidator(
|
||||
320,
|
||||
message="Response must be less than 320 characters.",
|
||||
)
|
||||
],
|
||||
required=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['email'] #, 'grade', 'sport']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NewMemberForm, self).__init__(*args, **kwargs)
|
||||
# self.fields['sport'].choices = []
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
# Lowercase the value of the 'email' field
|
||||
email_value = cleaned_data.get("email")
|
||||
if email_value:
|
||||
cleaned_data["email"] = email_value.lower()
|
||||
|
||||
# Check for an existing user (if there isn't any, send an invite)
|
||||
if email_value:
|
||||
try:
|
||||
existingUser = User.objects.get(email=email_value)
|
||||
except existingUser.DoesNotExist:
|
||||
raise forms.ValidationError("User with this email does not exist.")
|
||||
|
||||
# grade = cleaned_data.get('grade')
|
||||
# sport = cleaned_data.get('sport')
|
||||
|
||||
# # Handle sport options based on grade
|
||||
# if grade == 'Junior':
|
||||
# self.fields['sport'].choices = [('Basketball', 'Basketball'), ('Football', 'Football')]
|
||||
# elif grade == 'Varsity':
|
||||
# self.fields['sport'].choices = [('Swimming', 'Swimming'), ('Tennis', 'Tennis')]
|
||||
|
||||
# # Ensure both sport and grade are selected and valid
|
||||
# if not grade or not sport:
|
||||
# raise forms.ValidationError("Both grade and sport must be selected.")
|
||||
|
||||
return cleaned_data
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div class="mobile:grid-col-12 tablet:grid-col-6">
|
||||
<p class="float-right-tablet tablet:margin-y-0">
|
||||
<a href="#" class="usa-button"
|
||||
<a href="new-member" class="usa-button"
|
||||
>
|
||||
Add a new member
|
||||
</a>
|
||||
|
|
117
src/registrar/templates/portfolio_members_add_new.html
Normal file
117
src/registrar/templates/portfolio_members_add_new.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
{% extends 'portfolio_base.html' %}
|
||||
{% load static url_helpers %}
|
||||
{% load field_helpers %}
|
||||
|
||||
{% block title %} Members | New Member {% endblock %}
|
||||
|
||||
{% block wrapper_class %}
|
||||
{{ block.super }} dashboard--grey-1
|
||||
{% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock messages%}
|
||||
|
||||
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
|
||||
<ol class="usa-breadcrumb__list">
|
||||
<li class="usa-breadcrumb__list-item">
|
||||
<a href="/members/" class="usa-breadcrumb__link"><span>Members</span></a>
|
||||
</li>
|
||||
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
|
||||
<span>Add a new member</span>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
{% block new_member_header %}
|
||||
<h1>Add a new member</h1>
|
||||
{% endblock new_member_header %}
|
||||
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
{% block form_fields %}
|
||||
<form class="usa-form usa-form--large" method="post" id="form-container" novalidate="">
|
||||
<fieldset class="usa-fieldset margin-top-2">
|
||||
<legend>
|
||||
<h2>Email</h2>
|
||||
</legend>
|
||||
<!-- Member email -->
|
||||
{% csrf_token %}
|
||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||
{% input_with_errors form.email %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- {{ form.as_p }} -->
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="usa-fieldset margin-top-2">
|
||||
<legend>
|
||||
<h2>Member Access</h2>
|
||||
</legend>
|
||||
|
||||
<!-- Member Access Radio Form (Toggles other sections) -->
|
||||
<em>Select the level of access for this member. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
|
||||
<div class="usa-radio">
|
||||
<input type="radio" name="new_member-permission_level" class="usa-radio__input usa-radio__input--tile" value="True" id="id_new_member-permission_level_0" required="" checked="">
|
||||
<label class="usa-radio__label usa-legend" for="id_new_member-permission_level_0">
|
||||
Admin access
|
||||
<p class="margin-0 margin-top-2">
|
||||
Grants this member access to the organization-wide information on domains, domain requests, and members. Domain management can be assigned separately.
|
||||
</p>
|
||||
</label>
|
||||
<input type="radio" name="new_member-permission_level" class="usa-radio__input usa-radio__input--tile" value="False" id="id_new_member-permission_level_1" required="">
|
||||
<label class="usa-radio__label usa-legend" for="id_new_member-permission_level_1">
|
||||
Basic Access
|
||||
<p class="margin-0 margin-top-2">
|
||||
Grants this member access to the organization. They can be given extra permissions to view all organization domain requests and submit domain requests on behald of the organization. Basica access members can't view all members of an organization or manage them. Domain management can be assigned separacterly.
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<!-- Admin Access form -->
|
||||
<div id="new-member-admin-permissions" class="margin-top-2">
|
||||
<legend>
|
||||
<h2>Admin access permissions</h2>
|
||||
<p>Member permissions available for admin-level acccess.</p>
|
||||
</legend>
|
||||
|
||||
<h3 class="margin-bottom-0">Organization domain requests</h3>
|
||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||
{% input_with_errors form.admin_org_domain_request_permissions %}
|
||||
{% endwith %}
|
||||
|
||||
<h3 class="margin-bottom-0 margin-top-3">Organization Members</h3>
|
||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||
{% input_with_errors form.admin_org_members_permissions %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<!-- TODO: Basic Access form -->
|
||||
<div id="new-member-basic-permissions" class="margin-top-2">
|
||||
<legend>
|
||||
<h2>Basic member permissions</h2>
|
||||
<p>Member permissions available for basic-level access</p>
|
||||
{% input_with_errors form.basic_org_domain_request_permissions %}
|
||||
</legend>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="margin-top-3">
|
||||
<button
|
||||
href="/members/"
|
||||
class="usa-button usa-button--outline"
|
||||
name="btn-cancel-click"
|
||||
aria-label="Cancel adding new member"
|
||||
>Cancel
|
||||
</button>
|
||||
<button type="submit" class="usa-button">Invite Member</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock form_fields%}
|
||||
{% endblock portfolio_content%}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'portfolio_base.html' %}
|
||||
{% extends 'portfolio_no_domains.html' %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import logging
|
||||
from django.db import IntegrityError
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from registrar.forms.portfolio import PortfolioOrgAddressForm, PortfolioSeniorOfficialForm
|
||||
from registrar.forms import portfolio as portfolioForms
|
||||
from registrar.models import Portfolio, User
|
||||
from registrar.models.user_domain_role import UserDomainRole
|
||||
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
||||
from registrar.utility.email import EmailSendingError
|
||||
from registrar.views.utility.permission_views import (
|
||||
PortfolioDomainRequestsPermissionView,
|
||||
PortfolioDomainsPermissionView,
|
||||
|
@ -42,15 +45,6 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
|
|||
return render(request, "portfolio_requests.html")
|
||||
|
||||
|
||||
class PortfolioMembersView(PortfolioMembersPermissionView, View):
|
||||
|
||||
template_name = "portfolio_members.html"
|
||||
|
||||
def get(self, request):
|
||||
"""Add additional context data to the template."""
|
||||
return render(request, "portfolio_members.html")
|
||||
|
||||
|
||||
class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
|
||||
"""Some users have access to the underlying portfolio, but not any domains.
|
||||
This is a custom view which explains that to the user - and denotes who to contact.
|
||||
|
@ -116,7 +110,7 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
|||
|
||||
model = Portfolio
|
||||
template_name = "portfolio_organization.html"
|
||||
form_class = PortfolioOrgAddressForm
|
||||
form_class = portfolioForms.PortfolioOrgAddressForm
|
||||
context_object_name = "portfolio"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -179,7 +173,7 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
|
|||
|
||||
model = Portfolio
|
||||
template_name = "portfolio_senior_official.html"
|
||||
form_class = PortfolioSeniorOfficialForm
|
||||
form_class = portfolioForms.PortfolioSeniorOfficialForm
|
||||
context_object_name = "portfolio"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
|
@ -200,3 +194,238 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
|
|||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
class PortfolioMembersView(PortfolioMembersPermissionView, View):
|
||||
|
||||
template_name = "portfolio_members.html"
|
||||
|
||||
def get(self, request):
|
||||
"""Add additional context data to the template."""
|
||||
return render(request, "portfolio_members.html")
|
||||
|
||||
|
||||
class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||
|
||||
# template_name = "portfolio_members_add_new.html"
|
||||
# form = portfolioForms.NewMemberForm #[forms.NewMemberToggleForm, forms.OtherContactsFormSet, forms.NoOtherContactsForm]
|
||||
|
||||
model = UserPortfolioPermission
|
||||
template_name = "portfolio_members_add_new.html"
|
||||
form_class = portfolioForms.NewMemberForm
|
||||
context_object_name = "userPortfolioPermission"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""Get the portfolio object based on the session."""
|
||||
portfolio = self.request.session.get("portfolio")
|
||||
if portfolio is None:
|
||||
raise Http404("No organization found for this user")
|
||||
return portfolio
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""Include the instance in the form kwargs."""
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["instance"] = self.get_object()
|
||||
return kwargs
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Handle GET requests to display the form."""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
##########################################
|
||||
# TODO: future ticket
|
||||
# (save/invite new member)
|
||||
##########################################
|
||||
|
||||
# def _send_domain_invitation_email(self, email: str, requestor: User, add_success=True):
|
||||
# """Performs the sending of the member invitation email
|
||||
# email: string- email to send to
|
||||
# add_success: bool- default True indicates:
|
||||
# adding a success message to the view if the email sending succeeds
|
||||
|
||||
# raises EmailSendingError
|
||||
# """
|
||||
|
||||
# # Set a default email address to send to for staff
|
||||
# requestor_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
# # Check if the email requestor has a valid email address
|
||||
# if not requestor.is_staff and requestor.email is not None and requestor.email.strip() != "":
|
||||
# requestor_email = requestor.email
|
||||
# elif not requestor.is_staff:
|
||||
# messages.error(self.request, "Can't send invitation email. No email is associated with your account.")
|
||||
# logger.error(
|
||||
# f"Can't send email to '{email}' on domain '{self.object}'."
|
||||
# f"No email exists for the requestor '{requestor.username}'.",
|
||||
# exc_info=True,
|
||||
# )
|
||||
# return None
|
||||
|
||||
# # Check to see if an invite has already been sent
|
||||
# try:
|
||||
# invite = MemberInvitation.objects.get(email=email, domain=self.object)
|
||||
# # check if the invite has already been accepted
|
||||
# if invite.status == MemberInvitation.MemberInvitationStatus.RETRIEVED:
|
||||
# add_success = False
|
||||
# messages.warning(
|
||||
# self.request,
|
||||
# f"{email} is already a manager for this domain.",
|
||||
# )
|
||||
# else:
|
||||
# add_success = False
|
||||
# # else if it has been sent but not accepted
|
||||
# messages.warning(self.request, f"{email} has already been invited to this domain")
|
||||
# except Exception:
|
||||
# logger.error("An error occured")
|
||||
|
||||
# try:
|
||||
# send_templated_email(
|
||||
# "emails/member_invitation.txt",
|
||||
# "emails/member_invitation_subject.txt",
|
||||
# to_address=email,
|
||||
# context={
|
||||
# "portfolio": self.object,
|
||||
# "requestor_email": requestor_email,
|
||||
# },
|
||||
# )
|
||||
# except EmailSendingError as exc:
|
||||
# logger.warn(
|
||||
# "Could not sent email invitation to %s for domain %s",
|
||||
# email,
|
||||
# self.object,
|
||||
# exc_info=True,
|
||||
# )
|
||||
# raise EmailSendingError("Could not send email invitation.") from exc
|
||||
# else:
|
||||
# if add_success:
|
||||
# messages.success(self.request, f"{email} has been invited to this domain.")
|
||||
|
||||
# def _make_invitation(self, email_address: str, requestor: User):
|
||||
# """Make a Member invitation for this email and redirect with a message."""
|
||||
# try:
|
||||
# self._send_member_invitation_email(email=email_address, requestor=requestor)
|
||||
# except EmailSendingError:
|
||||
# messages.warning(self.request, "Could not send email invitation.")
|
||||
# else:
|
||||
# # (NOTE: only create a MemberInvitation if the e-mail sends correctly)
|
||||
# MemberInvitation.objects.get_or_create(email=email_address, domain=self.object)
|
||||
# return redirect(self.get_success_url())
|
||||
|
||||
# def form_valid(self, form):
|
||||
|
||||
# """Add the specified user as a member
|
||||
# for this portfolio.
|
||||
# Throws EmailSendingError."""
|
||||
# requested_email = form.cleaned_data["email"]
|
||||
# requestor = self.request.user
|
||||
# # look up a user with that email
|
||||
# try:
|
||||
# requested_user = User.objects.get(email=requested_email)
|
||||
# except User.DoesNotExist:
|
||||
# # no matching user, go make an invitation
|
||||
# return self._make_invitation(requested_email, requestor)
|
||||
# else:
|
||||
# # if user already exists then just send an email
|
||||
# try:
|
||||
# self._send_member_invitation_email(requested_email, requestor, add_success=False)
|
||||
# except EmailSendingError:
|
||||
# logger.warn(
|
||||
# "Could not send email invitation (EmailSendingError)",
|
||||
# self.object,
|
||||
# exc_info=True,
|
||||
# )
|
||||
# messages.warning(self.request, "Could not send email invitation.")
|
||||
# except Exception:
|
||||
# logger.warn(
|
||||
# "Could not send email invitation (Other Exception)",
|
||||
# self.object,
|
||||
# exc_info=True,
|
||||
# )
|
||||
# messages.warning(self.request, "Could not send email invitation.")
|
||||
|
||||
# try:
|
||||
# UserPortfolioPermission.objects.create(
|
||||
# user=requested_user,
|
||||
# portfolio=self.object,
|
||||
# role=UserDomainRole.Roles.MANAGER,
|
||||
# )
|
||||
# except IntegrityError:
|
||||
# messages.warning(self.request, f"{requested_email} is already a member of this portfolio")
|
||||
# else:
|
||||
# messages.success(self.request, f"Added user {requested_email}.")
|
||||
# return redirect(self.get_success_url())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||
# form = portfolioForms.NewMemberForm
|
||||
# template_name = 'portfolio_members_add_new.html' # Assuming you have a template file for the form
|
||||
|
||||
# # model = UserPortfolioPermission
|
||||
# # template_name = "portfolio_members_add_new.html"
|
||||
# # form_class = portfolioForms.NewMemberForm
|
||||
# # context_object_name = "userPortfolioPermission"
|
||||
|
||||
# def get_success_url(self):
|
||||
# return reverse('success') # Redirect after successful submission
|
||||
|
||||
# def get_context_data(self, **kwargs):
|
||||
# """Add additional context data to the template."""
|
||||
# #TODO: Add permissions to context
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# portfolio = self.request.session.get("portfolio")
|
||||
# context["has_invite_members_permission"] = self.request.user.has_edit_members_portfolio_permission(portfolio)
|
||||
# return context
|
||||
|
||||
# def form_valid(self, form):
|
||||
# # Get the cleaned data from the form
|
||||
# cleaned_data = form.cleaned_data
|
||||
# email = cleaned_data.get('email')
|
||||
# # grade = cleaned_data.get('grade')
|
||||
# # sport = cleaned_data.get('sport')
|
||||
|
||||
# ##########################################
|
||||
# # TODO: future ticket
|
||||
# # (validate and save/invite new member here)
|
||||
# ##########################################
|
||||
|
||||
# # Lookup member by email
|
||||
# # member = get_object_or_404(User, email=email)
|
||||
|
||||
# # Check existing portfolio permissions
|
||||
# # TODO: future ticket -- check for existing portfolio permissions, multipe portfolio flags, etc.
|
||||
# # school = self.get_context_data()['school']
|
||||
|
||||
# # Update student school information
|
||||
# # student.school = school
|
||||
# # student.save()
|
||||
|
||||
# # Create or update the SportEnrollment for this student
|
||||
# # SportEnrollment.objects.create(
|
||||
# # student=student,
|
||||
# # grade=grade,
|
||||
# # sport=sport
|
||||
# # )
|
||||
|
||||
# return super().form_valid(form)
|
||||
|
||||
# def form_invalid(self, form):
|
||||
# # If the form is invalid, show errors
|
||||
# return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
# def get(self, request):
|
||||
# return render(request, "portfolio_members_add_new.html")
|
||||
|
||||
|
|
|
@ -253,7 +253,7 @@ class PortfolioDomainRequestsPermissionView(PortfolioDomainRequestsPermission, P
|
|||
|
||||
|
||||
class PortfolioMembersPermissionView(PortfolioMembersPermission, PortfolioBasePermissionView, abc.ABC):
|
||||
"""Abstract base view for portfolio domain request views that enforces permissions.
|
||||
"""Abstract base view for portfolio members views that enforces permissions.
|
||||
|
||||
This abstract view cannot be instantiated. Actual views must specify
|
||||
`template_name`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue