mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-06 01:35:22 +02:00
Merge pull request #2517 from cisagov/za/2348-csv-export-org-member-domain-export
(on getgov-za) Ticket #2348: Handle portfolio permissions for csv export
This commit is contained in:
commit
20626539c9
9 changed files with 103 additions and 11 deletions
|
@ -4,6 +4,7 @@ from django.contrib.auth.models import AbstractUser
|
|||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
from registrar.models.domain_information import DomainInformation
|
||||
from registrar.models.user_domain_role import UserDomainRole
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||
|
||||
|
@ -265,6 +266,10 @@ class User(AbstractUser):
|
|||
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS
|
||||
) or self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_CREATED_REQUESTS)
|
||||
|
||||
def has_view_all_domains_permission(self):
|
||||
"""Determines if the current user can view all available domains in a given portfolio"""
|
||||
return self._has_portfolio_permission(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)
|
||||
|
||||
@classmethod
|
||||
def needs_identity_verification(cls, email, uuid):
|
||||
"""A method used by our oidc classes to test whether a user needs email/uuid verification
|
||||
|
@ -406,3 +411,10 @@ class User(AbstractUser):
|
|||
def is_org_user(self, request):
|
||||
has_organization_feature_flag = flag_is_active(request, "organization_feature")
|
||||
return has_organization_feature_flag and self.has_base_portfolio_permission()
|
||||
|
||||
def get_user_domain_ids(self, request):
|
||||
"""Returns either the domains ids associated with this user on UserDomainRole or Portfolio"""
|
||||
if self.is_org_user(request) and self.has_view_all_domains_permission():
|
||||
return DomainInformation.objects.filter(portfolio=self.portfolio).values_list("domain_id", flat=True)
|
||||
else:
|
||||
return UserDomainRole.objects.filter(user=self).values_list("domain_id", flat=True)
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</a>
|
||||
</p>
|
||||
|
||||
{% include "includes/domains_table.html" %}
|
||||
{% include "includes/domains_table.html" with user_domain_count=user_domain_count %}
|
||||
{% include "includes/domain_requests_table.html" %}
|
||||
|
||||
</div>
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{% if user_domain_count and user_domain_count > 0 %}
|
||||
<div class="section--outlined__utility-button mobile-lg:padding-right-105 {% if has_domains_portfolio_permission %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
|
||||
<section aria-label="Domains report component" class="mobile-lg:margin-top-205">
|
||||
<a href="{% url 'export_data_type_user' %}" class="usa-button usa-button--unstyled" role="button">
|
||||
|
@ -46,6 +47,7 @@
|
|||
</a>
|
||||
</section>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if has_domains_portfolio_permission %}
|
||||
<div class="display-flex flex-align-center">
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
|
||||
{% block portfolio_content %}
|
||||
<h1 id="domains-header">Domains</h1>
|
||||
{% include "includes/domains_table.html" with portfolio=portfolio %}
|
||||
{% include "includes/domains_table.html" with portfolio=portfolio user_domain_count=user_domain_count %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -6,6 +6,8 @@ from registrar.models import (
|
|||
Domain,
|
||||
UserDomainRole,
|
||||
)
|
||||
from registrar.models import Portfolio
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
||||
from registrar.utility.csv_export import (
|
||||
DomainDataFull,
|
||||
DomainDataType,
|
||||
|
@ -32,6 +34,7 @@ from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # typ
|
|||
from django.utils import timezone
|
||||
from api.tests.common import less_console_noise_decorator
|
||||
from .common import MockDbForSharedTests, MockDbForIndividualTests, MockEppLib, less_console_noise, get_time_aware_date
|
||||
from waffle.testutils import override_flag
|
||||
|
||||
|
||||
class CsvReportsTest(MockDbForSharedTests):
|
||||
|
@ -311,6 +314,80 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
self.maxDiff = None
|
||||
self.assertEqual(csv_content, expected_content)
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_domain_data_type_user_with_portfolio(self):
|
||||
"""Tests DomainDataTypeUser export with portfolio permissions"""
|
||||
|
||||
# Create a portfolio and assign it to the user
|
||||
portfolio = Portfolio.objects.create(creator=self.user, organization_name="Test Portfolio")
|
||||
self.user.portfolio = portfolio
|
||||
self.user.save()
|
||||
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain_2)
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_1).delete()
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_3).delete()
|
||||
|
||||
# Add portfolios to the first and third domains
|
||||
self.domain_1.domain_info.portfolio = portfolio
|
||||
self.domain_3.domain_info.portfolio = portfolio
|
||||
|
||||
self.domain_1.domain_info.save()
|
||||
self.domain_3.domain_info.save()
|
||||
|
||||
# Set up user permissions
|
||||
self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||
self.user.save()
|
||||
self.user.refresh_from_db()
|
||||
|
||||
# Create a request object
|
||||
factory = RequestFactory()
|
||||
request = factory.get("/")
|
||||
request.user = self.user
|
||||
|
||||
# Get the csv content
|
||||
csv_content = self._run_domain_data_type_user_export(request)
|
||||
|
||||
# We expect only domains associated with the user's portfolio
|
||||
self.assertIn(self.domain_1.name, csv_content)
|
||||
self.assertIn(self.domain_3.name, csv_content)
|
||||
self.assertNotIn(self.domain_2.name, csv_content)
|
||||
|
||||
# Test the output for readonly admin
|
||||
self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN_READ_ONLY]
|
||||
self.user.save()
|
||||
|
||||
self.assertIn(self.domain_1.name, csv_content)
|
||||
self.assertIn(self.domain_3.name, csv_content)
|
||||
self.assertNotIn(self.domain_2.name, csv_content)
|
||||
|
||||
# Get the csv content
|
||||
self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
||||
self.user.save()
|
||||
|
||||
csv_content = self._run_domain_data_type_user_export(request)
|
||||
|
||||
self.assertNotIn(self.domain_1.name, csv_content)
|
||||
self.assertNotIn(self.domain_3.name, csv_content)
|
||||
self.assertIn(self.domain_2.name, csv_content)
|
||||
self.domain_1.delete()
|
||||
self.domain_2.delete()
|
||||
self.domain_3.delete()
|
||||
portfolio.delete()
|
||||
|
||||
def _run_domain_data_type_user_export(self, request):
|
||||
"""Helper function to run the export_data_to_csv function on DomainDataTypeUser"""
|
||||
# Create a CSV file in memory
|
||||
csv_file = StringIO()
|
||||
# Call the export functions
|
||||
DomainDataTypeUser.export_data_to_csv(csv_file, request=request)
|
||||
# Reset the CSV file's position to the beginning
|
||||
csv_file.seek(0)
|
||||
# Read the content into a variable
|
||||
csv_content = csv_file.read()
|
||||
|
||||
return csv_content
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_domain_data_full(self):
|
||||
"""Shows security contacts, filtered by state"""
|
||||
|
|
|
@ -578,10 +578,9 @@ class DomainDataTypeUser(DomainDataType):
|
|||
if request is None or not hasattr(request, "user") or not request.user:
|
||||
# Return nothing
|
||||
return Q(id__in=[])
|
||||
|
||||
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
|
||||
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
|
||||
return Q(domain__id__in=domain_ids)
|
||||
else:
|
||||
# Get all domains the user is associated with
|
||||
return Q(domain__id__in=request.user.get_user_domain_ids(request))
|
||||
|
||||
|
||||
class DomainDataFull(DomainExport):
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import logging
|
||||
from django.http import JsonResponse
|
||||
from django.core.paginator import Paginator
|
||||
from registrar.models import UserDomainRole, Domain
|
||||
from registrar.models import UserDomainRole, Domain, DomainInformation
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from registrar.models.domain_information import DomainInformation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ def index(request):
|
|||
"""This page is available to anyone without logging in."""
|
||||
context = {}
|
||||
|
||||
if request.user.is_authenticated:
|
||||
if request and request.user and request.user.is_authenticated:
|
||||
# This controls the creation of a new domain request in the wizard
|
||||
request.session["new_request"] = True
|
||||
context["user_domain_count"] = request.user.get_user_domain_ids(request).count()
|
||||
|
||||
return render(request, "home.html", context)
|
||||
|
|
|
@ -22,7 +22,10 @@ class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
|||
template_name = "portfolio_domains.html"
|
||||
|
||||
def get(self, request):
|
||||
return render(request, "portfolio_domains.html")
|
||||
context = {}
|
||||
if self.request and self.request.user and self.request.user.is_authenticated:
|
||||
context["user_domain_count"] = self.request.user.get_user_domain_ids(request).count()
|
||||
return render(request, "portfolio_domains.html", context)
|
||||
|
||||
|
||||
class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue