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 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): _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 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 textCover(): p = Pupils(_schoolyear) klasses = [k for k in p.classes() if k >= '01' and k < '13'] #TODO: Maybe a validity test for text report classes? #TODO: dateofissue return render_template( 'text_cover_entry.html', schoolyear=str(_schoolyear), dateofissue=Dates.dateConv(_date), klasses=klasses) #['01', '01K', '02', '02K', '03', '03K']
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 klasses(): """View: select school-class (single report generation). """ schoolyear = session['year'] # Collect list of school-classes. # Accept all classes here, then – when it turns out that the class # has no possible templates – show a message indicating this state # of affairs. pupils = Pupils(schoolyear) # List the classes with the oldest pupils first, as these are more # likely to need grades. klasslist = sorted(pupils.classes(), reverse=True) return render_template(os.path.join(_BPNAME, 'klasses.html'), heading=_HEADING, klasses=klasslist)
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 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 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 setupSheet (self): tag1 = self._month.tag () tag2 = self._month.last_tag () # First entry row for a pupil, get its height self._row0 = CONF.ATTENDANCE.attendance_row_pupils.nat () self._rwH = self._table.getRowHeight (self._row0) # Get cell styles from existing cells: self._st_id = self._table.makeStyle ("s0id", CONF.ATTENDANCE.attendance_style_id) self._st_name = self._table.makeStyle ("s0name", CONF.ATTENDANCE.attendance_style_name) self._st_sum = self._table.makeStyle ("s0sum", CONF.ATTENDANCE.attendance_style_sum) self._st_N = self._table.makeStyle ("s0N", CONF.ATTENDANCE.attendance_style_N) self._st_W = self._table.makeStyle ("s0W", CONF.ATTENDANCE.attendance_style_W) self._st_F = self._table.makeStyle ("s0F", CONF.ATTENDANCE.attendance_style_F) self._st_X = self._table.makeStyle ("s0X", CONF.ATTENDANCE.attendance_style_X) self._st_T = self._table.makeStyle ("s0T", CONF.ATTENDANCE.attendance_style_T) # Write date, class and group startyear_cell = CONF.ATTENDANCE.attendance_cell_date1 endyear_cell = CONF.ATTENDANCE.attendance_cell_date2 self._table.setCell (endyear_cell, self._year) if startyear_cell: self._table.setCell (startyear_cell, self._year - 1) classText = "Klasse %s" % self._class self._table.setCell (CONF.ATTENDANCE.attendance_cell_class, classText) # Month sheet self._daystartcol = CONF.ATTENDANCE.attendance_col_daystart self._dayendcol = get_column_letter (column_index_from_string (self._daystartcol) + 30) self._table.setCell (CONF.ATTENDANCE.attendance_cell_classM, classText, sheet = self._wsMonth) # Special formula for summing days: daysum_cell = CONF.ATTENDANCE.attendance_cell_daysum self._validdaysrow = CONF.ATTENDANCE.attendance_row_days self._table.setCell (daysum_cell, formulaS.format ( row = self._validdaysrow, col1 = self._daystartcol, col2 = self._dayendcol), sheet = self._wsMonth) # Set up total school days on summary sheet: self._table.setCell (CONF.ATTENDANCE.attendance_cell_totaldays, formula0.format (sheet1=tag1, sheet2=tag2, cell=daysum_cell)) # Get the start columns for the formula cells dcol0 = column_index_from_string (CONF.ATTENDANCE.attendance_datacol0) dcol1 = column_index_from_string (CONF.ATTENDANCE.attendance_datacol1) ndcols = CONF.ATTENDANCE.attendance_datacols.nat () # Read formula-tags and formula-ids from second sheet, # check tags against first sheet and fetch corresponding formula template: fml = [] fcoderow = CONF.ATTENDANCE.attendance_row_codes fcodelist = [] row = self._row0 for colx in range (ndcols): # The "code" letters appear in both sheets, probably in different # columns, but I assume in the same row. column0 = get_column_letter (dcol0 + colx) column1 = get_column_letter (dcol1 + colx) celltag0 = column0 + fcoderow celltag1 = column1 + fcoderow # "Code" letter for this column: fcode = self._table.getCell (celltag0) fcodelist.append (fcode) if self._table.getCell (celltag1, sheet = self._wsMonth) != fcode: REPORT.Error ("Code-Fehler in Anwesenheitsvorlage %s:\n %s.%s ≠ %s.%s" % (self._tpath, self._wsInfo, celltag0, self._wsMonth, celltag1)) # Now select the formula celltag = column1 + str (row) try: f = formulae [self._table.getCell (celltag, self._wsMonth)] except: REPORT.Error ("Ungültige Formelbezeichnung in Anwesenheitsvorlage %s:\n %s.%s" % (self._tpath, self._wsMonth, celltag)) f = None fml.append (f) # Add pupil rows, and remember rows self._pupilRows = {} pupilDataList = Pupils (self._year).classPupils (self._class) for pdata in pupilDataList: pid = pdata ['PID'] self._pupilRows [pid] = row self._table.setRowHeight (row, self._rwH) self._table.setRowHeight (row, self._rwH, self._wsMonth) n1, n2 = pdata ['FIRSTNAME'], pdata['LASTNAME'] #NOTE: The column of these cells is not a configuration item self._table.setCell ("A%d" % row, pid, self._st_id) self._table.setCell ("B%d" % row, n1, self._st_name) self._table.setCell ("C%d" % row, n2, self._st_name) self._table.setCell ("B%d" % row, n1, self._st_name, sheet = self._wsMonth) self._table.setCell ("C%d" % row, n2, self._st_name, sheet = self._wsMonth) # The formula cells: for colx in range (ndcols): col = get_column_letter (dcol0 + colx) colM = get_column_letter (dcol1 + colx) rowstr = str(row) # Cell in summary page self._table.setCell (col + rowstr, formula0.format (sheet1=tag1, sheet2=tag2, cell=colM + rowstr), self._st_sum) # Cell in month page f = fml [colx] if f: self._table.setCell (colM + rowstr, f.format (row=rowstr, col1 = self._daystartcol, col2 = self._dayendcol, char = fcodelist [colx]), self._st_sum, self._wsMonth) colx += 1 row += 1 self._rowlimit = row
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 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 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 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 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 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()