diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index 6697e2e64..09b0fd211 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -281,3 +281,6 @@ class Domain(TimeStampedModel):
# ManyToManyField on User creates a "users" member for all of the
# users who have some role on this domain
+
+ # ForeignKey on DomainInvitation creates an "invitations" member for
+ # all of the invitations that have been sent for this domain
diff --git a/src/registrar/models/domain_invitation.py b/src/registrar/models/domain_invitation.py
index a1598e54e..80fed0486 100644
--- a/src/registrar/models/domain_invitation.py
+++ b/src/registrar/models/domain_invitation.py
@@ -22,6 +22,7 @@ class DomainInvitation(TimeStampedModel):
"registrar.Domain",
on_delete=models.CASCADE, # delete domain, then get rid of invitations
null=False,
+ related_name="invitations",
)
status = FSMField(
diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html
index a8091fa1c..9b30e7923 100644
--- a/src/registrar/templates/domain_users.html
+++ b/src/registrar/templates/domain_users.html
@@ -25,7 +25,7 @@
{% for permission in domain.permissions.all %}
-
+ |
{{ permission.user.email }}
|
{{ permission.role|title }} |
@@ -45,4 +45,29 @@
Add another user
+ {% if domain.invitations.all %}
+ Invitations
+
+ Domain invitations
+
+
+ Email |
+ Date created |
+ Status |
+
+
+
+ {% for invitation in domain.invitations.all %}
+
+
+ {{ invitation.email }}
+ |
+ {{ invitation.created_at|date }} |
+ {{ invitation.status|title }} |
+
+ {% endfor %}
+
+
+ {% endif %}
+
{% endblock %} {# domain_content #}
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index dc8ccc369..db46b163d 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -8,7 +8,7 @@ from django.urls import reverse
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin
-from registrar.models import Domain, User, UserDomainRole
+from registrar.models import Domain, DomainInvitation, User, UserDomainRole
from .utility import DomainPermission
@@ -31,15 +31,6 @@ class DomainAddUserForm(DomainPermission, forms.Form):
email = forms.EmailField(label="Email")
- def clean_email(self):
- requested_email = self.cleaned_data["email"]
- try:
- User.objects.get(email=requested_email)
- except User.DoesNotExist:
- # TODO: send an invitation email to a non-existent user
- raise forms.ValidationError("That user does not exist in this system.")
- return requested_email
-
class DomainAddUserView(DomainPermission, FormMixin, DetailView):
template_name = "domain_add_user.html"
@@ -53,16 +44,30 @@ class DomainAddUserView(DomainPermission, FormMixin, DetailView):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
+ # there is a valid email address in the form
return self.form_valid(form)
else:
return self.form_invalid(form)
+ def _make_invitation(self, email_address):
+ """Make a Domain invitation for this email and redirect with a message."""
+ invitation, created = DomainInvitation.objects.get_or_create(email=email_address, domain=self.object)
+ if not created:
+ # that invitation already existed
+ messages.warning(self.request, f"{email_address} has already been invited to this domain.")
+ else:
+ messages.success(self.request, f"Invited {email_address} to this domain.")
+ return redirect(self.get_success_url())
+
def form_valid(self, form):
"""Add the specified user on this domain."""
requested_email = form.cleaned_data["email"]
# look up a user with that email
- # they should exist because we checked in clean_email
- requested_user = User.objects.get(email=requested_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)
try:
UserDomainRole.objects.create(