Esempio n. 1
0
    def __init__(self, schoolyear, rtype, klass):
        """<rtype> is the report type, a key to the mapping
        GRADE.REPORT_TEMPLATES.
        <klass> is a <Klass> object, which may include stream tags.
        All streams passed in must map to the same template.
        """
        self.schoolyear = schoolyear
        self.klassdata = klass

        ### Set up categorized, ordered lists of grade fields for insertion
        ### in a report template.
        # If there is a list of streams in <klass> this will probably
        # only match '*' in the template mapping:
        self.template = getGradeTemplate(rtype, klass)
        self.alltags = getTemplateTags(self.template)
        # Extract grade-entry tags, i.e. those matching <str>_<int>:
        gtags = {}      # {subject group -> [(unsorted) index<int>, ...]}
        for tag in self.alltags:
            try:
                _group, index = tag.split('_')
                group = _group.split('.')[-1]
                i = int(index)
            except:
                continue
            try:
                gtags[group].append(i)
            except:
                gtags[group] = [i]
        # Build a sorted mapping of index lists:
        #   {subject group -> [(reverse sorted) index<int>, ...]}
        # The reversal is for popping in correct order.
        self.sgroup2indexes = {group: sorted(gtags[group], reverse=True)
                for group in gtags}
#        print("\n??? self.alltags", self.alltags)
#        print("\n??? self.sgroup2indexes", self.sgroup2indexes)

        ### Sort the subject tags into ordered groups
        self.courses = CourseTables(schoolyear)
        subjects = self.courses.filterGrades(klass)
        # Build a mapping: {subject group -> [(ordered) sid, ...]}
        self.sgroup2sids = {}
        for group in self.sgroup2indexes:
            sidlist = []
            self.sgroup2sids[group] = sidlist
            # CONF.GRADES.ORDERING: {subject group -> [(ordered) sid, ...]}
            for sid in CONF.GRADES.ORDERING[group]:
                # Include only sids relevant for the klass.
                try:
                    del(subjects[sid])
                    sidlist.append(sid)
                except:
                    pass
        # Entries remaining in <subjects> are not covered in ORDERING.
        for sid in subjects:
            REPORT.Error(_UNGROUPED_SID, sid=sid, tfile=self.template.filename)
Esempio n. 2
0
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
Esempio n. 3
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()
Esempio n. 4
0
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
Esempio n. 5
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
Esempio n. 6
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()
Esempio n. 7
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()
Esempio n. 8
0
class GradeReportData:
    """Manage data connected with a school-class, stream and report type.
    When referring to old report data, bear in mind that a pupil's stream,
    or even school-class, may have changed. The grade data includes the
    class and stream associated with the data.
    """
    def __init__(self, schoolyear, rtype, klass):
        """<rtype> is the report type, a key to the mapping
        GRADE.REPORT_TEMPLATES.
        <klass> is a <Klass> object, which may include stream tags.
        All streams passed in must map to the same template.
        """
        self.schoolyear = schoolyear
        self.klassdata = klass

        ### Set up categorized, ordered lists of grade fields for insertion
        ### in a report template.
        # If there is a list of streams in <klass> this will probably
        # only match '*' in the template mapping:
        self.template = getGradeTemplate(rtype, klass)
        self.alltags = getTemplateTags(self.template)
        # Extract grade-entry tags, i.e. those matching <str>_<int>:
        gtags = {}      # {subject group -> [(unsorted) index<int>, ...]}
        for tag in self.alltags:
            try:
                _group, index = tag.split('_')
                group = _group.split('.')[-1]
                i = int(index)
            except:
                continue
            try:
                gtags[group].append(i)
            except:
                gtags[group] = [i]
        # Build a sorted mapping of index lists:
        #   {subject group -> [(reverse sorted) index<int>, ...]}
        # The reversal is for popping in correct order.
        self.sgroup2indexes = {group: sorted(gtags[group], reverse=True)
                for group in gtags}
#        print("\n??? self.alltags", self.alltags)
#        print("\n??? self.sgroup2indexes", self.sgroup2indexes)

        ### Sort the subject tags into ordered groups
        self.courses = CourseTables(schoolyear)
        subjects = self.courses.filterGrades(klass)
        # Build a mapping: {subject group -> [(ordered) sid, ...]}
        self.sgroup2sids = {}
        for group in self.sgroup2indexes:
            sidlist = []
            self.sgroup2sids[group] = sidlist
            # CONF.GRADES.ORDERING: {subject group -> [(ordered) sid, ...]}
            for sid in CONF.GRADES.ORDERING[group]:
                # Include only sids relevant for the klass.
                try:
                    del(subjects[sid])
                    sidlist.append(sid)
                except:
                    pass
        # Entries remaining in <subjects> are not covered in ORDERING.
        for sid in subjects:
            REPORT.Error(_UNGROUPED_SID, sid=sid, tfile=self.template.filename)


    def getTagmap(self, grades, pname, grademap='GRADES'):
        """Prepare tag mapping for substitution in the report template,
        for the pupil <pname>.
        <grades> is a mapping {sid -> grade}, or something similar which
        can be handled by <dict(grades)>.
        <grademap> is the name of a configuration file (in 'GRADES')
        providing a grade -> text mapping.
        Grouped subjects expected by the template get two entries:
        one for the subject name and one for the grade. They are allocated
        according to the numbered slots defined for the predefined ordering
        (config: GRADES/ORDERING).
        "Grade" entries whose tag begins with '_' and which are not covered
        by the data in GRADES/ORDERING are copied directly to the output
        mapping.
        Return a mapping {template tag -> replacement text}.
        """
        tagmap = {}                     # for the result
        g2text = CONF.GRADES[grademap]  # grade -> text representation
        # Copy the grade mapping, because it will be modified to keep
        # track of unused grade entries:
        gmap = dict(grades)     # this accepts a variety of input types
        for group, sidlist in self.sgroup2sids.items():
            # Copy the indexes because the list is modified here (<pop()>)
            indexes = self.sgroup2indexes[group].copy()
            for sid in sidlist:
                try:
                    g = gmap.pop(sid)
                except:
                    REPORT.Error(_MISSING_GRADE, pname=pname, sid=sid)
                    g = '?'
                if g == _INVALID:
                    continue
                try:
                    i = indexes.pop()
                except:
                    REPORT.Fail(_TOO_MANY_SUBJECTS, group=group,
                            pname=pname, sids=repr(sidlist),
                            template=self.template.filename)
                sname = self.courses.subjectName(sid).split('|')[0].rstrip()
                tagmap["%s_%d_N" % (group, i)] = sname
                g1 = g2text.get(g)
                if not g1:
                    REPORT.Error(_NO_MAPPED_GRADE, grade=g)
                    g1 = '?'
                tagmap["%s_%d" % (group, i)] = g1
            # Process superfluous indexes
            for i in indexes:
                tagmap["%s_%d_N" % (group, i)] = g2text.NONE
                tagmap["%s_%d" % (group, i)] = g2text.NONE
        # Report unused grade entries
        unused = []
        for sid, g in gmap.items():
            if g == _INVALID:
                continue
            if sid[0] == '_':
                tagmap[sid] = g
            else:
                unused.append("%s: %s" % (sid, g))
        if unused:
            REPORT.Error(_UNUSED_GRADES, pname=pname, grades="; ".join(unused))
        return tagmap