From 8d38999ea128ccaaa0c2fe639012f3fda1abc433 Mon Sep 17 00:00:00 2001 From: Mariah J Date: Tue, 26 Sep 2023 19:30:42 +0000 Subject: [PATCH 1/3] Add custom paginator to interact with APCD API --- .../components/paginator/api_pagination.py | 101 ++++++++++++++++++ .../paginator/custom_api_paginator.py | 49 +++++++++ 2 files changed, 150 insertions(+) create mode 100644 apcd-cms/src/apps/components/paginator/api_pagination.py create mode 100644 apcd-cms/src/apps/components/paginator/custom_api_paginator.py diff --git a/apcd-cms/src/apps/components/paginator/api_pagination.py b/apcd-cms/src/apps/components/paginator/api_pagination.py new file mode 100644 index 00000000..fd1d0798 --- /dev/null +++ b/apcd-cms/src/apps/components/paginator/api_pagination.py @@ -0,0 +1,101 @@ +from django.utils.functional import cached_property +from django.core.paginator import Paginator, EmptyPage +import logging +import collections.abc + +logger = logging.getLogger(__name__) + + +class APIPagination(Paginator): + def __init__(self, table_rows, entries_per_page, total_count): + """ + + :param table_rows: paginator object_list + :param entries_per_page: entries per page (paginator - per_page) + :param total_count: Total count of record found in db -recieved from APCD API + """ + self.total_count = total_count + super(APIPagination, self).__init__(table_rows, entries_per_page) + + @cached_property + def count(self): + return self.total_count + + def page(self, number): + """Return a Page object for the given 1-based page number.""" + number = self.validate_number(number) + bottom = (number - 1) * self.per_page + top = bottom + self.per_page + if top + self.orphans >= self.count: + top = self.count + # return self._get_page(self.object_list[bottom:top], number, self) + return self._get_page(self.object_list, number, self) + + def _get_page(self, *args, **kwargs): + """ + Return an instance of a single page. + + This hook can be used by subclasses to use an alternative to the + standard :cls:`Page` object. + """ + return Page(*args, **kwargs) + + +class Page(collections.abc.Sequence): + def __init__(self, object_list, number, paginator): + self.object_list = object_list + self.number = number + self.paginator = paginator + + def __repr__(self): + return "" % (self.number, self.paginator.num_pages) + + def __len__(self): + return len(self.object_list) + + def __getitem__(self, index): + if not isinstance(index, (int, slice)): + raise TypeError( + "Page indices must be integers or slices, not %s." + % type(index).__name__ + ) + # The object_list is converted to a list so that if it was a QuerySet + # it won't be a database hit per __getitem__. + if not isinstance(self.object_list, list): + self.object_list = list(self.object_list) + return self.object_list[index] + + def has_next(self): + return self.number < self.paginator.num_pages + + def has_previous(self): + return self.number > 1 + + def has_other_pages(self): + return self.has_previous() or self.has_next() + + def next_page_number(self): + return self.paginator.validate_number(self.number + 1) + + def previous_page_number(self): + return self.paginator.validate_number(self.number - 1) + + def start_index(self): + """ + Return the 1-based index of the first object on this page, + relative to total objects in the paginator. + """ + # Special case, return zero if no items. + if self.paginator.count == 0: + return 0 + return (self.paginator.per_page * (self.number - 1)) + 1 + + def end_index(self): + """ + Return the 1-based index of the last object on this page, + relative to total objects found (hits). + """ + # Special case for the last page because there can be orphans. + if self.number == self.paginator.num_pages: + return self.paginator.count + return self.number * self.paginator.per_page diff --git a/apcd-cms/src/apps/components/paginator/custom_api_paginator.py b/apcd-cms/src/apps/components/paginator/custom_api_paginator.py new file mode 100644 index 00000000..adef3954 --- /dev/null +++ b/apcd-cms/src/apps/components/paginator/custom_api_paginator.py @@ -0,0 +1,49 @@ +from django.core.paginator import Paginator, EmptyPage +from apps.components.paginator.api_paginaton import APIPagination +import logging + +logger = logging.getLogger(__name__) + + +def paginator(request, api_content, entries_per_page=50): + try: + page_num = int(request.GET.get('page')) + except: + page_num = 1 + + """ + First entry: Content for page + 2nd Entry: Count of items per page + 3rd entry: Total records matching criteria + """ + p = APIPagination(api_content['items'], entries_per_page, api_content['total_count']) + + + try: + page = p.page(page_num) + except EmptyPage: + page = p.page(1) + + # print(f"Page info is: {page.object_list}") + + elided_pages = [] + on_ends = 1 + current_page_buffer = 2 + if page.paginator.num_pages <= (current_page_buffer + on_ends) * 2: + return {'page': page, 'elided_pages': page.paginator.page_range} + + if page_num > (1 + current_page_buffer + on_ends) + 1: + elided_pages.extend(range(1, on_ends + 1)) + elided_pages.append('...') + elided_pages.extend(range(page_num - current_page_buffer, page_num + 1)) + else: + elided_pages.extend(range(1, page_num + 1)) + + if page_num < (page.paginator.num_pages - current_page_buffer - on_ends) - 1: + elided_pages.extend(range(page_num + 1, page_num + current_page_buffer + 1)) + elided_pages.append('...') + elided_pages.extend(range(page.paginator.num_pages - on_ends + 1, page.paginator.num_pages + 1)) + else: + elided_pages.extend(range(page_num + 1, page.paginator.num_pages + 1)) + + return {'page': page, 'elided_pages': elided_pages} From 703cf2c555bd79572854feb243abf8757e54f3e4 Mon Sep 17 00:00:00 2001 From: Mariah J Date: Mon, 9 Oct 2023 20:20:41 +0000 Subject: [PATCH 2/3] Added api pagination to View Users Tab --- .../paginator/custom_api_paginator.py | 2 +- apcd-cms/src/apps/utils/apcd_database.py | 51 ++++++++++ .../templates/view_user_edit_modal.html | 7 +- .../view_users/templates/view_user_modal.html | 4 +- .../apps/view_users/templates/view_users.html | 2 +- apcd-cms/src/apps/view_users/views.py | 97 +++++++++---------- 6 files changed, 103 insertions(+), 60 deletions(-) diff --git a/apcd-cms/src/apps/components/paginator/custom_api_paginator.py b/apcd-cms/src/apps/components/paginator/custom_api_paginator.py index adef3954..c76fba43 100644 --- a/apcd-cms/src/apps/components/paginator/custom_api_paginator.py +++ b/apcd-cms/src/apps/components/paginator/custom_api_paginator.py @@ -1,5 +1,5 @@ from django.core.paginator import Paginator, EmptyPage -from apps.components.paginator.api_paginaton import APIPagination +from apps.components.paginator.api_pagination import APIPagination import logging logger = logging.getLogger(__name__) diff --git a/apcd-cms/src/apps/utils/apcd_database.py b/apcd-cms/src/apps/utils/apcd_database.py index d1a8f0eb..f0573d49 100644 --- a/apcd-cms/src/apps/utils/apcd_database.py +++ b/apcd-cms/src/apps/utils/apcd_database.py @@ -3,10 +3,61 @@ from datetime import datetime import re import logging +import requests +import json logger = logging.getLogger(__name__) APCD_DB = settings.APCD_DATABASE +API_URL = settings.APCD_API_URL + + +def get_api_users(page: int = 1, per_page: int = 50, status: str = None, org: str = None): + org = None if org is None or status == "All" else org + + payload = { + 'page': page, + 'per_page': per_page, + 'status': status, + 'org': org, + } + + apcd_api_res = requests.get(f'{API_URL}/users/paged_users', params=payload) + print(f"API Request: {apcd_api_res.url}") + if apcd_api_res.status_code != 200: + response = apcd_api_res.text + logger.error(f"API issue: {response}") + return {'pgerror': apcd_api_res.text} + + results = apcd_api_res.json() + return results + + +def update_api_users(user_id: int, payload: dict): + + headers = {'content-type': 'application/json'} + apcd_api_res = requests.put(url=f'{API_URL}/users/{user_id}', headers=headers, data=json.dumps(payload)) + print(f"API Request: {apcd_api_res.url}") + if apcd_api_res.status_code != 200: + response = apcd_api_res.text + logger.error(f"API issue: {response}") + return {'pgerror': apcd_api_res.text} + + results = apcd_api_res.json() + return results + + +def get_api_users_org_list(): + + apcd_api_res = requests.get(url=f'{API_URL}/users/orgs') + print(f"API Request: {apcd_api_res.url}") + if apcd_api_res.status_code != 200: + response = apcd_api_res.text + logger.error(f"API issue: {response}") + return {'pgerror': apcd_api_res.text} + + results = apcd_api_res.json() + return results def get_users(): diff --git a/apcd-cms/src/apps/view_users/templates/view_user_edit_modal.html b/apcd-cms/src/apps/view_users/templates/view_user_edit_modal.html index f6f5090c..67acfcbe 100644 --- a/apcd-cms/src/apps/view_users/templates/view_user_edit_modal.html +++ b/apcd-cms/src/apps/view_users/templates/view_user_edit_modal.html @@ -19,6 +19,7 @@ {% csrf_token %}
+
@@ -48,7 +49,7 @@
@@ -99,8 +100,8 @@
{{r.org_name}}
Role
{{r.role_name}}
-
Status
-
{{r.status}}
+
Active
+
{{r.active}}
Created
{{r.created_at}}
Updated
diff --git a/apcd-cms/src/apps/view_users/templates/view_user_modal.html b/apcd-cms/src/apps/view_users/templates/view_user_modal.html index b4907bfa..5eb5cf87 100644 --- a/apcd-cms/src/apps/view_users/templates/view_user_modal.html +++ b/apcd-cms/src/apps/view_users/templates/view_user_modal.html @@ -29,8 +29,8 @@
{{r.org_name}}
Role
{{r.role_name}}
-
Status
-
{{r.status}}
+
Active
+
{{r.active}}
Created
{{r.created_at}}
Updated
diff --git a/apcd-cms/src/apps/view_users/templates/view_users.html b/apcd-cms/src/apps/view_users/templates/view_users.html index b9008ab2..cce46bf1 100644 --- a/apcd-cms/src/apps/view_users/templates/view_users.html +++ b/apcd-cms/src/apps/view_users/templates/view_users.html @@ -51,7 +51,7 @@

