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 = [
|
ALLOWED_HOSTS = [
|
||||||
'45.79.52.230',
|
'45.79.52.230',
|
||||||
'housewars.c4thebomb101.com',
|
'housewars.c4thebomb101.com',
|
||||||
'collegiate-housewars.herokuapp.com',
|
'csmb-housewars.herokuapp.com',
|
||||||
'collegiate-housewars.up.railway.app'
|
'*'
|
||||||
]
|
]
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'https://housewars.c4thebomb101.com',
|
'https://housewars.c4thebomb101.com',
|
||||||
'https://collegiate-housewars.herokuapp.com',
|
'https://csmb-housewars.herokuapp.com'
|
||||||
'https://collegiate-housewars.up.railway.app'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Database
|
# 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.http import HttpResponse
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
|
from django.db.models import F
|
||||||
from django.utils.translation import ngettext
|
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.models import Activity, Award, Quota, UserEntry
|
||||||
|
from housewars.utils import load_pdf
|
||||||
|
|
||||||
|
|
||||||
class QuotaInline(admin.TabularInline):
|
class QuotaInline(admin.TabularInline):
|
||||||
|
|
@ -29,7 +34,7 @@ class ActivityAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
inlines = [QuotaInline, AwardsInline]
|
inlines = [QuotaInline, AwardsInline]
|
||||||
|
|
||||||
actions = ['generate_passwords']
|
actions = ['generate_passwords', 'export_a1_to_pdf', 'export_a2_to_pdf']
|
||||||
|
|
||||||
@admin.display(description='Hawk')
|
@admin.display(description='Hawk')
|
||||||
def hawk_signups(self, obj):
|
def hawk_signups(self, obj):
|
||||||
|
|
@ -57,12 +62,74 @@ class ActivityAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.action(description='Generate passwords for selected activities')
|
@admin.action(description='Generate passwords for selected activities')
|
||||||
def generate_passwords(self, request, queryset):
|
def generate_passwords(self, request, queryset):
|
||||||
for item in queryset:
|
for activity in queryset:
|
||||||
item.password = get_random_string(8)
|
activity.password = get_random_string(8)
|
||||||
item.save()
|
activity.save()
|
||||||
|
|
||||||
self.message_user(request, ngettext(
|
self.message_user(request, ngettext(
|
||||||
'Generated password for %d activity.',
|
'Generated password for %d activity.',
|
||||||
'Generated passwords for %d activities.',
|
'Generated passwords for %d activities.',
|
||||||
queryset.count(),
|
queryset.count(),
|
||||||
) % queryset.count(), messages.SUCCESS)
|
) % 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.db.models.functions import Concat
|
||||||
from django.http import HttpResponse, FileResponse
|
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 csv
|
||||||
import io
|
|
||||||
|
|
||||||
from housewars.models import UserEntry
|
from housewars.models import UserEntry
|
||||||
from housewars.utils import unpack_values
|
from housewars.utils import unpack_values, load_pdf
|
||||||
|
|
||||||
|
|
||||||
class MentorListFilter(admin.SimpleListFilter):
|
class MentorListFilter(admin.SimpleListFilter):
|
||||||
|
|
@ -97,30 +91,13 @@ class UserEntryAdmin(admin.ModelAdmin):
|
||||||
def export_to_pdf(self, request, queryset):
|
def export_to_pdf(self, request, queryset):
|
||||||
# Augment the querset with extra data
|
# Augment the querset with extra data
|
||||||
queryset = queryset.annotate(
|
queryset = queryset.annotate(
|
||||||
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity1__room_number'))
|
a1_room=F('activity1__room_number')).annotate(a2_room=F('activity2__room_number'))
|
||||||
|
|
||||||
# Initialize pdf object
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
|
||||||
elements = []
|
|
||||||
|
|
||||||
# Select headers that are to be unpacked
|
# Select headers that are to be unpacked
|
||||||
headers = ['first_name', 'last_name', 'grade', 'house',
|
headers = ['first_name', 'last_name', 'grade', 'house',
|
||||||
'activity1', 'a1_room', 'activity2', 'a2_room']
|
'activity1', 'a1_room', 'activity2', 'a2_room']
|
||||||
|
|
||||||
# Unpack queryset data
|
# Unpack queryset data
|
||||||
data = unpack_values(queryset, headers)
|
file = load_pdf(queryset, headers)
|
||||||
|
|
||||||
# Load data into table and set styles
|
return FileResponse(file, as_attachment=True, filename='export.pdf')
|
||||||
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')
|
|
||||||
|
|
|
||||||
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 .get_random_string import get_random_string
|
||||||
from .unpack_values import unpack_values
|
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 django.db.models import QuerySet
|
||||||
from typing import List
|
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
|
"""Unpacks a queryset into a 2D array
|
||||||
|
|
||||||
:param queryset: The queryset that will be unpacked
|
:param queryset: The queryset that will be unpacked
|
||||||
:param headers: The headers of the
|
:param headers: A list of the desired headers of the queryset
|
||||||
:returns A table headers, then rows
|
:returns A 2D array of headers, then rows
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
|
|
|
||||||
Reference in a new issue