~ubuntu-branches/ubuntu/trusty/schooltool.lyceum.journal/trusty-proposed

« back to all changes in this revision

Viewing changes to src/schooltool/lyceum/journal/browser/journal.py

  • Committer: Gediminas Paulauskas
  • Date: 2013-10-10 17:57:54 UTC
  • mto: (26.1.1 schooltool.lyceum.journal)
  • mto: This revision was merged to the branch mainline in revision 24.
  • Revision ID: menesis@pov.lt-20131010175754-zs6peynqx637imn1
Tags: upstream-2.6.0
ImportĀ upstreamĀ versionĀ 2.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
from schooltool.basicperson.interfaces import IDemographics
57
57
from schooltool.course.interfaces import ILearner, IInstructor
58
58
from schooltool.common.inlinept import InlineViewPageTemplate
 
59
from schooltool.common import SchoolToolMessage as s_
59
60
from schooltool.export import export
60
61
from schooltool.person.interfaces import IPerson
 
62
from schooltool.person.interfaces import IPersonFactory
61
63
from schooltool.app.browser.cal import month_names
62
64
from schooltool.app.interfaces import IApplicationPreferences
63
65
from schooltool.app.interfaces import ISchoolToolApplication
102
104
from schooltool.lyceum.journal import LyceumMessage as _
103
105
 
104
106
 
105
 
# set up translation from data base data to locale representation and back
106
 
ABSENT_LETTER = translate(_(u"Single letter that represents an absent mark for a student",
107
 
                           default='a'))
108
 
TARDY_LETTER = translate(_(u"Single letter that represents an tardy mark for a student",
109
 
                          default='t'))
110
 
 
111
107
JournalCSSViewlet = CSSViewlet("journal.css")
112
108
 
113
109
 
425
421
        return '<span>%s</span>' % translate(_("Excused"),
426
422
                                             context=formatter.request)
427
423
 
428
 
def journal_grades():
429
 
    grades = [
430
 
        {'keys': [ABSENT_LETTER.lower(), ABSENT_LETTER.upper()],
431
 
         'value': ABSENT_LETTER,
432
 
         'legend': _('Absent')},
433
 
        {'keys': [TARDY_LETTER.lower(), TARDY_LETTER.upper()],
434
 
         'value': TARDY_LETTER,
435
 
         'legend': _('Tardy')}]
436
 
    for i in range(9):
437
 
        grades.append({'keys': [chr(i + ord('1'))],
438
 
                       'value': unicode(i+1),
439
 
                       'legend': u''})
440
 
    grades.append({'keys': ['0'],
441
 
                   'value': u'10',
442
 
                   'legend': u''})
443
 
    return grades
444
 
 
445
 
 
446
 
class SectionJournalJSView(BrowserView):
447
 
 
448
 
    def grading_events(self):
449
 
        for grade in journal_grades():
450
 
            event_check = ' || '.join([
451
 
                'event.which == %d' % ord(key)
452
 
                for key in grade['keys']])
453
 
            yield {'js_condition': event_check,
454
 
                   'grade_value': "'%s'" % grade['value']}
455
 
 
456
 
 
457
424
class StudentSelectionMixin(object):
458
425
 
459
426
    selected_students = None
510
477
 
511
478
        return self.template()
512
479
 
513
 
    def getLegendItems(self):
514
 
        for grade in journal_grades():
515
 
            yield {'keys': u', '.join(grade['keys']),
516
 
                   'value': grade['value'],
517
 
                   'description': grade['legend']}
 
480
    @Lazy
 
481
    def timezone(self):
 
482
        prefs = IApplicationPreferences(ISchoolToolApplication(None))
 
483
        return pytz.timezone(prefs.timezone)
518
484
 
519
485
    def encodedSelectedEventId(self):
520
486
        event = self.selectedEvent()
551
517
    def meetings(self):
552
518
        for event in self.all_meetings:
553
519
            insecure_event = removeSecurityProxy(event)
554
 
            if insecure_event.dtstart.date().month == self.active_month:
 
520
            meeting_start = insecure_event.dtstart.astimezone(self.timezone)
 
521
            if meeting_start.month == self.active_month:
555
522
                yield event
556
523
 
