Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 12513)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 12515)
@@ -7,4 +7,6 @@
 * Check if p_id exists in payments_catalog when importing payment tickets in
   create mode.
+
+* Student statistic can now be broken down by faculties or by departments.
 
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/reports/browser_templates/studentstatisticsreportgeneratorpage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/reports/browser_templates/studentstatisticsreportgeneratorpage.pt	(revision 12513)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/reports/browser_templates/studentstatisticsreportgeneratorpage.pt	(revision 12515)
@@ -6,4 +6,15 @@
     to view/download.
   </p>
+  <br />
+  <label for="breakdown" i18n:translate="">Breakdown</label>
+    <select name="breakdown" class="form-control half">
+      <span tal:repeat="items view/breakdowns" 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" i18n:translate="">Study Modes Group</label>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/reports/student_statistics.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/reports/student_statistics.py	(revision 12513)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/reports/student_statistics.py	(revision 12515)
@@ -32,12 +32,12 @@
     creation_dt_string = Attribute('Human readable report creation datetime')
 
-def get_student_stats(session, mode):
+def get_student_stats(session, mode, breakdown):
     """Get students in a certain session and study mode.
 
-    Returns a table ordered by faculty code (one per row) and
+    Returns a table ordered by code (faculty or department, one per row) and
     registration state (cols). The result is a 3-tuple representing
-    ((<FACULTY_CODES>), (<STATES>), (<NUM_OF_STUDENTS>)). The
+    ((<CODES>), (<STATES>), (<NUM_OF_STUDENTS>)). The
     (<NUM_OF_STUDENTS>) is an n-tuple with each entry containing the
-    number of students found in that faculty and with the respective
+    number of students found in that faculty/department and with the respective
     state.
 
@@ -54,7 +54,16 @@
     states = tuple([x.value for x in registration_states_vocab])
     states = states + (u'Total',)
-    fac_codes = tuple(sorted([x for x in site['faculties'].keys()],
-                             key=lambda x: x.lower()))
-    fac_codes = fac_codes + (u'Total',)
+    if breakdown == 'faccode':
+        codes = tuple(sorted([x for x in site['faculties'].keys()],
+                                 key=lambda x: x.lower()))
+    elif breakdown == 'depcode':
+        faculties = site['faculties']
+        depcodes = []
+        for fac in faculties.values():
+            for dep in fac.values():
+                depcodes.append(dep.code)
+        codes = tuple(sorted([x for x in depcodes],
+                                 key=lambda x: x.lower()))
+    codes = codes + (u'Total',)
     # XXX: Here we do _one_ query and then examine the single
     #   students. One could also do multiple queries and just look for
@@ -63,13 +72,13 @@
     cat = queryUtility(ICatalog, name="students_catalog")
     result = cat.searchResults(current_session=(session, session))
-    table = [[0 for x in xrange(len(states))] for y in xrange(len(fac_codes))]
+    table = [[0 for x in xrange(len(states))] for y in xrange(len(codes))]
     mode_groups = getUtility(IKofaUtils).MODE_GROUPS
     for stud in result:
         if mode != 'All' and stud.current_mode not in mode_groups[mode]:
             continue
-        if stud.faccode not in fac_codes:
+        if getattr(stud, breakdown) not in codes:
             # studs can have a faccode ``None``
             continue
-        row = fac_codes.index(stud.faccode)
+        row = codes.index(getattr(stud, breakdown))
         col = states.index(stud.state)
         table[row][col] += 1
@@ -79,5 +88,5 @@
     # turn lists into tuples
     table = tuple([tuple(row) for row in table])
-    return (fac_codes, states, table)
+    return (codes, states, table)
 
 from reportlab.lib import colors
@@ -124,13 +133,14 @@
     mode = None
 
-    def __init__(self, session, mode, author='System'):
+    def __init__(self, session, mode, breakdown, author='System'):
         super(StudentStatisticsReport, self).__init__(
-            args=[session, mode], kwargs={'author':author})
+            args=[session, mode, breakdown], kwargs={'author':author})
         self.session = academic_sessions_vocab.getTerm(session).title
         self.mode = mode
+        self.breakdown = breakdown
         self.author = author
         self.creation_dt_string = self.creation_dt.astimezone(
             getUtility(IKofaUtils).tzinfo).strftime("%Y-%m-%d %H:%M:%S %Z")
-        self.data = get_student_stats(session, mode)
+        self.data = get_student_stats(session, mode, breakdown)
 
     def create_pdf(self):
@@ -156,6 +166,7 @@
     grok.name('student_stats')
 
-    def generate(self, site, session=None, mode=None, author=None):
-        result = StudentStatisticsReport(session=session, mode=mode, author=author)
+    def generate(self, site, session=None, mode=None, breakdown=None, author=None):
+        result = StudentStatisticsReport(
+            session=session, mode=mode, breakdown=breakdown, author=author)
         return result
 
@@ -185,8 +196,9 @@
         return None
 
-    def update(self, CREATE=None, session=None, mode=None):
+    def update(self, CREATE=None, session=None, mode=None, breakdown=None):
         self.parent_url = self.url(self.context.__parent__)
         self._set_session_values()
         self._set_mode_values()
+        self._set_breakdown_values()
         if CREATE and session:
             # create a new report job for students by session
@@ -195,5 +207,6 @@
             kw = dict(
                 session=int(session),
-                mode=mode)
+                mode=mode,
+                breakdown=breakdown)
             self.flash(_('New report is being created in background'))
             job_id = container.start_report_job(
@@ -201,6 +214,6 @@
             ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
             grok.getSite().logger.info(
-                '%s - report %s created: %s (session=%s, mode=%s)' % (
-                ob_class, job_id, self.context.title, session, mode))
+                '%s - report %s created: %s (session=%s, mode=%s, breakdown=%s)' % (
+                ob_class, job_id, self.context.title, session, mode, breakdown))
             self.redirect(self.parent_url)
             return
@@ -215,4 +228,8 @@
         mode_groups = getUtility(IKofaUtils).MODE_GROUPS
         self.modes = sorted([(key, key) for key in mode_groups.keys()])
+        return
+
+    def _set_breakdown_values(self):
+        self.breakdowns = [('Faculties', 'faccode'), ('Departments', 'depcode')]
         return
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 12513)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 12515)
@@ -24,5 +24,6 @@
     def test_iface(self):
         # ensure we fullfill interface contracts
-        obj = StudentStatisticsReport(2010, 'Undergraduate Full-Time')
+        obj = StudentStatisticsReport(
+            2010, 'Undergraduate Full-Time', 'faccode')
         verifyClass(IStudentStatisticsReport, StudentStatisticsReport)
         verifyObject(IStudentStatisticsReport, obj)
@@ -31,6 +32,6 @@
     def test_get_student_stats_session_simple(self):
         # we can get a table with one student
-        result1 = get_student_stats(2010, 'Undergraduate Full-Time')
-        result2 = get_student_stats(2009, 'Undergraduate Full-Time')
+        result1 = get_student_stats(2010, 'Undergraduate Full-Time', 'faccode')
+        result2 = get_student_stats(2009, 'Undergraduate Full-Time', 'faccode')
         self.assertEqual(
             result1,
@@ -49,7 +50,7 @@
     def test_get_student_stats_session_multiple(self):
         # we can get a table with several students
-        self.create_cert(u'fac2', u'dept2', u'CERT2')
-        result1 = get_student_stats(2010, 'Undergraduate Full-Time')
-        result2 = get_student_stats(2009, 'Undergraduate Full-Time')
+        self.create_cert(u'fac2', u'dep2', u'CERT2')
+        result1 = get_student_stats(2010, 'Undergraduate Full-Time', 'faccode')
+        result2 = get_student_stats(2009, 'Undergraduate Full-Time', 'faccode')
         self.assertEqual(
             result1,
@@ -68,7 +69,29 @@
         return
 
+    def test_get_student_stats_session_multiple_dep_breakdown(self):
+        # we can get a table with several students
+        self.create_cert(u'fac2', u'dep2', u'CERT2')
+        result1 = get_student_stats(2010, 'Undergraduate Full-Time', 'depcode')
+        result2 = get_student_stats(2009, 'Undergraduate Full-Time', 'depcode')
+        self.assertEqual(
+            result1,
+            ((u'dep1', u'dep2', u'Total'),
+             self.states,
+             ((1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),)))
+        self.assertEqual(
+            result2,
+            ((u'dep1', u'dep2', u'Total'),
+             self.states,
+             ((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+              (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),)))
+        return
+
     def test_create_pdf(self):
         self.create_cert(u'FAC2', u'dept2', u'CERT2')
-        report = StudentStatisticsReport(2010, 'Undergraduate Full-Time')
+        report = StudentStatisticsReport(
+            2010, 'Undergraduate Full-Time', 'faccode')
         result = report.create_pdf()
         self.assertTrue(result.startswith('%PDF-'))
@@ -104,4 +127,5 @@
         self.browser.getControl(name="generator").value = ['student_stats']
         self.browser.getControl("Configure").click()
+        self.browser.getControl(name="breakdown").value = ['depcode']
         self.browser.getControl(name="mode").value = ['All']
         self.browser.getControl(name="session").value = ['2004']
@@ -134,5 +158,5 @@
         self.assertTrue(
             'INFO - zope.mgr - students.reports.student_statistics.StudentStatisticsReportGeneratorPage - '
-            'report %s created: Student Statistics (session=2004, mode=All)'
+            'report %s created: Student Statistics (session=2004, mode=All, breakdown=depcode)'
             % job_id in logcontent
             )
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 12513)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 12515)
@@ -288,5 +288,4 @@
         # XXX: there is no addPayment method to give predictable names
         self.payment = student['payments']['my-payment'] = payment
-        notify(grok.ObjectModifiedEvent(self.payment))
         return payment
 
