Пример #1
0
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])
Пример #2
0
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])
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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']
Пример #6
0
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',))
Пример #7
0
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)
Пример #8
0
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)
Пример #9
0
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)
Пример #10
0
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()
Пример #11
0
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)
Пример #12
0
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)
Пример #13
0
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
Пример #14
0
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)
Пример #15
0
    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
Пример #16
0
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
Пример #17
0
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
Пример #18
0
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)
Пример #19
0
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)
Пример #20
0
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)
Пример #21
0
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()
Пример #22
0
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()