Added the ability to mass export activity attendance sheets
This commit is contained in:
parent
69902022f7
commit
76ef47caaf
7 changed files with 142 additions and 40 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
21
housewars/tests/utils/test_load_pdf.py
Normal file
21
housewars/tests/utils/test_load_pdf.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
from .get_random_string import get_random_string
|
||||
from .unpack_values import unpack_values
|
||||
from .load_pdf import load_pdf
|
||||
|
|
|
|||
38
housewars/utils/load_pdf.py
Normal file
38
housewars/utils/load_pdf.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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 = []
|
||||
|
|
|
|||
Reference in a new issue