def test_01 (): _term = '1' _date = '2016-01-29' for _klass, _pid in ('13', '200301'), ('12.RS', '200403'): klass = Klass(_klass) REPORT.Test("Reading basic grade data for class %s" % klass) #TODO: This might not be the optimal location for this file. filepath = Paths.getYearPath(_testyear, 'FILE_GRADE_TABLE', term=_term).replace('*', str(klass).replace('.', '-')) pgrades = readGradeTable(filepath) REPORT.Test(" ++ INFO: %s" % repr(pgrades.info)) for pid, grades in pgrades.items(): REPORT.Test("\n -- %s: %s\n" % (pid, repr(grades))) REPORT.Test("\nReading template data for class %s" % klass) # Get the report type from the term and klass/stream _rtype = klass.match_map(CONF.GRADES.REPORT_TEMPLATES['_' + _term]) gradedata = GradeReportData(_testyear, _rtype, klass) REPORT.Test(" Indexes:\n %s" % repr(gradedata.sgroup2indexes)) REPORT.Test(" Grade tags:\n %s" % repr(gradedata.sgroup2sids)) grademap = klass.match_map(CONF.MISC.GRADE_SCALE) REPORT.Test("\nTemplate grade map for pupil %s (using %s)" % (_pid, grademap)) tagmap = gradedata.getTagmap(pgrades[_pid], "Pupil_%s" % _pid, grademap) REPORT.Test(" Grade tags:\n %s" % repr(tagmap))
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 test_05(): _klass_stream = Klass('11') pdfBytes = makeReports(_year, _term, _klass_stream, _date) folder = Paths.getUserPath('DIR_GRADE_REPORT_TEMPLATES') fpath = os.path.join(folder, 'test_%s_%s.pdf' % (_klass_stream, _date)) with open(fpath, 'wb') as fh: fh.write(pdfBytes)
def klassview(termn, klass_stream): """View: Handle report generation for a group of pupils. This is specific to the selected term. A list of pupils with checkboxes is displayed, so that some can be deselected. Also the date-of-issue can be set. """ class _Form(FlaskForm): DATE_D = DateField('Ausgabedatum', validators=[InputRequired()]) schoolyear = session['year'] klass = Klass(klass_stream) form = _Form() if form.validate_on_submit(): # POST _d = form.DATE_D.data.isoformat() pids = request.form.getlist('Pupil') if pids: pdfBytes = REPORT.wrap(makeReports, schoolyear, termn, klass, _d, pids) session['filebytes'] = pdfBytes session['download'] = 'Notenzeugnis_%s.pdf' % klass return redirect(url_for('bp_grades.term', termn=termn)) else: flash("** Keine Schüler ... **", "Warning") # GET form.DATE_D.data = datetime.date.today() pdlist = db2grades(schoolyear, termn, klass, checkonly=True) return render_template(os.path.join(_BPNAME, 'klass.html'), form=form, heading=_HEADING, termn=termn, klass_stream=klass, pupils=pdlist)
def test_02(): pdfBytes = makeSheets(_year, _date, Klass(_klass)) folder = Paths.getUserPath('DIR_TEXT_REPORT_TEMPLATES') fpath = os.path.join(folder, 'test.pdf') with open(fpath, 'wb') as fh: fh.write(pdfBytes) REPORT.Test(" --> %s" % fpath)
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 test_01(): klass = Klass('13') bytefile = choiceTable(_testyear, klass) filepath = Paths.getYearPath(_testyear, 'FILE_SUBJECT_CHOICE_TABLE', make=-1).replace('*', str(klass).replace('.', '-')) + '.xlsx' with open(filepath, 'wb') as fh: fh.write(bytefile) REPORT.Test(" --> %s" % filepath)
def test_07(): # Reports for second term _term = '2' _date = '2016-06-22' for _ks in '11', '12.RS-HS-_', '12.Gym': _klass_stream = Klass(_ks) pdfBytes = makeReports(_year, _term, _klass_stream, _date) folder = Paths.getUserPath('DIR_GRADE_REPORT_TEMPLATES') fpath = os.path.join(folder, 'test_%s_%s.pdf' % (_klass_stream, _date)) with open(fpath, 'wb') as fh: fh.write(pdfBytes)
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 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 index(): schoolyear = session['year'] form = DateForm() if form.validate_on_submit(): # POST # Store date of issue _date = form.getDate() db = DB(schoolyear) db.setInfo('TEXT_DATE_OF_ISSUE', _date) # GET form.defaultIssueDate(schoolyear) p = Pupils(schoolyear) _kmap = CONF.TEXT.REPORT_TEMPLATES['Mantelbogen'] klasses = [] for k in p.classes(): klass = Klass(k) if klass.match_map(_kmap): klasses.append(str(klass)) return render_template(os.path.join(_BPNAME, 'index.html'), form=form, heading=_HEADING, klasses=klasses)
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 test_02(): _term = '2' for ks in '10', '11.Gym', '11.RS-HS-_', '12.RS-HS', '12.Gym', '13': klass = Klass(ks) bytefile = makeGradeTable(_testyear, _term, klass, "Noten: 1. Halbjahr") filepath = Paths.getYearPath(_testyear, 'FILE_GRADE_FULL', make=-1, term=_term).replace('*', str(klass).replace('.', '-')) + '.xlsx' with open(filepath, 'wb') as fh: fh.write(bytefile) REPORT.Test(" --> %s" % filepath) bytefile = stripTable(_testyear, _term, klass, "Noten: 1. Halbjahr") filepath = Paths.getYearPath(_testyear, 'FILE_GRADE_INPUT', make=-1, term=_term).replace('*', str(klass).replace('.', '-')) + '.xlsx' with open(filepath, 'wb') as fh: fh.write(bytefile) REPORT.Test(" --> %s" % filepath)
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 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 makeOneSheet(schoolyear, date, pdata, term, rtype): """ <schoolyear>: year in which school-year ends (int) <data>: date of issue ('YYYY-MM-DD') <pdata>: a <PupilData> instance for the pupil whose report is to be built <term>: keys the grades in the database (term or date) <rtype>: report category, determines template """ pid = pdata['PID'] # Read database entry for the grades gradedata = getGradeData(schoolyear, pid, term) gmap = gradedata['GRADES'] # grade mapping pname = pdata.name() # <GradeReportData> manages the report template, etc.: # From here on use klass and stream from <gradedata> klass = Klass.fromKandS(gradedata['KLASS'], gradedata['STREAM']) reportData = GradeReportData(schoolyear, rtype, klass) # Get the name of the relevant configuration file in folder GRADES: grademap = klass.match_map(CONF.MISC.GRADE_SCALE) # Build a grade mapping for the tags of the template: pdata.grades = reportData.getTagmap(gmap, pname, grademap) # Update grade database if term != date: updateGradeReport(schoolyear, pid, term, date=date, rtype=rtype) ### Generate html for the reports source = reportData.template.render(report_type=rtype, SCHOOLYEAR=printSchoolYear(schoolyear), DATE_D=date, todate=Dates.dateConv, STREAM=printStream, pupils=[pdata], klass=klass) # Convert to pdf html = HTML(string=source, base_url=os.path.dirname(reportData.template.filename)) pdfBytes = html.write_pdf(font_config=FontConfiguration()) REPORT.Info(_MADEPREPORT, pupil=pname) return pdfBytes
def pupil(pid): """View: select report type and [edit-existing vs. new] for single report. All existing report dates for this pupil will be presented for selection. If there are no existing dates for this pupil, the only option is to construct a new one. Also a report type can be selected. The list might include invalid types as it is difficult at this stage (considering potential changes of stream or even school-class) to determine exactly which ones are valid. """ class _Form(FlaskForm): KLASS = SelectField("Klasse") STREAM = SelectField("Maßstab") EDITNEW = SelectField("Ausgabedatum") RTYPE = SelectField("Zeugnistyp") schoolyear = session['year'] # Get pupil data pupils = Pupils(schoolyear) pdata = pupils.pupil(pid) pname = pdata.name() klass = pdata.getKlass(withStream=True) # Get existing dates. db = DB(schoolyear) rows = db.select('GRADES', PID=pid) dates = [_NEWDATE] for row in db.select('GRADES', PID=pid): dates.append(row['TERM']) # If the stream, or even school-class have changed since an # existing report, the templates and available report types may be # different. To keep it simple, a list of all report types from the # configuration file GRADES.REPORT_TEMPLATES is presented for selection. # An invalid choice can be flagged at the next step. # If there is a mismatch between school-class/stream of the pupil as # selected on this page and that of the existing GRADES entry, a # warning can be shown at the next step. rtypes = [ rtype for rtype in CONF.GRADES.REPORT_TEMPLATES if rtype[0] != '_' ] kname = klass.klass stream = klass.stream form = _Form(KLASS=kname, STREAM=stream, RTYPE=_DEFAULT_RTYPE) form.KLASS.choices = [(k, k) for k in reversed(pupils.classes())] form.STREAM.choices = [(s, s) for s in CONF.GROUPS.STREAMS] form.EDITNEW.choices = [(d, d) for d in dates] form.RTYPE.choices = [(t, t) for t in rtypes] if form.validate_on_submit(): # POST klass = Klass.fromKandS(form.KLASS.data, form.STREAM.data) rtag = form.EDITNEW.data rtype = form.RTYPE.data kmap = CONF.GRADES.REPORT_TEMPLATES[rtype] tfile = klass.match_map(kmap) if tfile: return redirect( url_for('bp_grades.make1', pid=pid, kname=klass, rtag=rtag, rtype=rtype)) else: flash( "Zeugnistyp '%s' nicht möglich für Gruppe %s" % (rtype, klass), "Error") # GET return render_template(os.path.join(_BPNAME, 'pupil.html'), form=form, heading=_HEADING, klass=kname, pname=pname)
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)
def make1(pid, rtype, rtag, kname): """View: Edit data for the report to be created, submit to build it. <rtype> is the report type. <rtag> is a TERM field entry from the GRADES table (term or date). <kname> is the school-class with stream tag. """ class _Form(FlaskForm): DATE_D = DateField('Ausgabedatum', validators=[InputRequired()]) def prepare(): # Get the name of the relevant grade scale configuration file: grademap = klass.match_map(CONF.MISC.GRADE_SCALE) gradechoices = [(g, g) for g in CONF.GRADES[grademap].VALID] # Get existing grades grades = getGradeData(schoolyear, pid, rtag) ### Get template fields which need to be set here gdata = GradeReportData(schoolyear, rtype, klass) groups = [] for sgroup in sorted(gdata.sgroup2sids): # grouped subject-ids fields = [] for sid in gdata.sgroup2sids[sgroup]: if sid.startswith('__'): gcalc.append(sid) continue sname = gdata.courses.subjectName(sid) try: grade = grades['GRADES'][sid] except: grade = '/' sfield = SelectField(sname, choices=gradechoices, default=grade) key = sgroup + '_' + sid setattr(_Form, key, sfield) fields.append(key) if fields: groups.append((sgroup, fields)) # "Extra" fields like "_GS" (one initial underline!) xfields = [] # Build roughly as for subjects, but in group <None> for tag in gdata.alltags: if tag.startswith('grades._'): xfield = tag.split('.', 1)[1] if xfield[1] == '_': # Calculated fields should not be presented here continue # Get options/field type try: xfconf = CONF.GRADES.XFIELDS[xfield] values = xfconf.VALUES except: flash( "Feld %s unbekannt: Vorlage %s" % (xfield, gdata.template.filename), "Error") continue # Check class/report-type validity rtypes = klass.match_map(xfconf.KLASS) if not rtypes: continue if rtype not in rtypes.split(): continue # Get existing value for this field try: val = grades['GRADES'][xfield] except: val = None # Determine field type if xfield.endswith('_D'): # An optional date field: d = datetime.date.fromisoformat(val) if val else None sfield = DateField(xfconf.NAME, default=d, validators=[Optional()]) else: try: # Assume a select field. # The choices depend on the tag. choices = [(c, c) for c in xfconf.VALUES] sfield = SelectField(xfconf.NAME, choices=choices, default=val) except: flash( "Unbekannter Feldtyp: %s in Vorlage %s" % (xfield, gdata.template.filename), "Error") continue key = 'Z_' + xfield setattr(_Form, key, sfield) xfields.append(key) if xfields: groups.append((None, xfields)) return groups def enterGrades(): # Add calculated grade entries gradeCalc(gmap, gcalc) # Enter grade data into db singleGrades2db(schoolyear, pid, klass, term=rtag, date=DATE_D, rtype=rtype, grades=gmap) return True schoolyear = session['year'] klass = Klass(kname) gcalc = [] # list of composite sids groups = REPORT.wrap(prepare, suppressok=True) if not groups: # Return to caller return redirect(request.referrer) # Get pupil data pupils = Pupils(schoolyear) pdata = pupils.pupil(pid) pname = pdata.name() form = _Form() if form.validate_on_submit(): # POST DATE_D = form.DATE_D.data.isoformat() gmap = {} # grade mapping {sid -> "grade"} for g, keys in groups: for key in keys: gmap[key.split('_', 1)[1]] = form[key].data if REPORT.wrap(enterGrades, suppressok=True): pdfBytes = REPORT.wrap(makeOneSheet, schoolyear, DATE_D, pdata, rtag, rtype) session['filebytes'] = pdfBytes session['download'] = 'Notenzeugnis_%s.pdf' % ( pdata['PSORT'].replace(' ', '_')) return redirect(url_for('bp_grades.pupils', klass=klass.klass)) #TODO: ? # There is no point to +/-, as these won't appear in the report and # are only intended for Notenkonferenzen. However, they might be of # interest for future reference? # GET # Set initial date of issue try: form.DATE_D.data = datetime.date.fromisoformat(rtag) except ValueError: form.DATE_D.data = datetime.date.today() return render_template(os.path.join(_BPNAME, 'make1.html'), form=form, groups=groups, heading=_HEADING, pid=pid, pname=pname, rtype=rtype, klass=klass.klass, stream=klass.stream)
def test_01(): template = getTextTemplate('Mantelbogen', Klass(_klass)) tags = getTemplateTags(template) REPORT.Test("Pupil fields: %s" % repr(pupilFields(tags)))