557
524
    def members(self):
558
 
        members = list(self.context.members)
559
525
        collator = ICollator(self.request.locale)
560
 
        members.sort(key=lambda a: collator.key(
561
 
                removeSecurityProxy(a).first_name))
562
 
        members.sort(key=lambda a: collator.key(
563
 
                removeSecurityProxy(a).last_name))
564
 
        return members
 
526
        factory = getUtility(IPersonFactory)
 
527
        sorting_key = lambda x: factory.getSortingKey(x, collator)
 
528
        return sorted(self.context.members, key=sorting_key)
565
529
 
566
530
    def updateGradebook(self):
567
531
        members = self.members()
651
615
        month = -1
652
616
        for meeting in self.all_meetings:
653
617
            insecure_meeting = removeSecurityProxy(meeting)
654
 
            # XXX: what about time zones?
655
 
            if insecure_meeting.dtstart.date().month != month:
656
 
                yield insecure_meeting.dtstart.date().month
657
 
                month = insecure_meeting.dtstart.date().month
 
618
            meeting_start = insecure_meeting.dtstart.astimezone(self.timezone)
 
619
            if meeting_start.month != month:
 
620
                yield meeting_start.month
 
621
                month = meeting_start.month
658
622
 
659
623
    @Lazy
660
624
    def selected_months(self):
689
653
 
690
654
        for meeting in self.all_meetings:
691
655
            insecure_meeting = removeSecurityProxy(meeting)
692
 
            if insecure_meeting.dtstart.date().month == selected_month:
693
 
                return insecure_meeting.dtstart.year
 
656
            meeting_start = insecure_meeting.dtstart.astimezone(self.timezone)
 
657
            if meeting_start.month == selected_month:
 
658
                return meeting_start.year
694
659
 
695
660
    @Lazy
696
661
    def active_month(self):
937
902
        result = []
938
903
        for event in self.all_meetings:
939
904
            insecure_event = removeSecurityProxy(event)
940
 
            if insecure_event.dtstart.date().month == self.active_month:
 
905
            meeting_start = insecure_event.dtstart.astimezone(self.timezone)
 
906
            if meeting_start.month == self.active_month:
941
907
                result.append(event)
942
908
        return result
943
909
 
1027
993
            return
1028
994
        setCurrentJournalMode(person, self.journal_mode)
1029
995
 
 
996
    @Lazy
 
997
    def name_sorting_columns(self):
 
998
        return getUtility(IPersonFactory).columns()
 
999
 
1030
1000
 
1031
1001
class FlourishLyceumSectionJournalGrades(FlourishLyceumSectionJournalBase):
1032
1002
 
1076
1046
    def table(self):
1077
1047
        result = []
1078
1048
        collator = ICollator(self.request.locale)
 
1049
        factory = getUtility(IPersonFactory)
 
1050
        self.sortBy = self.request.get('sort_by')
 
1051
        if self.sortBy == 'last_name':
 
1052
            sorting_key = lambda x: (collator.key(x.last_name),
 
1053
                                     collator.key(x.first_name))
 
1054
        elif self.sortBy == 'first_name':
 
1055
            sorting_key = lambda x: (collator.key(x.first_name),
 
1056
                                     collator.key(x.last_name))
 
1057
        else:
 
1058
            sorting_key = lambda x: factory.getSortingKey(x, collator)
1079
1059
        for person in self.members():
1080
1060
            grades = []
1081
1061
            for meeting in self.meetings:
1094
1074
                person = removeSecurityProxy(person)
1095
1075
            result.append(
1096
1076
                {'student': {'title': person.title,
 
1077
                             'first_name': person.first_name,
 
1078
                             'last_name': person.last_name,
1097
1079
                             'id': person.username,
1098
 
                             'sortKey': collator.key(person.title),
 
1080
                             'sortKey': sorting_key(person),
1099
1081
                             'url': absoluteURL(person, self.request)},
1100
1082
                 'grades': grades,
1101
1083
                 'has_hints': any([g['hint'] for g in grades]),
1102
1084
                 'average': self.average(person),
1103
1085
                })
1104
 
        self.sortBy = self.request.get('sort_by')
1105
1086
        return sorted(result, key=self.sortKey)
