mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 18:56:15 +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
|
* 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
|
* 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")
|
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):
|
class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
|
||||||
"""Some users have access to the underlying portfolio, but not any domains.
|
"""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.
|
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