diff --git a/CollegiateHouseWars/settings/prod.py b/CollegiateHouseWars/settings/prod.py index f500c56..acc81dd 100644 --- a/CollegiateHouseWars/settings/prod.py +++ b/CollegiateHouseWars/settings/prod.py @@ -11,14 +11,13 @@ DEBUG = False ALLOWED_HOSTS = [ '45.79.52.230', 'housewars.c4thebomb101.com', - 'collegiate-housewars.herokuapp.com', - 'collegiate-housewars.up.railway.app' + 'csmb-housewars.herokuapp.com', + '*' ] CSRF_TRUSTED_ORIGINS = [ 'https://housewars.c4thebomb101.com', - 'https://collegiate-housewars.herokuapp.com', - 'https://collegiate-housewars.up.railway.app' + 'https://csmb-housewars.herokuapp.com' ] # Database @@ -35,4 +34,4 @@ DATABASES = { } } -STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" +STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage" diff --git a/housewars/admin/Activity.py b/housewars/admin/Activity.py index 8acbdc7..4030a82 100644 --- a/housewars/admin/Activity.py +++ b/housewars/admin/Activity.py @@ -1,9 +1,14 @@ from django.http import HttpResponse from django.contrib import admin, messages +from django.db.models import F from django.utils.translation import ngettext -from housewars.utils import get_random_string +import io +import zipfile + +from housewars.utils import get_random_string, load_pdf from housewars.models import Activity, Award, Quota, UserEntry +from housewars.utils import load_pdf class QuotaInline(admin.TabularInline): @@ -29,7 +34,7 @@ class ActivityAdmin(admin.ModelAdmin): inlines = [QuotaInline, AwardsInline] - actions = ['generate_passwords'] + actions = ['generate_passwords', 'export_a1_to_pdf', 'export_a2_to_pdf'] @admin.display(description='Hawk') def hawk_signups(self, obj): @@ -57,12 +62,74 @@ class ActivityAdmin(admin.ModelAdmin): @admin.action(description='Generate passwords for selected activities') def generate_passwords(self, request, queryset): - for item in queryset: - item.password = get_random_string(8) - item.save() + for activity in queryset: + activity.password = get_random_string(8) + activity.save() self.message_user(request, ngettext( 'Generated password for %d activity.', 'Generated passwords for %d activities.', queryset.count(), ) % queryset.count(), messages.SUCCESS) + + @admin.action(description='Export activity 1 attendance to pdf') + def export_a1_to_pdf(self, request, queryset): + file_list = {} + + for activity in queryset: + user_entries = activity.activity1 + + # Augment the queryset with extra data + user_entries = user_entries.annotate( + a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number')) + + # Select headers that are to be unpacked + headers = ['first_name', 'last_name', 'grade', 'house', + 'activity1', 'a1_room', 'activity2', 'a2_room'] + + file = load_pdf(user_entries, headers) + + file_list[f"{activity.name} - 1"] = file + + outfile = io.BytesIO() + with zipfile.ZipFile(outfile, 'w') as zf: + for name, file in file_list.items(): + zf.writestr(f"{name}.pdf", file.getvalue()) + outfile = outfile.getvalue() + + response = HttpResponse( + outfile, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename=activity1_attendance.zip' + + return response + + @admin.action(description='Export activity 2 attendance to pdf') + def export_a2_to_pdf(self, request, queryset): + file_list = {} + + for activity in queryset: + user_entries = activity.activity2 + + # Augment the queryset with extra data + user_entries = user_entries.annotate( + a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number')) + + # Select headers that are to be unpacked + headers = ['first_name', 'last_name', 'grade', 'house', + 'activity1', 'a1_room', 'activity2', 'a2_room'] + + file = load_pdf(user_entries, headers) + + file_list[f"{activity.name} - 2"] = file + + outfile = io.BytesIO() + with zipfile.ZipFile(outfile, 'w') as zf: + for name, file in file_list.items(): + zf.writestr(f"{name}.pdf", file.getvalue()) + outfile = outfile.getvalue() + + response = HttpResponse( + outfile, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename=activity2_attendance.zip' + + return response diff --git a/housewars/admin/UserEntry.py b/housewars/admin/UserEntry.py index 236a733..efa99cd 100644 --- a/housewars/admin/UserEntry.py +++ b/housewars/admin/UserEntry.py @@ -3,16 +3,10 @@ from django.db.models import F, Value from django.db.models.functions import Concat from django.http import HttpResponse, FileResponse -from reportlab.pdfgen import canvas -from reportlab.lib.pagesizes import letter -from reportlab.lib.units import cm -from reportlab.platypus import Table, TableStyle, SimpleDocTemplate -from reportlab.lib.colors import black import csv -import io from housewars.models import UserEntry -from housewars.utils import unpack_values +from housewars.utils import unpack_values, load_pdf class MentorListFilter(admin.SimpleListFilter): @@ -97,30 +91,13 @@ class UserEntryAdmin(admin.ModelAdmin): def export_to_pdf(self, request, queryset): # Augment the querset with extra data queryset = queryset.annotate( - a1_room=F('activity1__room_number')).annotate(a2_room=F('activity1__room_number')) - - # Initialize pdf object - buffer = io.BytesIO() - doc = SimpleDocTemplate(buffer, pagesize=letter) - elements = [] + a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number')) # Select headers that are to be unpacked headers = ['first_name', 'last_name', 'grade', 'house', 'activity1', 'a1_room', 'activity2', 'a2_room'] # Unpack queryset data - data = unpack_values(queryset, headers) + file = load_pdf(queryset, headers) - # Load data into table and set styles - table = Table(data, repeatRows=1) - table.setStyle(TableStyle( - [('INNERGRID', (0, 0), (-1, -1), 0.25, black), ('BOX', (0, 0), (-1, -1), 0.25, black)])) - - # Load table into PDF file - elements.append(table) - - # Build document - doc.build(elements) - buffer.seek(0) - - return FileResponse(buffer, as_attachment=True, filename='download.pdf') + return FileResponse(file, as_attachment=True, filename='export.pdf') diff --git a/housewars/tests/utils/test_load_pdf.py b/housewars/tests/utils/test_load_pdf.py new file mode 100644 index 0000000..8be036d --- /dev/null +++ b/housewars/tests/utils/test_load_pdf.py @@ -0,0 +1,21 @@ +from django.test import TestCase + +import io + +from housewars.utils import load_pdf +from housewars.models import House + + +class UnpackValuesTest(TestCase): + def setUp(self): + House.objects.create(name="Hawk") + House.objects.create(name="Snowy") + + self.test_queryset = House.objects.all() + + def test_matching_object_instance(self): + """Correctly unpacks the headers into the first index""" + headers = [field.name for field in House._meta.fields] + + self.assertIsInstance( + load_pdf(self.test_queryset, headers), io.BytesIO) diff --git a/housewars/utils/__init__.py b/housewars/utils/__init__.py index d68f8dc..42a94af 100644 --- a/housewars/utils/__init__.py +++ b/housewars/utils/__init__.py @@ -1,2 +1,3 @@ from .get_random_string import get_random_string from .unpack_values import unpack_values +from .load_pdf import load_pdf diff --git a/housewars/utils/load_pdf.py b/housewars/utils/load_pdf.py new file mode 100644 index 0000000..2cf2ec9 --- /dev/null +++ b/housewars/utils/load_pdf.py @@ -0,0 +1,38 @@ +from reportlab.platypus import Table, TableStyle, SimpleDocTemplate +from reportlab.lib.pagesizes import letter +from reportlab.lib.colors import black + +import io + +from housewars.utils import unpack_values + + +def load_pdf(data, headers): + """Loads data from queryset into a pdf file + + :param queryset: The queryset that will be unpacked + :param headers: A list of the desired headers of the queryset + :returns A bytestream of a pdf file + """ + + # Initialize pdf object + buffer = io.BytesIO() + doc = SimpleDocTemplate(buffer, pagesize=letter) + elements = [] + + # Unpack queryset data + data = unpack_values(data, headers) + + # Load data into table and set styles + table = Table(data, repeatRows=1) + table.setStyle(TableStyle( + [('INNERGRID', (0, 0), (-1, -1), 0.25, black), ('BOX', (0, 0), (-1, -1), 0.25, black)])) + + # Load table into PDF file + elements.append(table) + + # Build document + doc.build(elements) + buffer.seek(0) + + return buffer diff --git a/housewars/utils/unpack_values.py b/housewars/utils/unpack_values.py index 2861932..9240e50 100644 --- a/housewars/utils/unpack_values.py +++ b/housewars/utils/unpack_values.py @@ -1,4 +1,3 @@ -import string from django.db.models import QuerySet from typing import List @@ -7,8 +6,8 @@ def unpack_values(queryset: QuerySet, headers: List[str]) -> List[List]: """Unpacks a queryset into a 2D array :param queryset: The queryset that will be unpacked - :param headers: The headers of the - :returns A table headers, then rows + :param headers: A list of the desired headers of the queryset + :returns A 2D array of headers, then rows """ data = []