1106
1087
 
1107
1088
    def sortKey(self, row):
1108
 
        if self.sortBy == 'student':
 
1089
        if self.sortBy in ('student', 'first_name', 'last_name'):
1109
1090
            return row['student']['sortKey']
1110
1091
        elif self.sortBy == 'average':
1111
1092
            try:
1143
1124
        for label, abbr, value, percent in scoresystem.scores:
1144
1125
            title = label
1145
1126
            if abbr:
1146
 
                title += ': %s' % abbr
 
1127
                title += ': %s' % translate(abbr, context=self.request)
1147
1128
            result.append({
1148
1129
                'label': title,
1149
1130
                'value': label,
1183
1164
        for label, abbr in scoresystem.scores:
1184
1165
            title = label
1185
1166
            if abbr:
1186
 
                title += ': %s' % abbr
 
1167
                title += ': %s' % translate(abbr, context=self.request)
1187
1168
            result.append({
1188
1169
                'label': title,
1189
1170
                'value': label,
1194
1175
    def table(self):
1195
1176
        result = []
1196
1177
        collator = ICollator(self.request.locale)
 
1178
        factory = getUtility(IPersonFactory)
 
1179
        self.sortBy = self.request.get('sort_by')
 
1180
        if self.sortBy == 'last_name':
 
1181
            sorting_key = lambda x: (collator.key(x.last_name),
 
1182
                                     collator.key(x.first_name))
 
1183
        elif self.sortBy == 'first_name':
 
1184
            sorting_key = lambda x: (collator.key(x.first_name),
 
1185
                                     collator.key(x.last_name))
 
1186
        else:
 
1187
            sorting_key = lambda x: factory.getSortingKey(x, collator)
1197
1188
        for person in self.members():
1198
1189
            grades = []
1199
1190
            for meeting in self.meetings:
1213
1204
            excused, excusable = self.excused(person)
1214
1205
            result.append(
1215
1206
                {'student': {'title': person.title,
 
1207
                             'first_name': person.first_name,
 
1208
                             'last_name': person.last_name,
1216
1209
                             'id': person.username,
1217
 
                             'sortKey': collator.key(person.title),
 
1210
                             'sortKey': sorting_key(person),
1218
1211
                             'url': absoluteURL(person, self.request)},
1219
1212
                 'grades': grades,
1220
1213
                 'absences': self.absences(person),
1226
1219
                 'unexcused': excused - excusable if excusable else 1,
1227
1220
                 'has_hints': any([g['hint'] for g in grades]),
1228
1221
                })
1229
 
        self.sortBy = self.request.get('sort_by')
1230
1222
        return sorted(result, key=self.sortKey)
1231
1223
 
1232
1224
    def sortKey(self, row):
1233
 
        if self.sortBy == 'student':
 
1225
        if self.sortBy in ('student', 'first_name', 'last_name'):
1234
1226
            return row['student']['sortKey']
1235
1227
        elif self.sortBy == 'absences':
1236
1228
            return (int(row['absences']), row['student']['sortKey'])
1617
1609
        for grade, title in score_system.scores:
1618
1610
            meaning = []
1619
1611
            if grade in score_system.tag_absent:
1620
 
                meaning.append(translate(_('Absent'), self.request))
 
1612
                meaning.append(translate(_('Absent'), context=self.request))
1621
1613
            if grade in score_system.tag_tardy:
1622
 
                meaning.append(translate(_('Tardy'), self.request))
 
1614
                meaning.append(translate(_('Tardy'), context=self.request))
1623
1615
            if not meaning:
1624
 
                meaning.append(translate(_('Present'), self.request))
 
1616
                meaning.append(translate(_('Present'), context=self.request))
1625
1617
            if grade in score_system.tag_excused:
1626
 
                meaning.append(translate(_('Excused'), self.request))
 
1618
                meaning.append(translate(_('Excused'), context=self.request))
1627
1619
            yield {'value': grade,
1628
1620
                   'description': title,
1629
1621
                   'meaning': ', '.join(meaning)
1762
1754
    def translate(self, message):
1763
1755
        return translate(message, context=self.request)
1764
1756
 
 
1757
    @Lazy
 
1758
    def name_sorting_columns(self):
 
1759
        return getUtility(IPersonFactory).columns()
 
1760
 
1765
1761
    def __call__(self):
 
1762
        column_id = self.request.get('column_id')
 
1763
        for column in self.name_sorting_columns:
 
1764
            if column.name == column_id:
 
1765
                break
1766
1766
        result = {
1767
 
            'header': self.translate(_('Name')),
 
1767
            'header': self.translate(column.title),
1768
1768
            'options': [
1769
1769
                {
1770
1770
                    'label': self.translate(_('Sort by')),
1771
 
                    'url': '?sort_by=student',
 
1771
                    'url': '?sort_by=%s' % column_id,
1772
1772
                    }
1773
1773
                ],
1774
1774
            }
1908
1908
        result = []
1909
1909
        for event in self.all_meetings:
1910
1910
            insecure_event = removeSecurityProxy(event)
1911
 
            if insecure_event.dtstart.date().month == self.active_month:
 
1911
            meeting_start = insecure_event.dtstart.astimezone(self.tzinfo)
 
1912
            if meeting_start.month == self.active_month:
1912
1913
                result.append(event)
1913
1914
        return result
1914
1915
 
1969
1970
    result.append({
1970
1971
            'id': 'journal-mode-attendance',
1971
1972
            'label': _('Attendance'),
1972
 
            'url': journal_url + '/index.html',
 
1973
            'url': journal_url,
1973
1974
            })
1974
1975
 
1975
1976
    takes_day_attendance = True
2216
2217
            description = dict(requirement.score_system.scores).get(grade, u'')
2217
2218
        else:
2218
2219
            description = ''
2219
 
        result = ' - '.join([translate(i, self.request)
 
2220
        result = ' - '.join([translate(i, context=self.request)
2220
2221
                             for i in (grade, description)])
2221
2222
        return result
2222
2223
 
2326
2327
      </div>
2327
2328
    ''')
2328
2329
 
 
2330
    container_class = 'container widecontainer'
 
2331
 
2329
2332
    @Lazy
2330
2333
    def year(self):
2331
2334
        year = self.request.get('year', '').strip()
2408
2411
        self.updateJournalMode()
2409
2412
        super(FlourishSchoolAttendanceView, self).update()
2410
2413
 
 
2414
    @Lazy
 
2415
    def name_sorting_columns(self):
 
2416
        return getUtility(IPersonFactory).columns()
 
2417
 
2411
2418
 
2412
2419
class AttendanceFilter(table.ajax.IndexedTableFilter):
2413
2420
 
2491
2498
    form_id = 'grid-form'
2492
2499
 
2493
2500
    def columns(self):
2494
 
        first_name = table.column.IndexedLocaleAwareGetterColumn(
2495
 
            index='first_name',
2496
 
            name='first_name',
2497
 
            title=_(u'First Name'),
2498
 
            getter=lambda i, f: i.first_name,
2499
 
            subsort=True)
2500
 
        last_name = table.column.IndexedLocaleAwareGetterColumn(
2501
 
            index='last_name',
2502
 
            name='last_name',
2503
 
            title=_(u'Last Name'),
2504
 
            getter=lambda i, f: i.last_name,
2505
 
            subsort=True)
2506
 
        return [first_name, last_name]
 
2501
        return getUtility(IPersonFactory).columns()
2507
2502
 
2508
2503
    def sortOn(self):
2509
 
        return (("last_name", False), ("first_name", False))
 
2504
        return getUtility(IPersonFactory).sortOn()
2510
2505
 
2511
2506
 
2512
2507
class AttendanceTableTable(flourish.viewlet.Viewlet):
2744
2739
 
2745
2740
 
2746
2741
class FlourishSchoolAttendanceCurrentTerm(OptionalViewlet):
2747
 
    template = InlineViewPageTemplate('''
2748
 
    <ul tal:repeat="term view/view/terms">
2749
 
      <li i18n:translate="">
2750
 
        School year
2751
 
        <tal:block i18n:name="schoolyear" content="term/__parent__/@@title" />,
2752
 
        term <tal:block i18n:name="term" content="term/@@title" />.
2753
 
      </li>
2754
 
    </ul>
2755
 
    ''')
2756
2742
 
2757
2743
    @property
2758
2744
    def enabled(self):
2764
2750
 
2765
2751
 
2766
2752
class FlourishSchoolAttendanceGroupPicker(OptionalViewlet):
2767
 
    template = InlineViewPageTemplate('''
2768
 
    <select name="group" class="navigator"
2769
 
            onchange="$(this).closest('form').submit()">
2770
 
      <option i18n:translate="" value="">Everybody</option>
2771
 
      <tal:block repeat="year view/groups_by_year">
2772
 
        <option disabled="disabled"
2773
 
                class="separator"
2774
 
                tal:content="year/title" />
2775
 
        <option tal:repeat="group year/groups"
2776
 
                tal:attributes="value group/value;
2777
 
                                selected group/selected"
2778
 
                tal:content="group/title" />
2779
 
      </tal:block>
2780
 
    </select>
2781
 
    ''')
2782
2753
 
2783
2754
    @property
2784
2755
    def enabled(self):
2815
2786
 
2816
2787
 
2817
2788
class FlourishSchoolAttendanceInstructorPicker(OptionalViewlet):
2818
 
    template = InlineViewPageTemplate('''
2819
 
    <select name="instructor" class="navigator"
2820
 
            onchange="$(this).closest('form').submit()">
2821
 
      <option i18n:translate="" value="">All instructors</option>
2822
 
      <option tal:repeat="instructor view/instructors"
2823
 
              tal:attributes="value instructor/value;
2824
 
                              selected instructor/selected"
2825
 
              tal:content="instructor/title" />
2826
 
    </select>
2827
 
    ''')
2828
2789
 
2829
2790
    @property
2830
2791
    def enabled(self):
2843
2804
                    instructors.update(section.instructors)
2844
2805
 
2845
2806
        collator = ICollator(self.request.locale)
2846
 
        instructors = sorted(instructors, key=lambda a: collator.key(
2847
 
                removeSecurityProxy(a).first_name))
2848
 
        instructors.sort(key=lambda a: collator.key(
2849
 
                removeSecurityProxy(a).last_name))
 
2807
        factory = getUtility(IPersonFactory)
 
2808
        sorting_key = lambda x: factory.getSortingKey(x, collator)
 
2809
        instructors = sorted(instructors, key=sorting_key)
2850
2810
 
2851
2811
        result = [
2852
2812
            {'title': instructor.title,
2999
2959
            tags = []
3000
2960
            value = score[0]
3001
2961
            if value in scoresystem.tag_absent:
3002
 
                tags.append(_('absent'))
 
2962
                tags.append(_('Absent'))
3003
2963
            if value in scoresystem.tag_tardy:
3004
 
                tags.append(_('tardy'))
 
2964
                tags.append(_('Tardy'))
3005
2965
            if not tags:
3006
 
                tags.append(_('present'))
 
2966
                tags.append(_('Present'))
3007
2967
            if value in scoresystem.tag_excused:
3008
 
                tags.append(_('excused'))
 
2968
                tags.append(_('Excused'))
3009
2969
            tags = ', '.join([translate(t, self.request)
3010
2970
                              for t in tags])
3011
2971
            results.append({
3030
2990
        self.actions['apply'].addClass('button-ok')
3031
2991
        self.actions['cancel'].addClass('button-cancel')
3032
2992
 
3033
 
    @z3c.form.button.buttonAndHandler(_('Done'), name="apply")
 
2993
    @z3c.form.button.buttonAndHandler(s_('Done'), name="apply")
3034
2994
    def handle_apply_action(self, action):
3035
2995
        super(EditDefaultJournalScoreSystems,self).handleApply.func(self, action)
3036
2996
        if (self.status == self.successMessage or
3037
2997
            self.status == self.noChangesMessage):
3038
2998
            self.request.response.redirect(self.nextURL())
3039
2999
 
3040
 
    @z3c.form.button.buttonAndHandler(_('Cancel'))
 
3000
    @z3c.form.button.buttonAndHandler(s_('Cancel'))
3041
3001
    def handle_cancel_action(self, action):
3042
3002
        self.request.response.redirect(self.nextURL())
3043
3003