diff --git a/coldfront/config/base.py b/coldfront/config/base.py index 3c70aa1d5..0b53e0435 100644 --- a/coldfront/config/base.py +++ b/coldfront/config/base.py @@ -51,7 +51,6 @@ 'django_tables2', 'table', 'rest_framework_datatables', - 'rest_framework', 'easy_pdf', ] @@ -148,19 +147,6 @@ }, ] -REST_FRAMEWORK = { - 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.JSONRenderer', - 'rest_framework.renderers.BrowsableAPIRenderer', - 'rest_framework_datatables.renderers.DatatablesRenderer', - ), - 'DEFAULT_FILTER_BACKENDS': ( - 'rest_framework_datatables.filters.DatatablesFilterBackend', - ), - 'DEFAULT_PAGINATION_CLASS': 'rest_framework_datatables.pagination.DatatablesPageNumberPagination', - 'PAGE_SIZE': 500, -} - # Add local site templates files if set SITE_TEMPLATES = ENV.str('SITE_TEMPLATES', default='') if len(SITE_TEMPLATES) > 0: diff --git a/coldfront/config/plugins/api.py b/coldfront/config/plugins/api.py index cee06d0c5..061510749 100644 --- a/coldfront/config/plugins/api.py +++ b/coldfront/config/plugins/api.py @@ -1,15 +1,15 @@ from coldfront.config.base import INSTALLED_APPS INSTALLED_APPS += [ - 'django_filters', - 'coldfront.plugins.api' - ] + 'django_filters', + 'rest_framework', + 'coldfront.plugins.api', +] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', - 'rest_framework.authentication.BasicAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated' diff --git a/coldfront/plugins/api/serializers.py b/coldfront/plugins/api/serializers.py index d58e89236..ba705f097 100644 --- a/coldfront/plugins/api/serializers.py +++ b/coldfront/plugins/api/serializers.py @@ -94,15 +94,27 @@ class Meta: 'usage', 'pct_full', 'cost', + 'created', ) + def get_type(self, obj): + resource = obj.get_parent_resource + if resource: + return resource.resource_type.name + return None + class AllocationRequestSerializer(serializers.ModelSerializer): project = serializers.SlugRelatedField(slug_field='title', read_only=True) - resource = serializers.ReadOnlyField(source='get_resources_as_string', read_only=True) + pi = serializers.ReadOnlyField(source='project.pi.full_name') + resource = serializers.ReadOnlyField(source='get_parent_resource.name', allow_null=True) + tier = serializers.ReadOnlyField(source='get_parent_resource.parent_resource.name', allow_null=True) status = serializers.SlugRelatedField(slug_field='name', read_only=True) - fulfilled_date = serializers.DateTimeField(read_only=True) + requested_size = serializers.ReadOnlyField(source='quantity') + current_size = serializers.ReadOnlyField(source='size') + created = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True) created_by = serializers.SerializerMethodField(read_only=True) + fulfilled_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True) fulfilled_by = serializers.SerializerMethodField(read_only=True) time_to_fulfillment = serializers.DurationField(read_only=True) @@ -111,10 +123,13 @@ class Meta: fields = ( 'id', 'project', + 'pi', 'resource', + 'tier', 'path', 'status', - 'size', + 'requested_size', + 'current_size', 'created', 'created_by', 'fulfilled_date', @@ -139,10 +154,14 @@ def get_fulfilled_by(self, obj): class AllocationChangeRequestSerializer(serializers.ModelSerializer): - allocation = AllocationSerializer(read_only=True) + project = serializers.ReadOnlyField(source='allocation.project.title') + pi = serializers.ReadOnlyField(source='allocation.project.pi.full_name') + resource = serializers.ReadOnlyField(source='allocation.get_resources_as_string') + tier = serializers.ReadOnlyField(source='allocation.get_parent_resource.parent_resource.name', allow_null=True) status = serializers.SlugRelatedField(slug_field='name', read_only=True) + created = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True) created_by = serializers.SerializerMethodField(read_only=True) - fulfilled_date = serializers.DateTimeField(read_only=True) + fulfilled_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M", read_only=True) fulfilled_by = serializers.SerializerMethodField(read_only=True) time_to_fulfillment = serializers.DurationField(read_only=True) @@ -151,6 +170,10 @@ class Meta: fields = ( 'id', 'allocation', + 'project', + 'pi', + 'resource', + 'tier', 'justification', 'status', 'created', @@ -205,7 +228,15 @@ class ProjectSerializer(serializers.ModelSerializer): class Meta: model = Project - fields = ('id', 'title', 'pi', 'status', 'project_users', 'allocations') + fields = ( + 'id', + 'title', + 'pi', + 'status', + 'project_users', + 'allocations', + 'created', + ) def get_project_users(self, obj): request = self.context.get('request', None) diff --git a/coldfront/plugins/api/views.py b/coldfront/plugins/api/views.py index 5622a246a..a17662292 100644 --- a/coldfront/plugins/api/views.py +++ b/coldfront/plugins/api/views.py @@ -1,12 +1,19 @@ +import csv from datetime import timedelta from django.contrib.auth import get_user_model -from django.db.models import OuterRef, Subquery, Q, F, ExpressionWrapper, fields + +from django.db.models import OuterRef, Subquery, Q, F, ExpressionWrapper, Case, When, Value, fields, DurationField from django.db.models.functions import Cast +from django.http import HttpResponse +from django.utils.http import urlencode from django_filters import rest_framework as filters from ifxuser.models import Organization from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated, IsAdminUser +from rest_framework.renderers import AdminRenderer, JSONRenderer +from rest_framework import filters as drf_filters + from simple_history.utils import get_history_model_for_model from coldfront.core.utils.common import import_from_settings @@ -19,13 +26,67 @@ 'PENDING_ALLOCATION_STATUSES', ['New', 'In Progress', 'On Hold', 'Pending Activation'] ) +class CustomAdminRenderer(AdminRenderer): + def render(self, data, accepted_media_type=None, renderer_context=None): + + # Get the count of objects + count = len(data['results']) if 'results' in data else len(data) + + # Create the count HTML + count_html = f'
Total Objects: {count}
' + + # Render the original content + original_content = super().render(data, accepted_media_type, renderer_context) + + # Ensure original_content is a string + if isinstance(original_content, bytes): + original_content = original_content.decode('utf-8') + + + # Get the request object + request = renderer_context.get('request') + + # Generate the CSV export URL + query_params = request.GET.copy() + params_present = request.build_absolute_uri(request.path) != request.build_absolute_uri() + connector = '&' if params_present else '?' + export_url = f"{request.build_absolute_uri()}{connector}export=csv" + + # Create the button HTML + button_html = f''' +
+ Export to CSV +
+ ''' + + # Insert the count HTML after the docstring and before the results table + parts = original_content.split('