mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-01 18:18:34 +02:00
check is_active on rejected and ineligible, delete domain and domain info and remove references, intercept and show friendly error in admin
This commit is contained in:
parent
482d900f57
commit
324bea9868
4 changed files with 100 additions and 34 deletions
|
@ -104,6 +104,7 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
inlines = [UserContactInline]
|
inlines = [UserContactInline]
|
||||||
|
|
||||||
list_display = (
|
list_display = (
|
||||||
|
"username",
|
||||||
"email",
|
"email",
|
||||||
"first_name",
|
"first_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
|
@ -202,6 +203,13 @@ class ContactAdmin(ListHeaderAdmin):
|
||||||
search_help_text = "Search by firstname, lastname or email."
|
search_help_text = "Search by firstname, lastname or email."
|
||||||
|
|
||||||
|
|
||||||
|
class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
|
"""Custom contact admin class to add search."""
|
||||||
|
|
||||||
|
search_fields = ["domain__name"]
|
||||||
|
search_help_text = "Search by domain name."
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Customize the applications listing view."""
|
"""Customize the applications listing view."""
|
||||||
|
@ -234,7 +242,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
# Detail view
|
# Detail view
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {"fields": ["status", "investigator", "creator"]}),
|
(None, {"fields": ["status", "investigator", "creator", "approved_domain"]}),
|
||||||
(
|
(
|
||||||
"Type of organization",
|
"Type of organization",
|
||||||
{
|
{
|
||||||
|
@ -306,29 +314,57 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
# Get the original application from the database
|
# Get the original application from the database
|
||||||
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
||||||
|
|
||||||
if obj.status != original_obj.status:
|
if (
|
||||||
status_method_mapping = {
|
obj
|
||||||
models.DomainApplication.STARTED: None,
|
and original_obj.status == models.DomainApplication.APPROVED
|
||||||
models.DomainApplication.SUBMITTED: obj.submit,
|
and (
|
||||||
models.DomainApplication.IN_REVIEW: obj.in_review,
|
obj.status == models.DomainApplication.REJECTED
|
||||||
models.DomainApplication.ACTION_NEEDED: obj.action_needed,
|
or obj.status == models.DomainApplication.INELIGIBLE
|
||||||
models.DomainApplication.APPROVED: obj.approve,
|
)
|
||||||
models.DomainApplication.WITHDRAWN: obj.withdraw,
|
and not obj.domain_is_not_active()
|
||||||
models.DomainApplication.REJECTED: obj.reject,
|
):
|
||||||
models.DomainApplication.INELIGIBLE: obj.reject_with_prejudice,
|
# If an admin tried to set an approved application to
|
||||||
}
|
# rejected or ineligible and the related domain is already
|
||||||
selected_method = status_method_mapping.get(obj.status)
|
# active, shortcut the action and throw a friendly
|
||||||
if selected_method is None:
|
# error message. This action would still not go through
|
||||||
logger.warning("Unknown status selected in django admin")
|
# shortcut or not as the rules are duplicated on the model,
|
||||||
else:
|
# but the error would be an ugly Django error screen.
|
||||||
# This is an fsm in model which will throw an error if the
|
|
||||||
# transition condition is violated, so we roll back the
|
|
||||||
# status to what it was before the admin user changed it and
|
|
||||||
# let the fsm method set it.
|
|
||||||
obj.status = original_obj.status
|
|
||||||
selected_method()
|
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
# Clear the success message
|
||||||
|
messages.set_level(request, messages.ERROR)
|
||||||
|
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
"This action is not permitted, the domain "
|
||||||
|
+ "is already active.",
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if obj.status != original_obj.status:
|
||||||
|
status_method_mapping = {
|
||||||
|
models.DomainApplication.STARTED: None,
|
||||||
|
models.DomainApplication.SUBMITTED: obj.submit,
|
||||||
|
models.DomainApplication.IN_REVIEW: obj.in_review,
|
||||||
|
models.DomainApplication.ACTION_NEEDED: obj.action_needed,
|
||||||
|
models.DomainApplication.APPROVED: obj.approve,
|
||||||
|
models.DomainApplication.WITHDRAWN: obj.withdraw,
|
||||||
|
models.DomainApplication.REJECTED: obj.reject,
|
||||||
|
models.DomainApplication.INELIGIBLE: (
|
||||||
|
obj.reject_with_prejudice
|
||||||
|
),
|
||||||
|
}
|
||||||
|
selected_method = status_method_mapping.get(obj.status)
|
||||||
|
if selected_method is None:
|
||||||
|
logger.warning("Unknown status selected in django admin")
|
||||||
|
else:
|
||||||
|
# This is an fsm in model which will throw an error if the
|
||||||
|
# transition condition is violated, so we roll back the
|
||||||
|
# status to what it was before the admin user changed it and
|
||||||
|
# let the fsm method set it.
|
||||||
|
obj.status = original_obj.status
|
||||||
|
selected_method()
|
||||||
|
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
else:
|
else:
|
||||||
# Clear the success message
|
# Clear the success message
|
||||||
messages.set_level(request, messages.ERROR)
|
messages.set_level(request, messages.ERROR)
|
||||||
|
@ -382,7 +418,7 @@ admin.site.register(models.User, MyUserAdmin)
|
||||||
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
||||||
admin.site.register(models.Contact, ContactAdmin)
|
admin.site.register(models.Contact, ContactAdmin)
|
||||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
||||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
admin.site.register(models.DomainInformation, DomainInformationAdmin)
|
||||||
admin.site.register(models.Domain, DomainAdmin)
|
admin.site.register(models.Domain, DomainAdmin)
|
||||||
admin.site.register(models.Host, MyHostAdmin)
|
admin.site.register(models.Host, MyHostAdmin)
|
||||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||||
|
|
|
@ -301,8 +301,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# TODO: implement a check -- should be performant so it can be called for
|
# TODO: implement a check -- should be performant so it can be called for
|
||||||
# any number of domains on a status page
|
# any number of domains on a status page
|
||||||
# this is NOT as simple as checking if Domain.Status.OK is in self.statuses
|
# this is NOT as simple as checking if Domain.Status.OK is in self.statuses
|
||||||
|
|
||||||
|
# NOTE: This was stubbed in all along for 852 and 811
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def delete_request(self):
|
||||||
|
"""Delete from host. Possibly a duplicate of _delete_host?"""
|
||||||
|
pass
|
||||||
|
|
||||||
def transfer(self):
|
def transfer(self):
|
||||||
"""Going somewhere. Not implemented."""
|
"""Going somewhere. Not implemented."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -338,9 +344,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
help_text="Very basic info about the lifecycle of this domain object",
|
help_text="Very basic info about the lifecycle of this domain object",
|
||||||
)
|
)
|
||||||
|
|
||||||
def isActive(self):
|
|
||||||
return self.state == Domain.State.CREATED
|
|
||||||
|
|
||||||
# ForeignKey on UserDomainRole creates a "permissions" member for
|
# ForeignKey on UserDomainRole creates a "permissions" member for
|
||||||
# all of the user-roles that are in place for this domain
|
# all of the user-roles that are in place for this domain
|
||||||
|
|
||||||
|
|
|
@ -411,7 +411,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="The approved domain",
|
help_text="The approved domain",
|
||||||
related_name="domain_application",
|
related_name="domain_application",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
requested_domain = models.OneToOneField(
|
requested_domain = models.OneToOneField(
|
||||||
|
@ -477,6 +477,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
except Exception:
|
except Exception:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def domain_is_not_active(self):
|
||||||
|
if self.approved_domain:
|
||||||
|
return not self.approved_domain.is_active()
|
||||||
|
return True
|
||||||
|
|
||||||
def _send_status_update_email(
|
def _send_status_update_email(
|
||||||
self, new_status, email_template, email_template_subject
|
self, new_status, email_template, email_template_subject
|
||||||
):
|
):
|
||||||
|
@ -600,11 +605,22 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/domain_request_withdrawn_subject.txt",
|
"emails/domain_request_withdrawn_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(field="status", source=[IN_REVIEW, APPROVED], target=REJECTED)
|
@transition(
|
||||||
|
field="status",
|
||||||
|
source=[IN_REVIEW, APPROVED],
|
||||||
|
target=REJECTED,
|
||||||
|
conditions=[domain_is_not_active],
|
||||||
|
)
|
||||||
def reject(self):
|
def reject(self):
|
||||||
"""Reject an application that has been submitted.
|
"""Reject an application that has been submitted.
|
||||||
|
|
||||||
As a side effect, an email notification is sent, similar to in_review"""
|
As a side effect this will delete the domain and domain_information
|
||||||
|
(will cascade), and send an email notification"""
|
||||||
|
|
||||||
|
if self.status == self.APPROVED:
|
||||||
|
self.approved_domain.delete_request()
|
||||||
|
self.approved_domain.delete()
|
||||||
|
self.approved_domain = None
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"action needed",
|
"action needed",
|
||||||
|
@ -612,14 +628,25 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/status_change_rejected_subject.txt",
|
"emails/status_change_rejected_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@transition(field="status", source=[IN_REVIEW, APPROVED], target=INELIGIBLE)
|
@transition(
|
||||||
|
field="status",
|
||||||
|
source=[IN_REVIEW, APPROVED],
|
||||||
|
target=INELIGIBLE,
|
||||||
|
conditions=[domain_is_not_active],
|
||||||
|
)
|
||||||
def reject_with_prejudice(self):
|
def reject_with_prejudice(self):
|
||||||
"""The applicant is a bad actor, reject with prejudice.
|
"""The applicant is a bad actor, reject with prejudice.
|
||||||
|
|
||||||
No email As a side effect, but we block the applicant from editing
|
No email As a side effect, but we block the applicant from editing
|
||||||
any existing domains/applications and from submitting new aplications.
|
any existing domains/applications and from submitting new aplications.
|
||||||
We do this by setting an ineligible status on the user, which the
|
We do this by setting an ineligible status on the user, which the
|
||||||
permissions classes test against"""
|
permissions classes test against. This will also delete the domain
|
||||||
|
and domain_information (will cascade)"""
|
||||||
|
|
||||||
|
if self.status == self.APPROVED:
|
||||||
|
self.approved_domain.delete_request()
|
||||||
|
self.approved_domain.delete()
|
||||||
|
self.approved_domain = None
|
||||||
|
|
||||||
self.creator.restrict_user()
|
self.creator.restrict_user()
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
|
||||||
class DomainInformation(TimeStampedModel):
|
class DomainInformation(TimeStampedModel):
|
||||||
|
|
||||||
"""A registrant's domain information for that domain, exported from
|
"""A registrant's domain information for that domain, exported from
|
||||||
DomainApplication. We use these field from DomainApplication with few exceptation
|
DomainApplication. We use these field from DomainApplication with few exceptions
|
||||||
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
||||||
management's user information are based on application, but we cannot change
|
management's user information are based on application, but we cannot change
|
||||||
the application once approved, so copying them that way we can make changes
|
the application once approved, so copying them that way we can make changes
|
||||||
|
@ -156,7 +156,7 @@ class DomainInformation(TimeStampedModel):
|
||||||
|
|
||||||
domain = models.OneToOneField(
|
domain = models.OneToOneField(
|
||||||
"registrar.Domain",
|
"registrar.Domain",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.CASCADE,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
# Access this information via Domain as "domain.domain_info"
|
# Access this information via Domain as "domain.domain_info"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue