mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 02:36:02 +02:00
Initial Scaffolding (in progress)
This commit is contained in:
parent
3495101e9b
commit
6954c1c2c1
5 changed files with 601 additions and 0 deletions
|
@ -1853,6 +1853,144 @@ class DomainRequestsTable extends LoadTableBase {
|
|||
}
|
||||
}
|
||||
|
||||
class MembersTable extends LoadTableBase {
|
||||
|
||||
constructor() {
|
||||
super('.members__table', '.members__table-wrapper', '#members__search-field', '#members__search-field-submit', '.members__reset-search', '.members__reset-filters', '.members__no-data', '.members__no-search-results');
|
||||
}
|
||||
/**
|
||||
* Loads rows in the members list, as well as updates pagination around the members list
|
||||
* based on the supplied attributes.
|
||||
* @param {*} page - the page number of the results (starts with 1)
|
||||
* @param {*} sortBy - the sort column option
|
||||
* @param {*} order - the sort order {asc, desc}
|
||||
* @param {*} scroll - control for the scrollToElement functionality
|
||||
* @param {*} status - control for the status filter
|
||||
* @param {*} searchTerm - the search term
|
||||
* @param {*} portfolio - the portfolio id
|
||||
*/
|
||||
loadTable(page, sortBy = this.currentSortBy, order = this.currentOrder, scroll = this.scrollToTable, status = this.currentStatus, searchTerm =this.currentSearchTerm, portfolio = this.portfolioValue) {
|
||||
|
||||
// fetch json of page of domais, given params
|
||||
let baseUrl = document.getElementById("get_members_json_url");
|
||||
if (!baseUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
let baseUrlValue = baseUrl.innerHTML;
|
||||
if (!baseUrlValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch json of page of members, given params
|
||||
let searchParams = new URLSearchParams(
|
||||
{
|
||||
"page": page,
|
||||
"sort_by": sortBy,
|
||||
"order": order,
|
||||
"status": status,
|
||||
"search_term": searchTerm
|
||||
}
|
||||
);
|
||||
if (portfolio)
|
||||
searchParams.append("portfolio", portfolio)
|
||||
|
||||
let url = `${baseUrlValue}?${searchParams.toString()}`
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
console.error('Error in AJAX call: ' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle the display of proper messaging in the event that no members exist in the list or search returns no results
|
||||
this.updateDisplay(data, this.tableWrapper, this.noTableWrapper, this.noSearchResultsWrapper, this.currentSearchTerm);
|
||||
|
||||
// identify the DOM element where the domain list will be inserted into the DOM
|
||||
const domainList = document.querySelector('.members__table tbody');
|
||||
domainList.innerHTML = '';
|
||||
|
||||
data.members.forEach(domain => {
|
||||
const options = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||
const expirationDate = domain.expiration_date ? new Date(domain.expiration_date) : null;
|
||||
const expirationDateFormatted = expirationDate ? expirationDate.toLocaleDateString('en-US', options) : '';
|
||||
const expirationDateSortValue = expirationDate ? expirationDate.getTime() : '';
|
||||
const actionUrl = domain.action_url;
|
||||
const suborganization = domain.domain_info__sub_organization ? domain.domain_info__sub_organization : '⎯';
|
||||
|
||||
const row = document.createElement('tr');
|
||||
|
||||
let markupForSuborganizationRow = '';
|
||||
|
||||
if (this.portfolioValue) {
|
||||
markupForSuborganizationRow = `
|
||||
<td>
|
||||
<span class="text-wrap" aria-label="${domain.suborganization ? suborganization : 'No suborganization'}">${suborganization}</span>
|
||||
</td>
|
||||
`
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<th scope="row" role="rowheader" data-label="Domain name">
|
||||
${domain.name}
|
||||
</th>
|
||||
<td data-sort-value="${expirationDateSortValue}" data-label="Expires">
|
||||
${expirationDateFormatted}
|
||||
</td>
|
||||
<td data-label="Status">
|
||||
${domain.state_display}
|
||||
<svg
|
||||
class="usa-icon usa-tooltip usa-tooltip--registrar text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
|
||||
data-position="top"
|
||||
title="${domain.get_state_help_text}"
|
||||
focusable="true"
|
||||
aria-label="${domain.get_state_help_text}"
|
||||
role="tooltip"
|
||||
>
|
||||
<use aria-hidden="true" xlink:href="/public/img/sprite.svg#info_outline"></use>
|
||||
</svg>
|
||||
</td>
|
||||
${markupForSuborganizationRow}
|
||||
<td>
|
||||
<a href="${actionUrl}">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#${domain.svg_icon}"></use>
|
||||
</svg>
|
||||
${domain.action_label} <span class="usa-sr-only">${domain.name}</span>
|
||||
</a>
|
||||
</td>
|
||||
`;
|
||||
domainList.appendChild(row);
|
||||
});
|
||||
// initialize tool tips immediately after the associated DOM elements are added
|
||||
initializeTooltips();
|
||||
|
||||
// Do not scroll on first page load
|
||||
if (scroll)
|
||||
ScrollToElement('class', 'members');
|
||||
this.scrollToTable = true;
|
||||
|
||||
// update pagination
|
||||
this.updatePagination(
|
||||
'domain',
|
||||
'#members-pagination',
|
||||
'#members-pagination .usa-pagination__counter',
|
||||
'#members',
|
||||
data.page,
|
||||
data.num_pages,
|
||||
data.has_previous,
|
||||
data.has_next,
|
||||
data.total,
|
||||
);
|
||||
this.currentSortBy = sortBy;
|
||||
this.currentOrder = order;
|
||||
this.currentSearchTerm = searchTerm;
|
||||
})
|
||||
.catch(error => console.error('Error fetching members:', error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An IIFE that listens for DOM Content to be loaded, then executes. This function
|
||||
|
@ -1926,6 +2064,23 @@ const utcDateString = (dateString) => {
|
|||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An IIFE that listens for DOM Content to be loaded, then executes. This function
|
||||
* initializes the domains list and associated functionality on the home page of the app.
|
||||
*
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const isMembersPage = document.querySelector("#members")
|
||||
if (isMembersPage){
|
||||
const membersTable = new MembersTable();
|
||||
if (membersTable.tableWrapper) {
|
||||
// Initial load
|
||||
membersTable.loadTable(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* An IIFE that displays confirmation modal on the user profile page
|
||||
*/
|
||||
|
|
216
src/registrar/templates/includes/members_table.html
Normal file
216
src/registrar/templates/includes/members_table.html
Normal file
|
@ -0,0 +1,216 @@
|
|||
{% load static %}
|
||||
|
||||
{% comment %} Stores the json endpoint in a url for easier access {% endcomment %}
|
||||
{% url 'get_portfolio_members_json' as url %}
|
||||
<span id="get_members_json_url" class="display-none">{{url}}</span>
|
||||
<section class="section-outlined members margin-top-0{% if portfolio %} section-outlined--border-base-light{% endif %}" id="members">
|
||||
<div class="section-outlined__header margin-bottom-3 {% if not portfolio %} section-outlined__header--no-portfolio justify-content-space-between{% else %} grid-row{% endif %}">
|
||||
{% if not portfolio %}
|
||||
<h2 id="members-header" class="display-inline-block">Members</h2>
|
||||
{% else %}
|
||||
<!-- Embedding the portfolio value in a data attribute -->
|
||||
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
|
||||
{% endif %}
|
||||
<!-- ---------- SEARCH ---------- -->
|
||||
<div class="section-outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %}">
|
||||
<section aria-label="Members search component" class="margin-top-2">
|
||||
<form class="usa-search usa-search--small" method="POST" role="search">
|
||||
{% csrf_token %}
|
||||
<button class="usa-button usa-button--unstyled margin-right-3 members__reset-search display-none" type="button">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||
</svg>
|
||||
Reset
|
||||
</button>
|
||||
<label class="usa-sr-only" for="members__search-field">Search by domain name</label>
|
||||
<input
|
||||
class="usa-input"
|
||||
id="members__search-field"
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder="Search by domain name"
|
||||
/>
|
||||
<button class="usa-button" type="submit" id="members__search-field-submit">
|
||||
<img
|
||||
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
||||
class="usa-search__submit-icon"
|
||||
alt="Search"
|
||||
/>
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<!-- ---------- Export as CSV ---------- -->
|
||||
{% if portfolio_members_count and portfolio_members_count > 0 %}
|
||||
<!--
|
||||
=====================
|
||||
TODO: future ticket?
|
||||
=====================
|
||||
-->
|
||||
<!-- <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 %}">
|
||||
<section aria-label="Members report component" class="margin-top-205">
|
||||
<a href="{% url 'export_data_type_user' %}" class="usa-button usa-button--unstyled usa-button--with-icon" role="button">
|
||||
<svg class="usa-icon usa-icon--big" 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>
|
||||
{% if portfolio %}
|
||||
|
||||
<!-- ---------- FILTERING ---------- -->
|
||||
<!--
|
||||
=====================
|
||||
TODO: future ticket?
|
||||
=====================
|
||||
-->
|
||||
<!-- <div class="display-flex flex-align-center">
|
||||
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span>
|
||||
<div class="usa-accordion usa-accordion--select margin-right-2">
|
||||
<div class="usa-accordion__heading">
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--small padding--8-8-9 usa-button--outline usa-button--filter usa-accordion__button"
|
||||
aria-expanded="false"
|
||||
aria-controls="filter-status"
|
||||
>
|
||||
<span class="filter-indicator text-bold display-none"></span> Status
|
||||
<svg class="usa-icon top-2px" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#expand_more"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="filter-status" class="usa-accordion__content usa-prose shadow-1">
|
||||
<h2>Status</h2>
|
||||
<fieldset class="usa-fieldset margin-top-0">
|
||||
<legend class="usa-legend">Select to apply <span class="sr-only">status</span> filter</legend>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-dns-needed"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="unknown"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-dns-needed"
|
||||
>DNS Needed</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-ready"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="ready"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-ready"
|
||||
>Ready</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-on-hold"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="on hold"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-on-hold"
|
||||
>On hold</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-expired"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="expired"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-expired"
|
||||
>Expired</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-deleted"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="deleted"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-deleted"
|
||||
>Deleted</label
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--small padding--8-12-9-12 usa-button--outline usa-button--filter members__reset-filters display-none"
|
||||
>
|
||||
Clear filters
|
||||
<svg class="usa-icon top-1px" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div> -->
|
||||
{% endif %}
|
||||
|
||||
<!-- ---------- MAIN TABLE ---------- -->
|
||||
<div class="members__table-wrapper display-none usa-table-container--scrollable margin-top-0" tabindex="0">
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked members__table">
|
||||
<caption class="sr-only">Your registered members</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sortable="member" scope="col" role="columnheader">Member</th>
|
||||
<th data-sortable="last_active" scope="col" role="columnheader">Last Active</th>
|
||||
<th
|
||||
scope="col"
|
||||
role="columnheader"
|
||||
>
|
||||
<span class="usa-sr-only">Action</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- AJAX will populate this tbody -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
class="usa-sr-only usa-table__announcement-region"
|
||||
aria-live="polite"
|
||||
></div>
|
||||
</div>
|
||||
<div class="members__no-data display-none">
|
||||
<p>You don't have any members.</p>
|
||||
<!--
|
||||
=====================
|
||||
TODO: discard me?
|
||||
=====================
|
||||
-->
|
||||
<!-- <p class="maxw-none clearfix">
|
||||
<a href="https://get.gov/help/faq/#do-not-see-my-domain" class="float-right-tablet usa-link usa-link--icon" target="_blank">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#help_outline"></use>
|
||||
</svg>
|
||||
Why don't I see my domain when I sign in to the registrar?
|
||||
</a>
|
||||
</p> -->
|
||||
</div>
|
||||
<div class="members__no-search-results display-none">
|
||||
<p>No results found</p>
|
||||
</div>
|
||||
</section>
|
||||
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="members-pagination">
|
||||
<span class="usa-pagination__counter text-base-dark padding-left-2 margin-bottom-1">
|
||||
<!-- Count will be dynamically populated by JS -->
|
||||
</span>
|
||||
<ul class="usa-pagination__list">
|
||||
<!-- Pagination links will be dynamically populated by JS -->
|
||||
</ul>
|
||||
</nav>
|
20
src/registrar/templates/portfolio_members.html
Normal file
20
src/registrar/templates/portfolio_members.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends 'portfolio_base.html' %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block title %} Members | {% endblock %}
|
||||
|
||||
{% block wrapper_class %}
|
||||
{{ block.super }} dashboard--grey-1
|
||||
{% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div id="main-content">
|
||||
<h1 id="members-header">Members</h1>
|
||||
{% include "includes/members_table.html" with portfolio=portfolio portfolio_members_count=portfolio_members_count %}
|
||||
</div>
|
||||
{% endblock %}
|
171
src/registrar/views/portfolio_members_json.py
Normal file
171
src/registrar/views/portfolio_members_json.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
from django.http import JsonResponse
|
||||
from django.core.paginator import Paginator
|
||||
from registrar.models import DomainRequest
|
||||
from django.utils.dateformat import format
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||
|
||||
|
||||
@login_required
|
||||
def get_portfolio_members_json(request):
|
||||
"""Given the current request,
|
||||
get all members that are associated with the given portfolio"""
|
||||
|
||||
member_ids = get_member_ids_from_request(request)
|
||||
unfiltered_total = member_ids.count()
|
||||
|
||||
# objects = apply_search(objects, request)
|
||||
# objects = apply_status_filter(objects, request)
|
||||
# objects = apply_sorting(objects, request)
|
||||
|
||||
paginator = Paginator(member_ids, 10)
|
||||
page_number = request.GET.get("page", 1)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
members = [
|
||||
serialize_members(request, member, request.user) for member in page_obj.object_list
|
||||
]
|
||||
|
||||
# return JsonResponse(
|
||||
# {
|
||||
# "domain_requests": domain_requests,
|
||||
# "has_next": page_obj.has_next(),
|
||||
# "has_previous": page_obj.has_previous(),
|
||||
# "page": page_obj.number,
|
||||
# "num_pages": paginator.num_pages,
|
||||
# "total": paginator.count,
|
||||
# "unfiltered_total": unfiltered_total,
|
||||
# }
|
||||
# )
|
||||
|
||||
|
||||
def get_member_ids_from_request(request):
|
||||
"""Given the current request,
|
||||
get all members that are associated with the given portfolio"""
|
||||
portfolio = request.GET.get("portfolio")
|
||||
# filter_condition = Q(creator=request.user)
|
||||
if portfolio:
|
||||
# TODO: Permissions??
|
||||
# if request.user.is_org_user(request) and request.user.has_view_all_requests_portfolio_permission(portfolio):
|
||||
# filter_condition = Q(portfolio=portfolio)
|
||||
# else:
|
||||
# filter_condition = Q(portfolio=portfolio, creator=request.user)
|
||||
|
||||
member_ids = UserPortfolioPermission.objects.filter(
|
||||
portfolio=portfolio
|
||||
).values_list("user__id", flat=True)
|
||||
return member_ids
|
||||
|
||||
|
||||
# def apply_search(queryset, request):
|
||||
# search_term = request.GET.get("search_term")
|
||||
# is_portfolio = request.GET.get("portfolio")
|
||||
|
||||
# if search_term:
|
||||
# search_term_lower = search_term.lower()
|
||||
# new_domain_request_text = "new domain request"
|
||||
|
||||
# # Check if the search term is a substring of 'New domain request'
|
||||
# # If yes, we should return domain requests that do not have a
|
||||
# # requested_domain (those display as New domain request in the UI)
|
||||
# if search_term_lower in new_domain_request_text:
|
||||
# queryset = queryset.filter(
|
||||
# Q(requested_domain__name__icontains=search_term) | Q(requested_domain__isnull=True)
|
||||
# )
|
||||
# elif is_portfolio:
|
||||
# queryset = queryset.filter(
|
||||
# Q(requested_domain__name__icontains=search_term)
|
||||
# | Q(creator__first_name__icontains=search_term)
|
||||
# | Q(creator__last_name__icontains=search_term)
|
||||
# | Q(creator__email__icontains=search_term)
|
||||
# )
|
||||
# # For non org users
|
||||
# else:
|
||||
# queryset = queryset.filter(Q(requested_domain__name__icontains=search_term))
|
||||
# return queryset
|
||||
|
||||
|
||||
# def apply_status_filter(queryset, request):
|
||||
# status_param = request.GET.get("status")
|
||||
# if status_param:
|
||||
# status_list = status_param.split(",")
|
||||
# statuses = [status for status in status_list if status in DomainRequest.DomainRequestStatus.values]
|
||||
# # Construct Q objects for statuses that can be queried through ORM
|
||||
# status_query = Q()
|
||||
# if statuses:
|
||||
# status_query |= Q(status__in=statuses)
|
||||
# # Apply the combined query
|
||||
# queryset = queryset.filter(status_query)
|
||||
|
||||
# return queryset
|
||||
|
||||
|
||||
# def apply_sorting(queryset, request):
|
||||
# sort_by = request.GET.get("sort_by", "id") # Default to 'id'
|
||||
# order = request.GET.get("order", "asc") # Default to 'asc'
|
||||
|
||||
# if order == "desc":
|
||||
# sort_by = f"-{sort_by}"
|
||||
# return queryset.order_by(sort_by)
|
||||
|
||||
|
||||
def serialize_members(request, member, user):
|
||||
|
||||
# ------- DELETABLE
|
||||
# deletable_statuses = [
|
||||
# DomainRequest.DomainRequestStatus.STARTED,
|
||||
# DomainRequest.DomainRequestStatus.WITHDRAWN,
|
||||
# ]
|
||||
|
||||
# # Determine if the request is deletable
|
||||
# if not user.is_org_user(request):
|
||||
# is_deletable = member.status in deletable_statuses
|
||||
# else:
|
||||
# portfolio = request.session.get("portfolio")
|
||||
# is_deletable = (
|
||||
# member.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio)
|
||||
# ) and member.creator == user
|
||||
|
||||
|
||||
# ------- EDIT / VIEW
|
||||
# # Determine action label based on user permissions and request status
|
||||
# editable_statuses = [
|
||||
# DomainRequest.DomainRequestStatus.STARTED,
|
||||
# DomainRequest.DomainRequestStatus.ACTION_NEEDED,
|
||||
# DomainRequest.DomainRequestStatus.WITHDRAWN,
|
||||
# ]
|
||||
|
||||
# if user.has_edit_request_portfolio_permission and member.creator == user:
|
||||
# action_label = "Edit" if member.status in editable_statuses else "Manage"
|
||||
# else:
|
||||
# action_label = "View"
|
||||
|
||||
# # Map the action label to corresponding URLs and icons
|
||||
# action_url_map = {
|
||||
# "Edit": reverse("edit-domain-request", kwargs={"id": member.id}),
|
||||
# "Manage": reverse("domain-request-status", kwargs={"pk": member.id}),
|
||||
# "View": "#",
|
||||
# }
|
||||
|
||||
# svg_icon_map = {"Edit": "edit", "Manage": "settings", "View": "visibility"}
|
||||
|
||||
|
||||
# ------- INVITED
|
||||
# TODO:....
|
||||
|
||||
|
||||
# ------- SERIALIZE
|
||||
# return {
|
||||
# "requested_domain": member.requested_domain.name if member.requested_domain else None,
|
||||
# "last_submitted_date": member.last_submitted_date,
|
||||
# "status": member.get_status_display(),
|
||||
# "created_at": format(member.created_at, "c"), # Serialize to ISO 8601
|
||||
# "creator": member.creator.email,
|
||||
# "id": member.id,
|
||||
# "is_deletable": is_deletable,
|
||||
# "action_url": action_url_map.get(action_label),
|
||||
# "action_label": action_label,
|
||||
# "svg_icon": svg_icon_map.get(action_label),
|
||||
# }
|
|
@ -41,6 +41,45 @@ 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."""
|
||||
# We can override the base class. This view only needs this item.
|
||||
context = {}
|
||||
portfolio = self.request.session.get("portfolio")
|
||||
if portfolio:
|
||||
|
||||
# # ------ Gets admin members
|
||||
# admin_ids = UserPortfolioPermission.objects.filter(
|
||||
# portfolio=portfolio,
|
||||
# roles__overlap=[
|
||||
# UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
|
||||
# ],
|
||||
# ).values_list("user__id", flat=True)
|
||||
|
||||
|
||||
# # ------ Gets non-admin members
|
||||
# # Filter UserPortfolioPermission objects related to the portfolio that do NOT have the "Admin" role
|
||||
# non_admin_permissions = UserPortfolioPermission.objects.filter(portfolio=obj).exclude(
|
||||
# roles__contains=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||
# )
|
||||
# # Get the user objects associated with these permissions
|
||||
# non_admin_users = User.objects.filter(portfolio_permissions__in=non_admin_permissions)
|
||||
|
||||
|
||||
# ------- Gets all members
|
||||
member_ids = UserPortfolioPermission.objects.filter(
|
||||
portfolio=portfolio
|
||||
).values_list("user__id", flat=True)
|
||||
|
||||
all_members = User.objects.filter(id__in=member_ids)
|
||||
context["portfolio_members"] = all_members
|
||||
context["portfolio_members_count"] = all_members.count()
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue