Index: main/waeup.aaue/trunk/src/waeup/aaue/students/reports/browser_templates/levelreportgeneratorpage.pt
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/reports/browser_templates/levelreportgeneratorpage.pt	(revision 14041)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/reports/browser_templates/levelreportgeneratorpage.pt	(revision 14041)
@@ -0,0 +1,48 @@
+<form  i18n:domain="waeup.kofa"  method="POST">
+  <p i18n:translate="">
+    Please pick the department, session and level
+    for which the report should be generated.
+    The report will be processed and then made available for you
+    to view/download.
+  </p>
+  <br />
+  <label for="session" i18n:translate="">Department</label>
+  <select name="faccode_depcode" class="form-control half">
+    <span tal:repeat="items view/faccode_depcodes" tal:omit-tag="">
+      <option
+          tal:define="name python: items[1]; title python: items[0]"
+          tal:attributes="value name">
+        <span tal:replace="title">TITLE</span>
+      </option>
+    </span>
+  </select>
+  <br />
+  <label for="session">Academic Session</label>
+  <div class="input">
+    <select name="session" class="form-control half">
+      <span tal:repeat="items view/sessions" tal:omit-tag="">
+        <option
+            tal:define="name python: items[1]; title python: items[0]"
+            tal:attributes="value name">
+          <span tal:replace="title">TITLE</span>
+        </option>
+      </span>
+    </select>
+  </div>
+  <br />
+  <label for="session">Level</label>
+  <select name="level" class="form-control half">
+    <span tal:repeat="items view/levels" tal:omit-tag="">
+      <option
+          tal:define="name python: items[1]; title python: items[0]"
+          tal:attributes="value name">
+        <span tal:replace="title">TITLE</span>
+      </option>
+    </span>
+  </select>
+  <br />
+  <input i18n:translate="" type="submit" class="btn btn-primary"
+         name="CREATE" value="Create" />
+  <a class="btn btn-default" href=""
+     tal:attributes="href view/parent_url">Cancel</a>
+</form>
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/reports/raw_score_report.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/reports/raw_score_report.py	(revision 14041)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/reports/raw_score_report.py	(revision 14041)
@@ -0,0 +1,333 @@
+## $Id$
+##
+## Copyright (C) 2013 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+import textwrap
+from zope.i18n import translate
+from zope.catalog.interfaces import ICatalog
+from zope.component import queryUtility, getUtility
+from zope.interface import implementer, Interface, Attribute
+from waeup.kofa.interfaces import (
+    IKofaUtils,
+    academic_sessions_vocab, registration_states_vocab)
+from waeup.kofa.interfaces import MessageFactory as _
+from waeup.kofa.reports import IReport
+from waeup.kofa.browser.pdf import get_signature_tables
+from waeup.kofa.students.vocabularies import StudyLevelSource
+from waeup.kofa.students.reports.student_statistics import (
+    StudentStatisticsReportPDFView)
+
+class IRawScoreReport(IReport):
+
+    session = Attribute('Session to report')
+    level = Attribute('Level to report')
+    faccode = Attribute('Faculty to report')
+    depcode = Attribute('Department to report')
+    creation_dt_string = Attribute('Human readable report creation datetime')
+
+def get_courses(faccode, depcode, level):
+    """Get certificate courses of a certain department at a certain level.
+    """
+    site = grok.getSite()
+    course_codes = []
+    department = site['faculties'][faccode][depcode]
+    for cert in department.certificates.values():
+        for certcourse in cert.values():
+            if certcourse.level == level:
+                course_codes.append(certcourse.getCourseCode())
+    return course_codes
+
+def get_students(faccode, depcode, session, level, course_codes):
+    """Get students in a certain department who registered courses
+    in a certain session at a certain level.
+
+    Returns a list of student data tuples.
+    """
+    cat = queryUtility(ICatalog, name="students_catalog")
+    result = cat.searchResults(
+        depcode = (depcode, depcode), faccode = (faccode, faccode)
+        )
+    table = []
+    for stud in result:
+        if not stud['studycourse'].has_key(str(level)):
+            continue
+        level_obj = stud['studycourse'][str(level)]
+        if level_obj.level_session != session:
+            continue
+        scores = dict()
+        for ticket in level_obj.values():
+            if ticket.code in course_codes:
+                if ticket.score:
+                    scores[ticket.code] = (ticket.score, ticket.grade)
+                else:
+                    scores[ticket.code] = ('N/A', '')
+        line = (
+                stud.matric_number,
+                stud.display_fullname,
+                scores,
+                )
+        table.append(line)
+    return sorted(table, key=lambda value:value[0])
+
+from reportlab.lib import colors
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.lib.units import cm
+from reportlab.platypus import Paragraph, Table, Spacer
+from reportlab.platypus.flowables import Flowable
+from waeup.kofa.reports import IReport, IReportGenerator
+from waeup.kofa.reports import Report
+from waeup.kofa.browser.interfaces import IPDFCreator
+
+STYLE = getSampleStyleSheet()
+
+class TTR(Flowable): #TableTextRotate
+    '''Rotates a text in a table cell.'''
+
+    def __init__(self, text):
+        Flowable.__init__(self)
+        self.text=text
+
+    def draw(self):
+        canvas = self.canv
+        canvas.rotate(45)
+        canvas.drawString( 0, -1, self.text)
+
+    #def wrap(self,aW,aH):
+    #    canv = self.canv
+    #    return canv._leading, canv.stringWidth(self.text)
+
+def tbl_data_to_table(data, course_codes):
+    col_names = (
+            #'Student Id',
+            'S/N',
+            'Matric No.',
+            'Name',
+            #TTR('xxx'),
+            )
+    for code in course_codes:
+        col_names += (TTR(code),)
+    table = [col_names]
+    sn = 1
+    for line in data:
+        composed_line = (sn,) + line[:-1]
+        scores = tuple([
+            '%s%s' % (line[-1][code][0],line[-1][code][1])
+            for code in course_codes])
+        composed_line += scores
+        table.append(composed_line)
+        sn += 1
+    return table
+
+TABLE_STYLE = [
+    ('FONT', (0,0), (-1,-1), 'Helvetica', 8),
+    ('VALIGN', (0, 1), (-1,-1), 'TOP'),
+    #('FONT', (0,0), (0,-1), 'Helvetica-Bold', 8), # 1st column
+    ('FONT', (0,0), (-1,0), 'Helvetica-Bold', 8), # 1st row
+    #('FONT', (0,-1), (-1,-1), 'Helvetica-Bold', 8), # last row
+    #('FONT', (-1,0), (-1,-1), 'Helvetica-Bold', 8), # last column
+    ('ALIGN', (3,1), (-1,-1), 'RIGHT'),
+    ('ALIGN', (0,0), (0,-1), 'RIGHT'),
+    #('ALIGN', (6,0), (6,-1), 'LEFT'),
+    ('INNERGRID', (0,1), (-1,-1), 0.25, colors.black),
+    ('BOX', (0,1), (-1,-1), 1, colors.black),
+    ]
+
+@implementer(IRawScoreReport)
+class RawScoreReport(Report):
+    data = None
+    session = None
+    level = None
+    faccode = None
+    depcode = None
+
+    def __init__(self, faccode, depcode, session, level, author='System'):
+        super(RawScoreReport, self).__init__(
+            args=[faccode, depcode, session, level], kwargs={'author':author})
+        site = grok.getSite()
+        self.studylevelsource = StudyLevelSource().factory
+        self.portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        self.session = academic_sessions_vocab.getTerm(session).title
+        self.level = translate(self.studylevelsource.getTitle(None, int(level)),
+            'waeup.kofa', target_language=self.portal_language)
+        self.levelcode = level
+        self.sessioncode = session
+        self.faccode = faccode
+        self.depcode = depcode
+        self.factitle = site['faculties'][faccode].longtitle
+        self.deptitle = site['faculties'][faccode][depcode].longtitle
+        self.author = author
+        self.creation_dt_string = self.creation_dt.astimezone(
+            getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
+        self.course_codes = get_courses(faccode, depcode, level)
+        self.data = get_students(
+            faccode, depcode, session, level, self.course_codes)
+
+    def create_pdf(self):
+        creator = getUtility(IPDFCreator, name='landscape')
+        table_data = tbl_data_to_table(self.data, self.course_codes)
+        col_widths = [1*cm, 4*cm, 5*cm] + [1*cm] * len(self.course_codes)
+        pdf_data = [Paragraph('<b>%s</b>' % self.creation_dt_string,
+                              STYLE["Normal"]),
+                    Spacer(1, 12),]
+        pdf_data += [Paragraph(
+                    translate(
+                        '${a}<br />${b}<br />Level: ${c}<br />Session: ${d}',
+                        mapping = {'a':self.factitle,
+                                   'b':self.deptitle,
+                                   'c':self.level,
+                                   'd':self.session, }),
+                    STYLE["Normal"]),
+                    Spacer(1, 12),]
+        pdf_data += [
+            Table(table_data, style=TABLE_STYLE, colWidths=col_widths)]
+        doc_title = translate(_('Raw Score Report'))
+
+        pdf_data.append(Spacer(1, 40))
+        signatures = [
+            translate(_('Head of Department')),
+            translate(_('Dean of Faculty'))]
+        signaturetables = get_signature_tables(signatures, landscape=True)
+        pdf_data.append(signaturetables[0])
+
+        pdf = creator.create_pdf(
+            pdf_data, None, doc_title, self.author,
+            doc_title + ' - %s/%s - %s - %s -' % (
+            self.faccode, self.depcode, self.levelcode, self.sessioncode)
+            )
+        return pdf
+
+@implementer(IReportGenerator)
+class RawScoreReportGenerator(grok.GlobalUtility):
+
+    title = _('RAW Score Report')
+    grok.name('raw_score_report')
+
+    def generate(self, site, faccode=None, depcode=None,
+                 session=None, level=None, author=None):
+        result = RawScoreReport(faccode=faccode, depcode=depcode,
+                             session=session, level=level, author=author)
+        return result
+
+###############################################################
+## Browser related stuff
+##
+## XXX: move to local browser module
+###############################################################
+from waeup.kofa.browser.layout import KofaPage
+from waeup.kofa.interfaces import academic_sessions_vocab
+from waeup.kofa.reports import get_generators
+from waeup.kofa.browser.breadcrumbs import Breadcrumb
+grok.templatedir('browser_templates')
+class RawScoreReportGeneratorPage(KofaPage):
+
+    grok.context(RawScoreReportGenerator)
+    grok.name('index.html')
+    grok.require('waeup.handleReports')
+    grok.template('levelreportgeneratorpage')
+
+    label = _('Create raw score report')
+
+    @property
+    def generator_name(self):
+        for name, gen in get_generators():
+            if gen == self.context:
+                return name
+        return None
+
+    def update(self, CREATE=None, faccode_depcode=None,
+               session=None, level=None):
+        self.parent_url = self.url(self.context.__parent__)
+        self._set_session_values()
+        self._set_level_values()
+        self._set_faccode_depcode_values()
+        if CREATE and session:
+            # create a new report job for students by session
+            faccode = faccode_depcode.split('_')[0]
+            depcode = faccode_depcode.split('_')[1]
+            container = self.context.__parent__
+            user_id = self.request.principal.id
+            if level:
+                level = int(level)
+            if session:
+                session = int(session)
+            kw = dict(
+                session=session,
+                level=level,
+                faccode=faccode,
+                depcode=depcode)
+            self.flash(_('New report is being created in background'))
+            job_id = container.start_report_job(
+                self.generator_name, user_id, kw=kw)
+            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+            grok.getSite().logger.info(
+                '%s - report %s created: %s (faculty=%s, department=%s, session=%s, level=%s)' % (
+                ob_class, job_id, self.context.title, faccode, depcode, session, level))
+            self.redirect(self.parent_url)
+            return
+        return
+
+    def _set_session_values(self):
+        vocab_terms = academic_sessions_vocab.by_value.values()
+        self.sessions = [(x.title, x.token) for x in vocab_terms]
+        return
+
+    #def _set_level_values(self):
+    #    vocab_terms = course_levels.by_value.values()
+    #    self.levels = sorted([(x.title, x.token) for x in vocab_terms])
+    #    return
+
+    def _set_level_values(self):
+        portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
+        studylevelsource = StudyLevelSource().factory
+        self.levels = []
+        for code in studylevelsource.getValues(None):
+            title = translate(studylevelsource.getTitle(None, code),
+                'waeup.kofa', target_language=portal_language)
+            self.levels.append((title, code))
+        return
+
+    def _set_faccode_depcode_values(self):
+        faccode_depcodes = []
+        faculties = grok.getSite()['faculties']
+        for fac in faculties.values():
+            for dep in fac.values():
+                faccode_depcodes.append(
+                    ('%s (%s)' %(dep.longtitle, fac.longtitle),
+                     '%s_%s' %(fac.code, dep.code)))
+        self.faccode_depcodes = sorted(
+            faccode_depcodes, key=lambda value: value[0])
+        return
+
+class RawScoreReportPDFView(StudentStatisticsReportPDFView):
+
+    grok.context(IRawScoreReport)
+    grok.name('pdf')
+    grok.require('waeup.handleReports')
+    prefix = 'RawScoreReport'
+
+    def _filename(self):
+        return 'RawScoreReport_%s_%s_%s_%s_%s.pdf' % (
+            self.context.faccode, self.context.depcode,
+            self.context.sessioncode, self.context.levelcode,
+            self.context.creation_dt_string)
+
+class RawScoreReportBreadcrumb(Breadcrumb):
+    """A breadcrumb for reports.
+    """
+    grok.context(RawScoreReportGenerator)
+    title = _(u'Raw Score Report')
+    target = None
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/reports/tests/test_raw_score_report.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/reports/tests/test_raw_score_report.py	(revision 14041)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/reports/tests/test_raw_score_report.py	(revision 14041)
@@ -0,0 +1,56 @@
+import os
+from zc.async.testing import wait_for_result
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component import getUtility, createObject
+from waeup.kofa.interfaces import IJobManager
+from waeup.kofa.students.tests.test_catalog import CatalogTestSetup
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
+from waeup.kofa.browser.tests.test_pdf import samples_dir
+
+from waeup.aaue.students.reports.raw_score_report import (
+    get_students, get_courses, RawScoreReport, IRawScoreReport)
+from waeup.aaue.testing import FunctionalLayer
+
+class RawScoreReportTests(CatalogTestSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(RawScoreReportTests, self).setUp()
+        self.course = createObject('waeup.Course')
+        self.course.code = 'Course1'
+        self.app['faculties']['fac1']['dep1'].courses.addCourse(
+            self.course)
+        self.app['faculties']['fac1']['dep1'].certificates[
+            'CERT1'].addCertCourse(self.course, level=100)
+
+    def test_iface(self):
+        # ensure we fullfill interface contracts
+        obj = RawScoreReport('fac1', 'dep1', 2010, 100)
+        verifyClass(IRawScoreReport, RawScoreReport)
+        verifyObject(IRawScoreReport, obj)
+        return
+
+    def test_get_courses(self):
+        # we can get a list with one course code
+        result = get_courses('fac1', 'dep1', 100)
+        self.assertEqual(result, [(u'Course1')])
+        return
+
+    def test_get_students(self):
+        # we can get a table with one student
+        course_codes = get_courses('fac1', 'dep1', 100)
+        result = get_students('fac1', 'dep1', 2010, 100, course_codes)
+        self.assertEqual(result,
+            [(u'1234', u'TESTER, Bob', {'Course1': (88, 'A')})])
+        return
+
+    def test_create_pdf(self):
+        report = RawScoreReport('fac1', 'dep1', 2010, 100)
+        result = report.create_pdf()
+        self.assertTrue(result.startswith('%PDF-'))
+        path = os.path.join(samples_dir(), 'raw_score_report.pdf')
+        open(path, 'wb').write(result)
+        print "Sample raw_score_report.pdf written to %s" % path
+        return
