def __init__(self, schoolyear, rtype, klass): """<rtype> is the report type, a key to the mapping GRADE.REPORT_TEMPLATES. <klass> is a <Klass> object, which may include stream tags. All streams passed in must map to the same template. """ self.schoolyear = schoolyear self.klassdata = klass ### Set up categorized, ordered lists of grade fields for insertion ### in a report template. # If there is a list of streams in <klass> this will probably # only match '*' in the template mapping: self.template = getGradeTemplate(rtype, klass) self.alltags = getTemplateTags(self.template) # Extract grade-entry tags, i.e. those matching <str>_<int>: gtags = {} # {subject group -> [(unsorted) index<int>, ...]} for tag in self.alltags: try: _group, index = tag.split('_') group = _group.split('.')[-1] i = int(index) except: continue try: gtags[group].append(i) except: gtags[group] = [i] # Build a sorted mapping of index lists: # {subject group -> [(reverse sorted) index<int>, ...]} # The reversal is for popping in correct order. self.sgroup2indexes = {group: sorted(gtags[group], reverse=True) for group in gtags} # print("\n??? self.alltags", self.alltags) # print("\n??? self.sgroup2indexes", self.sgroup2indexes) ### Sort the subject tags into ordered groups self.courses = CourseTables(schoolyear) subjects = self.courses.filterGrades(klass) # Build a mapping: {subject group -> [(ordered) sid, ...]} self.sgroup2sids = {} for group in self.sgroup2indexes: sidlist = [] self.sgroup2sids[group] = sidlist # CONF.GRADES.ORDERING: {subject group -> [(ordered) sid, ...]} for sid in CONF.GRADES.ORDERING[group]: # Include only sids relevant for the klass. try: del(subjects[sid]) sidlist.append(sid) except: pass # Entries remaining in <subjects> are not covered in ORDERING. for sid in subjects: REPORT.Error(_UNGROUPED_SID, sid=sid, tfile=self.template.filename)
def tSheets(schoolyear, manager, date): courses = CourseTables(schoolyear) tidmap = {} for k in courses.classes(): klass = Klass(k) sid2tids = courses.filterText(klass) for sid, tids in sid2tids.items(): if tids.TEXT: if not tids: tids = [_nn] for tid in tids: try: tmap = tidmap[tid] except: tidmap[tid] = {klass.klass: {sid}} continue try: tmap[klass.klass].add(sid) except: tmap[klass.klass] = {sid} noreports = [] teachers = [] for tid in courses.teacherData: lines = [] tname = courses.teacherData.getTeacherName(tid) try: tmap = tidmap[tid] except: noreports.append(tname) continue for k in sorted(tmap): for sid in tmap[k]: sname = courses.subjectName(sid) lines.append((k, sname)) teachers.append((tname, lines)) tpdir = Paths.getUserPath('DIR_TEXT_REPORT_TEMPLATES') templateLoader = jinja2.FileSystemLoader(searchpath=tpdir) templateEnv = jinja2.Environment(loader=templateLoader, autoescape=True) tpfile = 'summary-teachers.html' try: template = templateEnv.get_template(tpfile) except: REPORT.Fail(_NOTEMPLATE, path=os.path.join(tpdir, tpfile)) source = template.render(year=printSchoolYear(schoolyear), manager=manager, date=Dates.dateConv(date), teachers=teachers, noreports=noreports) html = HTML(string=source) pdfBytes = html.write_pdf() return pdfBytes
def choiceTable(schoolyear, klass): """Build a subject choice table for the given school-class. <klass> is a <Klass> instance. """ template = Paths.getUserPath('FILE_SUBJECT_CHOICE_TEMPLATE') table = KlassMatrix(template) # Title already set in template: #table.setTitle("Kurswahl") # "Translation" of info items: kmap = CONF.TABLES.COURSE_PUPIL_FIELDNAMES info = ( (kmap['SCHOOLYEAR'], str(schoolyear)), (kmap['CLASS'], klass.klass), ) table.setInfo(info) ### Manage subjects courses = CourseTables(schoolyear) sid2tlist = courses.classSubjects(klass) # <table.headers> is a list of cell values in the header row. rsid = table.rowindex - 1 # row tag for sid rsname = table.rowindex # row tag for subject name # Go through the template columns and check if they are needed: for sid in sid2tlist: if sid[0] != '_': sname = courses.subjectName(sid) # Add subject col = table.nextcol() table.write(rsid, col, sid) table.write(rsname, col, sname) # Delete excess columns table.delEndCols(col + 1) ### Add pupils pupils = Pupils(schoolyear) for pdata in pupils.classPupils(klass): row = table.nextrow() table.write(row, 0, pdata['PID']) table.write(row, 1, pdata.name()) table.write(row, 2, pdata['STREAM']) # Delete excess rows table.delEndRows(row + 1) ### Save file table.protectSheet() return table.save()
def ksSheets(schoolyear, manager, date): courses = CourseTables(schoolyear) tidmap = { tid: courses.teacherData.getTeacherName(tid) for tid in courses.teacherData } klasses = [] for k in courses.classes(): klass = Klass(k) sidmap = {} sid2tids = courses.filterText(klass) for sid, tids in sid2tids.items(): if tids.TEXT: if not tids: tids = [_nn] for tid in tids: try: sidmap[sid].add(tid) except: sidmap[sid] = {tid} lines = [] for sid, tids in sidmap.items(): sname = courses.subjectName(sid) for tid in tids: lines.append((sname, tidmap[tid])) klasses.append((klass.klass, lines)) tpdir = Paths.getUserPath('DIR_TEXT_REPORT_TEMPLATES') templateLoader = jinja2.FileSystemLoader(searchpath=tpdir) templateEnv = jinja2.Environment(loader=templateLoader, autoescape=True) tpfile = 'summary-classes.html' try: template = templateEnv.get_template(tpfile) except: REPORT.Fail(_NOTEMPLATE, path=os.path.join(tpdir, tpfile)) source = template.render(year=printSchoolYear(schoolyear), manager=manager, date=Dates.dateConv(date), klasses=klasses) html = HTML(string=source) pdfBytes = html.write_pdf() return pdfBytes
def makeAbiTables (schoolyear, klass, date): """Build grade tables (one for each student) for the final Abitur grades. These tables are used to prepare the grades for (automatic) entry into the certificates. The new tables are placed in a subfolder of the normal folder (see configuration file PATHS: FILE_ABITABLE_NEW). This is to avoid accidentally overwriting existing tables which already contain data. Most of the necessary "cleverness" is built into the template file. """ template = Paths.getUserPath ('FILE_ABITUR_GRADE_TEMPLATE') outpath = Paths.getYearPath (schoolyear, 'FILE_ABITABLE_NEW', klass=klass) for f in glob (os.path.join (os.path.dirname (outpath), '*')): os.remove (f) outpath += '.xlsx' sheetname = CONF.TABLES.ABITUR_RESULTS.GRADE_TABLE_SHEET FrHr = {} for v in CONF.TABLES.ABITUR_RESULTS.P_FrHr: k, v = v.split (':') FrHr [k] = v ncourses = CONF.TABLES.ABITUR_RESULTS.NCOURSES.nat () courseTables = CourseTables (schoolyear) pupils = Pupils (schoolyear).classPupils (klass, date) sid2info = courseTables.filterGrades (klass, realonly=True) subjects = [] for sid, sinfo in sid2info.items (): subjects.append ((sid, sinfo.COURSE_NAME)) teacherMatrix = FormattedMatrix.readMatrix (schoolyear, 'FILE_CLASS_SUBJECTS', klass) i = 0 # pid index for ordering files files = [] # list of created files (full paths) for pdata in pupils: pid = pdata ['PID'] pname = pdata.name () i += 1 filepath = outpath.replace ('*', '{pnum:02d}-{pid}-{name}'.format ( pnum=i, pid=pid, name=Paths.asciify (pname))) fields = {'YEAR': str (schoolyear), 'LASTNAME': pdata ['LASTNAME'], 'FIRSTNAMES': pdata ['FIRSTNAMES'], 'DOB_D': pdata ['DOB_D'], 'POB': pdata ['POB'], 'HOME': pdata ['HOME']} try: fields ['FrHr'] = FrHr [pdata ['SEX']] except: REPORT.Error (_BADSEXFIELD, klass=klass, pname=pname, sex=pdata ['SEX']) fields ['FrHr'] = ' ' f = 0 for sid, sname in subjects: if not teacherMatrix [pid][sid]: continue f += 1 fields ['F' + str (f)] = sname.split ('|') [0].rstrip () if ncourses and f != ncourses: REPORT.Error (_NOTNCOURSES, klass=klass, pname=pname, n=f, nc0=ncourses) continue unused = XLS_template (filepath, template, fields, sheetname=sheetname) files.append (filepath) REPORT.Info (_MADENTABLES, klass=klass, n=len (files)) return files
def makeGradeTable(schoolyear, term, klass, title): """Make a grade table for the given school-class/group. <klass> is a <Klass> instance. <term> is a string. """ # Info concerning grade tables: gtinfo = CONF.GRADES.GRADE_TABLE_INFO # Determine table template t = klass.match_map(gtinfo.GRADE_TABLE_TEMPLATE) if not t: REPORT.Fail(_NO_TEMPLATE, ks=klass) template = Paths.getUserPath('FILE_GRADE_TABLE_TEMPLATE').replace('*', t) table = KlassMatrix(template) ### Insert general info table.setTitle(title) # "Translation" of info items: kmap = CONF.TABLES.COURSE_PUPIL_FIELDNAMES info = ( (kmap['SCHOOLYEAR'], str(schoolyear)), (kmap['CLASS'], klass.klass), (kmap['TERM'], term) ) table.setInfo(info) ### Manage subjects courses = CourseTables(schoolyear) sid2tlist = courses.classSubjects(klass) # print ("???1", list(sid2tlist)) # Go through the template columns and check if they are needed: colmap = {} col = _FIRSTSIDCOL for k in table.headers[_FIRSTSIDCOL:]: if k: if k in sid2tlist: colmap[k] = col elif k == _UNUSED: table.hideCol(col, True) else: # Handle extra _tags klassmap = gtinfo.get(k) if klassmap: m = klass.match_map(klassmap) if m and term in m.split(): colmap[k] = col else: table.hideCol(col, True) else: table.hideCol(col, True) col += 1 # print("???COLMAP:", colmap) ### Add pupils pupils = Pupils(schoolyear) for pdata in pupils.classPupils(klass): row = table.nextrow() pid = pdata['PID'] table.write(row, 0, pid) table.write(row, 1, pdata.name()) table.write(row, 2, pdata['STREAM']) # Add existing grades gd = getGradeData(schoolyear, pid, term) # print("\n???", pid, gd) if gd: grades = gd['GRADES'] if grades: for k, v in grades.items(): try: col = colmap[k] except KeyError: # print("!!! excess subject:", k) continue if k: if k.startswith('__'): # Calculated entry continue table.write(row, col, v) # Delete excess rows table.delEndRows(row + 1) ### Save file table.protectSheet() return table.save()
def stripTable(schoolyear, term, klass, title): """Build a basic pupil/subject table for entering grades. <klass> is a <Klass> instance. <term> is a string. """ # Info concerning grade tables: gtinfo = CONF.GRADES.GRADE_TABLE_INFO ### Determine table template (output) t = klass.match_map(gtinfo.GRADE_INPUT_TEMPLATE) if not t: REPORT.Fail(_NO_ITEMPLATE, ks=klass) template = Paths.getUserPath('FILE_GRADE_TABLE_TEMPLATE').replace('*', t) table = KlassMatrix(template) table.setTitle(title) table.setInfo([]) ### Read input table template (for determining subjects and order) # Determine table template (input) t = klass.match_map(gtinfo.GRADE_TABLE_TEMPLATE) if not t: REPORT.Fail(_NO_TEMPLATE, ks=klass) template0 = Paths.getUserPath('FILE_GRADE_TABLE_TEMPLATE').replace('*', t) table0 = KlassMatrix(template0) i, x = 0, 0 for row0 in table0.rows: i += 1 if row0[0] and row0[0] != '#': # The subject key line break # <row0> is the title row. ### Manage subjects courses = CourseTables(schoolyear) sid2tlist = courses.classSubjects(klass) # print ("???1", list(sid2tlist)) # Set klass cell rowix = table.rowindex - 1 table.write(rowix, 0, table.headers[0].replace('*', klass.klass)) # Go through the template columns and check if they are needed: col = 0 for sid in row0: if sid and sid[0] != '_' and sid in sid2tlist: sname = courses.subjectName(sid) # Add subject col = table.nextcol() table.write(rowix, col, sname) # Delete excess columns table.delEndCols(col + 1) ### Add pupils pupils = Pupils(schoolyear) for pdata in pupils.classPupils(klass): row = table.nextrow() table.write(row, 0, pdata.name()) table.write(row, 1, pdata['STREAM']) # Delete excess rows table.delEndRows(row + 1) ### Save file table.protectSheet() return table.save()
class GradeReportData: """Manage data connected with a school-class, stream and report type. When referring to old report data, bear in mind that a pupil's stream, or even school-class, may have changed. The grade data includes the class and stream associated with the data. """ def __init__(self, schoolyear, rtype, klass): """<rtype> is the report type, a key to the mapping GRADE.REPORT_TEMPLATES. <klass> is a <Klass> object, which may include stream tags. All streams passed in must map to the same template. """ self.schoolyear = schoolyear self.klassdata = klass ### Set up categorized, ordered lists of grade fields for insertion ### in a report template. # If there is a list of streams in <klass> this will probably # only match '*' in the template mapping: self.template = getGradeTemplate(rtype, klass) self.alltags = getTemplateTags(self.template) # Extract grade-entry tags, i.e. those matching <str>_<int>: gtags = {} # {subject group -> [(unsorted) index<int>, ...]} for tag in self.alltags: try: _group, index = tag.split('_') group = _group.split('.')[-1] i = int(index) except: continue try: gtags[group].append(i) except: gtags[group] = [i] # Build a sorted mapping of index lists: # {subject group -> [(reverse sorted) index<int>, ...]} # The reversal is for popping in correct order. self.sgroup2indexes = {group: sorted(gtags[group], reverse=True) for group in gtags} # print("\n??? self.alltags", self.alltags) # print("\n??? self.sgroup2indexes", self.sgroup2indexes) ### Sort the subject tags into ordered groups self.courses = CourseTables(schoolyear) subjects = self.courses.filterGrades(klass) # Build a mapping: {subject group -> [(ordered) sid, ...]} self.sgroup2sids = {} for group in self.sgroup2indexes: sidlist = [] self.sgroup2sids[group] = sidlist # CONF.GRADES.ORDERING: {subject group -> [(ordered) sid, ...]} for sid in CONF.GRADES.ORDERING[group]: # Include only sids relevant for the klass. try: del(subjects[sid]) sidlist.append(sid) except: pass # Entries remaining in <subjects> are not covered in ORDERING. for sid in subjects: REPORT.Error(_UNGROUPED_SID, sid=sid, tfile=self.template.filename) def getTagmap(self, grades, pname, grademap='GRADES'): """Prepare tag mapping for substitution in the report template, for the pupil <pname>. <grades> is a mapping {sid -> grade}, or something similar which can be handled by <dict(grades)>. <grademap> is the name of a configuration file (in 'GRADES') providing a grade -> text mapping. Grouped subjects expected by the template get two entries: one for the subject name and one for the grade. They are allocated according to the numbered slots defined for the predefined ordering (config: GRADES/ORDERING). "Grade" entries whose tag begins with '_' and which are not covered by the data in GRADES/ORDERING are copied directly to the output mapping. Return a mapping {template tag -> replacement text}. """ tagmap = {} # for the result g2text = CONF.GRADES[grademap] # grade -> text representation # Copy the grade mapping, because it will be modified to keep # track of unused grade entries: gmap = dict(grades) # this accepts a variety of input types for group, sidlist in self.sgroup2sids.items(): # Copy the indexes because the list is modified here (<pop()>) indexes = self.sgroup2indexes[group].copy() for sid in sidlist: try: g = gmap.pop(sid) except: REPORT.Error(_MISSING_GRADE, pname=pname, sid=sid) g = '?' if g == _INVALID: continue try: i = indexes.pop() except: REPORT.Fail(_TOO_MANY_SUBJECTS, group=group, pname=pname, sids=repr(sidlist), template=self.template.filename) sname = self.courses.subjectName(sid).split('|')[0].rstrip() tagmap["%s_%d_N" % (group, i)] = sname g1 = g2text.get(g) if not g1: REPORT.Error(_NO_MAPPED_GRADE, grade=g) g1 = '?' tagmap["%s_%d" % (group, i)] = g1 # Process superfluous indexes for i in indexes: tagmap["%s_%d_N" % (group, i)] = g2text.NONE tagmap["%s_%d" % (group, i)] = g2text.NONE # Report unused grade entries unused = [] for sid, g in gmap.items(): if g == _INVALID: continue if sid[0] == '_': tagmap[sid] = g else: unused.append("%s: %s" % (sid, g)) if unused: REPORT.Error(_UNUSED_GRADES, pname=pname, grades="; ".join(unused)) return tagmap