def klassview(klass): _klass = Klass(klass) form = DateForm() # Set date schoolyear = session['year'] if form.validate_on_submit(): # POST _d = form.getDate() pids = request.form.getlist('Pupil') if pids: pdfBytes = makeSheets(schoolyear, _d, _klass, pids=pids) return send_file(io.BytesIO(pdfBytes), attachment_filename='Mantel_%s.pdf' % _klass, mimetype='application/pdf', as_attachment=True) flash("Keine Schüler gewählt", "Warning") # GET form.defaultIssueDate(schoolyear) p = Pupils(schoolyear) pdlist = p.classPupils(_klass) return render_template(os.path.join(_BPNAME, 'text_cover_klass.html'), form=form, heading=_HEADING, klass=str(_klass), pupils=[(pd['PID'], pd.name()) for pd in pdlist])
def pupilview(klass, pid): _klass = Klass(klass) schoolyear = session['year'] template = getTextTemplate('Mantelbogen', _klass) tags = getTemplateTags(template) _fields = dict(pupilFields(tags)) fields = [(f0, f1) for f0, f1 in CONF.TABLES.PUPILS_FIELDNAMES.items() if f0 in _fields] form = DateForm() if form.validate_on_submit(): # POST _d = form.getDate() pupil = SimpleNamespace(**{f: request.form[f] for f, _ in fields}) pdfBytes = makeOneSheet(schoolyear, _d, _klass, pupil) return send_file(io.BytesIO(pdfBytes), attachment_filename='Mantel_%s.pdf' % sortingName(pupil.FIRSTNAMES, pupil.LASTNAME), mimetype='application/pdf', as_attachment=True) # GET form.defaultIssueDate(schoolyear) p = Pupils(schoolyear) try: pdlist = p.classPupils(_klass) pdata = pdlist.pidmap[pid] pupil = {f: (fname, pdata[f]) for f, fname in fields} except: abort(404) return render_template(os.path.join(_BPNAME, 'text_cover_pupil.html'), form=form, heading=_HEADING, klass=str(_klass), pupil=pupil)
def klassview(klass): form = DateForm() if form.validate_on_submit(): # POST _d = form.dateofissue.data.isoformat() pdfBytes = makeSheets( _schoolyear, _d, klass, #TODO check list not empty ... pids=request.form.getlist('Pupil')) return send_file(io.BytesIO(pdfBytes), attachment_filename='Mantel_%s.pdf' % klass, mimetype='application/pdf', as_attachment=True) # GET p = Pupils(_schoolyear) pdlist = p.classPupils(klass) klasses = [k for k in p.classes() if k >= '01' and k < '13'] return render_template('text_cover_klass.html', form=form, schoolyear=str(_schoolyear), klass=klass, klasses=klasses, pupils=[(pd['PID'], pd.name()) for pd in pdlist])
def pupilview(klass, pid): fields = pupilFields(klass) form = DateForm() if form.validate_on_submit(): # POST _d = form.dateofissue.data.isoformat() pupil = SimpleNamespace(**{f: request.form[f] for f, _ in fields}) pdfBytes = makeOneSheet(_schoolyear, _d, klass, pupil) return send_file(io.BytesIO(pdfBytes), attachment_filename='Mantel_%s.pdf' % sortingName(pupil.FIRSTNAMES, pupil.LASTNAME), mimetype='application/pdf', as_attachment=True) # GET p = Pupils(_schoolyear) pdlist = p.classPupils(klass) pupils = [] for pdata in pdlist: _pid = pdata['PID'] pupils.append((_pid, pdata.name())) if _pid == pid: pupil = {f: (fname, pdata[f]) for f, fname in fields} return render_template('text_cover_pupil.html', form=form, schoolyear=str(_schoolyear), klass=klass, pupil=pupil, pupils=pupils)
def migratePupils (schoolyear): """Read the pupil data from the previous year and build a preliminary database table for the current (new) year, migrating the class names according to <CONF.MISC.MIGRATE_CLASS> """ # Get pupil data from previous year pdb = Pupils (schoolyear-1) # Maximum year number for various streams: maxyear = {} try: for x in CONF.MISC.STREAM_MAX_YEAR: k, v = x.split (':') maxyear [k] = v except: REPORT.Fail (_BAD_STREAM_MAX_YEAR, val=x) rows = [] for c_old in pdb.classes (): # Increment the year part of the class name try: cnum = int (c_old [:2]) + 1 ctag = c_old [2:] except: REPORT.Fail (_BADCLASSNAME, klass=c_old) c_new = '%02d%s' % (cnum, ctag) for prow in pdb.classPupils (c_old): left = False if prow ['EXIT_D']: # If there is an exit date, assume the pupil has left. left = True else: try: mxy = maxyear [prow ['STREAM']] except: mxy = maxyear [''] if cnum > int (mxy): left = True if left: REPORT.Info (_PUPIL_LEFT, klass=c_old, name=prow.name ()) continue prow ['CLASS'] = c_new rows.append (prow) # Create the database table PUPILS from the loaded pupil data. db = DB (schoolyear, flag='CANCREATE') # Use (CLASS, PSORT) as primary key, with additional index on PID. # This makes quite a small db (without rowid). db.makeTable2 ('PUPILS', PupilData.fields (), data=rows, force=True, pk=('CLASS', 'PSORT'), index=('PID',))
def test_03(): _k = '12K' _k = '12' _klass = Klass(_k) pupils = Pupils(_year) plist = pupils.classPupils(_klass) pdata = plist[0] pdfBytes = makeOneSheet(_year, _date, _klass, pdata) folder = Paths.getUserPath('DIR_TEXT_REPORT_TEMPLATES') fpath = os.path.join(folder, 'test1.pdf') with open(fpath, 'wb') as fh: fh.write(pdfBytes) REPORT.Test(" --> %s" % fpath)
def test_06(): #TODO: Perhaps if _GS is set, the type should be overriden? _klass = Klass('12') _pid = '200407' pupils = Pupils(_year) pall = pupils.classPupils(_klass) # list of data for all pupils pdata = pall.pidmap[_pid] pdfBytes = makeOneSheet(_year, '2016-02-03', pdata, _term, 'Abgang') folder = Paths.getUserPath('DIR_GRADE_REPORT_TEMPLATES') ptag = pdata['PSORT'].replace(' ', '_') fpath = os.path.join(folder, 'test_%s_Abgang.pdf' % ptag) with open(fpath, 'wb') as fh: fh.write(pdfBytes) REPORT.Test(" --> %s" % fpath)
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 pupils(klass): """View: select pupil from given school-class (single report generation). """ try: dfile = session.pop('download') except: dfile = None schoolyear = session['year'] # Collect list of pupils for this school-class. pupils = Pupils(schoolyear) # List the classes with the highest first, as these are more # likely to need grades. _klass = Klass(klass) plist = pupils.classPupils(_klass) return render_template(os.path.join(_BPNAME, 'pupils.html'), heading=_HEADING, klass=_klass.klass, pupils=plist, dfile=dfile)
def db2grades(schoolyear, term, klass, checkonly=False): """Fetch the grades for the given school-class/group, term, schoolyear. Return a list [(pid, pname, {subject -> grade}), ...] <klass> is a <Klass> instance, which can include a list of streams (including '_' for pupils without a stream). If there are streams, only grades for pupils in one of these streams will be included. """ slist = klass.streams plist = [] # Get the pupils from the pupils db and search for grades for these. pupils = Pupils(schoolyear) db = DB(schoolyear) for pdata in pupils.classPupils(klass): # Check pupil's stream if there is a stream filter pstream = pdata['STREAM'] if slist and (pstream not in slist): continue pid = pdata['PID'] gdata = db.select1('GRADES', PID=pid, TERM=term) if gdata: gstring = gdata['GRADES'] or None if gstring: if gdata['KLASS'] != klass.klass or gdata['STREAM'] != pstream: # Pupil has switched klass and/or stream. # This can only be handled via individual view. gstring = None else: gstring = None if gstring and not checkonly: try: gmap = grades2map(gstring) except ValueError: REPORT.Fail(_BAD_GRADE_DATA, pid=pid, term=term) plist.append((pid, pdata.name(), gmap)) else: plist.append((pid, pdata.name(), gstring)) return plist
def makeSheets(schoolyear, date, klass, pids=None): """ <schoolyear>: year in which school-year ends (int) <data>: date of issue ('YYYY-MM-DD') <klass>: a <Klass> instance for the school-class <pids>: a list of pids (must all be in the given klass), only generate reports for pupils in this list. If not supplied, generate reports for the whole klass. """ pupils = Pupils(schoolyear) plist = pupils.classPupils(klass) if pids: pall = plist pset = set(pids) plist = [] for pdata in pall: try: pset.remove(pdata['PID']) except KeyError: continue plist.append(pdata) if pset: REPORT.Bug(_PUPILSNOTINCLASS, pids=', '.join(pset), klass=klass) template = getTextTemplate('Mantelbogen', klass) source = template.render(SCHOOLYEAR=printSchoolYear(schoolyear), DATE_D=date, todate=Dates.dateConv, pupils=plist, klass=klass) if plist: html = HTML(string=source, base_url=os.path.dirname(template.filename)) pdfBytes = html.write_pdf(font_config=FontConfiguration()) return pdfBytes else: REPORT.Fail(_NOPUPILS)
def makeReports(schoolyear, term, klass, date, pids=None): """Build a single file containing reports for the given pupils. This only works for groups with the same report type and template. <term> is the term. <date> is the date of issue ('YYYY-MM-DD'). <pids>: a list of pids (must all be in the given klass), only generate reports for pupils in this list. If not supplied, generate reports for the whole klass/group. <klass> is a <Klass> instance: it can be a just a school-class, but it can also have a stream, or list of streams. """ # <db2grades> returns a list: [(pid, pname, grade map), ...] # <grades>: {pid -> (pname, grade map)} grades = { pid: (pname, gmap) for pid, pname, gmap in db2grades(schoolyear, term, klass) } pupils = Pupils(schoolyear) pall = pupils.classPupils(klass) # list of data for all pupils # If a pupil list is supplied, select the required pupil data. # Otherwise use the full list. if pids: pset = set(pids) plist = [] for pdata in pall: try: pset.remove(pdata['PID']) except KeyError: continue plist.append(pdata) if pset: REPORT.Bug(_PUPILS_NOT_IN_CLASS_STREAM, pids=', '.join(pset), ks=klass) else: plist = pall ### Get a tag mapping for the grade data of each pupil # Get the name of the relevant configuration file in folder GRADES: grademap = klass.match_map(CONF.MISC.GRADE_SCALE) # <GradeReportData> manages the report template, etc.: # Get the report type from the term and klass/stream rtype = klass.match_map(CONF.GRADES.REPORT_TEMPLATES['_' + term]) reportData = GradeReportData(schoolyear, rtype, klass) pmaplist = [] for pdata in plist: pid = pdata['PID'] pname, gmap = grades[pid] # get pupil name and grade map # Build a grade mapping for the tags of the template: pdata.grades = reportData.getTagmap(gmap, pname, grademap) pmaplist.append(pdata) # Update grade database updateGradeReport(schoolyear, pid, term, date=date, rtype=rtype) ### Generate html for the reports # Testing: # n = 0 # with change below, just generate nth of list # print("§§§", pmaplist[n]) source = reportData.template.render( report_type=rtype, SCHOOLYEAR=printSchoolYear(schoolyear), DATE_D=date, todate=Dates.dateConv, STREAM=printStream, pupils=pmaplist, # pupils = [pmaplist[n]], klass=klass) # Convert to pdf if not plist: REPORT.Fail(_NOPUPILS) html = HTML(string=source, base_url=os.path.dirname(reportData.template.filename)) pdfBytes = html.write_pdf(font_config=FontConfiguration()) REPORT.Info(_MADEKREPORTS, ks=klass) return pdfBytes
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()
def grades2db(schoolyear, gtable, term=None): """Enter the grades from the given table into the database. <schoolyear> is checked against the value in the info part of the table (gtable.info['SCHOOLYEAR']). <term>, if given, is only used as a check against the value in the info part of the table (gtable.info['TERM']). """ # Check school-year try: y = gtable.info.get('SCHOOLYEAR', '–––') yn = int(y) except ValueError: REPORT.Fail(_INVALID_YEAR, val=y) if yn != schoolyear: REPORT.Fail(_WRONG_YEAR, year=y) # Check term rtag = gtable.info.get('TERM', '–––') if term: if term != rtag: REPORT.Fail(_WRONG_TERM, term=term, termf=rtag) # Check klass klass = Klass(gtable.info.get('CLASS', '–––')) # Check validity pupils = Pupils(schoolyear) try: plist = pupils.classPupils(klass) if not plist: raise ValueError except: REPORT.Fail(_INVALID_KLASS, klass=klass) # Filter the relevant pids p2grades = {} p2stream = {} for pdata in plist: pid = pdata['PID'] try: p2grades[pid] = gtable.pop(pid) except KeyError: # The table may include just a subset of the pupils continue p2stream[pid] = pdata['STREAM'] # Anything left unhandled in <gtable>? for pid in gtable: REPORT.Error(_UNKNOWN_PUPIL, pid=pid) #TODO: Sanitize input ... only valid grades? # Now enter to database if p2grades: db = DB(schoolyear) for pid, grades in p2grades.items(): gstring = map2grades(grades) db.updateOrAdd('GRADES', { 'KLASS': klass.klass, 'STREAM': p2stream[pid], 'PID': pid, 'TERM': rtag, 'REPORT_TYPE': None, 'DATE_D': None, 'GRADES': gstring }, TERM=rtag, PID=pid ) REPORT.Info(_NEWGRADES, n=len(p2grades), klass=klass, year=schoolyear, term=rtag) else: REPORT.Warn(_NOPUPILS)