Changeset 1563:7269ecf397a5 in peach3-core


Ignore:
Timestamp:
01/09/2012 01:31:28 PM (4 months ago)
Author:
eriks
Branch:
core-split
Message:

Beginings of a major refactoring/cleanup. The goal is to split peach3 into several packages, all separate Django apps. This is the "peach3-core" app, that contains all core functionality of peach3, and a very basic UI. All "fancy" ui code is to be moved into a different package.

Files:
44 added
4 deleted
1 edited
30 copied

Legend:

Unmodified
Added
Removed
  • .hgignore

    r1555 r1563  
    2020syntax: regexp 
    2121^transifex-log\.txt$ 
     22syntax: regexp 
     23^src/peach3_core\.egg-info$ 
  • src/peach3/admin/assignment.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
    6  
    75from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (AssignmentSet,  
    11                      Assignment,  
    12                      AssignmentLayout,  
    13                      AssignmentSlot, 
    14                      AssignmentEdition, 
    15                      AssignmentEditionOption, 
    16                      IndividualTimeRange, 
    17                      ClusterTimeRange, 
    18                     ) 
    19  
    20 from peach3.i18n.models import TranslatedNameInline 
     6from django.forms.models import modelform_factory 
     7from peach3.models.assignment import * #@UnusedWildImport 
     8from peach3.admin.i18n import I18NModelForm 
    219 
    2210class AssignmentSetAdmin(admin.ModelAdmin): 
    23     inlines = TranslatedNameInline, 
     11    form = modelform_factory(AssignmentSet, I18NModelForm) 
     12    fields = 'i18n_names', 'courseedition', 'order', 
    2413    raw_id_fields = 'courseedition', 
    25      
     14 
    2615    list_display = 'courseedition', '__unicode__', 'order', 
    2716    list_filter = 'courseedition', 
     
    2918admin.site.register(AssignmentSet, AssignmentSetAdmin) 
    3019 
    31  
    32  
    33 class AssignmentAdmin(admin.ModelAdmin): 
    34     inlines = TranslatedNameInline, 
    35     raw_id_fields  = 'course', 
    36      
    37     list_display = 'course', 'get_latest_name', 
    38     list_filter = 'course', 
    39  
    40     def get_latest_name(self, obj): 
    41         try: 
    42             return obj.assignmentedition_set.latest().name 
    43         except AssignmentEdition.DoesNotExist: 
    44             return '' 
    45     get_latest_name.short_description='Name' 
    46  
    47 admin.site.register(Assignment, AssignmentAdmin) 
    48  
    49  
     20#class AssignmentAdmin(admin.ModelAdmin): 
     21#    inlines = TranslatedNameInline, 
     22#    raw_id_fields  = 'course', 
     23# 
     24#    list_display = 'get_latest_name', 'get_course_name' 
     25# 
     26#    def _get_latest_edition(self, obj): 
     27#        try: 
     28#            return obj.assignmentedition_set.latest() 
     29#        except AssignmentEdition.DoesNotExist: 
     30#            return None 
     31# 
     32#    def get_latest_name(self, obj): 
     33#        l = self._get_latest_edition(obj) 
     34#        if l: 
     35#            return l.name 
     36#        return '' 
     37#    get_latest_name.short_description='Name' 
     38# 
     39#    def get_course_name(self, obj): 
     40#        l = self._get_latest_edition(obj) 
     41#        if l: 
     42#            ce = l.courseedition 
     43#            return '%s: %s' % (ce.code, ce.name) 
     44#        return '' 
     45#    get_course_name.short_description='Course' 
     46# 
     47#admin.site.register(Assignment, AssignmentAdmin) 
    5048 
    5149#class AssignmentSlotAdmin(admin.ModelAdmin): 
     
    5452 
    5553 
    56      
     54 
    5755#class AssignmentSlotInline(admin.TabularInline): 
    5856#    model = AssignmentSlot 
    59 #     
     57# 
    6058#class AssignmentLayoutAdmin(admin.ModelAdmin): 
    6159#    inlines = AssignmentSlotInline, 
     
    7068#    raw_id_fields = 'user', 'assignmentedition', 
    7169#    fields = 'user', 'assignmentedition', 'type', 'state_from', 'state_until', 
    72 #     
     70# 
    7371#    list_display = 'assignmentedition', 'user', 'type', 
    74 #     
    75 #    def get_form(self, request, obj=None):  
     72# 
     73#    def get_form(self, request, obj=None): 
    7674#        # Sort user field on username 
    77 #        f = super(IndividualDeadlineAdmin, self).get_form(request, obj)  
    78 #        qs = f.base_fields['user'].queryset  
    79 #        f.base_fields['user'].queryset = qs.order_by('username')  
     75#        f = super(IndividualDeadlineAdmin, self).get_form(request, obj) 
     76#        qs = f.base_fields['user'].queryset 
     77#        f.base_fields['user'].queryset = qs.order_by('username') 
    8078#        return f 
    8179#admin.site.register(IndividualDeadline, IndividualDeadlineAdmin) 
    82  
    83  
    8480 
    8581class AssignmentEditionOptionInline(admin.TabularInline): 
    8682    model = AssignmentEditionOption 
    8783    extra = 1 
    88      
     84 
    8985class ClusterTimeRangeInline(admin.TabularInline): 
    9086    model = ClusterTimeRange 
     
    9894 
    9995class AssignmentEditionAdmin(admin.ModelAdmin): 
     96    form = modelform_factory(AssignmentEdition, I18NModelForm) 
    10097    fieldsets = ( 
    10198        (None, { 
    102             'fields' : ('courseedition', 'assignment', 'assignmentset',  
    103                         'default_name', 'default_language', 
     99            'fields' : ('courseedition', 'assignment', 'assignmentset', 
     100                        'i18n_names', 
    104101                        'created', 'order',), 
    105102        }), 
    106103        ('Advanced', { 
    107104            'classes': ('collapse',), 
    108             'fields' : ('gradingsystems','languages',)     
     105            'fields' : ('gradingsystems','languages',) 
    109106        }) 
    110107    ) 
     
    112109    raw_id_fields = 'courseedition','assignment','assignmentset', 
    113110    inlines = AssignmentEditionOptionInline, ClusterTimeRangeInline, IndividualTimeRangeInline, 
    114      
    115     list_display = 'slug', '__unicode__', 'edition', 'order', 
     111 
     112    list_display = 'slug', '__unicode__', 'edition', 'courseedition', 'order', 
    116113    list_filter = 'courseedition', 
    117114    date_hierarchy = 'created' 
    118115    ordering = 'order', 
    119      
     116 
    120117admin.site.register(AssignmentEdition, AssignmentEditionAdmin) 
    121118 
    122 class ClusterTimeRangeAdmin(admin.ModelAdmin): 
    123     pass 
    124 admin.site.register(ClusterTimeRange, ClusterTimeRangeAdmin) 
    125  
    126 class IndividualTimeRangeAdmin(admin.ModelAdmin): 
    127     pass 
    128 admin.site.register(IndividualTimeRange, IndividualTimeRangeAdmin) 
    129  
     119#class ClusterTimeRangeAdmin(admin.ModelAdmin): 
     120#    pass 
     121#admin.site.register(ClusterTimeRange, ClusterTimeRangeAdmin) 
     122# 
     123#class IndividualTimeRangeAdmin(admin.ModelAdmin): 
     124#    pass 
     125#admin.site.register(IndividualTimeRange, IndividualTimeRangeAdmin) 
  • src/peach3/admin/checking.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
    6  
    75from django.contrib import admin 
    8 from .models import LegacyChecker 
     6from peach3.models.checking import * #@UnusedWildImport 
    97 
    108class LegacyCheckerAdmin(admin.ModelAdmin): 
  • src/peach3/admin/course.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2      
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
    6  
     5from django.utils.translation import ugettext_lazy as _ 
    76from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (Realm, 
    11                      Course, 
    12                      Period, 
    13                      CourseEdition, 
    14                      FinalGrade, 
    15                      Cluster, 
    16                      ClusterMember, 
    17                      ClusterStaff, 
    18                     ) 
    19  
    20 from peach3.i18n.models import TranslatedNameInline 
     7from django.forms.models import modelform_factory 
     8from peach3.models.course import * #@UnusedWildImport 
     9from peach3.admin.i18n import I18NModelForm 
    2110 
    2211class RealmAdmin(admin.ModelAdmin): 
    23     list_display = 'default_name',    
    24     inlines = TranslatedNameInline, 
     12    list_display = 'default_name', 
     13    form = modelform_factory(Realm, I18NModelForm) 
     14    fields = 'i18n_names', 'site' 
    2515admin.site.register(Realm, RealmAdmin) 
     16 
     17class PeriodAdmin(admin.ModelAdmin): 
     18    list_display = 'slug', 'default_name', 'range' 
     19    form = modelform_factory(Period, I18NModelForm) 
     20    fields = 'i18n_names', 'slug', '_begin', '_end' 
     21    date_hierarchy = '_begin' 
     22admin.site.register(Period, PeriodAdmin) 
    2623 
    2724class CourseEditionInline(admin.TabularInline): 
    2825    model = CourseEdition 
    29     raw_id_fields = 'course', 'managers', 
     26    form = modelform_factory(CourseEdition, I18NModelForm) 
     27    fields = ('code', 'i18n_names', 'period', 'cluster_selection', 
     28              'scoreboard', 'managers', 'created') 
     29    readonly_fields = 'created', 
     30    raw_id_fields = 'managers', 
    3031    radio_fields = { 
    3132        'cluster_selection' : admin.VERTICAL, 
    3233    } 
     34    ordering = 'created', 
     35    extra = 1 
     36 
     37#class CourseEditionAdmin(admin.ModelAdmin): 
     38#    inlines = TranslatedNameInline, 
     39# 
     40#    radio_fields = { 
     41#        'cluster_selection' : admin.VERTICAL, 
     42#    } 
     43#    raw_id_fields = 'course', 'managers', 
     44# 
     45#    ordering = 'course', 
     46#    list_display = 'code', 'default_name', 'period', 
     47#    list_filter = 'code', 'period', 
     48#    date_hierarchy = 'created' 
     49#admin.site.register(CourseEdition, CourseEditionAdmin) 
     50 
     51def _unique(i): 
     52    l=None 
     53    for v in i: 
     54        if v!=l: 
     55            yield v 
     56        l=v 
    3357 
    3458class CourseAdmin(admin.ModelAdmin): 
    3559    inlines = CourseEditionInline, 
     60    list_display = 'get_codes', 'get_name', 'edition_count' 
     61 
     62    def get_codes(self, obj): 
     63        try: 
     64            codes = _unique(sorted(ce.code for ce in obj.courseedition_set.all())) 
     65            return ', '.join(codes) 
     66        except CourseEdition.DoesNotExist: 
     67            return '[%d]' % obj.pk 
     68    get_codes.short_description = _("Course code(s)") 
     69 
     70    def get_name(self, obj): 
     71        try: 
     72            return obj.courseedition_set.latest().get_display_name(with_period=False) 
     73        except CourseEdition.DoesNotExist: 
     74            return '' 
     75    get_name.short_description = 'Course name (latest edition)' 
     76 
     77    def edition_count(self, obj): 
     78        return obj.courseedition_set.count() 
     79    edition_count.short_description = _("Editions") 
     80 
    3681admin.site.register(Course, CourseAdmin) 
    3782 
    3883 
    39 class PeriodAdmin(admin.ModelAdmin): 
    40     inlines = TranslatedNameInline, 
    41      
    42     list_display = 'slug', 'default_name', 'range' 
    43     date_hierarchy = '_begin' 
    44 admin.site.register(Period, PeriodAdmin) 
    45  
    46  
    47 class CourseEditionAdmin(admin.ModelAdmin): 
    48     inlines = TranslatedNameInline, 
    49  
    50     radio_fields = { 
    51         'cluster_selection' : admin.VERTICAL, 
    52     } 
    53     raw_id_fields = 'course', 'managers', 
    54  
    55     ordering = 'course', 
    56     list_display = 'code', 'default_name', 'period', 
    57     list_filter = 'code', 'period', 
    58     date_hierarchy = 'created' 
    59 admin.site.register(CourseEdition, CourseEditionAdmin) 
    60  
    61  
    62  
    6384#class FinalGradeAdmin(admin.ModelAdmin): 
    64 #    def get_form(self, request, obj=None):  
     85#    def get_form(self, request, obj=None): 
    6586#        # Sort user field on username 
    66 #        f = super(FinalGradeAdmin, self).get_form(request, obj)  
    67 #        qs = f.base_fields['user'].queryset  
    68 #        f.base_fields['user'].queryset = qs.order_by('username')  
     87#        f = super(FinalGradeAdmin, self).get_form(request, obj) 
     88#        qs = f.base_fields['user'].queryset 
     89#        f.base_fields['user'].queryset = qs.order_by('username') 
    6990#        return f 
    7091#admin.site.register(FinalGrade, FinalGradeAdmin) 
     
    80101    model = ClusterStaff 
    81102    raw_id_fields = 'cluster', 'user', 
    82      
     103 
    83104class ClusterAdmin(admin.ModelAdmin): 
     105    form = modelform_factory(Cluster, I18NModelForm) 
    84106    fieldsets = ( 
    85107        (None, { 
    86             'fields' : ('realms', 'courseedition', 'default_name',  
    87                         'default_language', 'admin_cluster', 
    88                         'order',) 
     108            'fields' : ('realms', 'courseedition', 'i18n_names', 
     109                        'admin_cluster', 'order',) 
    89110        }), 
    90111        ("Availability", { 
     
    93114                        'active', 'active_from', 'active_until',) 
    94115        }) 
    95          
     116 
    96117    ) 
    97118    raw_id_fields = 'courseedition', 
    98119    filter_horizontal = 'realms', 
    99     inlines = TranslatedNameInline, ClusterMembersInline, ClusterStaffInline, 
    100      
     120    inlines = ClusterMembersInline, ClusterStaffInline, 
     121 
    101122    list_display = 'courseedition', 'default_name', 'admin_cluster', 'order', 
    102123    list_filter = 'realms', 'courseedition', 
    103124    ordering = 'order', 
    104      
     125 
    105126admin.site.register(Cluster, ClusterAdmin) 
  • src/peach3/admin/files.py

    r1436 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
     5from django.contrib import admin 
     6from django.forms.models import modelform_factory 
     7from peach3.models.files import * #@UnusedWildImport 
     8from peach3.admin.i18n import I18NModelForm 
    69 
    7 from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (FileValidator, 
    11                      FileValidatorAlternativeMessage, 
    12                      FileType, 
    13                      File, 
    14                      FileRevision, 
    15                     ) 
    16  
    17 from peach3.i18n.models import TranslatedNameInline 
    18  
    19 class FileValidatorAlternativeMessageInline(admin.TabularInline): 
    20     model = FileValidatorAlternativeMessage 
    21  
    22 class FileValidatorAdmin(admin.ModelAdmin): 
    23     inlines = FileValidatorAlternativeMessageInline, 
    24  
    25     list_display = 'name', 
    26     ordering = 'name', 
    27 admin.site.register(FileValidator, FileValidatorAdmin) 
    28  
     10#class FileValidatorAlternativeMessageInline(admin.TabularInline): 
     11#    model = FileValidatorAlternativeMessage 
     12# 
     13#class FileValidatorAdmin(admin.ModelAdmin): 
     14#    inlines = FileValidatorAlternativeMessageInline, 
     15# 
     16#    list_display = 'name', 
     17#    ordering = 'name', 
     18#admin.site.register(FileValidator, FileValidatorAdmin) 
    2919 
    3020class FileTypeAdmin(admin.ModelAdmin): 
    31     filter_horizontal = 'validators', 
    32     inlines = TranslatedNameInline, 
     21    #filter_horizontal = 'validators', 
     22    form = modelform_factory(FileType, I18NModelForm) 
     23 
     24    fields = ('i18n_names', 'lexer', 'lexer_parameters', 
     25              'binary_content', 'base_type', 
     26              'namepatterns', 'mimetypes', 
     27              'iconcls') 
    3328 
    3429    list_display = 'default_name', 'base_type', 
  • src/peach3/admin/forum.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
    6  
    75from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (Comment, 
    11                      CommentRevision, 
    12                     ) 
     6from peach3.models.forum import * #@UnusedWildImport 
    137 
    148class CommentInline(generic.GenericTabularInline): 
     
    1711    ct_fk_field = 'parent_id' 
    1812    raw_id_fields = 'author', 'response_to', 
    19      
     13 
    2014class CommentAdmin(admin.ModelAdmin): 
    2115    raw_id_fields = 'author', 'response_to', 
  • src/peach3/admin/grade.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
    6  
    75from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (GradingSystem, 
    11                      Grade, 
    12                     ) 
    13  
    14 from peach3.i18n.models import TranslatedNameInline 
     6from django.forms.models import modelform_factory 
     7from peach3.models.grade import * #@UnusedWildImport 
     8from peach3.admin.i18n import I18NModelForm 
    159 
    1610class GradeInline(admin.TabularInline): 
    1711    model = Grade 
     12    form = modelform_factory(Grade, I18NModelForm) 
     13    fields = 'i18n_names', 'icon', 'value_low', 'value_high', 'passing' 
     14    extra = 1 
    1815 
    1916class GradingSystemAdmin(admin.ModelAdmin): 
    20     inlines = TranslatedNameInline, GradeInline, 
     17    form = modelform_factory(GradingSystem, I18NModelForm) 
     18    fields = 'i18n_names', 
     19    inlines = GradeInline, 
    2120admin.site.register(GradingSystem, GradingSystemAdmin) 
    22  
    23 class GradeAdmin(admin.ModelAdmin): 
    24     inlines = TranslatedNameInline, 
    25     list_display = 'system', 'default_name', 
    26 admin.site.register(Grade, GradeAdmin) 
  • src/peach3/admin/i18n.py

    r764 r1563  
     1from django.utils.translation import ugettext_lazy as _ 
     2from django.core.exceptions import ValidationError 
     3from django import forms 
     4 
     5from peach3.models.i18n import * #@UnusedWildImport 
     6 
     7# TODO: Build a fancy widget for editing translations 
     8 
     9class I18NModelForm(forms.ModelForm): 
     10    """ A ModelForm for models based on peach3.models.i18n.I18NModel 
     11        This adds a field 'i18n_names' to the available form fields that 
     12        allows editing of the default name and all translated names. 
     13 
     14        Example usage, for a class XYZ subclassing from I18NModel: 
     15 
     16            from django.forms.models import modelform_factory 
     17            from peach3.admin.i18n import I18NModelForm 
     18 
     19            class XYZAdmin(ModelAdmin): 
     20                form = modelform_factory(XYZ, I18NModelForm) 
     21                fields = 'i18n_names', ... 
     22            admin.site.register(XYZ, XYZAdmin) 
     23 
     24            class XYZInline(TabularInline): 
     25                form = modelform_factory(XYZ, I18NModelForm) 
     26                model = XYZ 
     27    """ 
     28    i18n_names = forms.CharField(label=_("Name"), widget=forms.Textarea(), required=True) 
     29 
     30    def __init__(self, *arg, **kwargs): 
     31        super(I18NModelForm, self).__init__(*arg, **kwargs) 
     32        self.initial['i18n_names'] = self.instance.get_all_names() 
     33 
     34        # If the 'default_name' field of the model can be blank, the 'i18n_names' form field 
     35        # is not required 
     36        for field in self._meta.model._meta.fields: 
     37            if field.name=='default_name': 
     38                self.fields['i18n_names'].required = not field.blank 
     39                break 
     40 
     41    def clean_i18n_names(self): 
     42        data = self.cleaned_data['i18n_names'] 
     43 
     44        try: 
     45            names, lang = parse_names(data) 
     46        except ValueError, e: 
     47            raise ValidationError(str(e)) 
     48 
     49        if self.fields['i18n_names'].required and not names.get(lang): 
     50            raise ValidationError(_("At least one name is required")) 
     51 
     52        return data 
     53 
     54    def save(self, commit=True): 
     55        r = super(I18NModelForm, self).save(commit) 
     56        self.instance.set_all_names(self.cleaned_data['i18n_names']) 
     57        return r 
     58 
  • src/peach3/admin/news.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
     
    77from django.contrib import admin 
    88from django.contrib.contenttypes import generic 
    9  
    10 from .models import (NewsItem 
    11                     ) 
     9from peach3.models.news import NewsItem 
    1210 
    1311class NewsItemInline(generic.GenericStackedInline): 
     
    2018class NewsItemAdmin(admin.ModelAdmin): 
    2119    raw_id_fields = 'courseedition', 'receipient', 'cluster', 'read', 'created_by', 
    22      
     20 
    2321    list_display = 'courseedition', 'receipient', 'created', 
    2422    date_hierarchy = 'created' 
  • src/peach3/admin/pdf.py

    r1174 r1563  
    11from django.contrib import admin 
    2  
    3 from peach3.pdf.models import RefPDFInfo 
     2from peach3.models.pdf import RefPDFInfo 
    43from pdfviewer.admin import PDFPageInfoInline 
    54 
    65class RefPDFInfoAdmin(admin.ModelAdmin): 
    76    inlines = PDFPageInfoInline, 
    8      
     7 
    98    list_display = 'path', 'ref_ct', 'ref_id', 'title', 'pagecount', 'last_access', 
    109 
  • src/peach3/admin/proglang.py

    r1247 r1563  
    11# -*- coding: utf-8 -*- 
    22""" This file is part of PEACH. 
    3        
     3 
    44    Copyright (C) 2006-2009 Eindhoven University of Technology 
    55    Copyright (C) 2009 Eljakim Information Technology bv. 
    66""" 
    7 from __future__ import absolute_import 
    8  
    97from django.contrib import admin 
    10 from django.contrib.contenttypes import generic 
    11  
    12 from .models import (ProgrammingLanguage, 
    13                     ) 
    14  
    15 from peach3.i18n.models import TranslatedNameInline 
     8from django.forms.models import modelform_factory 
     9from peach3.models.proglang import ProgrammingLanguage 
     10from peach3.admin.i18n import I18NModelForm 
    1611 
    1712class ProgrammingLanguageAdmin(admin.ModelAdmin): 
    18     inlines = TranslatedNameInline, 
     13    form = modelform_factory(ProgrammingLanguage, I18NModelForm) 
    1914    list_display = 'default_name', 
     15    fields = 'code', 'i18n_names' 
    2016admin.site.register(ProgrammingLanguage, ProgrammingLanguageAdmin) 
  • src/peach3/admin/report.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    55from django.contrib import admin 
    6  
    7 from peach3.report.models import Report 
     6from peach3.models.report import Report 
    87 
    98class ReportAdmin(admin.ModelAdmin): 
  • src/peach3/admin/submission.py

    r1528 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
     
    66 
    77from django.contrib import admin 
    8 from django.contrib.contenttypes import generic 
    9  
    10 from .models import (Submission, 
    11                      SubmissionFile, 
    12                      CheckResult, 
    13                      CheckResultStep, 
    14                      Reason, 
    15                      ReasonAltDescription, 
    16                      Review, 
    17                     ) 
    18 from peach3.forum.admin import CommentInline 
    19 from peach3.news.admin import NewsItemInline 
     8from peach3.models.submission import * #@UnusedWildImport 
     9from peach3.admin.forum import CommentInline 
     10from peach3.admin.news import NewsItemInline 
    2011 
    2112class SubmissionNewsItemInline(NewsItemInline): 
    22     #fields =  
     13    #fields = 
    2314    pass 
    2415 
     
    2617    model = SubmissionFile 
    2718    raw_id_fields = 'file', 'slot', 
    28      
     19 
    2920class SubmissionAdmin(admin.ModelAdmin): 
    30     raw_id_fields = ('assignmentedition', 'courseedition',  
     21    raw_id_fields = ('assignmentedition', 'courseedition', 
    3122                     'authors', 'created_by', 'submitted_by',) 
    3223    inlines = SubmissionFileInline, SubmissionNewsItemInline, 
    33      
     24 
    3425    list_display = 'assignmentedition','created','created_by', 
    3526admin.site.register(Submission, SubmissionAdmin) 
    36      
    3727 
    3828 
    39 class CheckResultStepInline(admin.TabularInline):     
     29 
     30class CheckResultStepInline(admin.TabularInline): 
    4031    model = CheckResultStep 
    41      
     32 
    4233class CheckResultAdmin(admin.ModelAdmin): 
    4334    raw_id_fields = 'submission', 
    44      
     35 
    4536    list_display = 'submission','created','checked','state', 
    4637    inlines = CheckResultStepInline, 
     
    6354    raw_id_fields = 'submission', 'created_by', 'authors', 
    6455    inlines = CommentInline, 
    65      
     56 
    6657    list_display = 'submission', 'created_by', 
    6758 
    68 #    def get_form(self, request, obj=None):  
     59#    def get_form(self, request, obj=None): 
    6960#        # Sort user field on username 
    70 #        f = super(ReviewAdmin, self).get_form(request, obj)  
     61#        f = super(ReviewAdmin, self).get_form(request, obj) 
    7162# 
    72 #        qs = f.base_fields['created_by'].queryset  
    73 #        f.base_fields['created_by'].queryset = qs.order_by('username')  
    74 #         
    75 #        qs = f.base_fields['authors'].queryset  
    76 #        f.base_fields['authors'].queryset = qs.order_by('username')  
    77 #         
     63#        qs = f.base_fields['created_by'].queryset 
     64#        f.base_fields['created_by'].queryset = qs.order_by('username') 
     65# 
     66#        qs = f.base_fields['authors'].queryset 
     67#        f.base_fields['authors'].queryset = qs.order_by('username') 
     68# 
    7869#        return f 
    79          
     70 
    8071admin.site.register(Review, ReviewAdmin) 
    8172 
  • src/peach3/admin/user.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
    5 from __future__ import absolute_import 
     5from django.contrib import admin 
     6from peach3.models.user import Profile, VerificationCode 
    67 
    7 from django.contrib import admin 
    8 from .models import Profile, VerificationCode 
    9                      
    108class ProfileAdmin(admin.ModelAdmin): 
    119    raw_id_fields = 'user', 
    12      
     10 
    1311    list_display = 'user', 
    1412    ordering = 'user', 
    1513 
    1614admin.site.register(Profile, ProfileAdmin) 
    17      
     15 
    1816class VerificationCodeAdmin(admin.ModelAdmin): 
    1917    raw_id_fields = 'user', 
    20      
     18 
    2119    list_display = 'user', 
    2220    ordering = 'user', 
  • src/peach3/admin/wiki.py

    r1436 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
     
    66 
    77from django.contrib import admin 
    8 from .models import (Page, 
    9                      PagePermission, 
    10                      PageRevisionText, 
    11                      PageRevision, 
    12                      PageComment, 
    13                     ) 
     8from peach3.models.wiki import * #@UnusedWildImport 
    149 
    1510class PagePermissionInline(admin.TabularInline): 
    1611    model = PagePermission 
    17                      
     12 
    1813class PageAdmin(admin.ModelAdmin): 
    1914    fieldsets = ( 
     
    3429 
    3530admin.site.register(Page, PageAdmin) 
    36      
     31 
    3732 
    3833class PageRevisionTextAdmin(admin.ModelAdmin): 
     
    5348 
    5449 
    55 class PageCommentAdmin(admin.ModelAdmin): 
    56     raw_id_fields = 'created_by', 
    57      
    58 admin.site.register(PageComment, PageCommentAdmin) 
     50#class PageCommentAdmin(admin.ModelAdmin): 
     51#    raw_id_fields = 'created_by', 
     52# 
     53#admin.site.register(PageComment, PageCommentAdmin) 
  • src/peach3/models/__init__.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     2 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    5 from django.db import models 
     5#from django.db import models 
    66from django.dispatch import Signal 
     7 
     8APP_LABEL = 'peach3' 
     9 
     10cleanup_signal = Signal() 
     11 
     12from i18n import * 
     13from grade import * 
     14from course import * 
     15from files import * 
     16from proglang import * 
     17from assignment import * 
     18from checking import *  # Move to seperate package? 
     19from forum import * 
     20from news import * 
     21from pdf import * 
     22from report import * 
     23from submission import * 
     24from wiki import * 
     25from user import * 
    726 
    827# Create your models here. 
    928 
    10 cleanup_signal = Signal() 
  • src/peach3/models/assignment.py

    r1417 r1563  
    11""" This file is part of PEACH. 
    2        
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     2 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    5 from django.conf import settings 
    65from django.core.cache import cache 
    76from django.db import models 
    8  
    9 from peach3.course.models import Course, CourseEdition, Cluster 
    10 from peach3.grade.models import GradingSystem 
    11 from peach3.i18n.models import NamedModel, TranslatedName 
    12 from peach3.files.models import FileType, FileValidator 
    13 from peach3.programming_language.models import ProgrammingLanguage 
    14  
    157from django.utils.translation import ugettext_lazy as _ 
    16 from django.utils.translation import get_language 
    17  
    188from django.contrib.auth.models import User 
    199 
     10from peach3.models.course import Course, CourseEdition, Cluster 
     11from peach3.models.grade import GradingSystem 
     12from peach3.models.i18n import NamedModel, TranslatedName 
     13from peach3.models.files import FileType 
     14from peach3.models.proglang import ProgrammingLanguage 
     15from peach3.models.mixins import EditionMixin, OrderMixin 
     16from peach3.models import APP_LABEL 
     17from peach3.managers.assignment import TimeRangeAbstractManager 
     18from peach3.utils.cache import cache_key 
     19from peach3.utils.params import parse_parameters 
     20from peach3.utils.dates import TimeRange 
     21 
    2022from datetime import datetime 
    21 from peach3.utils import EditionMixin, OrderMixin, cache_key, parse_parameters, TimeRange 
    2223 
    2324# Create your models here. 
     
    2526class AssignmentSet(NamedModel, OrderMixin): 
    2627    courseedition = models.ForeignKey(CourseEdition) 
    27      
     28 
    2829    order = models.PositiveIntegerField(null=True, blank=True) 
    29      
    30     class Meta: 
     30 
     31    class Meta(NamedModel.Meta): 
     32        app_label = APP_LABEL 
    3133        ordering = 'order', 
    32      
     34 
    3335#    ### Permission Interface 
    3436#    def _get_permissions(self, user): 
    35 #        return ( user.get_model_permissions(self)  
     37#        return ( user.get_model_permissions(self) 
    3638#               | self.course.get_permissions(user) 
    3739#               ) 
    38 #         
     40# 
    3941    def has_access(self, user, access=None): 
    4042        return self.course.has_access(user, access) 
     
    4244    def save(self, *arg, **kwargs): 
    4345        super(AssignmentSet, self).save(*arg, **kwargs) 
    44          
     46 
    4547        # If order is not set, set it to the primary key value 
    4648        if self.pk and self.order is None: 
    4749            self.order = self.pk 
    4850            super(AssignmentSet, self).save() 
    49              
     51 
    5052    def clone(self, courseedition): 
    5153        # Return a clone of this set for a new courseedition 
    52          
     54 
    5355        if courseedition==self.courseedition: 
    5456            return self 
    55          
     57 
    5658        newset = AssignmentSet.objects.create(courseedition = courseedition, 
    5759                                              default_name = self.default_name, 
    5860                                              default_language = self.default_language) 
    59          
     61 
    6062        for translation in TranslatedName.objects.filter_by_object(self): 
    61             TranslatedName.objects.create_for_object(newset,  
     63            TranslatedName.objects.create_for_object(newset, 
    6264                                                     language=translation.language, 
    6365                                                     name=translation.name) 
    64              
     66 
    6567        return newset 
    66      
     68 
    6769 
    6870class Assignment(models.Model): 
    6971    ### Model definition 
    7072    course = models.ForeignKey(Course) 
    71      
     73 
    7274    def has_access(self, user, access=None): 
    7375        return self.course.has_access(user, access) 
    74      
     76 
     77    class Meta: 
     78        app_label = APP_LABEL 
     79 
    7580class AssignmentEdition(NamedModel, EditionMixin, OrderMixin): 
    7681    ### Model definition 
    7782    slug = models.SlugField(max_length=32, db_index=True) 
    78      
     83 
    7984    assignment = models.ForeignKey(Assignment) 
    8085    assignmentset = models.ForeignKey(AssignmentSet, blank=True, null=True) 
    81      
     86 
    8287    courseedition = models.ForeignKey(CourseEdition) 
    83      
     88 
    8489    created = models.DateTimeField() 
    8590 
     
    9095    timeranges = models.ManyToManyField(Cluster, through="ClusterTimeRange") 
    9196    individual_timeranges = models.ManyToManyField(User, through="IndividualTimeRange") 
    92      
    93     observelevel = models.PositiveSmallIntegerField(default=1,  
     97 
     98    observelevel = models.PositiveSmallIntegerField(default=1, 
    9499                                                    help_text="Minimum observelevel required to observe submissions for this assignment") 
    95100    reviewlevel = models.PositiveSmallIntegerField(default=1, 
     
    97102    reviewpublishlevel = models.PositiveSmallIntegerField(default=1, 
    98103                                                    help_text="Minimum reviewlevel required to publish a review for submissions for this assignment") 
    99      
     104 
    100105    order = models.PositiveIntegerField(null=True, blank=True) 
    101      
    102     class Meta: 
     106 
     107    class Meta(NamedModel.Meta): 
     108        app_label = APP_LABEL 
    103109        get_latest_by = 'created' 
    104110        ordering = 'order', 
    105111        unique_together = ('courseedition','slug'), 
    106          
     112 
    107113    ### Model methods 
    108      
     114 
    109115    # Updates tracking 
    110116#    def mark_updated(self, when=None): 
     
    114120#        cache.set(key, when, 60*60) 
    115121#        return when 
    116 #         
     122# 
    117123#    def last_updated(self, user=None): 
    118124#        now = datetime.utcnow().replace(microsecond=0) 
    119 #         
     125# 
    120126#        key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'updated') 
    121127#        updated = cache.get(key) 
     
    129135#            if isinstance(deadline, datetime) and date_inrange(deadline, updated, now): 
    130136#                updated = self.mark_updated(deadline) 
    131 #         
     137# 
    132138#        # Check if submission updated 
    133139#        if user: 
     
    139145#            elif subupdated > updated: 
    140146#                updated = self.mark_updated(subupdated) 
    141 #         
     147# 
    142148#        return updated 
    143 #         
     149# 
    144150#    def save(self, *arg, **kwarg): 
    145151#        super(AssignmentEdition, self).save(*arg, **kwarg) 
    146152#        self.mark_updated() 
    147 #         
     153# 
    148154#    def was_updated(self, stamp, user=None): 
    149155#        updated = self.last_updated(user) # Do this even if stamp=None (to update cache) 
     
    155161        # Return the number of submissions for this assignment 
    156162        return self.submission_set.count() 
    157      
     163 
    158164    # Timeranges 
    159165    def get_creation_timerange(self): 
     
    161167            self._creation_timerange = TimeRange(begin=self.created) 
    162168        return self._creation_timerange 
    163      
     169 
    164170    def _set_cluster_timerange(self, type, cluster, range): 
    165171        """ Set a timerange of `type` for the given `cluster`. 
     
    169175        """ 
    170176        ClusterTimeRange.objects.set_timerange(assignmentedition=self, type=type, cluster=cluster, range=range) 
    171      
     177 
    172178    def _get_cluster_timerange(self, type, cluster): 
    173179        """ Get the timerange of given `type` for the given `cluster`. Returns a `TimeRange`. 
     
    177183        if cluster=='*': 
    178184            return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type) 
    179          
     185 
    180186        if isinstance(cluster, (list, tuple, models.query.QuerySet)): 
    181187            return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type, cluster__in=cluster) 
    182          
     188 
    183189        return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type, cluster=cluster) 
    184      
     190 
    185191    def clone_timeranges(self, old_cluster, new_cluster): 
    186192        for t in ['o', 'v']: 
    187193            range = self._get_cluster_timerange(t, old_cluster) 
    188194            self._set_cluster_timerange(t, new_cluster, range) 
    189          
     195 
    190196    def _set_individual_timerange(self, type, user, range): 
    191197        """ Set a timerange of `type` for the given `user`. 
     
    195201        """ 
    196202        IndividualTimeRange.objects.set_timerange(assignmentedition=self, type=type, user=user, range=range) 
    197      
     203 
    198204    def _get_individual_timerange(self, type, user): 
    199205        "Get the timerange of given `type` for the given `user`. Returns a `TimeRange`." 
    200206        return IndividualTimeRange.objects.get_timerange(assignmentedition=self, type=type, user=user) 
    201      
     207 
    202208    def _get_timerange(self, type, user=None, cluster=None): 
    203209        """ Get a timerange of `type` for given `user` or `cluster`. 
    204210            If `cluster` is given, it must be valid for the `user`. 
    205             Returns the `TimeRange` for the `user`.  
     211            Returns the `TimeRange` for the `user`. 
    206212            If `cluster` is '*', returns the union of the timeranges for all clusters 
    207213        """ 
     
    215221            individual = self._get_individual_timerange(type, user) 
    216222            return (timerange + individual) 
    217          
     223 
    218224        elif cluster: 
    219225            return self._get_cluster_timerange(type, cluster) 
    220          
     226 
    221227        else: 
    222228            return TimeRange(closed=True) 
    223          
     229 
    224230    def set_cluster_open_timerange(self, cluster, range): 
    225231        self._set_cluster_timerange('o', cluster, range) 
    226      
     232 
    227233    def set_individual_open_timerange(self, user, range): 
    228234        self._set_individual_timerange('o', user, range) 
    229      
     235 
    230236    def set_individual_deadline(self, user, deadline): 
    231237        user_range = TimeRange(begin=datetime.utcnow(), end=deadline) 
    232238        self._set_individual_timerange('o', user, TimeRange(begin=datetime.utcnow(), end=deadline)) 
    233239        self._set_individual_timerange('v', user, TimeRange(begin=datetime.utcnow(), end=deadline)) 
    234      
     240 
    235241    def get_open_timerange(self, user=None, cluster=None): 
    236242        " Shortcut for `self.get_timerange('o', user, cluster)` " 
    237243        return self._get_timerange('o', user=user, cluster=cluster) 
    238      
     244 
    239245    def is_open(self, user=None, cluster=None, when=None): 
    240246        """ Returns True if assignment is open at given date (or current date if when=None) 
     
    246252        elif user: 
    247253            # Assignment is also open if user's review level exceeds self.observelevel 
    248             return (when in self.get_creation_timerange()  
     254            return (when in self.get_creation_timerange() 
    249255                    and (user.is_superuser 
    250256                         or self.courseedition.get_reviewlevel(user,'*') >= self.observelevel 
    251257                        )) 
    252          
     258 
    253259        return False 
    254      
     260 
    255261    def compare_open(self, user=None, cluster=None, when=None): 
    256         """ Returns  
    257               -1   if assignment opens in the future      
     262        """ Returns 
     263              -1   if assignment opens in the future 
    258264               0   if assignment is currently open 
    259                1   if assignment was open in the past and is now closed      
     265               1   if assignment was open in the past and is now closed 
    260266              None if assignment never opens 
    261267        """ 
     
    264270            return None 
    265271        return cmp(range, when) 
    266          
     272 
    267273    def get_deadline(self, user=None, cluster=None): 
    268274        """ Get this assignment's deadline for given user or cluster. 
     
    276282            return False 
    277283        return range.end if range.has_end() else True 
    278      
     284 
    279285    def set_cluster_visible_timerange(self, cluster, range): 
    280286        self._set_cluster_timerange('v', cluster, range) 
    281      
     287 
    282288    def set_individual_visible_timerange(self, user, range): 
    283289        self._set_individual_timerange('v', user, range) 
    284      
     290 
    285291    def get_visible_timerange(self, user=None, cluster=None): 
    286292        " Shortcut for `self.get_timerange('v', user, cluster)` " 
    287293        return self._get_timerange('v', user, cluster) 
    288      
     294 
    289295    def can_observe(self, user): 
    290         return self.courseedition.get_observelevel(user, '*') >= self.observelevel         
    291      
     296        return self.courseedition.get_observelevel(user, '*') >= self.observelevel 
     297 
    292298    def is_visible(self, user=None, cluster=None, when=None): 
    293299        if not cluster: 
    294300            cluster = self.courseedition.get_user_cluster(user) 
    295          
     301 
    296302        if when in self.get_visible_timerange(user, cluster): 
    297303            return True 
     
    299305            # Assignment is also visible if user's observer level exceeds self.observelevel 
    300306            return when in self.get_creation_timerange() and self.can_observe(user) 
    301                  
     307 
    302308        return False 
    303309 
    304310    # Options 
    305          
     311 
    306312    def _get_option(self, optioncode): 
    307313        key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) 
     
    315321 
    316322            cache.set(key, result) 
    317              
     323 
    318324        return result 
    319      
     325 
    320326    def has_option(self, optioncode): 
    321327        return self._get_option(optioncode)[0] 
    322      
     328 
    323329    def get_option(self, optioncode): 
    324330        return self._get_option(optioncode)[1] 
    325      
     331 
    326332    def set_option(self, optioncode, **kwargs): 
    327333        key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) 
    328334        cache.delete(key) 
    329          
     335 
    330336        parameters="\n".join((key if value is True else '%s=%s' % (key, value)) for key, value in kwargs.iteritems()) 
    331          
     337 
    332338        obj, created = AssignmentEditionOption.objects.get_or_create(assignmentedition=self, 
    333                                                                      option=optioncode,  
     339                                                                     option=optioncode, 
    334340                                                                     defaults={'parameters':parameters}) 
    335341        if not created: 
    336342            obj.parameters = parameters 
    337343            obj.save() 
    338              
     344 
    339345    def delete_option(self, optioncode): 
    340346        key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) 
    341347        cache.delete(key) 
    342          
     348 
    343349        AssignmentEditionOption.objects.filter(assignmentedition=self, 
    344350                                               option=optioncode).delete() 
    345          
    346              
     351 
     352 
    347353    def has_option_parameter(self, optioncode, key): 
    348354        return key in self.get_option(optioncode) 
    349      
     355 
    350356    def get_option_parameter(self, optioncode, key, default=None): 
    351357        return self.get_option(optioncode).get(key, default) 
    352      
     358 
    353359    def set_option_parameter(self, optioncode, key, value): 
    354360        option = self.get_option(optioncode) 
    355361        option[key] = value 
    356362        self.set_option(optioncode, **option) 
    357          
     363 
    358364    def delete_option_parameter(self, optioncode, key): 
    359365        option = self.get_option(optioncode) 
    360366        del option[key] 
    361367        self.set_option(optioncode, **option) 
    362          
     368 
    363369    def __unicode__(self): 
    364370        return u'%s (%s)' % (self.name, _('ed.%d') % self.edition) 
    365          
     371 
    366372#    ### Permission Interface 
    367373#    def _get_permissions(self, user): 
    368 #        return ( user.get_model_permissions(self)  
     374#        return ( user.get_model_permissions(self) 
    369375#               | self.assignment.get_permissions(user) 
    370376#               | self.assignmentset.get_permissions(user) if self.assignmentset else frozenset() 
    371377#               ) 
    372378#        return perms 
    373 #         
     379# 
    374380 
    375381    def has_access(self, user, access=None): 
     
    379385    @classmethod 
    380386    def get_uid_fields(cls): 
    381         return 'courseedition__period__slug', 'courseedition__code', 'slug',   
    382      
     387        return 'courseedition__period__slug', 'courseedition__code', 'slug', 
     388 
    383389    def get_uid(self): 
    384         return self.courseedition.period.slug, self.courseedition.code, self.slug,   
    385      
     390        return self.courseedition.period.slug, self.courseedition.code, self.slug, 
     391 
    386392    ### Ordering mixin interface 
    387393    @staticmethod 
    388394    def get_grouping_field_name(): 
    389395        return 'assignment' 
    390      
     396 
    391397    def save(self, *arg, **kwargs): 
    392398        super(AssignmentEdition, self).save(*arg, **kwargs) 
    393          
     399 
    394400        # If order is not set, set it to the primary key value 
    395401        if self.pk and self.order is None: 
    396402            self.order = self.pk 
    397403            super(AssignmentEdition, self).save() 
    398      
     404 
    399405class AssignmentEditionOption(models.Model): 
    400406    OPTIONS = ( 
     
    402408        ('COAUTHORS', _("Co-Authors")), # Parameters: min, max 
    403409    ) 
    404      
     410 
    405411    assignmentedition = models.ForeignKey(AssignmentEdition) 
    406412    option = models.CharField(max_length=16, choices=OPTIONS) 
    407413    parameters = models.TextField(blank=True, help_text=_("'key' or 'key=value' pairs; one per line")) 
    408      
     414 
    409415    class Meta: 
     416        app_label = APP_LABEL 
     417        db_table = APP_LABEL+'_asgnedoption' 
    410418        unique_together = (('assignmentedition', 'option',),) 
    411         db_table = 'assignment_asgnedoption' 
    412          
     419 
    413420    class Admin: 
    414421        list_display = ('assignmentedition', 'option',) 
     
    416423class AssignmentLayout(models.Model): 
    417424    name = models.CharField(max_length=80, unique=True) 
    418      
     425 
    419426    def __unicode__(self): 
    420427        return self.name 
    421      
     428 
     429    class Meta: 
     430        app_label = APP_LABEL 
     431 
    422432class AssignmentSlot(NamedModel): 
    423433    assignmentsubmlayout = models.ForeignKey(AssignmentLayout) 
     
    425435    order = models.PositiveIntegerField() 
    426436 
    427     validators = models.ManyToManyField(FileValidator, blank=True) 
     437    #validators = models.ManyToManyField(FileValidator, blank=True) 
    428438    allowedTypes = models.ManyToManyField(FileType, blank=True) 
    429439    namePattern = models.CharField(max_length=200, blank=True) 
    430      
     440 
    431441    required = models.BooleanField() 
    432442 
     
    434444    def __unicode__(self): 
    435445        return self.get_display_name() 
    436      
    437 class TimeRangeAbstractManager(models.Manager): 
    438     def get_timerange(self, **kwargs): 
    439         objs = self.filter(**kwargs) 
    440          
    441         if len(objs)==1: 
    442             # Optimization for most common case 
    443             obj=objs[0] 
    444             return TimeRange(begin=obj.state_from, end=obj.state_until) 
    445          
    446         # Combine filtered timeranges 
    447         tr = TimeRange(closed=True) 
    448         for obj in objs: 
    449             tr += TimeRange(begin=obj.state_from, end=obj.state_until) 
    450              
    451         return tr 
    452      
    453     def set_timerange(self, range, **kwargs): 
    454         if range.is_closed(): 
    455             # Delete any existing row 
    456             self.filter(**kwargs).delete() 
    457              
    458         else: 
    459             # Create new row or update existing row 
    460             state_from = range.begin if range.has_begin() else None 
    461             state_until = range.end if range.has_end() else None 
    462              
    463             obj, created = self.get_or_create(defaults={ 
    464                                                   'state_from' : state_from, 
    465                                                   'state_until' : state_until, 
    466                                               }, 
    467                                               **kwargs) 
    468             if not created: 
    469                 obj.state_from = state_from 
    470                 obj.state_until = state_until 
    471                 obj.save() 
    472          
     446 
     447    class Meta(NamedModel.Meta): 
     448        app_label = APP_LABEL 
     449 
    473450class TimeRangeAbstractModel(models.Model): 
    474451    TYPE_CHOICES = (('o', _('Open'),), 
     
    478455    assignmentedition = models.ForeignKey(AssignmentEdition) 
    479456    type = models.CharField(max_length=1, choices = TYPE_CHOICES) 
    480      
     457 
    481458    state_from = models.DateTimeField(null=True, blank=True) 
    482459    state_until = models.DateTimeField(null=True, blank=True) 
    483      
     460 
    484461    objects = TimeRangeAbstractManager() 
    485      
     462 
    486463    class Meta: 
    487464        abstract = True 
    488          
     465 
    489466class ClusterTimeRange(TimeRangeAbstractModel): 
    490467    cluster = models.ForeignKey(Cluster) 
    491      
     468 
    492469    class Meta(TimeRangeAbstractModel.Meta): 
     470        app_label = APP_LABEL 
    493471        unique_together = (('cluster', 'assignmentedition', 'type',), 
    494472                          ) 
    495          
     473 
    496474    ### Naming interface 
    497475    def __unicode__(self): 
     
    500478class IndividualTimeRange(TimeRangeAbstractModel): 
    501479    user = models.ForeignKey(User) 
    502      
     480 
    503481    class Meta(TimeRangeAbstractModel.Meta): 
    504         unique_together = (('user', 'assignmentedition', 'type',), 
    505                           ) 
    506          
     482        app_label = APP_LABEL 
     483        unique_together = (('user', 'assignmentedition', 'type',),) 
     484 
    507485    ### Naming interface 
    508486    def __unicode__(self): 
  • src/peach3/models/checking.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     2 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    55 
    66from django.conf import settings 
    77from django.db import models 
    8 from django.utils.translation import get_language 
    98 
    10 from peach3.assignment.models import AssignmentEdition 
    11 from peach3.programming_language.models import ProgrammingLanguage 
    12  
    13 from peach3.utils import create_path 
     9from peach3.models.assignment import AssignmentEdition 
     10from peach3.models import APP_LABEL 
     11from peach3.utils.files import create_path 
    1412 
    1513import os.path 
     
    2220    ce = ae.courseedition 
    2321    period = ce.period 
    24      
    25     return create_path(period.slug,  
    26                        ce.code,  
    27                        ae.slug,  
     22 
     23    return create_path(period.slug, 
     24                       ce.code, 
     25                       ae.slug, 
    2826                       created.strftime('%Y%m%d%H%M%S')) 
    2927 
     
    3230    filepath = models.CharField(max_length=300) 
    3331    filename = models.CharField(max_length=100) 
    34      
     32 
    3533    enabled = models.BooleanField(default=True) 
    3634    created = models.DateTimeField(blank=False, null=False, default=datetime.utcnow) 
    37      
     35 
    3836    class Meta: 
     37        app_label = APP_LABEL 
    3938        get_latest_by = 'created' 
    4039 
     
    4342    def get_store(): 
    4443        return os.path.join(settings.STORE, 'legacy_checkers') 
    45      
     44 
    4645    def get_subdir(self): 
    4746        return os.path.join(self.get_store(), self.filepath) 
    48      
     47 
    4948    def get_file_path(self): 
    5049        return os.path.join(self.get_store(), self.filepath, self.filename) 
    51      
     50 
    5251    @models.permalink 
    5352    def get_absolute_url(self): 
     
    5655    def delete(self, *args, **kwargs): 
    5756        import os 
    58          
     57 
    5958        try: 
    6059            os.remove(self.get_file_path()) 
     
    6665        except: 
    6766            pass 
    68          
     67 
    6968        super(LegacyChecker, self).delete(*args, **kwargs) 
    70      
  • src/peach3/models/course.py

    r1551 r1563  
    11""" This file is part of PEACH. 
    22 
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    5 from django.conf import settings 
    65from django.core.cache import cache 
    7 from django.contrib.auth.models import User, Permission 
     6from django.contrib.auth.models import User 
    87from django.contrib.sites.models import Site 
    98from django.contrib.sites.managers import CurrentSiteManager 
    109from django.db import models, transaction 
    11 from django.db.models import Q 
    1210 
    1311from django.utils.translation import ugettext_lazy, ugettext as _ 
    14 from django.utils.translation import get_language 
    15  
    16 from peach3.grade.models import Grade, GradingSystem 
    17 from peach3.utils import EditionMixin, OrderMixin, cache_key, unique_cache_hash, parse_parameters, TimeRange 
    18 from peach3.i18n.models import NamedModel, UniqueNamedModel, BlankNamedModel 
    19  
    20 from datetime import datetime 
     12 
     13from peach3.models.grade import Grade 
     14from peach3.models.i18n import NamedModel, UniqueNamedModel, BlankNamedModel 
     15from peach3.models.mixins import EditionMixin, OrderMixin 
     16from peach3.models import APP_LABEL 
     17from peach3.managers.course import PeriodManager, ClusterManager 
     18from peach3.utils.cache import cache_key, unique_cache_hash 
     19from peach3.utils.dates import TimeRange 
    2120 
    2221# Create your models here. 
     
    2928    objects = models.Manager() 
    3029 
     30    class Meta(UniqueNamedModel.Meta): 
     31        app_label = APP_LABEL 
     32 
    3133    def has_access(self, user, access=None): 
    3234        return user.is_superuser or access is None 
    3335 
    3436class Course(models.Model): 
     37    class Meta: 
     38        app_label = APP_LABEL 
     39 
    3540    def has_access(self, user, access=None): 
    3641        if user.is_superuser: 
     
    4449        return False 
    4550 
    46 class PeriodManager(models.Manager): 
    47     def get_available(self, date=None): 
    48         # Filters periods that are available ('open' ranges or end date has not passed) 
    49         # If date is None, current date is used 
    50         return self.filter(Q(_end__gt=date or datetime.utcnow()) | Q(_end__isnull=True))\ 
    51                    .filter(Q(_begin__lt=TimeRange.CLOSED[0]) | Q(_begin__isnull=True)) 
    52         # TODO: Replace last filter with ".exclude(_begin__gt=F('_end'))" when Django 1,1 has landed 
    53  
    5451class Period(NamedModel): 
    5552    slug = models.CharField(max_length=8, unique=True) 
     
    6057    objects = PeriodManager() 
    6158 
     59    class Meta(NamedModel.Meta): 
     60        app_label = APP_LABEL 
     61        ordering = '_begin', '-_end', 
     62 
    6263    def get_range(self): 
    63         range = getattr(self, '_range', None) 
    64  
    65         if not range: 
     64        r = getattr(self, '_range', None) 
     65 
     66        if not r: 
    6667            if self._begin is None and self._end is None: 
    67                 range = TimeRange(open=True) 
     68                r = TimeRange(open=True) 
    6869            elif self._begin is None: 
    69                 range = TimeRange(begin=None, end=self._end) 
     70                r = TimeRange(begin=None, end=self._end) 
    7071            elif self._end is None: 
    71                 range = TimeRange(begin=self._begin, end=None) 
     72                r = TimeRange(begin=self._begin, end=None) 
    7273            elif self._end <= self._begin: 
    73                 range = TimeRange(closed=True) 
     74                r = TimeRange(closed=True) 
    7475            else: 
    75                 range = TimeRange(begin=self._begin, end=self._end) 
    76  
    77             self._range = range 
    78  
    79         return range 
    80  
    81     def set_range(self, range): 
    82         if range.is_closed(): 
     76                r = TimeRange(begin=self._begin, end=self._end) 
     77 
     78            self._range = r 
     79 
     80        return r 
     81 
     82    def set_range(self, r): 
     83        if r.is_closed(): 
    8384            self._begin, self._end = TimeRange.CLOSED 
    84         elif range.is_open(): 
     85        elif r.is_open(): 
    8586            self._begin = None 
    8687            self._end = None 
    8788        else: 
    88             self._begin = range.begin 
    89             self._end = range.end 
    90  
    91         self._range = range 
    92  
    93     range = property(get_range, set_range) 
     89            self._begin = r.begin 
     90            self._end = r.end 
     91 
     92        self._range = r 
     93 
     94    range = property(get_range, set_range) #@ReservedAssignment 
    9495 
    9596    def available(self, date=None): 
     
    99100        self._range = None 
    100101        super(Period, self).save(*arg, **kwarg) 
    101  
    102     class Meta: 
    103         ordering = '_begin', '-_end', 
    104  
    105  
    106102 
    107103class CourseEdition(NamedModel, EditionMixin): 
     
    130126    managers = models.ManyToManyField(User, related_name='courseedition_manager_set', blank=True) 
    131127 
    132     class Meta: 
     128    class Meta(NamedModel.Meta): 
     129        app_label = APP_LABEL 
    133130        get_latest_by = 'created' 
    134131        unique_together = ('course', 'period'), ('code', 'period') 
     
    560557    grade = models.ForeignKey(Grade) 
    561558 
     559    class Meta: 
     560        app_label = APP_LABEL 
     561 
    562562    ### Naming interface 
    563563    def __unicode__(self): 
    564564        return u'%s: %s' % (self.user, self.grade) 
    565  
    566  
    567 class ClusterManager(models.Manager): 
    568     def get_aggregated_active(self, **filter): 
    569         q = self.filter(**filter) 
    570         if q.filter(active=True).count()>0: 
    571             return TimeRange(open=True) 
    572         else: 
    573             active = TimeRange(closed=True) 
    574             for cl in q.filter(active__isnull=True): 
    575                 active += cl.active_range 
    576             return active 
    577565 
    578566class Cluster(BlankNamedModel, OrderMixin): 
     
    600588    objects = ClusterManager() 
    601589 
    602     class Meta: 
     590    class Meta(BlankNamedModel.Meta): 
     591        app_label = APP_LABEL 
    603592        unique_together = (('courseedition', 'default_name',),) 
    604593 
     
    926915 
    927916    class Meta: 
     917        app_label = APP_LABEL 
    928918        unique_together = (('cluster', 'user',), 
    929919                           #('cluster__courseedition__course', 'user',), 
     
    10271017 
    10281018    class Meta: 
     1019        app_label = APP_LABEL 
    10291020        unique_together = (('cluster', 'user',),) 
  • src/peach3/models/files.py

    r1552 r1563  
    11""" This file is part of PEACH. 
    22 
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    55from __future__ import with_statement 
    66 
    77from django.conf import settings 
    8 from django.core.cache import cache 
    98from django.db import models 
    10 from django.contrib.auth.models import User, Permission 
    11  
    12 from django.utils.translation import get_language 
     9from django.contrib.auth.models import User 
     10 
    1311from django.utils.translation import ugettext_lazy as _ 
    1412 
    15 from peach3.i18n.models import UniqueNamedModel 
    16 from peach3.utils import (RevisionMixin, cache_key, parse_parameters, 
    17                           parse_patterns, match_patterns, 
    18                           normalize_charset_name) 
    19  
    20 import random, os, shutil, filecmp 
    21 from datetime import datetime 
     13from peach3.models.i18n import UniqueNamedModel 
     14from peach3.models.mixins import RevisionMixin 
     15from peach3.models import APP_LABEL 
     16 
     17from peach3.managers.files import FileTypeManager, FileRevisionManager 
     18 
     19from peach3.utils.params import parse_parameters, parse_patterns, match_patterns 
     20 
     21import os 
    2222import codecs 
    2323 
     
    3131 
    3232# Create your models here. 
    33  
    34 class VALIDATORS(object): 
    35     def __iter__(self): 
    36         for v in settings.FILE_VALIDATORS: 
    37             m = import_by_string(v) 
    38             yield (v, m.name) 
    39             del m 
    40  
    41 class FileValidator(models.Model): 
    42     name = models.CharField(max_length=80) 
    43  
    44     validator = models.CharField(max_length=80, choices=VALIDATORS()) 
    45     parameters = models.TextField(blank=True) 
    46     strict = models.BooleanField() 
    47  
    48     priority = models.PositiveIntegerField() 
    49  
    50     default_message = models.TextField(blank=True) 
    51     default_language = models.CharField(max_length=16, 
    52                                         choices=settings.LANGUAGES, 
    53                                         default=settings.LANGUAGE_CODE) 
    54  
    55     ### 
    56  
    57     def _get_validator_class(self): 
    58         if not hasattr(self, '_validator_class'): 
    59             try: 
    60                 self._validator_class = import_by_string(self.validator) 
    61             except ImportError: 
    62                 self._validator_class = None 
    63         return self._validator_class 
    64  
    65     def _parse_validator_parameters(self): 
    66         if not hasattr(self, '_parsed_parameters'): 
    67             self._parsed_parameters = parse_parameters(self.parameters) 
    68         return self._parsed_parameters 
    69  
    70     def validate(self, filepath): 
    71         if not hasattr(self, '_validator_instance'): 
    72             vcls = self._get_validator_class() 
    73             if vcls: 
    74                 param = self._parse_validator_parameters() 
    75                 self._validator_instance = vcls(**param) 
    76             else: 
    77                 self._validator_instance = None 
    78  
    79         if not self._validator_instance: 
    80             return False, "Unable to load validator %r" % self.validator 
    81  
    82         vres, verr = self._validator_instance.validate(filepath) 
    83         if not vres: 
    84             try: 
    85                 msg = self.get_message() % verr 
    86             except ValueError: 
    87                 msg = self.get_message() 
    88         else: 
    89             msg = '' 
    90  
    91         return vres, msg 
    92  
    93     ### Naming interface 
    94     def get_message(self, lang=None): 
    95         if lang is None: 
    96             lang = get_language() 
    97  
    98         key = cache_key('peach3.files.models.FileValidator.get_message', self, lang) 
    99         cached = cache.get(key) 
    100  
    101         if cached is None: 
    102             try: 
    103                 message = self.filevalidatoralternativemessage_set.get(language=lang).message 
    104             except FileValidatorAlternativeMessage.DoesNotExist: 
    105                 message = None 
    106  
    107             cache.set(key, (message,)) 
    108  
    109         else: 
    110             message = cached[0] 
    111  
    112         if message: 
    113             return message 
    114         else: 
    115             return self.default_message 
    116  
    117     message = property(get_message) 
    118     get_display_message = get_message 
    119  
    120     def __unicode__(self): 
    121         return self.name 
    122  
    123  
    124 class FileValidatorAlternativeMessage(models.Model): 
    125     ### Model definition 
    126     filevalidator = models.ForeignKey(FileValidator) 
    127     language = models.CharField(max_length=16, choices=settings.LANGUAGES) 
    128     message = models.TextField() 
    129  
    130     class Meta: 
    131         unique_together = (('filevalidator','language',),) 
    132         db_table = 'files_filevalaltmessage' 
     33# 
     34# Validators models disabled, as validators have not been implemented yet 
     35# 
     36#class VALIDATORS(object): 
     37#    def __iter__(self): 
     38#        for v in settings.FILE_VALIDATORS: 
     39#            m = import_by_string(v) 
     40#            yield (v, m.name) 
     41#            del m 
     42# 
     43#class FileValidator(models.Model): 
     44#    name = models.CharField(max_length=80) 
     45# 
     46#    validator = models.CharField(max_length=80, choices=VALIDATORS()) 
     47#    parameters = models.TextField(blank=True) 
     48#    strict = models.BooleanField() 
     49# 
     50#    priority = models.PositiveIntegerField() 
     51# 
     52#    default_message = models.TextField(blank=True) 
     53#    default_language = models.CharField(max_length=16, 
     54#                                        choices=settings.LANGUAGES, 
     55#                                        default=settings.LANGUAGE_CODE) 
     56# 
     57#    class Meta: 
     58#        app_label = APP_LABEL 
     59# 
     60#    ### 
     61# 
     62#    def _get_validator_class(self): 
     63#        if not hasattr(self, '_validator_class'): 
     64#            try: 
     65#                self._validator_class = import_by_string(self.validator) 
     66#            except ImportError: 
     67#                self._validator_class = None 
     68#        return self._validator_class 
     69# 
     70#    def _parse_validator_parameters(self): 
     71#        if not hasattr(self, '_parsed_parameters'): 
     72#            self._parsed_parameters = parse_parameters(self.parameters) 
     73#        return self._parsed_parameters 
     74# 
     75#    def validate(self, filepath): 
     76#        if not hasattr(self, '_validator_instance'): 
     77#            vcls = self._get_validator_class() 
     78#            if vcls: 
     79#                param = self._parse_validator_parameters() 
     80#                self._validator_instance = vcls(**param) 
     81#            else: 
     82#                self._validator_instance = None 
     83# 
     84#        if not self._validator_instance: 
     85#            return False, "Unable to load validator %r" % self.validator 
     86# 
     87#        vres, verr = self._validator_instance.validate(filepath) 
     88#        if not vres: 
     89#            try: 
     90#                msg = self.get_message() % verr 
     91#            except ValueError: 
     92#                msg = self.get_message() 
     93#        else: 
     94#            msg = '' 
     95# 
     96#        return vres, msg 
     97# 
     98#    ### Naming interface 
     99#    def get_message(self, lang=None): 
     100#        if lang is None: 
     101#            lang = get_language() 
     102# 
     103#        key = cache_key('peach3.files.models.FileValidator.get_message', self, lang) 
     104#        cached = cache.get(key) 
     105# 
     106#        if cached is None: 
     107#            try: 
     108#                message = self.filevalidatoralternativemessage_set.get(language=lang).message 
     109#            except FileValidatorAlternativeMessage.DoesNotExist: 
     110#                message = None 
     111# 
     112#            cache.set(key, (message,)) 
     113# 
     114#        else: 
     115#            message = cached[0] 
     116# 
     117#        if message: 
     118#            return message 
     119#        else: 
     120#            return self.default_message 
     121# 
     122#    message = property(get_message) 
     123#    get_display_message = get_message 
     124# 
     125#    def __unicode__(self): 
     126#        return self.name 
     127# 
     128# 
     129#class FileValidatorAlternativeMessage(models.Model): 
     130#    ### Model definition 
     131#    filevalidator = models.ForeignKey(FileValidator) 
     132#    language = models.CharField(max_length=16, choices=settings.LANGUAGES) 
     133#    message = models.TextField() 
     134# 
     135#    class Meta: 
     136#        app_label = APP_LABEL 
     137#        unique_together = (('filevalidator','language',),) 
    133138 
    134139class LEXER_CHOICES(object): 
     
    147152        return iter(self.lexers) 
    148153 
    149 class FileTypeManager(models.Manager): 
    150     def get_text_type(self): 
    151         if not hasattr(self, '_text_type'): 
    152             self._text_type = self.get(binary_content=False, base_type=True) 
    153         return self._text_type 
    154  
    155     def get_binary_type(self): 
    156         if not hasattr(self, '_binary_type'): 
    157             self._binary_type = self.get(binary_content=True, base_type=True) 
    158         return self._binary_type 
    159  
    160     def all_matches(self, *arg, **kwarg): 
    161         charset = kwarg.get('charset') 
    162         if charset!='': 
    163             default = self.get_text_type() 
    164         else: 
    165             default = self.get_binary_type() 
    166  
    167         matches = [(default, 0.0)] 
    168         for filetype in self.all(): 
    169             match = filetype.match(*arg, **kwarg) 
    170             if match>=0.1: 
    171                 matches.append( (filetype, match) ) 
    172  
    173         matches.sort(key=lambda x:-x[1]) 
    174  
    175         return matches 
    176  
    177     def best_match(self, *arg, **kwarg): 
    178         return self.all_matches(*arg, **kwarg)[0][0] 
    179  
    180154class FileType(UniqueNamedModel): 
    181155    ### Model definition 
     
    191165    iconcls = models.CharField(max_length=30, blank=True) 
    192166 
    193     validators = models.ManyToManyField(FileValidator, blank=True) # Remove after conversion completed 
     167    #validators = models.ManyToManyField(FileValidator, blank=True) # Remove after conversion completed 
    194168 
    195169    objects = FileTypeManager() 
     170 
     171    class Meta(UniqueNamedModel.Meta): 
     172        app_label = APP_LABEL 
    196173 
    197174    ### 
     
    287264 
    288265class File(models.Model): 
    289     pass 
    290  
    291 class FileRevisionManager(models.Manager): 
    292     def __unique_path(self, filename): 
    293         store = self.model.get_store() 
    294  
    295         while True: 
    296             filepath = '/'.join('%02x' % random.randrange(x) 
    297                                 for x in [32,64,128,256,256]) 
    298             fullpath = os.path.join(store, filepath) 
    299             if not os.path.exists(os.path.join(fullpath, filename)): 
    300                 if not os.path.exists(fullpath): 
    301                     os.makedirs(fullpath, 0700) 
    302                 return filepath 
    303  
    304     def create(self, path, copy=False, **kwargs): 
    305         kwargs.setdefault('filename', os.path.basename(path)) 
    306         kwargs.setdefault('created', datetime.utcnow()) 
    307  
    308         kwargs['filepath'] = self.__unique_path(kwargs['filename']) 
    309  
    310         r = super(FileRevisionManager, self).create(**kwargs) 
    311  
    312         if copy: 
    313             shutil.copy2(path, r.get_file_path()) 
    314         else: 
    315             shutil.move(path, r.get_file_path()) 
    316  
    317         return r 
    318  
    319     def get_or_create(self, path, copy=False, **kwargs): 
    320         defaults = kwargs.pop('defaults', {}) 
    321  
    322         for rev in self.filter(**kwargs): 
    323             if filecmp.cmp(path, rev.get_file_path(), True): 
    324                 os.unlink(path) 
    325                 return rev, False 
    326  
    327         params = dict([(k, v) for k, v in kwargs.iteritems() if '__' not in k]) 
    328         params.update(defaults) 
    329  
    330         return self.create(path, copy, **params), True 
     266    class Meta: 
     267        app_label = APP_LABEL 
    331268 
    332269_codecs = {} 
     
    334271class FileRevision(models.Model, RevisionMixin): 
    335272    ### Model definition 
    336     file = models.ForeignKey(File) 
     273    file = models.ForeignKey(File) #@ReservedAssignment 
    337274 
    338275    filepath = models.CharField(max_length=300) 
     
    350287 
    351288    objects = FileRevisionManager() 
     289 
     290    class Meta: 
     291        app_label = APP_LABEL 
    352292 
    353293    ### Model methods 
  • src/peach3/models/forum.py

    r1554 r1563  
    88from django.utils.translation import ugettext_lazy as _ 
    99from django.utils.html import escape, linebreaks 
     10 
     11from peach3.models import APP_LABEL 
    1012 
    1113from django.db import models 
     
    2224 
    2325    response_to = models.ForeignKey('self', related_name='response_set', null=True, blank=True) 
     26 
     27    class Meta: 
     28        app_label = APP_LABEL 
     29        get_latest_by = 'created' 
    2430 
    2531    def get_header(self): 
     
    4349        return u'Comment by %r for %r' % (self.author, self.parent) 
    4450 
    45     class Meta: 
    46         get_latest_by = 'created' 
    47  
    4851MARKUP_CHOICES = ( 
    49   ('plain', _("Plain text")), 
    50   ('rst'  , _("Restructured text")), 
     52    ('plain', _("Plain text")), 
     53    ('rst'  , _("Restructured text")), 
    5154) 
    5255 
     
    6164    message = models.TextField(blank=True) 
    6265 
     66    class Meta: 
     67        app_label = APP_LABEL 
     68        get_latest_by = 'created' 
     69 
    6370    def get_formatted(self): 
    6471        if self.markup=='plain': 
     
    6774            return rst2html(self.message)['html_body'] 
    6875    formatted = property(get_formatted) 
    69  
    70     class Meta: 
    71         get_latest_by = 'created' 
  • src/peach3/models/grade.py

    r1554 r1563  
    11""" This file is part of PEACH. 
    22 
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    55 
    66from django.db import models 
    77 
    8 from peach3.i18n.models import UniqueNamedModel, NamedModel 
     8from peach3.models.i18n import UniqueNamedModel, NamedModel 
     9from peach3.models import APP_LABEL 
     10 
     11from peach3.managers.grade import GradeManager 
    912 
    1013# Create your models here. 
    1114 
    1215class GradingSystem(UniqueNamedModel): 
    13     pass 
    14  
    15 class GradeManager(models.Manager): 
    16     def get_boolean_accept(self): 
    17         if not hasattr(self, '_accept'): 
    18             self._accept = self.get(system__id=1, passing=True) 
    19         return self._accept 
    20     accept = property(get_boolean_accept) 
    21  
    22     def get_boolean_reject(self): 
    23         if not hasattr(self, '_reject'): 
    24             self._reject = self.get(system__id=1, passing=False) 
    25         return self._reject 
    26     reject = property(get_boolean_reject) 
     16    class Meta(UniqueNamedModel.Meta): 
     17        app_label = APP_LABEL 
    2718 
    2819class Grade(NamedModel): 
     
    3728    objects = GradeManager() 
    3829 
    39     class Meta: 
     30    class Meta(NamedModel.Meta): 
     31        app_label = APP_LABEL 
    4032        unique_together = (('system','default_name',),) 
    4133 
  • src/peach3/models/i18n.py

    r1355 r1563  
     1""" This file is part of PEACH. 
     2 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
     4""" 
    15from django.conf import settings 
    26from django.contrib.contenttypes.models import ContentType 
     
    610from django.utils.translation import get_language 
    711 
     12from peach3.models import APP_LABEL 
     13 
     14from peach3.managers.i18n import TranslatedNameManager 
     15 
    816LANGCODES = [l[0] for l in settings.LANGUAGES] 
    917 
     
    1119    if isinstance(all_names, dict): 
    1220        return all_names 
    13      
     21 
    1422    default = None 
    1523    names = {} 
     
    2028            if ':' not in langname: 
    2129                raise ValueError, 'Invalid format' 
    22              
     30 
    2331            lang, name = langname.split(':', 1) 
    24              
     32 
    2533            if lang not in LANGCODES: 
    2634                raise ValueError, 'Invalid language code' 
    27              
     35 
    2836            if default is None: 
    2937                default = lang 
    30                  
     38 
    3139            names[lang] = name 
    32              
     40 
    3341    return names, default 
    34                  
    3542 
    36 class _NamingMixin: 
     43 
     44class I18NModel(models.Model): 
    3745    def get_name(self, lang=None): 
    3846        if lang is None: 
    3947            lang = get_language() 
    40              
     48 
    4149        try: 
    4250            return TranslatedName.objects.get_by_object(object=self, language=lang).name 
    4351        except TranslatedName.DoesNotExist: 
    4452            return self.default_name 
    45                  
     53 
    4654    name = property(get_name) 
    47          
     55 
    4856    def set_name(self, name, lang=None, set_default=False): 
    4957        if lang is None: 
     
    5159        elif lang not in LANGCODES: 
    5260            raise ValueError, 'Invalid language code' 
    53              
     61 
    5462        if lang==self.default_language or set_default: 
    5563            # Save old default 
    5664            old_default_name = self.default_name 
    5765            old_default_language = self.default_language 
    58              
     66 
    5967            # Set new default 
    6068            TranslatedName.objects.filter_by_object(object=self, language=lang).delete() 
     
    6270            self.default_language = lang 
    6371            self.save() 
    64              
     72 
    6573            # If language of old default differs from new default, save old default as translation 
    6674            if old_default_language != lang: 
    6775                self.set_name(old_default_name, old_default_language) 
    68              
     76 
    6977        else: 
    70             object, created = TranslatedName.objects.get_or_create_by_object(object=self,  
     78            object, created = TranslatedName.objects.get_or_create_by_object(object=self, 
    7179                                                                             language=lang, 
    7280                                                                             defaults={ 
    7381                                                                                 'name':name, 
    7482                                                                             }) 
    75              
     83 
    7684            if not created: 
    7785                object.name = name 
    7886                object.save() 
    79                  
     87 
    8088    def get_all_names(self): 
    8189        names =  [(self.default_language, self.default_name)] \ 
    82                 +[(o.language, o.name)  
     90                +[(o.language, o.name) 
    8391                  for o in TranslatedName.objects.filter_by_object(object=self)] 
    84                  
     92 
    8593        return "\n".join("%s:%s" % n for n in names) 
    86      
     94 
    8795    def set_all_names(self, all_names): 
    8896        names, default = parse_names(all_names) 
    89          
     97 
    9098        # Update/add translations 
    9199        for lang, name in names.iteritems(): 
    92100            self.set_name(name, lang, lang==default) 
    93          
     101 
    94102        # Delete removed translations 
    95103        TranslatedName.objects.filter_by_object(object=self) \ 
    96104                              .exclude(language__in=names.keys()) \ 
    97105                              .delete() 
    98          
     106 
    99107    all_names = property(get_all_names, set_all_names) 
    100      
     108 
    101109    get_display_name = get_name 
    102      
    103     def __unicode__(self): 
    104         return self.get_display_name() 
    105      
    106110 
    107 class NamedModel(models.Model, _NamingMixin): 
    108     default_name = models.CharField(max_length=100, blank=False, unique=False) 
    109     default_language = models.CharField(max_length=16, 
    110                                         choices=settings.LANGUAGES,  
    111                                         default=settings.LANGUAGE_CODE) 
    112     
    113111    class Meta: 
    114112        abstract = True 
    115113 
    116 class UniqueNamedModel(models.Model, _NamingMixin): 
    117     default_name = models.CharField(max_length=100, blank=False, unique=True) 
     114    def __unicode__(self): 
     115        return self.get_display_name() 
     116 
     117class NamedModel(I18NModel): 
     118    default_name = models.CharField(max_length=100, blank=False, unique=False) 
    118119    default_language = models.CharField(max_length=16, 
    119                                         choices=settings.LANGUAGES,  
     120                                        choices=settings.LANGUAGES, 
    120121                                        default=settings.LANGUAGE_CODE) 
    121     
     122 
    122123    class Meta: 
    123124        abstract = True 
    124125 
    125 class BlankNamedModel(models.Model, _NamingMixin): 
    126     default_name = models.CharField(max_length=100, blank=True, unique=False) 
     126class UniqueNamedModel(I18NModel): 
     127    default_name = models.CharField(max_length=100, blank=False, unique=True) 
    127128    default_language = models.CharField(max_length=16, 
    128                                         choices=settings.LANGUAGES,  
     129                                        choices=settings.LANGUAGES, 
    129130                                        default=settings.LANGUAGE_CODE) 
    130     
     131 
    131132    class Meta: 
    132133        abstract = True 
    133134 
    134 class TranslatedNameManager(models.Manager):         
    135     def create_for_object(self, object, *args, **kwargs): 
    136         return self.create(content_type = ContentType.objects.get_for_model(object),  
    137                            object_id = object.pk,  
    138                            *args, **kwargs) 
    139          
    140     def get_by_object(self, object, *args, **kwargs): 
    141         return self.get(content_type = ContentType.objects.get_for_model(object),  
    142                         object_id = object.pk,  
    143                         *args, **kwargs) 
    144          
    145     def filter_by_object(self, object, *args, **kwargs): 
    146         return self.filter(content_type = ContentType.objects.get_for_model(object),  
    147                            object_id = object.pk,  
    148                            *args, **kwargs) 
    149          
    150     def get_or_create_by_object(self, object, *args, **kwargs): 
    151         return self.get_or_create(content_type = ContentType.objects.get_for_model(object),  
    152                                   object_id = object.pk,  
    153                                   *args, **kwargs) 
    154      
     135class BlankNamedModel(I18NModel): 
     136    default_name = models.CharField(max_length=100, blank=True, unique=False) 
     137    default_language = models.CharField(max_length=16, 
     138                                        choices=settings.LANGUAGES, 
     139                                        default=settings.LANGUAGE_CODE) 
     140 
     141    class Meta: 
     142        abstract = True 
     143 
    155144class TranslatedName(models.Model): 
    156145    content_type = models.ForeignKey(ContentType) 
     
    160149    language = models.CharField(max_length=16, choices=settings.LANGUAGES) 
    161150    name = models.CharField(max_length=100) 
    162      
     151 
    163152    objects = TranslatedNameManager() 
    164      
     153 
    165154    class Meta: 
     155        app_label = APP_LABEL 
    166156        unique_together = (('content_type','object_id','language',),) 
    167          
    168 class TranslatedNameInline(generic.GenericTabularInline): 
    169     model = TranslatedName 
  • src/peach3/models/news.py

    r1552 r1563  
    11""" This file is part of PEACH. 
    22 
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    55from django.contrib.auth.models import User 
     
    1010from django.utils.translation import ugettext_lazy as _ 
    1111 
    12 from peach3.core.models import cleanup_signal 
    13 from peach3.course.models import CourseEdition, Cluster 
     12from peach3.models.course import CourseEdition, Cluster 
     13from peach3.models import APP_LABEL, cleanup_signal 
     14from peach3.managers.news import NewsItemManager 
     15 
    1416from peach3.utils.rst import rst2html 
    1517 
    16 from datetime import datetime, timedelta 
     18from datetime import datetime 
    1719 
    1820# Create your models here. 
     
    2224  ('rst'  , _("Restructured text")), 
    2325) 
    24  
    25 class NewsItemManager(models.Manager): 
    26     def create(self, **kwargs): 
    27         now = datetime.utcnow() 
    28  
    29         kwargs.setdefault('created', now) 
    30         kwargs.setdefault('appears', now) 
    31  
    32         # Expires attribute can be a timedelta object which will be converted into a datetime object 
    33         expires = kwargs.get('expires', timedelta(days=365)) 
    34         if isinstance(expires, timedelta): 
    35             kwargs['expires'] = kwargs['appears'] + expires 
    36  
    37         if kwargs['expires'] > now: 
    38             return super(NewsItemManager, self).create(**kwargs) 
    39  
    40     def filter_related(self, related, id=None): 
    41         # Can be called in 3 ways: 
    42         #    filter_related(object) 
    43         #    filter_related(class) 
    44         #    filter_related(class, id) 
    45         q={ 
    46             'related_content_type' : ContentType.objects.get_for_model(related) 
    47         } 
    48  
    49         if hasattr(related, '__base__'): 
    50             if id is not None: 
    51                 q['related_id'] = id 
    52         else: 
    53             q['related_id'] = related.pk 
    54  
    55         return self.filter(**q) 
    5626 
    5727class NewsItem(models.Model): 
     
    9767    objects = NewsItemManager() 
    9868 
     69    class Meta: 
     70        app_label = APP_LABEL 
     71        get_latest_by = 'appears' 
     72 
    9973    def is_read(self, user): 
    10074        return self.read.filter(pk=user.pk).count()>0 
     
    128102        return subj, body, link 
    129103 
    130     class Meta: 
    131         get_latest_by = 'appears' 
    132  
    133104class NewsBody(models.Model): 
    134105    subject = models.CharField(max_length=80) 
    135106    message = models.TextField() 
    136107    markup = models.SlugField(max_length=8, choices=MARKUP_CHOICES, default=MARKUP_CHOICES[0][0]) 
     108 
     109    class Meta: 
     110        app_label = APP_LABEL 
    137111 
    138112    def get_formatted(self): 
     
    145119def cleanup_news(sender, **kwargs): 
    146120    from django.db import transaction 
    147     from datetime import datetime 
    148121    NewsItem.objects.filter(expires__lt=datetime.utcnow()).delete() 
    149122    transaction.commit_unless_managed() 
     123 
    150124cleanup_signal.connect(cleanup_news) 
  • src/peach3/models/pdf.py

    r1429 r1563  
    66# Create your models here. 
    77 
    8 from peach3.core.models import cleanup_signal 
     8from peach3.models import APP_LABEL, cleanup_signal 
    99 
    1010from pdfviewer.models import PDFInfo, PDFInfoManager 
     
    1515    ref_id = models.PositiveIntegerField(blank=True, null=True) 
    1616    ref = generic.GenericForeignKey('ref_ct', 'ref_id') 
    17      
     17 
    1818    objects = PDFInfoManager() 
     19 
     20    class Meta: 
     21        app_label = APP_LABEL 
    1922 
    2023def cleanup_pdf(sender, **kwargs): 
    2124    from pdfviewer.management.commands.clean_pdf import do_clean_pdf 
    22      
     25 
    2326    # Clean pdf db entries after 1 week, clean cached pages after 1 hour 
    2427    do_clean_pdf(7*24*3600, 3600) 
  • src/peach3/models/proglang.py

    r1247 r1563  
    11# -*- coding: utf-8 -*- 
    22""" This file is part of PEACH. 
    3        
    4     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3 
     4    Copyright (C) 2006-2012 Eindhoven University of Technology 
    55    Copyright (C) 2009 Eljakim Information Technology bv. 
    66""" 
    77 
    88from django.db import models 
    9 from peach3.i18n.models import UniqueNamedModel 
     9from peach3.models.i18n import UniqueNamedModel 
     10from peach3.models import APP_LABEL 
    1011 
    1112class ProgrammingLanguage(UniqueNamedModel): 
    1213    code = models.CharField(max_length=16) 
     14 
     15    class Meta(UniqueNamedModel.Meta): 
     16        app_label = APP_LABEL 
     17        db_table = APP_LABEL+'_proglang' 
  • src/peach3/models/report.py

    r1247 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
     
    77from django.db import models 
    88 
    9 from datetime import datetime 
     9from peach3.models import APP_LABEL, cleanup_signal 
     10 
    1011import os 
    11  
    12 from peach3.core.models import cleanup_signal 
    1312 
    1413# Create your models here. 
     
    1918    filename = models.CharField(max_length=256) 
    2019    mimetype = models.CharField(max_length=50) 
    21      
     20 
    2221    created = models.DateTimeField() 
    2322    last_access = models.DateTimeField(null=True, blank=True) 
    24      
     23 
    2524    users = models.ManyToManyField(User) 
    26      
     25 
     26    class Meta: 
     27        app_label = APP_LABEL 
     28 
    2729    def get_subdir(self, autocreate=True): 
    2830        dir = os.path.join(settings.TEMP, 'report%d' % self.id) 
     
    3032            os.makedirs(dir) 
    3133        return dir 
    32          
     34 
    3335    def get_full_path(self, autocreate=True): 
    3436        return os.path.join(self.get_subdir(autocreate), self.filename) 
    3537    full_path = property(get_full_path) 
    36      
     38 
    3739    def exists(self): 
    3840        path = self.get_full_path(False) 
    3941        return os.path.exists(path) 
    40      
     42 
    4143    def get_hash(self): 
    4244        if not hasattr(self, '_hash'): 
     
    4648        return self._hash 
    4749    hash = property(get_hash) 
    48      
     50 
    4951    def get_filesize(self): 
    5052        path = self.get_full_path() 
     
    5456            return 0 
    5557    filesize = property(get_filesize) 
    56      
     58 
    5759    def delete(self, *args, **kwargs): 
    5860        import os 
    59          
     61 
    6062        try: 
    6163            os.remove(self.get_full_path(False)) 
     
    6769        except: 
    6870            pass 
    69          
     71 
    7072        super(Report, self).delete(*args, **kwargs) 
    71          
     73 
    7274    @models.permalink 
    7375    def get_absolute_url(self): 
    7476        return ('peach3.report.views.download', [self.id, self.filename]) 
    7577    absolute_url = property(get_absolute_url) 
    76      
     78 
    7779    def get_download_html(self): 
    7880        from django.template.loader import render_to_string 
    7981        return render_to_string('report/download_link.html', {'report':self}) 
    8082    download_html = property(get_download_html) 
    81      
     83 
    8284    def __unicode__(self): 
    8385        return self.filename 
    84      
     86 
    8587def cleanup_reports(sender, **kwargs): 
    8688    from django.db import transaction 
  • src/peach3/models/submission.py

    r1546 r1563  
    11""" This file is part of PEACH. 
    2        
     2 
    33    Copyright (C) 2006-2009 Eindhoven University of Technology 
    44""" 
     
    66from django.core.cache import cache 
    77from django.db import models, transaction 
    8 from django.db.models import Q 
    98from django.utils.translation import ugettext_lazy, ugettext as _ 
    109from django.utils.translation import get_language 
     
    1514from django import dispatch 
    1615 
    17 from peach3.assignment.models import AssignmentEdition, AssignmentSlot 
    18 from peach3.course.models import CourseEdition, Cluster 
    19 from peach3.files.models import FileRevision 
    20 from peach3.forum.models import Comment 
    21 from peach3.grade.models import Grade 
    22 from peach3.news.models import NewsItem 
    23 from peach3.programming_language.models import ProgrammingLanguage 
    24  
    25 from peach3.utils import RevisionMixin, cache_key, unique_cache_hash 
     16from peach3.models.assignment import AssignmentEdition, AssignmentSlot 
     17from peach3.models.course import CourseEdition 
     18from peach3.models.files import FileRevision 
     19from peach3.models.forum import Comment 
     20from peach3.models.grade import Grade 
     21from peach3.models.news import NewsItem 
     22from peach3.models.proglang import ProgrammingLanguage 
     23from peach3.models.mixins import RevisionMixin 
     24from peach3.models import APP_LABEL, cleanup_signal 
     25from peach3.managers.submission import SubmissionManager, CheckResultManager, ReasonManager 
     26 
     27from peach3.utils.cache import cache_key, unique_cache_hash 
    2628 
    2729from datetime import datetime, timedelta 
    28 from string import Template 
    2930 
    3031# Create your models here. 
     
    3233submission_created = dispatch.Signal() 
    3334 
    34 class SubmissionManager(models.Manager): 
    35     def create(self, *arg, **kwarg): 
    36         if 'created' not in kwarg: 
    37             kwarg['created'] = datetime.utcnow() 
    38         sb = super(SubmissionManager, self).create(*arg, **kwarg) 
    39 #        sb.flush_cache() 
    40         return sb 
    41      
    4235class Submission(models.Model): 
    4336    assignmentedition = models.ForeignKey(AssignmentEdition) 
    4437    courseedition = models.ForeignKey(CourseEdition) 
    45      
     38 
    4639    authors = models.ManyToManyField(User) 
    4740    comments = generic.GenericRelation(Comment, object_id_field='parent_id', 
     
    5548 
    5649    files = models.ManyToManyField(FileRevision, through='SubmissionFile') 
    57      
     50 
    5851    modified = models.DateTimeField(null=True, blank=True, db_index=True) # Last modified time 
    5952 
     
    6154 
    6255    objects = SubmissionManager() 
    63      
     56 
     57    class Meta: 
     58        app_label = APP_LABEL 
     59 
    6460    def mark_updated(self, skip_local_update=False): 
    6561        now = datetime.utcnow().replace(microsecond=0) 
    6662        ae = self.assignmentedition 
    67          
     63 
    6864        self.flush_cache() 
    6965 
     
    8480            key = cache_key('peach3.submission.ajax.list_all', au, ae) 
    8581            cache.delete(key) 
    86              
     82 
    8783        self._observelevel = {} 
    8884        self._reviewlevel = {} 
    89              
     85 
    9086        if not skip_local_update: 
    9187            self.modified = now 
    9288            self.save() 
    93          
     89 
    9490    def last_updated(self): 
    9591        key = cache_key('peach3.submission.models.Submission', self.author, self.assignmentedition, 'updated') 
     
    9894            self.mark_updated() 
    9995        return updated 
    100          
     96 
    10197    def save(self, *arg, **kwarg): 
    10298        self.modified = datetime.utcnow() 
    10399        super(Submission, self).save(*arg, **kwarg) 
    104100        self.mark_updated(True) 
    105      
     101 
    106102    @transaction.commit_manually 
    107103    def submit(self, stamp, user, comment=None, progLangId=None): 
    108104        # User can be None (submitted by system) 
    109105        assert user is None or self.can_submit(user) 
    110          
     106 
    111107        try: 
    112108            self.submitted = stamp 
     
    122118 
    123119            self.save() 
    124              
     120 
    125121            if comment: 
    126122                c=self.comments.create(author=user, created=stamp) 
     
    130126                                             message=comment, 
    131127                                            ) 
    132              
     128 
    133129            if self.assignmentedition.legacychecker_set.filter(enabled=True).count()>0: 
    134130                self.checkresult_set.create(created=stamp) 
    135131                transaction.commit() # Must commit changes before sending signal 
    136                  
     132 
    137133                submission_created.send(sender=self.__class__, 
    138134                                        instance=self) 
    139135            else: 
    140136                self.send_initial_news() 
    141          
     137 
    142138        except: 
    143139            transaction.rollback() 
    144140            raise 
    145          
     141 
    146142        finally: 
    147143            transaction.commit() 
     
    151147 
    152148        return stamp is None or updated is None or updated>stamp 
    153      
     149 
    154150    def _get_state(self, user): 
    155151        if self.submitted is None: 
    156152            return 'NEW' 
    157          
     153 
    158154        # IF there is a review with a grade, return that state 
    159155        try: 
     
    169165            else: 
    170166                return 'REVIEWING' 
    171          
     167 
    172168        # IF there is a checkresult, return that state 
    173169        try: 
     
    183179            if not scr.grade.passing: 
    184180                return 'REJECTED' 
    185          
     181 
    186182        # OTHERWISE it's accepted 
    187183        ae = self.assignmentedition 
     
    194190        else: 
    195191            return 'ACCEPTED' 
    196      
     192 
    197193    def get_state(self, user): 
    198194        """ Can return 
     
    208204        """ 
    209205        key = self._get_cachekey('state', user) 
    210          
     206 
    211207        cached = cache.get(key) 
    212208        if cached is None: 
    213209            state = self._get_state(user) 
    214210            cache.set(key, (state,)) 
    215          
     211 
    216212        else: 
    217213            state = cached[0] 
    218              
     214 
    219215        return state 
    220216 
     
    224220        except self.checkresult_set.model.DoesNotExist: 
    225221            return None 
    226      
     222 
    227223    def get_tests_count(self, user): 
    228224        try: 
     
    230226        except self.checkresult_set.model.DoesNotExist: 
    231227            return None 
    232      
     228 
    233229    def get_file(self, filename): 
    234230        try: 
     
    238234 
    239235    def is_replaced(self, user=None): 
    240         # Returns True if this submission was replaced by a newer submission  
     236        # Returns True if this submission was replaced by a newer submission 
    241237        #  If user is None: for all authors, otherwise for requested user 
    242          
     238 
    243239        authors = [user] if user else self.authors.all() 
    244          
     240 
    245241        newersbs = Submission.objects.filter(authors__in=authors, 
    246242                                             assignmentedition=self.assignmentedition, 
    247243                                             created__gt=self.created) 
    248          
     244 
    249245        return newersbs.count()>0 
    250          
    251     @transaction.commit_on_success         
     246 
     247    @transaction.commit_on_success 
    252248    def send_initial_news(self): 
    253249        # Send the initial news message after upload and accept/reject by the system 
    254          
     250 
    255251        msg = None 
    256252        try: 
     
    266262            else: 
    267263                return 
    268              
     264 
    269265        authors = list(self.authors.all()) 
    270          
     266 
    271267        self_ct = ContentType.objects.get_for_model(self) 
    272268        now = datetime.utcnow() 
    273269        exp = max(self.created + timedelta(days=365), 
    274270                  now + timedelta(days=30)) 
    275              
     271 
    276272        # Mark any news item pertaining to an older submission for this assignment 
    277273        # by this author as not-current 
     
    279275                                            assignmentedition=self.assignmentedition): 
    280276            NewsItem.objects.filter_related(sb).update(current=False) 
    281              
     277 
    282278        admin_cluster = self.courseedition.get_admin_cluster() 
    283279        if admin_cluster: 
    284280            # Message to the course staff 
    285              
     281 
    286282            existing_news = NewsItem.objects.filter( 
    287283                courseedition = self.courseedition, 
     
    292288                msgtype = msg+'-admin', 
    293289            ).order_by('-created') 
    294                  
     290 
    295291            if msg=='received': 
    296292                if len(existing_news)==0: 
     
    308304                else: 
    309305                    news = existing_news[0] 
    310          
     306 
    311307                    if not news.current: 
    312308                        news.current = True 
    313309                        news.save() 
    314                  
     310 
    315311            else: 
    316312                existing_news.delete() 
    317                  
     313 
    318314        # Message to the authors 
    319315        for author in authors: 
    320316            msg_end = '-author' if author==self.created_by else '-coauthor' 
    321317            umsg = msg+msg_end 
    322              
     318 
    323319            existing_news = NewsItem.objects.filter( 
    324320                courseedition = self.courseedition, 
     
    329325                msgtype__endswith = msg_end, 
    330326            ).order_by('-created') 
    331              
     327 
    332328            if len(existing_news)==0: 
    333329                NewsItem.objects.create( 
     
    340336                    appears = self.created, 
    341337                    expires = exp, 
    342                     msgtype = umsg,  
     338                    msgtype = umsg, 
    343339                ) 
    344340            else: 
    345341                news = existing_news[0] 
    346                  
     342 
    347343                if news.msgtype != umsg or news.expires < exp or not news.current: 
    348344                    news.current = True 
     
    350346                    news.expires = exp 
    351347                    news.save() 
    352                      
     348 
    353349                    if news.msgtype != umsg: 
    354350                        news.mark_unread() 
    355              
     351 
    356352    def get_news_text(self, newsitem): 
    357353        type = newsitem.msgtype 
    358354 
    359         # Do not supply any hints to the score in these messages, to encourage users to visit the  
     355        # Do not supply any hints to the score in these messages, to encourage users to visit the 
    360356        # full submission and not just read a summary 
    361357        texts = { 
     
    385381            ), 
    386382        } 
    387          
     383 
    388384        args = { 
    389385            'username' : self.created_by.username, 
     
    391387            'assignmentname' : self.assignmentedition.name, 
    392388        } 
    393          
     389 
    394390        s,t = texts[type] 
    395391        return s % args, t % args 
    396      
     392 
    397393    def get_observelevel(self, user): 
    398394        """ Returns the user's observelevel for this submission 
     
    402398        if not hasattr(self, '_observelevel'): 
    403399            self._observelevel = {} 
    404              
     400 
    405401        if user not in self._observelevel: 
    406402            self._observelevel[user] = self.courseedition.get_observelevel(user, self.get_clusters()) 
    407              
    408         return self._observelevel[user]  
    409      
     403 
     404        return self._observelevel[user] 
     405 
    410406    def get_reviewlevel(self, user): 
    411407        """ Returns the user's reviewlevel for this submission 
     
    418414        if user not in self._reviewlevel: 
    419415            clusters = self.get_clusters() 
    420              
     416 
    421417            if len(clusters)==1 and clusters[0].admin_cluster: 
    422418                # Special case: all authors are reviewers 
    423419                clusters = '*' 
    424                  
     420 
    425421            self._reviewlevel[user] = self.courseedition.get_reviewlevel(user, clusters) 
    426              
     422 
    427423        return self._reviewlevel[user] 
    428      
     424 
    429425    def get_authors(self): 
    430426        # Generator returning all authors; the author that submitted the submission 
    431427        # is at the front 
    432428        main_author = None 
    433          
     429 
    434430        if self.submitted_by and self.is_author(self.submitted_by): 
    435431            main_author = self.submitted_by 
    436432        elif self.created_by and self.is_author(self.created_by): 
    437433            main_author = self.created_by 
    438              
     434 
    439435        if main_author: 
    440436            yield main_author 
     
    442438        else: 
    443439            q = self.authors.all() 
    444                  
     440 
    445441        for author in q: 
    446442            yield author 
    447              
     443 
    448444    def get_clusters(self): 
    449445        # Generator returning all author's clusters 
     
    459455                    result.append(cluster) 
    460456                clusters[cluster.pk] = True 
    461                  
     457 
    462458            cache.set(key, result) 
    463              
     459 
    464460        return result 
    465      
     461 
    466462    def same_authors(self, other_submission): 
    467463        # Returns the number of authors that are the same for both submissions 
    468464        pks = [au.pk for au in other_submission.get_authors()] 
    469465        return self.authors.filter(pk__in=pks).count() 
    470          
     466 
    471467    def is_visible(self, user): 
    472468        return self.is_author(user) or self.is_observer(user) 
    473      
     469 
    474470    def is_author(self, user): 
    475471        " Returns True if the given user is an author " 
    476472        return self.authors.filter(pk=user.pk).count()>0 
    477      
     473 
    478474    def is_observer(self, user): 
    479         """ Returns True if the user is an observer for this submission  
     475        """ Returns True if the user is an observer for this submission 
    480476        """ 
    481477        return self.get_observelevel(user) >= self.assignmentedition.observelevel 
    482          
     478 
    483479    def is_reviewer(self, user): 
    484480        " Returns True if the user is a reviewer for this submission " 
    485481        if not self.assignmentedition.has_option('REVIEW'): 
    486482            return False 
    487          
     483 
    488484        try: 
    489485            review = self.review_set.latest() 
    490486        except self.review_set.model.DoesNotExist: 
    491487            review = None 
    492          
     488 
    493489        if review: 
    494490            return review.can_modify(user) 
    495          
     491 
    496492        return self.get_reviewlevel(user) >= self.assignmentedition.reviewlevel 
    497      
     493 
    498494    def can_publish_review(self, user, allow_republish=False, initial=False): 
    499495        " Returns True if the user can publish the review for this submission " 
     
    504500                return False 
    505501        else: 
    506             if review.is_published() and not allow_republish:  
     502            if review.is_published() and not allow_republish: 
    507503                # Cannot publish if it is already published 
    508504                return False 
    509          
     505 
    510506        return user.is_superuser \ 
    511507               or self.courseedition.is_manager(user) \ 
    512508               or (self.is_reviewer(user) \ 
    513509                   and self.get_reviewlevel(user) >= self.assignmentedition.reviewpublishlevel) 
    514      
     510 
    515511    def can_submit(self, user): 
    516         return (self.submitted is None  
     512        return (self.submitted is None 
    517513                and (user.is_superuser 
    518                      or self.get_reviewlevel(user) >= self.assignmentedition.reviewlevel  
    519                      or (self.is_author(user)  
     514                     or self.get_reviewlevel(user) >= self.assignmentedition.reviewlevel 
     515                     or (self.is_author(user) 
    520516                          and self.assignmentedition.is_open(user, when=timedelta(minutes=-5)) 
    521517                        ) 
    522518                     ) 
    523519               ) and self.has_access(user) 
    524      
     520 
    525521    def has_access(self, user, access=None): 
    526522        if user.is_superuser: 
     
    528524        elif user.is_anonymous(): 
    529525            return False 
    530          
     526 
    531527        is_reviewer = self.is_reviewer(user) 
    532528        if access=='review': 
     
    536532        if access=='observe': 
    537533            return is_observer 
    538          
     534 
    539535        return access is None and (is_reviewer or is_observer or self.is_author(user)) 
    540          
     536 
    541537#    def flush_cache(self): 
    542538#        aeid = self.assignmentedition.id 
     
    544540#            key = 'submissions/user:%d/assignment:%d' % (au.id, aeid) 
    545541#            cache.delete(key) 
    546      
     542 
    547543    def __unicode__(self): 
    548544        return _("%(created_by)s for %(courseedition)s:%(assignmentedition)s on %(created)s") % {'created_by':self.created_by, 'courseedition':self.courseedition, 'assignmentedition':self.assignmentedition, 'created':self.created} 
    549      
     545 
    550546    @models.permalink 
    551547    def get_absolute_url(self): 
     
    557553        submission_cache_hash = unique_cache_hash(key, self) 
    558554        return cache_key('peach3.submission.models.Submission', self, 'info', submission_cache_hash, *args) 
    559      
     555 
    560556    def _del_cachekey(self): 
    561557        key = cache_key('peach3.submission.models.Submission', self, 'key') 
     
    564560    def flush_cache(self): 
    565561        self._del_cachekey() 
    566      
     562 
    567563    def delete(self, *args, **kwargs): 
    568564        self.flush_cache() 
    569565        super(Submission, self).delete(*args, **kwargs) 
    570      
     566 
    571567 
    572568class SubmissionFile(models.Model): 
    573569    submission = models.ForeignKey(Submission) 
    574570    file = models.ForeignKey(FileRevision) 
    575      
     571 
    576572    slot = models.ForeignKey(AssignmentSlot, null=True, blank=True) 
    577573 
    578 class CheckResultManager(models.Manager): 
    579     def create(self, *arg, **kwarg): 
    580         if 'created' not in kwarg: 
    581             kwarg['created'] = datetime.utcnow() 
    582         r = super(CheckResultManager, self).create(*arg, **kwarg) 
    583 #        r.submission.flush_cache() 
    584         return r 
     574    class Meta: 
     575        app_label = APP_LABEL 
    585576 
    586577class CheckResult(models.Model, RevisionMixin): 
     
    590581        ('SYSFAIL',  ugettext_lazy("System failure")), 
    591582    ) 
    592      
     583 
    593584    submission = models.ForeignKey(Submission) 
    594585    created = models.DateTimeField() 
    595586    checked = models.DateTimeField(null=True, blank=True) 
    596587    checked_level = models.PositiveSmallIntegerField(null=True, blank=True) 
    597      
     588 
    598589    state = models.CharField(max_length=8, blank=True, choices=STATES) 
    599590    grade = models.ForeignKey(Grade, null=True, blank=True) 
     
    612603    score_9 = models.IntegerField(null=True, blank=True) 
    613604    score_10 = models.IntegerField(null=True, blank=True) 
    614               
     605 
    615606    objects = CheckResultManager() 
    616      
     607 
    617608    class Meta: 
     609        app_label = APP_LABEL 
    618610        get_latest_by = 'created' 
    619          
     611 
    620612    def mark_updated(self): 
    621613        if self.submission: 
    622614            self.submission.mark_updated() 
    623          
     615 
    624616    def save(self, *arg, **kwarg): 
    625617        super(CheckResult, self).save(*arg, **kwarg) 
    626618        self.mark_updated() 
    627          
     619 
    628620    def get_score(self, user): 
    629621        if user: 
     
    632624            level = 0 
    633625        return getattr(self, 'score_%d' % level) 
    634      
     626 
    635627    def get_tests_count(self, user): 
    636628        " Returns a tuple (passed, total) with the number of test runs in this checkresult that are visible for user " 
    637          
     629 
    638630        level = self.submission.get_observelevel(user) or 0 
    639          
     631 
    640632        q = self.checkresultstep_set.exclude(level__gt=level).filter(stage='TESTRUN') 
    641          
     633 
    642634        total = q.count() 
    643635        passed = q.filter(passed=True).count() 
    644          
     636 
    645637        return (passed, total) 
    646638 
     
    648640#        super(CheckResult, self).save() 
    649641#        self.submission.flush_cache() 
    650          
     642 
    651643    ### Ordering mixin interface 
    652644    @staticmethod 
    653645    def get_grouping_field_name(): 
    654646        return 'submission' 
    655      
    656 class NumTemplate(Template): 
    657     """ A string.Template subclass accepting numbers as placeholder ids """ 
    658     idpattern=r"[0-9]+" 
    659  
    660 class ReasonManager(models.Manager): 
    661     def _get_by_code(self, code, **kwarg): 
    662         codeparts = code.split(':') 
    663         code = codeparts.pop(0) 
    664  
    665         param = codeparts 
    666         while len(param)<2: 
    667             param.append('') 
    668          
    669         try: 
    670             reason = self.get(code=code, param1=param[0], param2=param[1], **kwarg) 
    671         except self.model.DoesNotExist: 
    672             pass 
    673         else: 
    674             return reason, param 
    675          
    676         if param[0]: 
    677             try: 
    678                 reason = self.get(code=code, param1='', param2=param[1], **kwarg) 
    679             except self.model.DoesNotExist: 
    680                 pass 
    681             else: 
    682                 return reason, param 
    683          
    684         if param[1]: 
    685             try: 
    686                 reason = self.get(code=code, param1=param[0], param2='', **kwarg) 
    687             except self.model.DoesNotExist: 
    688                 pass 
    689             else: 
    690                 return reason, param 
    691  
    692         reason = self.get(code=code, param1='', param2='', **kwarg) 
    693         return reason, param 
    694      
    695     def create(self, code, **kwarg): 
    696         codeparts = code.split(':') 
    697         code = codeparts.pop(0) 
    698  
    699         param = codeparts 
    700         while len(param)<2: 
    701             param.append('') 
    702              
    703         return super(ReasonManager, self).create(code=code, param1=param[0], param2=param[1], **kwarg) 
    704          
    705     def get_description(self, code, **kwarg): 
    706         try: 
    707             reason, param = self._get_by_code(code, **kwarg) 
    708         except self.model.DoesNotExist: 
    709             reason = self.create(code=code, default_description=code) 
    710             param = '','' 
    711          
    712         kws = {'0':param[0], 
    713                '1':param[1] 
    714               }  
    715         return NumTemplate(reason.description).safe_substitute(kws) 
    716647 
    717648class Reason(models.Model): 
     
    719650    param1 = models.CharField(max_length=16, blank=True) 
    720651    param2 = models.CharField(max_length=16, blank=True) 
    721      
     652 
    722653    default_description = models.TextField() 
    723654    default_language = models.CharField(max_length=16, 
    724                                         choices=settings.LANGUAGES,  
     655                                        choices=settings.LANGUAGES, 
    725656                                        default=settings.LANGUAGE_CODE) 
    726      
     657 
    727658    objects = ReasonManager() 
     659 
     660    class Meta: 
     661        app_label = APP_LABEL 
    728662 
    729663    def get_description(self, lang=None): 
    730664        if lang is None: 
    731665            lang = get_language() 
    732          
     666 
    733667        key = cache_key('peach3.submission.models.Reason.get_description', self, lang) 
    734668 
    735669        cached = cache.get(key) 
    736         if cached is None:  
     670        if cached is None: 
    737671            try: 
    738672                description = self.reasonaltdescription_set.get(language=lang).description 
    739673            except self.reasonaltdescription_set.model.DoesNotExist: 
    740674                description = None 
    741                  
     675 
    742676            cache.set(key, (description,)) 
    743              
     677 
    744678        else: 
    745679            description = cached[0] 
    746          
     680 
    747681        if description: 
    748682            return description 
    749683        else: 
    750684            return self.default_description 
    751          
     685 
    752686    description = property(get_description) 
    753      
     687 
    754688    def __unicode__(self): 
    755689        code = self.code 
     
    765699    language = models.CharField(max_length=16, choices=settings.LANGUAGES) 
    766700    description = models.CharField(max_length=80) 
    767      
     701 
    768702    class Meta: 
     703        app_label = APP_LABEL 
    769704        unique_together = (('reason','language',),) 
    770705 
     
    775710        ('TESTRUN',       ugettext_lazy("Test Run")), 
    776711    ) 
    777      
     712 
    778713    REPORTFORMATS = ( 
    779714        ('2', ugettext_lazy("Peach2 legacy")), 
     
    782717        ('r', ugettext_lazy("rST")), 
    783718    ) 
    784      
     719 
    785720    checkresult = models.ForeignKey(CheckResult) 
    786721    stage = models.CharField(max_length=16, choices=STAGES, blank=True) 
    787722    step = models.PositiveSmallIntegerField() 
    788723    level = models.PositiveSmallIntegerField() 
    789      
     724 
    790725    passed = models.BooleanField() 
    791726 
    792727    reason = models.CharField(max_length=32, blank=True) 
    793      
     728 
    794729    report = models.TextField(blank=True) 
    795730    report_format = models.CharField(max_length=1, choices=REPORTFORMATS) 
    796731    score = models.IntegerField(null=True, blank=True) 
    797      
     732 
    798733    runtime = models.PositiveIntegerField(null=True, blank=True, help_text=ugettext_lazy("Unit: miliseconds")) 
    799      
     734 
    800735    generatedfiles = models.ManyToManyField(FileRevision, null=True, blank=True, editable=False) 
    801      
     736 
     737    class Meta: 
     738        app_label = APP_LABEL 
     739 
    802740    def is_visible(self, user): 
    803741        return self.level <= (self.checkresult.submission.get_observelevel(user) or 0) 
    804      
     742 
    805743    def mark_updated(self): 
    806744        if self.checkresult: 
    807745            self.checkresult.mark_updated() 
    808          
     746 
    809747    def save(self, *arg, **kwarg): 
    810748        super(CheckResultStep, self).save(*arg, **kwarg) 
    811749        self.mark_updated() 
    812      
     750 
    813751class CheckResultStepIO(models.Model): 
    814752    STREAMS = ( 
     
    817755        ('2', ugettext_lazy("stderr")), 
    818756    ) 
    819      
     757 
    820758    checkresultstep = models.ForeignKey(CheckResultStep) 
    821759    stream = models.CharField(max_length=1, choices=STREAMS) 
     
    824762    data = models.TextField() 
    825763 
     764    class Meta: 
     765        app_label = APP_LABEL 
     766 
    826767class ReviewManager(models.Manager): 
    827768    def create(self, *arg, **kwarg): 
     
    834775    created = models.DateTimeField() 
    835776    created_by = models.ForeignKey(User, related_name='review_creator') 
    836      
     777 
    837778    reviewlevel = models.PositiveSmallIntegerField() 
    838779    visibilitylevel = models.PositiveSmallIntegerField() 
    839      
     780 
    840781    grade = models.ForeignKey(Grade, null=True, blank=True) 
    841      
     782 
    842783    authors = models.ManyToManyField(User, related_name='review_author') 
    843784    comments = generic.GenericRelation(Comment, object_id_field='parent_id', 
    844785                                                content_type_field='parent_content_type') 
    845      
     786 
    846787    objects = CheckResultManager() 
    847      
     788 
    848789    class Meta: 
     790        app_label = APP_LABEL 
    849791        get_latest_by = 'created' 
    850          
     792 
    851793    def mark_updated(self): 
    852794        if self.submission: 
    853795            self.submission.mark_updated() 
    854          
     796 
    855797    def save(self, *arg, **kwarg): 
    856798        super(Review, self).save(*arg, **kwarg) 
    857799        self.mark_updated() 
    858          
     800 
    859801    @transaction.commit_on_success 
    860802    def send_reviewed_news(self): 
    861803        # Any older news about this submission is not current 
    862804        assert self.is_published() 
    863          
     805 
    864806        NewsItem.objects.filter_related(self.submission).update(current=False) 
    865807 
     
    874816                msgtype = 'reviewed', 
    875817            ) 
    876              
     818 
    877819    def can_modify(self, user): 
    878         return (self.created_by==user  
    879                 or self.submission.courseedition.is_manager(user)  
     820        return (self.created_by==user 
     821                or self.submission.courseedition.is_manager(user) 
    880822                or self.reviewlevel < self.submission.get_reviewlevel(user)) 
    881      
     823 
    882824    def is_visible(self, user): 
    883825        return self.can_modify(user) or self.visibilitylevel <= (self.submission.get_observelevel(user) or 0) 
    884      
     826 
    885827    def is_published(self): 
    886828        return self.visibilitylevel==0 
    887      
     829 
    888830    ### Ordering mixin interface 
    889831    @staticmethod 
     
    891833        return 'submission' 
    892834 
    893 from peach3.submission.tools import submit_unfinished 
    894 from peach3.core.models import cleanup_signal 
    895  
    896 cleanup_signal.connect(submit_unfinished) 
     835# TODO: 
     836#from peach3.submission.tools import submit_unfinished 
     837 
     838#cleanup_signal.connect(submit_unfinished) 
  • src/peach3/models/user.py

    r1480 r1563  
    11# -*- coding: utf-8 -*- 
    22""" This file is part of PEACH. 
    3        
     3 
    44    Copyright (C) 2006-2009 Eindhoven University of Technology 
    55""" 
     
    1010from django.utils.translation import ugettext_lazy as _ 
    1111 
    12 from peach3.programming_language.models import ProgrammingLanguage 
    13  
    14 from altnames import AlternativeNames 
     12from peach3.models.proglang import ProgrammingLanguage 
     13from peach3.models import APP_LABEL 
     14from peach3.managers.user import ProfileManager, VerificationCodeManager 
     15 
     16from peach3.core.user import AlternativeNames 
    1517 
    1618# Create your models here. 
    17  
    18 class ProfileManager(models.Manager): 
    19     def create(self, *arg, **kwarg): 
    20         tz = kwarg.pop('timezone') 
    21         if tz: 
    22             kwarg['timezone_str']=str(tz) 
    23         return super(ProfileManager, self).create(*arg, **kwarg) 
    2419 
    2520class Profile(models.Model): 
     
    3631      ("Y/m/d",)*2,         # 2007/01/09 
    3732    ) 
    38      
     33 
    3934    # Mapping from long dates (above) to short dates (without month and day names) 
    4035    SHORT_DATES = { 
    4136      "D j M Y"  : "j-m-Y", 
    42       "j M Y"    : "j-m-Y",   
     37      "j M Y"    : "j-m-Y", 
    4338      "D M j Y"  : "n.j.Y", 
    4439      "M j Y"    : "n.j.Y", 
     
    5045      "Y/m/d"    : "Y/m/d", 
    5146    } 
    52      
     47 
    5348    TIME_CHOICES = ( 
    5449      ("G:i",)*2,           # 15:05 
    5550      ("g:i a",)*2,         # 3:05 pm 
    5651    ) 
    57      
     52 
    5853    STATE_VERIFIED = 'V' 
    5954    STATE_UNVERIFIED = 'U' 
    6055    STATE_BLOCKED = 'X' 
    61      
     56 
    6257    STATE_CHOICES = ( 
    6358      (STATE_UNVERIFIED, _("Unverified")), 
     
    6560      (STATE_BLOCKED, _("Blocked")), 
    6661    ) 
    67      
     62 
    6863    user = models.ForeignKey(User) 
    6964    initials = models.CharField(max_length=30, blank=True) 
     
    7570    date_format = models.CharField(max_length=16, choices=DATE_CHOICES, default=DATE_CHOICES[0][0]) 
    7671    time_format = models.CharField(max_length=8, choices=TIME_CHOICES, default=TIME_CHOICES[0][0]) 
    77     language = models.CharField(max_length=16,  
    78                                 choices=(('',_("Browser default"),),)+settings.LANGUAGES,  
     72    language = models.CharField(max_length=16, 
     73                                choices=(('',_("Browser default"),),)+settings.LANGUAGES, 
    7974                                default='', blank=True) 
    8075    programmingLanguage = models.ForeignKey(ProgrammingLanguage, blank=True, null=True) 
    81     state = models.CharField(max_length=1, choices=STATE_CHOICES, default=STATE_CHOICES[0][0],  
     76    state = models.CharField(max_length=1, choices=STATE_CHOICES, default=STATE_CHOICES[0][0], 
    8277                             blank=False, null=False) 
    83      
     78 
     79    objects = ProfileManager() 
     80 
     81    class Meta: 
     82        app_label = APP_LABEL 
     83 
    8484    def __unicode__(self): 
    8585        return str(self.user) 
    86      
     86 
    8787    def format_initials(self): 
    8888        i = self.initials 
     
    9191        else: 
    9292            return '.'.join(list(i))+'.' 
    93      
     93 
    9494    def get_username_sortable(self): 
    9595        name = self.user.last_name 
     
    106106            if self.last_name_prefix: 
    107107                name += u' '+self.last_name_prefix 
    108          
     108 
    109109        return name 
    110      
     110 
    111111    def get_username_formal(self): 
    112112        name = self.user.last_name 
     
    119119            if self.last_name_prefix: 
    120120                name += u' '+self.last_name_prefix 
    121          
     121 
    122122        return name 
    123      
     123 
    124124    def get_username(self): 
    125125        name = self.user.last_name 
     
    134134                name = self.format_initials() + u' ' + name 
    135135        return name 
    136      
     136 
    137137    def get_timezone(self): 
    138138        if not hasattr(self, '_tz'): 
     
    144144                self._tz = pytz.timezone(settings.TIME_ZONE) 
    145145        return self._tz 
    146      
     146 
    147147    def set_timezone(self, tz): 
    148148        self._tz = tz 
    149149        self.timezone_str = str(tz) 
    150          
     150 
    151151    timezone = property(get_timezone, set_timezone) 
    152      
     152 
    153153    def get_datetime_format(self): 
    154154        return self.date_format+' '+self.time_format 
    155155    datetime_format = property(get_datetime_format) 
    156      
     156 
    157157    def get_userinfo(self): 
    158158        df = self.date_format 
    159          
     159 
    160160        info = { 
    161             'logged_in'       : True,  
     161            'logged_in'       : True, 
    162162            'username'        : self.user.username, 
    163163            'date_format'     : df, 
     
    167167            'language'        : self.language, 
    168168        } 
    169          
     169 
    170170        if self.user.is_staff: 
    171171            info['staff'] = True 
    172172        if self.user.is_superuser: 
    173173            info['superuser'] = True 
    174          
     174 
    175175        return info 
    176      
     176 
    177177    def get_alternative_names(self): 
    178178        return AlternativeNames().get_alternative_names(self.user.username) 
    179      
    180 class VerificationCodeManager(models.Manager): 
    181     def create(self, *args, **kwargs): 
    182         if 'code' not in kwargs: 
    183             kwargs['code'] = self.generate_code() 
    184          
    185         if 'created' not in kwargs: 
    186             from datetime import datetime 
    187             kwargs['created'] = datetime.utcnow() 
    188          
    189         if 'expires' not in kwargs: 
    190             from datetime import timedelta 
    191             kwargs['expires'] = kwargs['created']+timedelta(days=self.model.EXPIRATION) 
    192              
    193         return super(VerificationCodeManager, self).create(*args, **kwargs) 
    194          
    195     def generate_code(self): 
    196         while True: 
    197             code = User.objects.make_random_password(length=8) 
    198             if self.filter(code=code).count()==0: 
    199                 return code 
    200179 
    201180class VerificationCode(models.Model): 
    202181    EXPIRATION = 7 
    203      
     182 
    204183    TYPE_REGISTRATION = 'R' 
    205184    TYPE_PASSWORD_RESET = 'P' 
    206185    TYPE_EMAIL_CHANGE = 'E' 
    207      
     186 
    208187    TYPE_CHOICES = ( 
    209188      (TYPE_REGISTRATION, _("Registration")), 
     
    211190      (TYPE_EMAIL_CHANGE, _("Email change")), 
    212191    ) 
    213      
     192 
    214193    user = models.ForeignKey(User) 
    215194    type = models.CharField(max_length=1, blank=False, null=False, choices=TYPE_CHOICES) 
     
    218197    expires = models.DateTimeField(blank=True, null=True) 
    219198    parameters = models.TextField(blank=True) 
    220      
     199 
    221200    objects = VerificationCodeManager() 
    222      
     201 
     202    class Meta: 
     203        app_label = APP_LABEL 
     204 
    223205    def get_challenge(self): 
    224206        from hashlib import md5 
    225          
     207 
    226208        hash = md5() 
    227209        hash.update(settings.SECRET_KEY) 
     
    230212        hash.update(self.code) 
    231213        hash.update(self.parameters) 
    232          
     214 
    233215        return hash.hexdigest() 
    234      
     216 
    235217    def get_absolute_url(self): 
    236218        from django.core.urlresolvers import reverse 
  • src/peach3/models/wiki.py

    r1552 r1563  
    11""" This file is part of PEACH. 
    22 
    3     Copyright (C) 2006-2009 Eindhoven University of Technology 
     3    Copyright (C) 2006-2012 Eindhoven University of Technology 
    44""" 
    55from django.db import models 
    66 
    77from django.conf import settings 
    8 from django.core import urlresolvers 
    98from django.core.cache import cache 
    10 from django.core.exceptions import PermissionDenied 
    11 from django.contrib.auth.models import User, Permission 
     9from django.contrib.auth.models import User 
    1210from django.contrib.contenttypes.models import ContentType 
    1311from django.contrib.contenttypes import generic 
    14 from django.contrib.markup.templatetags import markup 
    15 from django.db import transaction 
    16 from django.utils.encoding import iri_to_uri 
    17  
    18 from django.utils.translation import ugettext_noop, ugettext_lazy as _, get_language 
    19  
    20 from peach3.assignment.models import Assignment, AssignmentEdition 
    21 from peach3.course.models import Course, CourseEdition 
    22  
    23 from peach3.utils import RevisionMixin, MarkupMixin, uidencode, uiddecode, UID_ESCAPE, cache_key 
     12 
     13from django.utils.translation import ugettext_lazy as _, get_language 
     14 
     15from peach3.models.mixins import RevisionMixin 
     16from peach3.models import APP_LABEL 
     17from peach3.managers.wiki import PageManager, PageRevisionTextManager, PageRevisionManager 
     18from peach3.utils.wiki import decode_full_path, user_can_create_page, construct_full_path 
     19from peach3.utils.cache import cache_key 
    2420 
    2521from datetime import datetime, timedelta 
    26  
    27 def construct_full_path(parent, path, mustexist=False): 
    28     path = path.strip(' /') 
    29  
    30     if parent is None: 
    31         return '/'+path 
    32     else: 
    33         if hasattr(parent, 'get_uid'): 
    34             ouid = parent.get_uid() 
    35         else: 
    36             ouid = None 
    37  
    38         if ouid: 
    39             ouids = '/'.join(uidencode(i) for i in ouid) 
    40         else: 
    41             ouids = '%s%d' % (UID_ESCAPE, parent.id) 
    42  
    43         ct = ContentType.objects.get_for_model(parent) 
    44  
    45         path = '%s%d' % (UID_ESCAPE, ct.id)+'/'+'/'.join((ouids,path,)) 
    46  
    47         if mustexist: 
    48             # Check if page actually exists 
    49             try: 
    50                 page = Page.objects.get_by_path(path) 
    51             except Page.DoesNotExist: 
    52                 return None 
    53  
    54         return path 
    55  
    56 def wiki_path(parent, path): 
    57     return urlresolvers.reverse('peach3.wiki.views.wiki', 
    58                                 kwargs={'path':iri_to_uri(construct_full_path(parent, path))}) 
    59  
    60 def decode_full_path(full_path): 
    61     key = cache_key('peach3.wiki.models.decode_full_path', full_path) 
    62     result = cache.get(key) 
    63  
    64     if result is None: 
    65         full_path = full_path.lstrip(' /') 
    66  
    67         if not full_path.startswith(UID_ESCAPE): 
    68             ptype = None 
    69             pid = None 
    70             path = full_path.rstrip(' /') 
    71         else: 
    72             path_parts = full_path.split('/') 
    73  
    74             if len(path_parts)<3: 
    75                 raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'Path incomplete')) 
    76  
    77             try: 
    78                 ctid = path_parts.pop(0)[len(UID_ESCAPE):] 
    79                 ptype = ContentType.objects.get(pk=ctid) 
    80             except ValueError: 
    81                 raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'ContentType incomplete')) 
    82             except ContentType.DoesNotExist: 
    83                 raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'ContentType invalid')) 
    84  
    85             head = path_parts[0] 
    86             if len(head)>1 and head[0]==UID_ESCAPE and head[1].isdigit(): 
    87                 try: 
    88                     pid = int(head[1:]) 
    89                 except ValueError: 
    90                     raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'ID invalid')) 
    91  
    92                 path_parts.pop(0) 
    93             else: 
    94                 model = ptype.model_class() 
    95  
    96                 if not hasattr(model, 'get_uid_fields'): 
    97                     raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'ID invalid')) 
    98  
    99                 fields = model.get_uid_fields() 
    100  
    101                 latest_by_created=False 
    102                 kwargs = {} 
    103                 for field in fields: 
    104                     if path_parts: 
    105                         value = uiddecode(path_parts.pop(0)) 
    106                     else: 
    107                         value = '' 
    108  
    109                     if value=='' and field=='created': 
    110                         latest_by_created=True 
    111                     elif value is None: 
    112                         kwargs['%s__isnull' % field] = True 
    113                     else: 
    114                         if field=='created': 
    115                             # Convert value to a datetime object 
    116                             # Format of value 'YYYYMMDDhhmmss' 
    117                             value = datetime.strptime(value, "%Y%m%d%H%M%S") 
    118                         kwargs[field] = value 
    119  
    120                 pid = None 
    121                 try: 
    122                     q = model.objects.filter(**kwargs) 
    123                     if latest_by_created: 
    124                         pid = q.latest().id 
    125                     else: 
    126                         ids = q.values_list('id', flat=True) 
    127                         if len(ids)==1: 
    128                             pid = ids[0] 
    129                         elif len(ids)>1: 
    130                             raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'Multiple parents match')) 
    131                 except model.DoesNotExist: 
    132                     pass 
    133  
    134                 if pid is None: 
    135                     raise Page.DoesNotExist, u'%s (%s)' % (_(u'Page matching query does not exist'), _(u'Parent not found')) 
    136  
    137             while len(path_parts)>0 and path_parts[-1]=='': 
    138                 path_parts.pop() 
    139  
    140             path = '/'.join(path_parts) 
    141  
    142         if ptype: 
    143             parent = ptype.get_object_for_this_type(pk=pid) 
    144         else: 
    145             parent = None 
    146  
    147         result = parent, path 
    148  
    149         cache.set(key, result) 
    150  
    151     return result 
    152  
    153 def user_can_create_page(user, parent, path): 
    154     if user.is_anonymous(): 
    155         return False 
    156     elif user.is_superuser: 
    157         return True 
    158     elif user.has_perm('wiki.add_page'): 
    159         return True 
    160  
    161     if parent and hasattr(parent, 'has_access') and parent.has_access(user, 'createwiki'): 
    162         return True 
    163  
    164     # Find a parent page 
    165     pathparts = path.strip(' /').split('/') 
    166     if parent: 
    167         args={'parent':parent} 
    168     else: 
    169         args={'parent__isnull':True} 
    170  
    171     while len(pathparts)>0: 
    172         pathparts.pop() 
    173         path = '/'.join(pathparts) 
    174         try: 
    175             parentpage = Page.objects.get(path=path, **args) 
    176             if parentpage.is_editor(user): 
    177                 return True 
    178         except Page.DoesNotExist: 
    179             pass 
    180  
    181     return False 
    182  
    183 class PageManager(models.Manager): 
    184  
    185     def create(self, user, path='', parent=None, **kwarg): 
    186         path = path.strip('/ ') 
    187  
    188         # See http://code.djangoproject.com/ticket/7551 
    189         if parent: 
    190             kwarg['parent'] = parent 
    191  
    192         if not user_can_create_page(user, parent, path): 
    193             raise PermissionDenied 
    194  
    195         return super(PageManager, self).create(path = path, 
    196                                                created = datetime.utcnow(), 
    197                                                created_by = user, 
    198                                                #parent = parent, 
    199                                                **kwarg 
    200                                               ) 
    201  
    202     def get_by_path(self, full_path): 
    203         parent, path = decode_full_path(full_path) 
    204  
    205         query = {'path':path} 
    206  
    207         if parent: 
    208             query['parent'] = parent 
    209         else: 
    210             query['parent__isnull'] = True 
    211  
    212         return self.get(**query) 
    213  
    214     def get(self, **kwarg): 
    215         if 'parent' in kwarg: 
    216             parent = kwarg.pop('parent') 
    217             assert parent 
    218             kwarg['parent_type'] = ContentType.objects.get_for_model(parent) 
    219             kwarg['parent_id'] = parent.pk 
    220         elif 'parent__isnull' in kwarg: 
    221             kwarg['parent_type__isnull'] = kwarg.pop('parent__isnull') 
    222  
    223         return super(PageManager, self).get(**kwarg) 
    224  
    225     def filter_parent(self, parent): 
    226         if parent is None: 
    227             return self.filter(parent_type__isnull=True) 
    228         else: 
    229             type = ContentType.objects.get_for_model(parent) 
    230             return self.filter(parent_type=type, parent_id=parent.pk) 
    23122 
    23223# The ordering here is important! 
     
    26960 
    27061    class Meta: 
     62        app_label = APP_LABEL 
    27163        get_latest_by = 'created' 
    27264        unique_together = (('parent_type','parent_id','path',),) 
     
    536328    permission = models.PositiveSmallIntegerField(choices=PAGE_PERMISSION) 
    537329 
     330    class Meta: 
     331        app_label = APP_LABEL 
     332 
    538333MARKUP_CHOICES = ( 
    539334  ('plain', _("Plain text")), 
    540335  ('rst'  , _("Restructured text")), 
    541336) 
    542  
    543 class PageRevisionTextManager(models.Manager): 
    544     def __update_arg(self, kwarg): 
    545         if 'content' in kwarg: 
    546             kwarg['diff_base'] = None 
    547             kwarg['content_or_diff'] = kwarg.pop('content') 
    548  
    549     def create(self, **kwarg): 
    550         self.__update_arg(kwarg) 
    551         return super(PageRevisionTextManager, self).create(**kwarg) 
    552  
    553     def get_or_create(self, **kwarg): 
    554         self.__update_arg(kwarg) 
    555         if kwarg['diff_base'] is None: 
    556             del kwarg['diff_base'] 
    557             kwarg['diff_base__isnull'] = True 
    558             kwarg['defaults'] = {'diff_base':None} 
    559  
    560         return super(PageRevisionTextManager, self).get_or_create(**kwarg) 
    561  
    562337 
    563338class PageRevisionText(models.Model): 
     
    572347 
    573348    objects = PageRevisionTextManager() 
     349 
     350    class Meta: 
     351        app_label = APP_LABEL 
    574352 
    575353    ### Model methods 
     
    587365        self.save() 
    588366 
    589 class PageRevisionManager(models.Manager): 
    590     def create(self, *arg, **kwarg): 
    591         if 'created' not in kwarg: 
    592             kwarg['created'] = datetime.utcnow() 
    593  
    594         if 'content' in kwarg: 
    595             page = kwarg['page'] 
    596             content = kwarg.pop('content') 
    597             markup = kwarg.pop('markup') 
    598             assert markup, "Markup required in combination with content" 
    599             kwarg['text'], created = PageRevisionText.objects.get_or_create(markup=markup, content=content) 
    600  
    601         return super(PageRevisionManager, self).create(*arg, **kwarg) 
    602  
    603 #    @transaction.commit_on_success 
    604 #    def revert_to(self, revision, user, reason): 
    605 #        assert isinstance(revision, PageRevision) 
    606 # 
    607 #        new_revision = super(PageRevisionManager, self).create(page=revision.page, 
    608 #                                                               created_by=user, 
    609 #                                                               changes=reason) 
    610 # 
    611 #        l = list(revision.pagerevisiontext_set.all()) 
    612 #        new_revision.pagerevisiontext_set.add(*l) 
    613 # 
    614 #        return new_revision 
    615  
    616367class PageRevision(models.Model, RevisionMixin): 
    617368    ### Model definition 
     
    628379 
    629380    objects = PageRevisionManager() 
     381 
     382    class Meta: 
     383        app_label = APP_LABEL 
     384        get_latest_by = 'created' 
    630385 
    631386    ### Model methods 
     
    647402        return u'%s (%s) [%d]' % (self.page, self.language, self.revision) 
    648403 
    649     class Meta: 
    650         get_latest_by = 'created' 
    651  
    652 class PageComment(models.Model, MarkupMixin): 
    653     ### Model definition 
    654     revisiontext = models.ForeignKey(PageRevisionText) 
    655  
    656     created = models.DateTimeField() 
    657     created_by = models.ForeignKey(User) 
    658  
    659     reply_on = models.ForeignKey("self", null=True, blank=True, 
    660                                  related_name="replies_set") 
    661  
    662     content = models.TextField() 
     404#class PageComment(models.Model, MarkupMixin): 
     405#    ### Model definition 
     406#    revisiontext = models.ForeignKey(PageRevisionText) 
     407# 
     408#    created = models.DateTimeField() 
     409#    created_by = models.ForeignKey(User) 
     410# 
     411#    reply_on = models.ForeignKey("self", null=True, blank=True, 
     412#                                 related_name="replies_set") 
     413# 
     414#    content = models.TextField() 
     415# 
     416#    class Meta: 
     417#        app_label = APP_LABEL 
Note: See TracChangeset for help on using the changeset viewer.