mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 04:58:42 +02:00
Merge branch 'main' into nl/3075-action-td-adjustments
This commit is contained in:
commit
feb588187f
11 changed files with 571 additions and 40 deletions
|
@ -1329,6 +1329,14 @@ class UserPortfolioPermissionAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
get_roles.short_description = "Roles" # type: ignore
|
get_roles.short_description = "Roles" # type: ignore
|
||||||
|
|
||||||
|
def delete_queryset(self, request, queryset):
|
||||||
|
"""We override the delete method in the model.
|
||||||
|
When deleting in DJA, if you select multiple items in a table using checkboxes and apply a delete action
|
||||||
|
the model delete does not get called. This method gets called instead.
|
||||||
|
This override makes sure our code in the model gets executed in these situations."""
|
||||||
|
for obj in queryset:
|
||||||
|
obj.delete() # Calls the overridden delete method on each instance
|
||||||
|
|
||||||
|
|
||||||
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"""Custom user domain role admin class."""
|
"""Custom user domain role admin class."""
|
||||||
|
@ -1661,6 +1669,14 @@ class PortfolioInvitationAdmin(BaseInvitationAdmin):
|
||||||
# Call the parent save method to save the object
|
# Call the parent save method to save the object
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def delete_queryset(self, request, queryset):
|
||||||
|
"""We override the delete method in the model.
|
||||||
|
When deleting in DJA, if you select multiple items in a table using checkboxes and apply a delete action,
|
||||||
|
the model delete does not get called. This method gets called instead.
|
||||||
|
This override makes sure our code in the model gets executed in these situations."""
|
||||||
|
for obj in queryset:
|
||||||
|
obj.delete() # Calls the overridden delete method on each instance
|
||||||
|
|
||||||
|
|
||||||
class DomainInformationResource(resources.ModelResource):
|
class DomainInformationResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
|
|
@ -20,7 +20,6 @@ from registrar.views.report_views import (
|
||||||
AnalyticsView,
|
AnalyticsView,
|
||||||
ExportDomainRequestDataFull,
|
ExportDomainRequestDataFull,
|
||||||
ExportDataTypeUser,
|
ExportDataTypeUser,
|
||||||
ExportDataTypeRequests,
|
|
||||||
ExportMembersPortfolio,
|
ExportMembersPortfolio,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -260,11 +259,6 @@ urlpatterns = [
|
||||||
ExportDataTypeUser.as_view(),
|
ExportDataTypeUser.as_view(),
|
||||||
name="export_data_type_user",
|
name="export_data_type_user",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"reports/export_data_type_requests/",
|
|
||||||
ExportDataTypeRequests.as_view(),
|
|
||||||
name="export_data_type_requests",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"domain-request/<int:id>/edit/",
|
"domain-request/<int:id>/edit/",
|
||||||
views.DomainRequestWizard.as_view(),
|
views.DomainRequestWizard.as_view(),
|
||||||
|
|
|
@ -8,6 +8,7 @@ from registrar.models import DomainInvitation, UserPortfolioPermission
|
||||||
from .utility.portfolio_helper import (
|
from .utility.portfolio_helper import (
|
||||||
UserPortfolioPermissionChoices,
|
UserPortfolioPermissionChoices,
|
||||||
UserPortfolioRoleChoices,
|
UserPortfolioRoleChoices,
|
||||||
|
cleanup_after_portfolio_member_deletion,
|
||||||
validate_portfolio_invitation,
|
validate_portfolio_invitation,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
@ -115,3 +116,27 @@ class PortfolioInvitation(TimeStampedModel):
|
||||||
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
||||||
super().clean()
|
super().clean()
|
||||||
validate_portfolio_invitation(self)
|
validate_portfolio_invitation(self)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
email = self.email # Capture the email before the instance is deleted
|
||||||
|
portfolio = self.portfolio # Capture the portfolio before the instance is deleted
|
||||||
|
|
||||||
|
# Call the superclass delete method to actually delete the instance
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.status == self.PortfolioInvitationStatus.INVITED:
|
||||||
|
|
||||||
|
# Query the user by email
|
||||||
|
users = User.objects.filter(email=email)
|
||||||
|
|
||||||
|
if users.count() > 1:
|
||||||
|
# This should never happen, log an error if more than one object is returned
|
||||||
|
logger.error(f"Multiple users found with the same email: {email}")
|
||||||
|
|
||||||
|
# Retrieve the first user, or None if no users are found
|
||||||
|
user = users.first()
|
||||||
|
|
||||||
|
cleanup_after_portfolio_member_deletion(portfolio=portfolio, email=email, user=user)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from registrar.models.utility.portfolio_helper import (
|
||||||
UserPortfolioRoleChoices,
|
UserPortfolioRoleChoices,
|
||||||
DomainRequestPermissionDisplay,
|
DomainRequestPermissionDisplay,
|
||||||
MemberPermissionDisplay,
|
MemberPermissionDisplay,
|
||||||
|
cleanup_after_portfolio_member_deletion,
|
||||||
validate_user_portfolio_permission,
|
validate_user_portfolio_permission,
|
||||||
)
|
)
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
@ -188,3 +189,13 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
||||||
super().clean()
|
super().clean()
|
||||||
validate_user_portfolio_permission(self)
|
validate_user_portfolio_permission(self)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
|
||||||
|
user = self.user # Capture the user before the instance is deleted
|
||||||
|
portfolio = self.portfolio # Capture the portfolio before the instance is deleted
|
||||||
|
|
||||||
|
# Call the superclass delete method to actually delete the instance
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
cleanup_after_portfolio_member_deletion(portfolio=portfolio, email=user.email, user=user)
|
||||||
|
|
|
@ -210,3 +210,32 @@ def validate_portfolio_invitation(portfolio_invitation):
|
||||||
"This user is already assigned to a portfolio invitation. "
|
"This user is already assigned to a portfolio invitation. "
|
||||||
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
"Based on current waffle flag settings, users cannot be assigned to multiple portfolios."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_after_portfolio_member_deletion(portfolio, email, user=None):
|
||||||
|
"""
|
||||||
|
Cleans up after removing a portfolio member or a portfolio invitation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
portfolio: portfolio
|
||||||
|
user: passed when removing a portfolio member.
|
||||||
|
email: passed when removing a portfolio invitation, or passed as user.email
|
||||||
|
when removing a portfolio member.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DomainInvitation = apps.get_model("registrar.DomainInvitation")
|
||||||
|
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
||||||
|
|
||||||
|
# Fetch domain invitations matching the criteria
|
||||||
|
invitations = DomainInvitation.objects.filter(
|
||||||
|
email=email, domain__domain_info__portfolio=portfolio, status=DomainInvitation.DomainInvitationStatus.INVITED
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call `cancel_invitation` on each invitation
|
||||||
|
for invitation in invitations:
|
||||||
|
invitation.cancel_invitation()
|
||||||
|
invitation.save()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
# Remove user's domain roles for the current portfolio
|
||||||
|
UserDomainRole.objects.filter(user=user, domain__domain_info__portfolio=portfolio).delete()
|
||||||
|
|
|
@ -51,20 +51,7 @@
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{% if portfolio %}
|
|
||||||
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}" id="export-csv">
|
|
||||||
<section aria-label="Domain Requests report component" class="margin-top-205">
|
|
||||||
<!----------------------------------------------------------------------
|
|
||||||
This link is commented out because we intend to add it back in later.
|
|
||||||
------------------------------------------------------------------------->
|
|
||||||
<!-- <a href="{% url 'export_data_type_requests' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right">
|
|
||||||
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#file_download"></use>
|
|
||||||
</svg>Export as CSV
|
|
||||||
</a> -->
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if portfolio %}
|
{% if portfolio %}
|
||||||
|
|
|
@ -92,11 +92,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if has_organization_members_flag %}
|
{% if has_organization_members_flag %}
|
||||||
|
{% if has_view_members_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
<a href="{% url 'members' %}" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
<a href="{% url 'members' %}" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
|
|
|
@ -164,6 +164,7 @@ class TestPortfolioInvitations(TestCase):
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
UserPortfolioPermission.objects.all().delete()
|
UserPortfolioPermission.objects.all().delete()
|
||||||
|
UserDomainRole.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
PortfolioInvitation.objects.all().delete()
|
PortfolioInvitation.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
@ -442,6 +443,294 @@ class TestPortfolioInvitations(TestCase):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_delete_portfolio_invitation_deletes_portfolio_domain_invitations(self):
|
||||||
|
"""Deleting a portfolio invitation causes domain invitations for the same email on the same
|
||||||
|
portfolio to be canceled."""
|
||||||
|
|
||||||
|
email_with_no_user = "email-with-no-user@email.gov"
|
||||||
|
|
||||||
|
domain_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_1.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_1, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_1, _ = DomainInvitation.objects.get_or_create(email=email_with_no_user, domain=domain_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_2, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_2, _ = DomainInvitation.objects.get_or_create(email=email_with_no_user, domain=domain_in_portfolio_2)
|
||||||
|
|
||||||
|
domain_not_in_portfolio, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio)
|
||||||
|
invite_3, _ = DomainInvitation.objects.get_or_create(email=email_with_no_user, domain=domain_not_in_portfolio)
|
||||||
|
|
||||||
|
invitation_of_email_with_no_user, _ = PortfolioInvitation.objects.get_or_create(
|
||||||
|
email=email_with_no_user,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
roles=[self.portfolio_role_base, self.portfolio_role_admin],
|
||||||
|
additional_permissions=[self.portfolio_permission_1, self.portfolio_permission_2],
|
||||||
|
)
|
||||||
|
|
||||||
|
# The domain invitations start off as INVITED
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# Delete member (invite)
|
||||||
|
invitation_of_email_with_no_user.delete()
|
||||||
|
|
||||||
|
# Reload the objects from the database
|
||||||
|
invite_1 = DomainInvitation.objects.get(pk=invite_1.pk)
|
||||||
|
invite_2 = DomainInvitation.objects.get(pk=invite_2.pk)
|
||||||
|
invite_3 = DomainInvitation.objects.get(pk=invite_3.pk)
|
||||||
|
|
||||||
|
# The domain invitations to the portfolio domains have been canceled
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
|
||||||
|
# Invite 3 is unaffected
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_deleting_a_retrieved_invitation_has_no_side_effects(self):
|
||||||
|
"""Deleting a retrieved portfolio invitation causes no side effects."""
|
||||||
|
|
||||||
|
domain_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_1.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_1, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_1, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_2, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_2, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_in_portfolio_2)
|
||||||
|
|
||||||
|
domain_in_portfolio_3, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_3.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_3, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_3, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_in_portfolio_4, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_4.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_4, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_4, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_1)
|
||||||
|
invite_3, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_not_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_2)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_not_in_portfolio_2, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# The domain invitations start off as INVITED
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# The user domain roles exist
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# retrieve the invitation
|
||||||
|
self.invitation.retrieve()
|
||||||
|
self.invitation.save()
|
||||||
|
|
||||||
|
# Delete member (invite)
|
||||||
|
self.invitation.delete()
|
||||||
|
|
||||||
|
# Reload the objects from the database
|
||||||
|
invite_1 = DomainInvitation.objects.get(pk=invite_1.pk)
|
||||||
|
invite_2 = DomainInvitation.objects.get(pk=invite_2.pk)
|
||||||
|
invite_3 = DomainInvitation.objects.get(pk=invite_3.pk)
|
||||||
|
|
||||||
|
# Test that no side effects have been triggered
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_delete_portfolio_invitation_deletes_user_domain_roles(self):
|
||||||
|
"""Deleting a portfolio invitation causes domain invitations for the same email on the same
|
||||||
|
portfolio to be canceled, also deletes any exiting user domain roles on the portfolio for the
|
||||||
|
user if the user exists."""
|
||||||
|
|
||||||
|
domain_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_1.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_1, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_1, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_2, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_2, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_in_portfolio_2)
|
||||||
|
|
||||||
|
domain_in_portfolio_3, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_3.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_3, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_3, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_in_portfolio_4, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_4.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_4, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_4, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_1)
|
||||||
|
invite_3, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=domain_not_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_2)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_not_in_portfolio_2, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# The domain invitations start off as INVITED
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# The user domain roles exist
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete member (invite)
|
||||||
|
self.invitation.delete()
|
||||||
|
|
||||||
|
# Reload the objects from the database
|
||||||
|
invite_1 = DomainInvitation.objects.get(pk=invite_1.pk)
|
||||||
|
invite_2 = DomainInvitation.objects.get(pk=invite_2.pk)
|
||||||
|
invite_3 = DomainInvitation.objects.get(pk=invite_3.pk)
|
||||||
|
|
||||||
|
# The domain invitations to the portfolio domains have been canceled
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
|
||||||
|
# Invite 3 is unaffected
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# The user domain roles have been deleted for the domains in portfolio
|
||||||
|
self.assertFalse(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# The user domain role on the domain not in portfolio still exists
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUserPortfolioPermission(TestCase):
|
class TestUserPortfolioPermission(TestCase):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@ -457,6 +746,7 @@ class TestUserPortfolioPermission(TestCase):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainRequest.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
|
DomainInvitation.objects.all().delete()
|
||||||
UserPortfolioPermission.objects.all().delete()
|
UserPortfolioPermission.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
@ -750,6 +1040,129 @@ class TestUserPortfolioPermission(TestCase):
|
||||||
# Should return the forbidden permissions for member role
|
# Should return the forbidden permissions for member role
|
||||||
self.assertEqual(member_only_permissions, set(member_forbidden))
|
self.assertEqual(member_only_permissions, set(member_forbidden))
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_delete_portfolio_permission_deletes_user_domain_roles(self):
|
||||||
|
"""Deleting a user portfolio permission causes domain invitations for the same email on the same
|
||||||
|
portfolio to be canceled, also deletes any exiting user domain roles on the portfolio for the
|
||||||
|
user if the user exists."""
|
||||||
|
|
||||||
|
domain_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_1.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_1, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_1, _ = DomainInvitation.objects.get_or_create(email=self.user.email, domain=domain_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_2, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
invite_2, _ = DomainInvitation.objects.get_or_create(email=self.user.email, domain=domain_in_portfolio_2)
|
||||||
|
|
||||||
|
domain_in_portfolio_3, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_3.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_3, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_3, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_in_portfolio_4, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_in_portfolio_and_invited_4.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user, domain=domain_in_portfolio_4, portfolio=self.portfolio
|
||||||
|
)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_in_portfolio_4, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_1, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_1)
|
||||||
|
invite_3, _ = DomainInvitation.objects.get_or_create(email=self.user.email, domain=domain_not_in_portfolio_1)
|
||||||
|
|
||||||
|
domain_not_in_portfolio_2, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_not_in_portfolio_2.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domain_not_in_portfolio_2)
|
||||||
|
UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.user, domain=domain_not_in_portfolio_2, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create portfolio permission
|
||||||
|
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
portfolio=self.portfolio, user=self.user, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
|
||||||
|
# The domain invitations start off as INVITED
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# The user domain roles exist
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete member (user portfolio permission)
|
||||||
|
portfolio_permission.delete()
|
||||||
|
|
||||||
|
# Reload the objects from the database
|
||||||
|
invite_1 = DomainInvitation.objects.get(pk=invite_1.pk)
|
||||||
|
invite_2 = DomainInvitation.objects.get(pk=invite_2.pk)
|
||||||
|
invite_3 = DomainInvitation.objects.get(pk=invite_3.pk)
|
||||||
|
|
||||||
|
# The domain invitations to the portfolio domains have been canceled
|
||||||
|
self.assertEqual(invite_1.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
self.assertEqual(invite_2.status, DomainInvitation.DomainInvitationStatus.CANCELED)
|
||||||
|
|
||||||
|
# Invite 3 is unaffected
|
||||||
|
self.assertEqual(invite_3.status, DomainInvitation.DomainInvitationStatus.INVITED)
|
||||||
|
|
||||||
|
# The user domain roles have been deleted for the domains in portfolio
|
||||||
|
self.assertFalse(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_3,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_in_portfolio_4,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
# The user domain role on the domain not in portfolio still exists
|
||||||
|
self.assertTrue(
|
||||||
|
UserDomainRole.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
domain=domain_not_in_portfolio_2,
|
||||||
|
).exists()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestUser(TestCase):
|
class TestUser(TestCase):
|
||||||
"""Test actions that occur on user login,
|
"""Test actions that occur on user login,
|
||||||
|
|
|
@ -1097,8 +1097,10 @@ class TestPortfolio(WebTest):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_no_permissions(self):
|
def test_main_nav_when_user_has_no_permissions(self):
|
||||||
"""Test the nav contains a link to the no requests page"""
|
"""Test the nav contains a link to the no requests page
|
||||||
|
Also test that members link not present"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
||||||
)
|
)
|
||||||
|
@ -1118,20 +1120,23 @@ class TestPortfolio(WebTest):
|
||||||
self.assertNotContains(portfolio_landing_page, "basic-nav-section-two")
|
self.assertNotContains(portfolio_landing_page, "basic-nav-section-two")
|
||||||
# link to requests
|
# link to requests
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/requests/')
|
self.assertNotContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create request
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertNotContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_all_permissions(self):
|
def test_main_nav_when_user_has_all_permissions(self):
|
||||||
"""Test the nav contains a dropdown with a link to create and another link to view requests
|
"""Test the nav contains a dropdown with a link to create and another link to view requests
|
||||||
Also test for the existence of the Create a new request btn on the requests page"""
|
Also test for the existence of the Create a new request btn on the requests page
|
||||||
|
Also test for the existence of the members link"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
portfolio=self.portfolio,
|
portfolio=self.portfolio,
|
||||||
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||||
additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS],
|
|
||||||
)
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
# create and submit a domain request
|
# create and submit a domain request
|
||||||
|
@ -1151,6 +1156,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create
|
||||||
self.assertContains(portfolio_landing_page, 'href="/request/')
|
self.assertContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
requests_page = self.client.get(reverse("domain-requests"))
|
requests_page = self.client.get(reverse("domain-requests"))
|
||||||
|
|
||||||
|
@ -1160,15 +1167,18 @@ class TestPortfolio(WebTest):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_view_but_not_edit_permissions(self):
|
def test_main_nav_when_user_has_view_but_not_edit_permissions(self):
|
||||||
"""Test the nav contains a simple link to view requests
|
"""Test the nav contains a simple link to view requests
|
||||||
Also test for the existence of the Create a new request btn on the requests page"""
|
Also test for the existence of the Create a new request btn on the requests page
|
||||||
|
Also test for the existence of members link"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
portfolio=self.portfolio,
|
portfolio=self.portfolio,
|
||||||
additional_permissions=[
|
additional_permissions=[
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
@ -1189,6 +1199,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
requests_page = self.client.get(reverse("domain-requests"))
|
requests_page = self.client.get(reverse("domain-requests"))
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,20 @@ from django.views import View
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Avg, F
|
from django.db.models import Avg, F
|
||||||
|
|
||||||
|
from registrar.views.utility.mixins import DomainAndRequestsReportsPermission, PortfolioReportsPermission
|
||||||
from .. import models
|
from .. import models
|
||||||
import datetime
|
import datetime
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from registrar.utility import csv_export
|
from registrar.utility import csv_export
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class AnalyticsView(View):
|
class AnalyticsView(View):
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
|
thirty_days_ago = datetime.datetime.today() - datetime.timedelta(days=30)
|
||||||
|
@ -149,6 +152,7 @@ class AnalyticsView(View):
|
||||||
return render(request, "admin/analytics.html", context)
|
return render(request, "admin/analytics.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataType(View):
|
class ExportDataType(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# match the CSV example with all the fields
|
# match the CSV example with all the fields
|
||||||
|
@ -158,7 +162,7 @@ class ExportDataType(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ExportDataTypeUser(View):
|
class ExportDataTypeUser(DomainAndRequestsReportsPermission, View):
|
||||||
"""Returns a domain report for a given user on the request"""
|
"""Returns a domain report for a given user on the request"""
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -169,7 +173,7 @@ class ExportDataTypeUser(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ExportMembersPortfolio(View):
|
class ExportMembersPortfolio(PortfolioReportsPermission, View):
|
||||||
"""Returns a members report for a given portfolio"""
|
"""Returns a members report for a given portfolio"""
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
@ -197,17 +201,7 @@ class ExportMembersPortfolio(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ExportDataTypeRequests(View):
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
"""Returns a domain requests report for a given user on the request"""
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
response = HttpResponse(content_type="text/csv")
|
|
||||||
response["Content-Disposition"] = 'attachment; filename="domain-requests.csv"'
|
|
||||||
csv_export.DomainRequestDataType.export_data_to_csv(response, request=request)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class ExportDataFull(View):
|
class ExportDataFull(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# Smaller export based on 1
|
# Smaller export based on 1
|
||||||
|
@ -217,6 +211,7 @@ class ExportDataFull(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataFederal(View):
|
class ExportDataFederal(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# Federal only
|
# Federal only
|
||||||
|
@ -226,6 +221,7 @@ class ExportDataFederal(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDomainRequestDataFull(View):
|
class ExportDomainRequestDataFull(View):
|
||||||
"""Generates a downloaded report containing all Domain Requests (except started)"""
|
"""Generates a downloaded report containing all Domain Requests (except started)"""
|
||||||
|
|
||||||
|
@ -237,6 +233,7 @@ class ExportDomainRequestDataFull(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataDomainsGrowth(View):
|
class ExportDataDomainsGrowth(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -249,6 +246,7 @@ class ExportDataDomainsGrowth(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataRequestsGrowth(View):
|
class ExportDataRequestsGrowth(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -261,6 +259,7 @@ class ExportDataRequestsGrowth(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataManagedDomains(View):
|
class ExportDataManagedDomains(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
@ -272,6 +271,7 @@ class ExportDataManagedDomains(View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataUnmanagedDomains(View):
|
class ExportDataUnmanagedDomains(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
start_date = request.GET.get("start_date", "")
|
start_date = request.GET.get("start_date", "")
|
||||||
|
|
|
@ -153,6 +153,48 @@ class PermissionsLoginMixin(PermissionRequiredMixin):
|
||||||
return super().handle_no_permission()
|
return super().handle_no_permission()
|
||||||
|
|
||||||
|
|
||||||
|
class DomainAndRequestsReportsPermission(PermissionsLoginMixin):
|
||||||
|
"""Permission mixin for domain and requests csv downloads"""
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Check if this user has access to this domain.
|
||||||
|
|
||||||
|
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"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.request.user.is_restricted():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class PortfolioReportsPermission(PermissionsLoginMixin):
|
||||||
|
"""Permission mixin for portfolio csv downloads"""
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Check if this user has access to this domain.
|
||||||
|
|
||||||
|
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"]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.request.user.is_restricted():
|
||||||
|
return False
|
||||||
|
|
||||||
|
portfolio = self.request.session.get("portfolio")
|
||||||
|
if not self.request.user.has_view_members_portfolio_permission(portfolio):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.request.user.is_org_user(self.request)
|
||||||
|
|
||||||
|
|
||||||
class DomainPermission(PermissionsLoginMixin):
|
class DomainPermission(PermissionsLoginMixin):
|
||||||
"""Permission mixin that redirects to domain if user has access,
|
"""Permission mixin that redirects to domain if user has access,
|
||||||
otherwise 403"""
|
otherwise 403"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue