-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathcompadmin.py
1502 lines (1246 loc) · 67.8 KB
/
compadmin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Some auxiliary functions and constants for competition
# administration.
from __future__ import unicode_literals
import tempfile
from wsgiref.util import FileWrapper
from PyPDF2 import PdfFileMerger
from models import SchoolStudent, School, Invigilator, Venue, ResponsibleTeacher, Competition, LOCATIONS
from datetime import date, datetime
import xlwt
from django.http import HttpResponse, HttpResponseRedirect
import zipfile
import datetime
from django.core import exceptions
import views
#A few administration constants and associated methods to be used around the website.
from django.views.decorators import csrf
from django.template.context_processors import csrf
import ho.pisa as pisa
# StrIO can accept str and unicode values
import StringIO as StrIO
# CStringIO doesn't work well with unicode values. Only use it for UTF-8 encoded str.
import cStringIO as StringIO
from django.template.loader import get_template
from django.template import loader, Context
from models import LOCATIONS
import reports
import shutil
import os
import sys
sys.path.append("../")
sys.setrecursionlimit(10000)
from background_task import background
from uctMaths.background_tasks import bg_generate_school_answer_sheets, bg_email_results, bg_generate_as_grade_distinction
def admin_emailaddress():
"""Get the competition admin's email address from the Competition.objects entry"""
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
return comp[0].admin_emailaddress
else:
return 'Not specified' #ERROR - essentially
def admin_number_of_pairs():
"""Get the number of pairs allowed in the competition"""
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
return comp[0].number_of_pairs
else:
return 0 #ERROR - essentially
def admin_number_of_individuals():
"""Get the number of individuals allowed in the competition"""
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
return comp[0].number_of_individuals
else:
return 0 #ERROR - essentially
def admin_individuals_range(begin=0):
"""Get the range of individuals allowed in the competition"""
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
return range(begin, comp[0].number_of_individuals)
else:
return 0 #ERROR - essentially
def admin_pairs_range(begin=0):
"""Get the range of pairs allowed in the competition"""
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
return range(begin, comp[0].number_of_pairs)
else:
return 0 #ERROR - essentially
def isOpen():
"""Logic to compare the closing date of the competition with today's date"""
comp = Competition.objects.all()
if comp.count() == 1:
if date.today() > comp[0].newentries_Closedate or date.today() < comp[0].newentries_Opendate:
#print 'The competition is closed'
return False
else: #'The competition is open'
return True
else:
return False #Error!!
def closingDate():
""" Display formatted dd/mm/yyyy date as string. Or message on unspecified date (to be displayed on 'Profile' page) """
comp = Competition.objects.all()
if comp.count() == 1:
comp_closingdate = comp[0].newentries_Closedate
return str(comp_closingdate.day) + '/' + str(comp_closingdate.month) + '/' + str(comp_closingdate.year)
else: #Error!
return 'a date yet to be set by the admin'
def gradeBucket(student_list):
"""
Sort ("bucket") the QuerySet list of students into a dict with key based on
grade (integer), pairing status (boolean), and location (string).
"""
#The key is a tuple: (grade, is_paired, location)
grade_bucket = {}
for grade in range(8,13):
for is_paired in [True, False]:
for location in LOCATIONS:
grade_bucket[grade, is_paired, location[0]] = []
grade_bucket[grade, is_paired, 'ALL'] = []
try:
for student in student_list:
grade_bucket[student.grade, student.paired, student.location].append(student)
grade_bucket[student.grade, student.paired, 'ALL'].append(student)
except IndexError:
# Empty QuerySet
print 'Index Error'
return grade_bucket
def auto_allocate(venue_list):
""" Auto allocates currently unallocated (to avoid double-allocation when QuerySet is a subset of venues) students to the provided QuerySet (a list of venues selected at the admin interface. Grade set to 'None' venues are ignored in the allocation process."""
venue_deallocate(venue_list)
student_list = SchoolStudent.objects.all().filter(venue='').order_by('grade') #Order by grade (ASCENDING)
print len(student_list), ' students are unallocated'
grade_bucket = gradeBucket(student_list)
for venue in venue_list.order_by('-seats'): #Allocate from the largest venue first.
#Each venue in QuerySet where grade!=None; while students exist in grade bucket
#See method 'grade_bucket' for bucket format (Key is a tuple!)
while venue.grade and grade_bucket[venue.grade, venue.allocated_to_pairs, venue.location]:
#Pair logic
if venue.occupied_seats < venue.seats - 1 and venue.allocated_to_pairs:
pair = grade_bucket[venue.grade, venue.allocated_to_pairs, venue.location].pop()
pair.venue = venue.code
pair.save()
#Update venue
venue.occupied_seats += 2
venue.save()
#Individual logic
elif venue.occupied_seats < venue.seats and not venue.allocated_to_pairs:
student = grade_bucket[venue.grade, venue.allocated_to_pairs, venue.location].pop()
student.venue = venue.code
student.save()
venue.occupied_seats += 1
venue.save()
else:
break
def venue_deallocate(venue_list):
""" Deallocate students from venues. Clear student.venue and venue.pairs, venue.individuals """
student_list = SchoolStudent.objects.all().order_by('grade')
for venue in venue_list:
venue.occupied_seats = 0
venue.save()
for student in student_list: #Edit student records to reflect deallocation
if student.venue == venue.code:
student.venue = ''
student.save()
#Used in confirmation.py and views.py
def processGrade(student_list):
""" Helper function for sorting students into grades, pairs. Returns two lists: individuals, pairs. Somewhat deprecated by gradeBucket but still used in some places in the code. eg. Generating confirmation email when only the number of pairs for each grade are needed."""
pair_list = { 8 : 0, 9 : 0, 10 : 0, 11 : 0, 12 : 0}
individual_list = { 8 : [] , 9 : [] , 10 : [] , 11 : [] , 12 : [] }
try:
for student in student_list:
if student.paired:
#Count number of pairs for each grade
pair_list[student.grade]+=1
else:
individual_list[student.grade].append(student)
except IndexError:
print 'Index Error'
return individual_list, pair_list
#Export venue lists to workbook.
#See http://scienceoss.com/write-excel-files-with-python-using-xlwt/
def output_register(venue_list):
"""Generate a register for each venue. Output QuerySet (venues) as separated sheets on an xls (excel document). A summary sheet of all venues in QuerySet is generated as first sheet."""
output_workbook = xlwt.Workbook()
student_header = ['Reference No.','School', 'First name(s)','Surname']
#Generate summary sheet
#----------------------
summary_sheet = output_workbook.add_sheet('Venue_summary')
summary_sheet.write(0,0,'Summary page')
venue_h = ['Location', 'Venue', 'Building', 'Grade', 'Available seats', 'Occupied seats', 'Allocation']
for index, header in enumerate(venue_h):
summary_sheet.write(1, index, header)
venue_list.order_by('grade')
for v_index, venue in enumerate(venue_list):
summary_sheet.write(v_index+2,0,str(venue.location))
summary_sheet.write(v_index+2,1,str(venue.code))
summary_sheet.write(v_index+2,2,venue.building)
summary_sheet.write(v_index+2,3,str(venue.grade))
summary_sheet.write(v_index+2,4,str(venue.seats))
summary_sheet.write(v_index+2,5,str(venue.occupied_seats or 0))
if venue.allocated_to_pairs:
summary_sheet.write(v_index+2,6,'Pairs')
else:
summary_sheet.write(v_index+2,6,'Individuals')
#TODO?:Print out the unallocated students?
#Generate a 'Register' sheet for each venue in QuerySet
#------------------------------------------------------
for venue in venue_list:
student_list = SchoolStudent.objects.all().filter(venue=venue.code)
#TODO? Include invigilators in the sheet?
#invigilator_list = Invigilator.objects.all().filter(venue=venue.code)
if student_list:
venue_sheet = output_workbook.add_sheet(str(venue.code))
venue_header = [ #Heading for each sheet. ie. what this sheet contains (for when it's printed)
'Location:', str(venue.location),
'Venue:', str(venue.code),
'Building: ', str(venue.building),
'Grade:', str(venue.grade),
'Occupancy:', str(venue.occupied_seats or 0)+'/'+str(venue.seats),
'Allocation:', 'Pairs' if venue.allocated_to_pairs else 'Individuals'
]
#Print venue_header to the sheet
for index in range(0,6):
venue_sheet.write(index,0, venue_header[index*2])
venue_sheet.write(index,1, venue_header[index*2+1])
# Print student header (name columns) to sheet
for h_index, word in enumerate(student_header):
venue_sheet.write(7,h_index,student_header[h_index])
# Print the students in that venue to sheet
for s_index, student in enumerate(student_list):
venue_sheet.write(s_index+8,0,str(student.reference))
venue_sheet.write(s_index+8,2,student.firstname)
venue_sheet.write(s_index+8,3,student.surname)
venue_sheet.write(s_index+8,1,unicode(student.school))
else:
pass # Venue is empty - no point making a sheet for it...
# Generate response and serve file to the user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=venue_register(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
#Export venue lists to workbook.
#See http://scienceoss.com/write-excel-files-with-python-using-xlwt/
def output_studentlists(student_list):
"""Output Pair and Individual lists (SchoolStudent list QuerySet) for each grade and location as sheets on an xls"""
grade_bucket = gradeBucket(student_list)
output_workbook = xlwt.Workbook()
student_header = ['School', 'Reference No.', 'First name(s)', 'Surname', 'Venue']
for location in LOCATIONS:
for grade in range(8, 13):
#Process individual page
student_sheet = output_workbook.add_sheet(location[1] + ' Grade ' + str(grade)+' individuals')
#Print title and header
student_sheet.write(0, 0, location[1] + ' Grade ' + str(grade) + ' individuals')
for h_index, word in enumerate(student_header):
student_sheet.write(1, h_index,word)
#Print each student's details
for index, student in enumerate(grade_bucket[grade, False, location[0]]):
student_sheet.write(index+2, 0, unicode(student.school))
student_sheet.write(index+2, 1, str(student.reference))
student_sheet.write(index+2, 2, student.firstname)
student_sheet.write(index+2, 3, student.surname)
student_sheet.write(index+2, 4, student.venue)
#Process pairs page
student_sheet = output_workbook.add_sheet(location[1] + ' Grade ' + str(grade)+' pairs')
#Print title and header
student_sheet.write(0, 0, location[1] + ' Grade ' + str(grade) + ' pairs')
for h_index, word in enumerate(student_header):
student_sheet.write(1,h_index,word)
#Print each student's details
for index, student in enumerate(grade_bucket[grade, True, location[0]]):
student_sheet.write(index+2, 0, unicode(student.school))
student_sheet.write(index+2, 1, str(student.reference))
student_sheet.write(index+2, 2, student.firstname)
student_sheet.write(index+2, 3, student.surname)
student_sheet.write(index+2, 4, student.venue)
#Generate response and serve file (xls) to user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=studentlist(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
def output_studenttags(student_list):
"""Generate individual and pair MailMerge lists for SchoolStudent QuerySet per location and grade.
Served to user as a .zip file containing all the lists."""
grade_bucket = gradeBucket(student_list)
#Generate individuals name tags
#Eg: "Ref#","Name Surname","School name",Grade(int),"Building Room(Code)"
venue_list = Venue.objects.all()
output_stringIO = StringIO.StringIO() #Used to write to files then zip
with zipfile.ZipFile(output_stringIO, 'w') as zipf:
for location in LOCATIONS:
for grade in range(8, 13):
output_string = StrIO.StringIO()
for student in grade_bucket[grade, False, location[0]]: #Individuals in grade + location
venue_object = [venue for venue in venue_list if venue.code == student.venue]
s_line = u''
s_line += '\"' + student.reference + '\",'
s_line += '\"' + student.firstname + ' ' + student.surname + '\",'
s_line += '\"' + unicode(student.school) + '\",'
s_line += str(student.grade) + ','
venue_str = venue_object[0] if len(venue_object) == 1 else 'Unallocated'
s_line += '\"' + unicode(venue_str) + '\"\n'
output_string.write(s_line)
#Generate file from StringIO and write to zip (ensure unicode UTF-* encoding is used)
zipf.writestr('Mailmerge_' + location[0] + '_GRD' + str(grade) + '_IND.txt', output_string.getvalue().encode('utf-8'))
output_string.close()
output_string = StrIO.StringIO()
for student in grade_bucket[grade, True, location[0]]: #Pairs in grade + location
venue_object = [venue for venue in venue_list if venue.code == student.venue]
s_line = u''
s_line += '\"' + student.reference + '\",'
s_line += '\"' + student.firstname + ' ' + student.surname + '\",'
s_line += '\"' + unicode(student.school) + '\",'
s_line += str(student.grade) + ','
venue_str = venue_object[0] if len(venue_object) == 1 else 'Unallocated'
s_line += '\"' + unicode(venue_str) + '\"\n'
output_string.write(s_line)
#Generate file from StringIO and write to zip (ensure unicode UTF-* encoding is used)
zipf.writestr('Mailmerge_' + location[0] + '_GRD' +str(grade) + '_PAR.txt',
output_string.getvalue().encode('utf-8'))
output_string.close()
#Generate response and serve file to the user
response = HttpResponse(output_stringIO.getvalue())
response['Content-Disposition'] = 'attachment; filename=mailmergestudents(%s).zip'%(timestamp_now())
response['Content-Type'] = 'application/x-zip-compressed'
return response
#Called by admin to remove users associated with schools (Just clear that field)
def remove_user_assoc(school_list):
""" Remove School-User association for School QuerySet. """
for school in school_list:
school.assigned_to = None
school.save()
#Called by admin to generate formatted 'tag list' for selected schools
def output_schooltaglists(school_list):
""" Generate the tags for a School QuerySet. Served as a single text file in HttpResponse. """
output_stringio = StrIO.StringIO()
#Generate and format school entry (as in spec. sheet)
for school in school_list:
s_entry = '\"' + school.contact + '\",'
s_entry += '\"' + school.name + '\",'
s_entry += '\"' + school.address + '\"\n'
output_stringio.write(s_entry)
#Serve to user as text file
response = HttpResponse(output_stringio.getvalue().encode('utf-8'))
response['Content-Disposition'] = 'attachment; filename=schooltags(%s).txt'%(timestamp_now())
return response
def upload_results():
"""Facilitate upload of Ranked.csv (the results) files. Redirects to custom Admin page (upload_results.html), the logic contained in compadmin_views.py."""
#Return response of redirect page
response = HttpResponseRedirect('../../../../competition/admin/upload_results.html')
return response
def upload_declaration():
response = HttpResponseRedirect('../../../../competition/admin/upload_declaration.html')
return response
def rank_schools():
""" Ranks schools based on a sum of the top X scores. X is set via the 'Competition' form. """
comp = Competition.objects.all() #Should only be one!
if comp.count() == 1:
top_score_candidates = comp[0].num_schoolcandidate_scores
else:
top_score_candidates = 0
all_schools = School.objects.all()
#Calculate total scores for all schools
for school in all_schools:
#Get ONLY the candidates from that school and order by score DESCENDING
#FIXME ?: These DB operations just wouldn't work with the distinct command. Using less efficient python-lists methods
#candidates = SchoolStudent.objects.order_by('reference').distinct('reference')#.filter(school=school).exclude(score=None)
#candidates = candidates.order_by('-score')
candidates = SchoolStudent.objects.filter(school=school).exclude(score=None).order_by('-score')
#Calculate schools' total scores
total_score = 0
#Sum candidates scores (already sorted in descending order)
for i, c in enumerate(candidates):
if i < top_score_candidates:
total_score = total_score + c.score
school.report_emailed = None
school.score = total_score
school.save()
#Rank schools
#Order all schools in descending order
all_schools = all_schools.order_by('-score')
#Ensure that schools with equal scores are assigned the same rank
#Generate a list from the schools (so that I can use .pop(0) commands on it)
school_selection = []
for s in all_schools:
school_selection.append(s)
rank_base = 1
rank_delta = 0 #Used when multiple schools have the same score
while school_selection: #while the list is not empty
rank_base = rank_base + rank_delta
school = school_selection.pop(0)
school.rank = rank_base
current_score = school.score
school.save()
rank_delta = 1
#Assign all schools with the same score the same rank
#Use the rank_delta as a counter
while school_selection and school_selection[0].score == current_score:
school = school_selection.pop(0)
school.rank = rank_base
rank_delta = rank_delta + 1
school.save()
def rank_students():
"""Rank students on their uploaded score. Used if a score has been changed and the remaining students need to be re-classified"""
#Rank students
#Order all students in descending score order
#Ensure that students with equal scores are assigned the same rank
#Generate a list from the students (so that I can use .pop(0) commands on it)
absent_students = SchoolStudent.objects.all().filter(score=None)
for ab_stu in absent_students:
ab_stu.rank = None
ab_stu.save()
#Need to do this for each grade, for paired/individuals.
for grade_i in range(8, 13):
pstudent_list=SchoolStudent.objects.all().filter(paired=True, grade=grade_i).exclude(score=None).order_by('-score')
score_studentlist(pstudent_list)
istudent_list=SchoolStudent.objects.all().filter(paired=False, grade=grade_i).exclude(score=None).order_by('-score')
score_studentlist(istudent_list)
def score_studentlist(student_list):
student_selection = []
for s in student_list:
student_selection.append(s)
rank_base = 1
rank_delta = 0 #Used when multiple schools have the same score
while student_selection: #while the list is not empty
rank_base = rank_base + rank_delta
student = student_selection.pop(0)
student.rank = rank_base
student.award = ''
current_score = student.score
student.save()
rank_delta = 1
#Assign all schools with the same score the same rank
#Use the rank_delta as a counter
while student_selection and student_selection[0].score == current_score:
student = student_selection.pop(0)
student.rank = rank_base
student.award = ''
rank_delta = rank_delta + 1
student.save()
def export_awards(request):
""" Assign awards to participants (QuerySet is list of students) to students based on their rank. Serves an excel workbook with the awards for each student."""
output_workbook = xlwt.Workbook()
#Ranked gold for each grade (pairs, individuals separated) (alphabetical by surname)
#Alphabetical list of school award winners
#Generate gold-awards list (Top 10 individuals, top 3 pairs)
school_list = School.objects.all()
student_list = SchoolStudent.objects.all() #Regardless of admin UI selection
for igrade in range(8, 13):
#Gold awards
wb_sheet = output_workbook.add_sheet('Gold Grade %d'%(igrade))
#Generate QuerySets for GOLD medal winners (sorted by rank (descending))
pairQS = student_list.filter(grade = igrade, paired=True, award= 'G').order_by('rank')
individualQS = student_list.filter(grade = igrade, paired=False, award = 'G').order_by('rank')
pairs_offset = 4 #Using an offset accounts for situations where more than 10 people are getting gold (ties at rank=10)
wb_sheet.write(1,0,'Gold Award Winners: Grade %d Individuals'%(igrade))
header = ['Rank', 'School', 'Reference', 'First Name', 'Last Name', 'Grade']
for i, h in enumerate(header):
wb_sheet.write(2, i, '%s'%h)
pairs_offset = pairs_offset + 2
for index, individual in enumerate(individualQS):
wb_sheet.write(index+3,0,str(individual.rank))
wb_sheet.write(index+3,1,unicode(individual.school))
wb_sheet.write(index+3,2,str(individual.reference))
wb_sheet.write(index+3,3,individual.firstname)
wb_sheet.write(index+3,4,individual.surname)
wb_sheet.write(index+3,5,individual.grade)
school_list=school_list.exclude(name=individual.school) #Exclude school for Oxford prize
pairs_offset = pairs_offset + 1
wb_sheet.write(pairs_offset,0,'Gold Award Winners: Grade %d Pairs'%(igrade))
header = ['Rank', 'School', 'Reference', 'First Name', 'Last Name', 'Grade']
for i, h in enumerate(header):
wb_sheet.write(pairs_offset+1, i, '%s'%h)
pairs_offset = pairs_offset + 2
for index, pair in enumerate(pairQS):
wb_sheet.write(index+pairs_offset,0,str(pair.rank))
wb_sheet.write(index+pairs_offset,1,unicode(pair.school))
wb_sheet.write(index+pairs_offset,2,str(pair.reference))
wb_sheet.write(index+pairs_offset,3,pair.firstname)
wb_sheet.write(index+pairs_offset,4,pair.surname)
wb_sheet.write(index+pairs_offset,5,pair.grade)
school_list=school_list.exclude(name=pair.school) #Exclude school for Oxford prize
#Merit awards
wb_sheet = output_workbook.add_sheet('Merit Grade %d'%(igrade))
#Generate QuerySets for MERIT medal winners (sorted by school (name descending))
pairQS = student_list.filter(grade = igrade, paired=True, award__contains = 'M').order_by('school')
individualQS = student_list.filter(grade = igrade, paired=False, award__contains = 'M').order_by('school')
pairs_offset = 4 #Using an offset accounts for situations where more than 10 people are getting merit (ties at rank=200)
wb_sheet.write(1,0,'Merit Award Winners: Grade %d Individuals'%(igrade))
header = ['Rank', 'School', 'Reference', 'First Name', 'Last Name', 'Grade']
for i, h in enumerate(header):
wb_sheet.write(2, i, '%s'%h)
pairs_offset = pairs_offset + 2
for index, individual in enumerate(individualQS):
wb_sheet.write(index+3,0,str(individual.rank))
wb_sheet.write(index+3,1,unicode(individual.school))
wb_sheet.write(index+3,2,str(individual.reference))
wb_sheet.write(index+3,3,individual.firstname)
wb_sheet.write(index+3,4,individual.surname)
wb_sheet.write(index+3,5,individual.grade)
pairs_offset = pairs_offset + 1
wb_sheet.write(pairs_offset,0,'Merit Award Winners: Grade %d Pairs'%(igrade))
header = ['Rank', 'School', 'Reference', 'First Name', 'Last Name', 'Grade']
for i, h in enumerate(header):
wb_sheet.write(pairs_offset+1, i, '%s'%h)
pairs_offset = pairs_offset + 2
for index, pair in enumerate(pairQS):
wb_sheet.write(index+pairs_offset,0,str(pair.rank))
wb_sheet.write(index+pairs_offset,1,unicode(pair.school))
wb_sheet.write(index+pairs_offset,2,str(pair.reference))
wb_sheet.write(index+pairs_offset,3,pair.firstname)
wb_sheet.write(index+pairs_offset,4,pair.surname)
wb_sheet.write(index+pairs_offset,5,pair.grade)
#TODO Oxford prizes.
#School awards (Oxford prizes) are assigned to the top individual in each school where the school did not receive an individual or pair Gold award
wb_sheet = output_workbook.add_sheet('Oxford Prizes (School Award)')
award_winners = []
for school in school_list:
#Get the students from the eligible school, order by score (descending)
school_students = SchoolStudent.objects.filter(school=school, paired=False, award__contains = 'OX').order_by('-score')
#Appends all students who have the OX award
for student in school_students:
award_winners.append(student)
wb_sheet.write(0, 0, 'Oxford School Award')
header = ['', 'School', 'Reference', 'First Name', 'Last Name', 'Grade', 'Rank', 'Award']
for i, h in enumerate(header):
wb_sheet.write(1, i, '%s'%h)
for index, aw in enumerate(award_winners):
wb_sheet.write(index+2,1,unicode(aw.school))
wb_sheet.write(index+2,2,str(aw.reference))
wb_sheet.write(index+2,3,aw.firstname)
wb_sheet.write(index+2,4,aw.surname)
wb_sheet.write(index+2,5,aw.grade)
wb_sheet.write(index+2,6,aw.rank)
wb_sheet.write(index+2,7,aw.award)
#Return the response with attached content to the user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=awardlist(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
def makeCertificates(students, assigned_school):
certPath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'Certificates/')
if len(students) > 0:
awardCerts = {"G": "goldTemplate.docx", "M": "meritTemplate.docx", None: "participationTemplate.docx", "MOX": "meritOxfordTemplate.docx", "OX": "oxfordTemplate"}
schoolname = ''.join([i if ord(i) < 128 else '' for i in repr(assigned_school)])
schoolname = schoolname[8:-1].strip().replace(" ", "_")
tmpDir = tempfile.mkdtemp()
path = os.path.abspath(tmpDir)+'/'+schoolname
os.mkdir(path)
try:
certs = []
for student in students:
if student.score == 0 or student.score is None: # student is absent
continue
award = student.award
if award in certs:
continue
certs.append(award)
certLoc = certPath + awardCerts.get(award)
shutil.copy(certLoc, path)
shutil.make_archive(path, 'zip', path)
path = path + ".zip"
outFile = open(path, 'rb')
outFile.seek(0)
wrapper = FileWrapper(outFile)
response = HttpResponse(wrapper, content_type="application/x-zip-compressed")
filename = schoolname + "_Certificates.zip"
response['Content-Disposition'] = 'attachment; filename=' + filename
response['Content-Length'] = os.path.getsize(path)
outFile.close()
finally:
shutil.rmtree(tmpDir)
return response
else:
return HttpResponse("Certificates from your school cannot be downloaded at this time")
def assign_student_awards():
school_list = School.objects.all()
student_list = SchoolStudent.objects.all()
for student in student_list:
student.award = None
student.save()
for igrade in range(8, 13):
#Assign gold awards
pairQS = student_list.filter(grade = igrade, paired=True, rank__lt=4, score__gt=0).order_by('rank')
individualQS = student_list.filter(grade = igrade, paired=False, rank__lt=11, score__gt=0).order_by('rank')
for individual in individualQS:
school_list=school_list.exclude(name=individual.school)
individual.award='G'
individual.save()
for pair in pairQS:
school_list=school_list.exclude(name=pair.school)
pair.award='G'
pair.save()
#Merit awards
pairQS = student_list.filter(grade = igrade, paired=True, rank__lt=101, rank__gt=3, score__gt=0).order_by('school')
individualQS = student_list.filter(grade = igrade, paired=False, rank__lt=201, rank__gt=10, score__gt=0).order_by('school')
for individual in individualQS:
individual.award='M'
individual.save()
for pair in pairQS:
pair.award='M'
pair.save()
for ischool in school_list:
#See condition below (student must have entered AND written)
#TODO: Do this better - so far assumes no student actually scored 0
school_students = SchoolStudent.objects.filter(school=ischool, paired=False, score__gt=1).order_by('rank')
#School may only receive an OX award if 10 or more individuals entered AND wrote.
if len(school_students)>= 10:
#The award winner is the one with the highest rank at the school (including possible ties)
if school_students:
for student in school_students:
if student.rank == school_students[0].rank:
if student.award is None:
student.award = ''
student.award=student.award+'OX'
student.save()
else:
break
def school_summary(request):
""" Return for DL a summary list of all the schools that have made an entry; also create a "email these people" line with all the relevant email addresses. Or something like that."""
output_workbook = xlwt.Workbook()
school_list = School.objects.all().order_by('name') #ie. regardless of selection at admin screen
wb_sheet = output_workbook.add_sheet('School Summary')
school_summary_sheet(school_list, wb_sheet)
wb_sheet = output_workbook.add_sheet('School Ranking Summary')
school_rank = school_list.order_by('-rank')
school_summary_sheet(school_rank, wb_sheet, rank_extend=True)
#Return the response with attached content to the user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=school_summary(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
def export_courier_address(request, school_list):
""" Return for DL a list of school courier addresses"""
output_workbook = xlwt.Workbook()
errorSheet = False
wb_sheet = output_workbook.add_sheet('School Addresses')
header = ['Company Name','Email','Address','City','Postal/Zip Code','Province/Region','Country','Contact Name','Tel. No.']
cell_row_offset = 0
for index, h in enumerate(header):
wb_sheet.write(cell_row_offset,index,'%s'%(h))
cell_row_offset+=1
error_sheet = None
error_row = 0
for ischool in school_list:
errors = []
resp_teacher = ResponsibleTeacher.objects.filter(school = ischool).filter(is_primary=True)
alt_resp_teacher = ResponsibleTeacher.objects.filter(school = ischool).filter(is_primary=False)
full = ischool.address.split(',')
full+=['']*(3-len(full))
if not (resp_teacher or alt_resp_teacher):
errors.append("responsible teacher")
if(not full[0]):
errors.append("address")
if(not full[1]):
errors.append("city")
if(not full[2]):
errors.append("postal code")
errorMessage ='No %s assigned to school' % ((', ').join(errors))
if(errors):
if(ischool.entered == 0):
errorMessage = "Not entered"
if(not errorSheet):
error_sheet = output_workbook.add_sheet('Errors')
error_sheet.write(error_row,0,"School")
error_sheet.write(error_row,1,"Error")
error_row+=1
error_sheet.write(error_row,0,ischool.name)
error_sheet.write(error_row,1,errorMessage)
errorSheet = True
continue
resp_teacher = resp_teacher[0]
cell_row_offset = cell_row_offset + 1
wb_sheet.write(cell_row_offset,0,unicode(ischool.name))
wb_sheet.write(cell_row_offset,1,resp_teacher.email_school)
wb_sheet.write(cell_row_offset,2,full[0])
wb_sheet.write(cell_row_offset,3,full[2])
wb_sheet.write(cell_row_offset,4,full[1])
wb_sheet.write(cell_row_offset,5,"Western Cape")
wb_sheet.write(cell_row_offset,6,"South Africa")
wb_sheet.write(cell_row_offset,7,resp_teacher.firstname + " " + resp_teacher.surname)
wb_sheet.write(cell_row_offset,8,ischool.phone)
#Return the response with attached content to the user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=school_addresses(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
def timestamp_now():
now = datetime.datetime.now()
to_return = '%s:%s[%s-%s-%s]'%(now.hour, now.minute, now.day, now.month, now.year)
return to_return
def export_competition(request):
""" Export all the information of this year's competition in single excel file"""
output_workbook = xlwt.Workbook()
school_list = School.objects.all().order_by('name') #ie. regardless of selection at admin screen
student_list = SchoolStudent.objects.all().order_by('school')
#resp_teachers = ResponsibleTeacher.objects.all().order_by('school')
invigilator_list = Invigilator.objects.all().order_by('school')
# --------------------- Generate School Summary ---------------------------
wb_sheet = output_workbook.add_sheet('School Summary')
school_summary_sheet(school_list, wb_sheet, rank_extend=True)
wb_sheet = output_workbook.add_sheet('Student Summary')
archive_all_students(student_list, wb_sheet)
wb_sheet = output_workbook.add_sheet('Invigilator Summary')
archive_all_invigilators(invigilator_list, wb_sheet)
#Return the response with attached content to the user
response = HttpResponse()
response['Content-Disposition'] = 'attachment; filename=competition_archive(%s).xls'%(timestamp_now())
response['Content-Type'] = 'application/ms-excel'
output_workbook.save(response)
return response
def school_summary_sheet(school_list, wb_sheet, rank_extend=False):
""" Helper function to export_entire_competition and school_summary methods."""
wb_sheet.write(0,0,'School summary sheet')
wb_sheet.write(1,0,'Generated')
wb_sheet.write(1,1,'%s'%(timestamp_now()))
header = ['School', 'Location', 'Resp. Teach. Name', 'Resp. Teach. Email (School)', 'Resp. Teach. Email (Personal)', 'Resp. Teach. Phone', 'Resp. Teach. Alt Phone', 'Resp. Teach. Cell', 'Alt. Teach. Name', 'Alt Teach. Email (School)', 'Alt. Teach. Email (Personal)', 'Alt. Teach. Phone', 'Alt. Teach. Alt Phone', 'Alt. Teach. Cell', 'Individuals', 'Pairs', 'Total']
if rank_extend:
header.append('Rank')
header.append('Score')
responsible_teacher_mailinglist = []
cell_row_offset = 6
for index, h in enumerate(header):
wb_sheet.write(cell_row_offset,index,'%s'%(h))
for school_obj in school_list:
try: #Try get the student list for the school assigned to the requesting user
student_list = SchoolStudent.objects.all().filter(school=school_obj)
resp_teacher = ResponsibleTeacher.objects.filter(is_primary=True).get(school=school_obj)
alt_resp_teacher = ResponsibleTeacher.objects.filter(is_primary=False).get(school=school_obj)
except exceptions.ObjectDoesNotExist:#Non-entry
pass #Handled in if-not-empty statement below
if student_list and (resp_teacher or alt_resp_teacher): #If the lists are not empty
grade_summary = gradeBucket(student_list) #Bin into categories (Pairing, grade)
count_individuals = 0
count_pairs = 0
for i in range(8,13):
count_pairs = count_pairs + len(grade_summary[i,True,'ALL'])
count_individuals = count_individuals + len(grade_summary[i,False,'ALL'])
cell_row_offset = cell_row_offset + 1
wb_sheet.write(cell_row_offset,0,unicode(school_obj.name))
wb_sheet.write(cell_row_offset,1,unicode(school_obj.location))
wb_sheet.write(cell_row_offset,2,('%s %s')%(resp_teacher.firstname, resp_teacher.surname))
wb_sheet.write(cell_row_offset,3,resp_teacher.email_school)
wb_sheet.write(cell_row_offset,4,resp_teacher.email_personal)
wb_sheet.write(cell_row_offset,5,resp_teacher.phone_primary)
wb_sheet.write(cell_row_offset,6,resp_teacher.phone_alt)
wb_sheet.write(cell_row_offset,7,resp_teacher.phone_cell)
wb_sheet.write(cell_row_offset,8,('%s %s')%(alt_resp_teacher.firstname, alt_resp_teacher.surname))
wb_sheet.write(cell_row_offset,9,alt_resp_teacher.email_school)
wb_sheet.write(cell_row_offset,10,alt_resp_teacher.email_personal)
wb_sheet.write(cell_row_offset,11,alt_resp_teacher.phone_primary)
wb_sheet.write(cell_row_offset,12,alt_resp_teacher.phone_alt)
wb_sheet.write(cell_row_offset,13,alt_resp_teacher.phone_cell)
wb_sheet.write(cell_row_offset,14,count_individuals)
wb_sheet.write(cell_row_offset,15,count_pairs)
wb_sheet.write(cell_row_offset,16,int(count_pairs*2 + count_individuals))
if rank_extend:
wb_sheet.write(cell_row_offset,17,school_obj.rank)
wb_sheet.write(cell_row_offset,18,school_obj.score)
responsible_teacher_mailinglist.append(resp_teacher.email_school)
responsible_teacher_mailinglist.append(alt_resp_teacher.email_school)
wb_sheet.write(3,0,'Mailing list')
wb_sheet.write(3,1,', '.join(responsible_teacher_mailinglist))
return wb_sheet
def archive_all_students(student_list, wb_sheet):
""" Helper function to export_entire_competition."""
wb_sheet.write(0,0,'Student summary sheet')
wb_sheet.write(1,0,'Generated')
wb_sheet.write(1,1,'%s'%(timestamp_now()))
header = ['Reference', 'School', 'Location', 'Firstname', 'Surname', 'Grade', 'Score', 'Rank', 'Award','Language']
cell_row_offset = 3
for index, h in enumerate(header):
wb_sheet.write(cell_row_offset,index,'%s'%(h))
cell_row_offset = cell_row_offset + 1
student_lang = {'b':'Bilingual', 'a':'Afrikaans', 'e':'English'}
for student in student_list:#print details for every student on the list
wb_sheet.write(cell_row_offset,0, student.reference)
wb_sheet.write(cell_row_offset,1,unicode(student.school))
wb_sheet.write(cell_row_offset,2,unicode(student.location))
wb_sheet.write(cell_row_offset,3, student.firstname)
wb_sheet.write(cell_row_offset,4, student.surname)
wb_sheet.write(cell_row_offset,5, student.grade)
wb_sheet.write(cell_row_offset,6, student.score)
wb_sheet.write(cell_row_offset,7, student.rank)
wb_sheet.write(cell_row_offset,8, student.award)
wb_sheet.write(cell_row_offset,9, student_lang[student.language])
cell_row_offset = cell_row_offset + 1
return wb_sheet
def archive_all_invigilators(invigilator_list, wb_sheet):
""" Helper function to export_err'thing."""
wb_sheet.write(0,0,'Invigilator summary sheet')
wb_sheet.write(1,0,'Generated')
wb_sheet.write(1,1,'%s'%(timestamp_now()))
header = ['School', 'Location', 'Firstname', 'Surname', 'Phone Primary', 'Alternate', 'Email']
cell_row_offset = 3
for index, h in enumerate(header):
wb_sheet.write(cell_row_offset,index,'%s'%(h))
cell_row_offset = cell_row_offset + 1
for invigilator in invigilator_list:#Print details for all invigilators on the list
wb_sheet.write(cell_row_offset,0,unicode(invigilator.school))
wb_sheet.write(cell_row_offset,1,unicode(invigilator.location))
wb_sheet.write(cell_row_offset,2, invigilator.firstname)
wb_sheet.write(cell_row_offset,3, invigilator.surname)
wb_sheet.write(cell_row_offset,4, invigilator.phone_primary)
wb_sheet.write(cell_row_offset,5, invigilator.phone_alt)
wb_sheet.write(cell_row_offset,6, invigilator.email)
wb_sheet.write(cell_row_offset,7, invigilator.notes)
cell_row_offset = cell_row_offset + 1
return wb_sheet
def print_school_confirmations(request, school_list):
result = views.printer_entry_result(request, school_list)
response = HttpResponse(result.getvalue())
response['Content-Disposition'] = 'attachment; filename=school_confirmation(%s).pdf'%(timestamp_now())
response['Content-Type'] = 'application/pdf'
return response
def timestamp_now():
""" Time-stamp-formatting method. Used for all files served by server and a few xls sheets. NB: check cross-OS compatibility! """
now = datetime.datetime.now()
to_return = '%s:%s-%s%s%s'%(str(now.hour).zfill(2), str(now.minute).zfill(2), str(now.day).zfill(2), str(now.month).zfill(2), str(now.year).zfill(4))
return to_return
def output_PRN_files():
"""Generate PRN files lists for all students, regardless of selection at admin UI. Served to user as a .zip file with each (10 files) Paired/Grade list."""
student_list = SchoolStudent.objects.all()