View Users

{{r.user_name}} {{r.org_name}} {{r.role_name}} - {{r.status}} + {{r.active}} {{r.user_number}} {% include "view_user_modal.html" %} diff --git a/apcd-cms/src/apps/view_users/views.py b/apcd-cms/src/apps/view_users/views.py index d1549127..386367d2 100644 --- a/apcd-cms/src/apps/view_users/views.py +++ b/apcd-cms/src/apps/view_users/views.py @@ -2,31 +2,43 @@ from django.core.paginator import Paginator, EmptyPage from django.views.generic.base import TemplateView from django.template import loader -from apps.utils.apcd_database import get_users, update_user +from apps.utils.apcd_database import get_users, update_user, get_api_users, update_api_users, get_api_users_org_list from apps.utils.apcd_groups import is_apcd_admin from apps.utils.utils import table_filter -from apps.components.paginator.paginator import paginator +from apps.components.paginator.custom_api_paginator import paginator import logging logger = logging.getLogger(__name__) + class ViewUsersTable(TemplateView): template_name = 'view_users.html' + ##FORM FUNCTION def post(self, request): form = request.POST.copy() - + def _err_msg(resp): + if 'pgerror' in resp: + return resp['pgerror'] if hasattr(resp, 'pgerror'): return resp.pgerror if isinstance(resp, Exception): return str(resp) return None - + def _edit_user(form): errors = [] - user_response = update_user(form) + data = { + "user_name": form['user_name'], + "user_email": form['user_email'], + "user_id": form['user_id'], + "role_id": form['role_id'], + "active": form['status'], + "notes": form['notes'], + } + user_response = update_api_users(form['user_number'], data) if _err_msg(user_response): errors.append(_err_msg(user_response)) if len(errors) != 0: @@ -36,51 +48,34 @@ def _edit_user(form): logger.debug(print("success")) template = loader.get_template('view_user_edit_success.html') return template - + template = _edit_user(form) return HttpResponse(template.render({}, request)) - def get(self, request, *args, **kwargs): - user_content = get_users() - - context = self.get_context_data(user_content, *args,**kwargs) - template = loader.get_template(self.template_name) - return HttpResponse(template.render(context, request)) - ##END FORM FUNCTION - - user_content = get_users() + # def get(self, request, *args, **kwargs): + # user_content = get_users() + # # print(f"Get API users: {get_api_users()}") + # + # context = self.get_context_data(user_content, *args, **kwargs) + # template = loader.get_template(self.template_name) + # return HttpResponse(template.render(context, request)) + # ##END FORM FUNCTION + # + # user_content = get_users() def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not is_apcd_admin(request.user): - return HttpResponseRedirect('/') + return HttpResponseRedirect('/') return super(ViewUsersTable, self).dispatch(request, *args, **kwargs) - def get_context_data(self, user_content=user_content, *args, **kwargs): + def get_context_data(self, *args, **kwargs): context = super(ViewUsersTable, self).get_context_data(*args, **kwargs) - def _set_user(usr): - return { - 'role_id': usr[0], - 'user_id': usr[1], - 'user_email': usr[2], - 'user_name': usr[3], - 'org_name': usr[4], - 'created_at': usr[5], - 'updated_at': usr[6], - 'notes': usr[7], - 'status': 'Active' if usr[8] else 'Inactive', - 'user_number': usr[9], - 'role_name': usr[10], - 'org_name_no_parens': usr[4].replace("(", "").replace(")", ""), # just for filtering purposes - 'active': usr[8], - } - - context['header'] = ['User ID', 'Name', 'Organization', 'Role', 'Status', 'User Number', 'See More'] + context['header'] = ['User ID', 'Name', 'Organization', 'Role', 'Active', 'User Number', 'See More'] context['status_options'] = ['All', 'Active', 'Inactive'] - context['filter_options'] = ['All'] - context['role_options'] = ['SUBMITTER_USER', 'SUBMITTER_ADMIN','APCD_ADMIN'] - - context['status'] = ['Active', 'Inactive'] + context['role_options'] = ['SUBMITTER_USER', 'SUBMITTER_ADMIN', 'APCD_ADMIN'] + context['status'] = ['True', 'False'] + context['filter_options'] = ['All'] + get_api_users_org_list() # this kind of sucks, we should make this not hard coded, just getting it to work for now context['roles'] = [ @@ -88,17 +83,11 @@ def _set_user(usr): {'role_id': 2, 'role_name': 'SUBMITTER_ADMIN'}, {'role_id': 3, 'role_name': 'SUBMITTER_USER'} ] - + try: page_num = int(self.request.GET.get('page')) except: - page_num = 1 - table_entries = [] - for user in user_content: - table_entries.append(_set_user(user,)) - org_name = user[4] - if org_name not in context['filter_options']: # prevent duplicates - context['filter_options'].append(user[4]) + page_num = 1 queryStr = '' status_filter = self.request.GET.get('status') @@ -106,23 +95,25 @@ def _set_user(usr): role_filter = self.request.GET.get('role_name') context['selected_status'] = None - if status_filter is not None and status_filter !='All': + if status_filter is not None and status_filter != 'All': context['selected_status'] = status_filter queryStr += f'&status={status_filter}' - table_entries = table_filter(status_filter, table_entries, 'status', False) + # table_entries = table_filter(status_filter, table_entries, 'status', False) - if role_filter is not None and role_filter !='All': + if role_filter is not None and role_filter != 'All': context['selected_role'] = role_filter - table_entries = table_filter(role_filter, table_entries, 'role_name', False) + # table_entries = table_filter(role_filter, table_entries, 'role_name', False) context['selected_org'] = None if org_filter is not None and org_filter != 'All': context['selected_org'] = org_filter queryStr += f'&org={org_filter}' - table_entries = table_filter(org_filter.replace("(", "").replace(")",""), table_entries, 'org_name_no_parens') + + limit = 50 + user_content = get_api_users(page_num, limit, status_filter, org_filter) context['query_str'] = queryStr - context.update(paginator(self.request, table_entries)) + context.update(paginator(self.request, user_content, limit)) context['pagination_url_namespaces'] = 'administration:view_users' return context From c29b9790e7cfaeafe2ccfea9a95b29263b59651d Mon Sep 17 00:00:00 2001 From: Mariah J Date: Thu, 12 Oct 2023 19:00:31 +0000 Subject: [PATCH 3/3] removed comments and unused imports --- apcd-cms/src/apps/view_users/views.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/apcd-cms/src/apps/view_users/views.py b/apcd-cms/src/apps/view_users/views.py index 386367d2..4c75ab4b 100644 --- a/apcd-cms/src/apps/view_users/views.py +++ b/apcd-cms/src/apps/view_users/views.py @@ -1,10 +1,8 @@ -from django.http import HttpResponseRedirect, HttpResponse, JsonResponse -from django.core.paginator import Paginator, EmptyPage +from django.http import HttpResponseRedirect, HttpResponse from django.views.generic.base import TemplateView from django.template import loader -from apps.utils.apcd_database import get_users, update_user, get_api_users, update_api_users, get_api_users_org_list +from apps.utils.apcd_database import get_api_users, update_api_users, get_api_users_org_list from apps.utils.apcd_groups import is_apcd_admin -from apps.utils.utils import table_filter from apps.components.paginator.custom_api_paginator import paginator import logging @@ -52,17 +50,6 @@ def _edit_user(form): template = _edit_user(form) return HttpResponse(template.render({}, request)) - # def get(self, request, *args, **kwargs): - # user_content = get_users() - # # print(f"Get API users: {get_api_users()}") - # - # context = self.get_context_data(user_content, *args, **kwargs) - # template = loader.get_template(self.template_name) - # return HttpResponse(template.render(context, request)) - # ##END FORM FUNCTION - # - # user_content = get_users() - def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated or not is_apcd_admin(request.user): return HttpResponseRedirect('/') @@ -98,11 +85,9 @@ def get_context_data(self, *args, **kwargs): if status_filter is not None and status_filter != 'All': context['selected_status'] = status_filter queryStr += f'&status={status_filter}' - # table_entries = table_filter(status_filter, table_entries, 'status', False) if role_filter is not None and role_filter != 'All': context['selected_role'] = role_filter - # table_entries = table_filter(role_filter, table_entries, 'role_name', False) context['selected_org'] = None if org_filter is not None and org_filter != 'All':