Changeset 1563:7269ecf397a5 in peach3-core
- Timestamp:
- 01/09/2012 01:31:28 PM (4 months ago)
- Branch:
- core-split
- Files:
-
- 44 added
- 4 deleted
- 1 edited
- 30 copied
-
.hgignore (modified) (1 diff)
-
.tx/config (deleted)
-
bootstrap.py (deleted)
-
buildout-dev.cfg (deleted)
-
buildout.cfg (deleted)
-
setup.py (added)
-
src/peach3/__init__.py (added)
-
src/peach3/admin/__init__.py (added)
-
src/peach3/admin/assignment.py (copied) (copied from peach3/assignment/admin.py) (6 diffs)
-
src/peach3/admin/checking.py (copied) (copied from peach3/checking/admin.py) (1 diff)
-
src/peach3/admin/course.py (copied) (copied from peach3/course/admin.py) (3 diffs)
-
src/peach3/admin/files.py (copied) (copied from peach3/files/admin.py) (1 diff)
-
src/peach3/admin/forum.py (copied) (copied from peach3/forum/admin.py) (2 diffs)
-
src/peach3/admin/grade.py (copied) (copied from peach3/grade/admin.py) (1 diff)
-
src/peach3/admin/i18n.py (copied) (copied from peach3/i18n/admin.py) (1 diff)
-
src/peach3/admin/news.py (copied) (copied from peach3/news/admin.py) (3 diffs)
-
src/peach3/admin/pdf.py (copied) (copied from peach3/pdf/admin.py) (1 diff)
-
src/peach3/admin/proglang.py (copied) (copied from peach3/programming_language/admin.py) (1 diff)
-
src/peach3/admin/report.py (copied) (copied from peach3/report/admin.py) (1 diff)
-
src/peach3/admin/submission.py (copied) (copied from peach3/submission/admin.py) (4 diffs)
-
src/peach3/admin/user.py (copied) (copied from peach3/user/admin.py) (1 diff)
-
src/peach3/admin/wiki.py (copied) (copied from peach3/wiki/admin.py) (4 diffs)
-
src/peach3/core/__init__.py (added)
-
src/peach3/core/auth.py (added)
-
src/peach3/core/djangohelpers.py (added)
-
src/peach3/core/user.py (added)
-
src/peach3/management/__init__.py (added)
-
src/peach3/management/commands/__init__.py (added)
-
src/peach3/management/commands/cleanup.py (added)
-
src/peach3/management/commands/refactor_database.py (added)
-
src/peach3/managers/__init__.py (added)
-
src/peach3/managers/assignment.py (added)
-
src/peach3/managers/course.py (added)
-
src/peach3/managers/files.py (added)
-
src/peach3/managers/grade.py (added)
-
src/peach3/managers/i18n.py (added)
-
src/peach3/managers/news.py (added)
-
src/peach3/managers/submission.py (added)
-
src/peach3/managers/user.py (added)
-
src/peach3/managers/wiki.py (added)
-
src/peach3/migrations/0001_initial.py (added)
-
src/peach3/migrations/__init__.py (added)
-
src/peach3/models/TEMP/stats.py (copied) (copied from peach3/stats/models.py)
-
src/peach3/models/TEMP/utils.py (added)
-
src/peach3/models/__init__.py (copied) (copied from peach3/core/models.py) (1 diff)
-
src/peach3/models/assignment.py (copied) (copied from peach3/assignment/models.py) (26 diffs)
-
src/peach3/models/checking.py (copied) (copied from peach3/checking/models.py) (6 diffs)
-
src/peach3/models/course.py (copied) (copied from peach3/course/models.py) (10 diffs)
-
src/peach3/models/files.py (copied) (copied from peach3/files/models.py) (7 diffs)
-
src/peach3/models/forum.py (copied) (copied from peach3/forum/models.py) (5 diffs)
-
src/peach3/models/grade.py (copied) (copied from peach3/grade/models.py) (2 diffs)
-
src/peach3/models/i18n.py (copied) (copied from peach3/i18n/models.py) (7 diffs)
-
src/peach3/models/mixins.py (added)
-
src/peach3/models/news.py (copied) (copied from peach3/news/models.py) (6 diffs)
-
src/peach3/models/pdf.py (copied) (copied from peach3/pdf/models.py) (2 diffs)
-
src/peach3/models/proglang.py (copied) (copied from peach3/programming_language/models.py) (1 diff)
-
src/peach3/models/report.py (copied) (copied from peach3/report/models.py) (7 diffs)
-
src/peach3/models/submission.py (copied) (copied from peach3/submission/models.py) (50 diffs)
-
src/peach3/models/user.py (copied) (copied from peach3/user/models.py) (15 diffs)
-
src/peach3/models/wiki.py (copied) (copied from peach3/wiki/models.py) (7 diffs)
-
src/peach3/tests/__init__.py (added)
-
src/peach3/tests/utils_dates.py (added)
-
src/peach3/utils/__init__.py (added)
-
src/peach3/utils/cache.py (added)
-
src/peach3/utils/dates.py (added)
-
src/peach3/utils/files.py (added)
-
src/peach3/utils/json.py (added)
-
src/peach3/utils/mail.py (added)
-
src/peach3/utils/params.py (added)
-
src/peach3/utils/rst/__init__.py (added)
-
src/peach3/utils/rst/directives/__init__.py (added)
-
src/peach3/utils/rst/directives/sourcecode.py (added)
-
src/peach3/utils/rst/forms.py (added)
-
src/peach3/utils/rst/writers/__init__.py (added)
-
src/peach3/utils/rst/writers/html4css1.py (added)
-
src/peach3/utils/slug.py (added)
-
src/peach3/utils/uid.py (added)
-
src/peach3/utils/wiki.py (added)
-
src/peach3/views/__init__.py (added)
Legend:
- Unmodified
- Added
- Removed
-
.hgignore
r1555 r1563 20 20 syntax: regexp 21 21 ^transifex-log\.txt$ 22 syntax: regexp 23 ^src/peach3_core\.egg-info$ -
src/peach3/admin/assignment.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import6 7 5 from 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 6 from django.forms.models import modelform_factory 7 from peach3.models.assignment import * #@UnusedWildImport 8 from peach3.admin.i18n import I18NModelForm 21 9 22 10 class AssignmentSetAdmin(admin.ModelAdmin): 23 inlines = TranslatedNameInline, 11 form = modelform_factory(AssignmentSet, I18NModelForm) 12 fields = 'i18n_names', 'courseedition', 'order', 24 13 raw_id_fields = 'courseedition', 25 14 26 15 list_display = 'courseedition', '__unicode__', 'order', 27 16 list_filter = 'courseedition', … … 29 18 admin.site.register(AssignmentSet, AssignmentSetAdmin) 30 19 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) 50 48 51 49 #class AssignmentSlotAdmin(admin.ModelAdmin): … … 54 52 55 53 56 54 57 55 #class AssignmentSlotInline(admin.TabularInline): 58 56 # model = AssignmentSlot 59 # 57 # 60 58 #class AssignmentLayoutAdmin(admin.ModelAdmin): 61 59 # inlines = AssignmentSlotInline, … … 70 68 # raw_id_fields = 'user', 'assignmentedition', 71 69 # fields = 'user', 'assignmentedition', 'type', 'state_from', 'state_until', 72 # 70 # 73 71 # list_display = 'assignmentedition', 'user', 'type', 74 # 75 # def get_form(self, request, obj=None): 72 # 73 # def get_form(self, request, obj=None): 76 74 # # 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') 80 78 # return f 81 79 #admin.site.register(IndividualDeadline, IndividualDeadlineAdmin) 82 83 84 80 85 81 class AssignmentEditionOptionInline(admin.TabularInline): 86 82 model = AssignmentEditionOption 87 83 extra = 1 88 84 89 85 class ClusterTimeRangeInline(admin.TabularInline): 90 86 model = ClusterTimeRange … … 98 94 99 95 class AssignmentEditionAdmin(admin.ModelAdmin): 96 form = modelform_factory(AssignmentEdition, I18NModelForm) 100 97 fieldsets = ( 101 98 (None, { 102 'fields' : ('courseedition', 'assignment', 'assignmentset', 103 ' default_name', 'default_language',99 'fields' : ('courseedition', 'assignment', 'assignmentset', 100 'i18n_names', 104 101 'created', 'order',), 105 102 }), 106 103 ('Advanced', { 107 104 'classes': ('collapse',), 108 'fields' : ('gradingsystems','languages',) 105 'fields' : ('gradingsystems','languages',) 109 106 }) 110 107 ) … … 112 109 raw_id_fields = 'courseedition','assignment','assignmentset', 113 110 inlines = AssignmentEditionOptionInline, ClusterTimeRangeInline, IndividualTimeRangeInline, 114 115 list_display = 'slug', '__unicode__', 'edition', ' order',111 112 list_display = 'slug', '__unicode__', 'edition', 'courseedition', 'order', 116 113 list_filter = 'courseedition', 117 114 date_hierarchy = 'created' 118 115 ordering = 'order', 119 116 120 117 admin.site.register(AssignmentEdition, AssignmentEditionAdmin) 121 118 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 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import6 7 5 from django.contrib import admin 8 from .models import LegacyChecker6 from peach3.models.checking import * #@UnusedWildImport 9 7 10 8 class LegacyCheckerAdmin(admin.ModelAdmin): -
src/peach3/admin/course.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import 6 5 from django.utils.translation import ugettext_lazy as _ 7 6 from 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 7 from django.forms.models import modelform_factory 8 from peach3.models.course import * #@UnusedWildImport 9 from peach3.admin.i18n import I18NModelForm 21 10 22 11 class 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' 25 15 admin.site.register(Realm, RealmAdmin) 16 17 class 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' 22 admin.site.register(Period, PeriodAdmin) 26 23 27 24 class CourseEditionInline(admin.TabularInline): 28 25 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', 30 31 radio_fields = { 31 32 'cluster_selection' : admin.VERTICAL, 32 33 } 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 51 def _unique(i): 52 l=None 53 for v in i: 54 if v!=l: 55 yield v 56 l=v 33 57 34 58 class CourseAdmin(admin.ModelAdmin): 35 59 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 36 81 admin.site.register(Course, CourseAdmin) 37 82 38 83 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 63 84 #class FinalGradeAdmin(admin.ModelAdmin): 64 # def get_form(self, request, obj=None): 85 # def get_form(self, request, obj=None): 65 86 # # 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') 69 90 # return f 70 91 #admin.site.register(FinalGrade, FinalGradeAdmin) … … 80 101 model = ClusterStaff 81 102 raw_id_fields = 'cluster', 'user', 82 103 83 104 class ClusterAdmin(admin.ModelAdmin): 105 form = modelform_factory(Cluster, I18NModelForm) 84 106 fieldsets = ( 85 107 (None, { 86 'fields' : ('realms', 'courseedition', 'default_name', 87 'default_language', 'admin_cluster', 88 'order',) 108 'fields' : ('realms', 'courseedition', 'i18n_names', 109 'admin_cluster', 'order',) 89 110 }), 90 111 ("Availability", { … … 93 114 'active', 'active_from', 'active_until',) 94 115 }) 95 116 96 117 ) 97 118 raw_id_fields = 'courseedition', 98 119 filter_horizontal = 'realms', 99 inlines = TranslatedNameInline,ClusterMembersInline, ClusterStaffInline,100 120 inlines = ClusterMembersInline, ClusterStaffInline, 121 101 122 list_display = 'courseedition', 'default_name', 'admin_cluster', 'order', 102 123 list_filter = 'realms', 'courseedition', 103 124 ordering = 'order', 104 125 105 126 admin.site.register(Cluster, ClusterAdmin) -
src/peach3/admin/files.py
r1436 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import 5 from django.contrib import admin 6 from django.forms.models import modelform_factory 7 from peach3.models.files import * #@UnusedWildImport 8 from peach3.admin.i18n import I18NModelForm 6 9 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) 29 19 30 20 class 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') 33 28 34 29 list_display = 'default_name', 'base_type', -
src/peach3/admin/forum.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import6 7 5 from django.contrib import admin 8 from django.contrib.contenttypes import generic 9 10 from .models import (Comment, 11 CommentRevision, 12 ) 6 from peach3.models.forum import * #@UnusedWildImport 13 7 14 8 class CommentInline(generic.GenericTabularInline): … … 17 11 ct_fk_field = 'parent_id' 18 12 raw_id_fields = 'author', 'response_to', 19 13 20 14 class CommentAdmin(admin.ModelAdmin): 21 15 raw_id_fields = 'author', 'response_to', -
src/peach3/admin/grade.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import6 7 5 from 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 6 from django.forms.models import modelform_factory 7 from peach3.models.grade import * #@UnusedWildImport 8 from peach3.admin.i18n import I18NModelForm 15 9 16 10 class GradeInline(admin.TabularInline): 17 11 model = Grade 12 form = modelform_factory(Grade, I18NModelForm) 13 fields = 'i18n_names', 'icon', 'value_low', 'value_high', 'passing' 14 extra = 1 18 15 19 16 class GradingSystemAdmin(admin.ModelAdmin): 20 inlines = TranslatedNameInline, GradeInline, 17 form = modelform_factory(GradingSystem, I18NModelForm) 18 fields = 'i18n_names', 19 inlines = GradeInline, 21 20 admin.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 1 from django.utils.translation import ugettext_lazy as _ 2 from django.core.exceptions import ValidationError 3 from django import forms 4 5 from peach3.models.i18n import * #@UnusedWildImport 6 7 # TODO: Build a fancy widget for editing translations 8 9 class 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 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ … … 7 7 from django.contrib import admin 8 8 from django.contrib.contenttypes import generic 9 10 from .models import (NewsItem 11 ) 9 from peach3.models.news import NewsItem 12 10 13 11 class NewsItemInline(generic.GenericStackedInline): … … 20 18 class NewsItemAdmin(admin.ModelAdmin): 21 19 raw_id_fields = 'courseedition', 'receipient', 'cluster', 'read', 'created_by', 22 20 23 21 list_display = 'courseedition', 'receipient', 'created', 24 22 date_hierarchy = 'created' -
src/peach3/admin/pdf.py
r1174 r1563 1 1 from django.contrib import admin 2 3 from peach3.pdf.models import RefPDFInfo 2 from peach3.models.pdf import RefPDFInfo 4 3 from pdfviewer.admin import PDFPageInfoInline 5 4 6 5 class RefPDFInfoAdmin(admin.ModelAdmin): 7 6 inlines = PDFPageInfoInline, 8 7 9 8 list_display = 'path', 'ref_ct', 'ref_id', 'title', 'pagecount', 'last_access', 10 9 -
src/peach3/admin/proglang.py
r1247 r1563 1 1 # -*- coding: utf-8 -*- 2 2 """ This file is part of PEACH. 3 3 4 4 Copyright (C) 2006-2009 Eindhoven University of Technology 5 5 Copyright (C) 2009 Eljakim Information Technology bv. 6 6 """ 7 from __future__ import absolute_import8 9 7 from 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 8 from django.forms.models import modelform_factory 9 from peach3.models.proglang import ProgrammingLanguage 10 from peach3.admin.i18n import I18NModelForm 16 11 17 12 class ProgrammingLanguageAdmin(admin.ModelAdmin): 18 inlines = TranslatedNameInline,13 form = modelform_factory(ProgrammingLanguage, I18NModelForm) 19 14 list_display = 'default_name', 15 fields = 'code', 'i18n_names' 20 16 admin.site.register(ProgrammingLanguage, ProgrammingLanguageAdmin) -
src/peach3/admin/report.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 5 from django.contrib import admin 6 7 from peach3.report.models import Report 6 from peach3.models.report import Report 8 7 9 8 class ReportAdmin(admin.ModelAdmin): -
src/peach3/admin/submission.py
r1528 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ … … 6 6 7 7 from 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 8 from peach3.models.submission import * #@UnusedWildImport 9 from peach3.admin.forum import CommentInline 10 from peach3.admin.news import NewsItemInline 20 11 21 12 class SubmissionNewsItemInline(NewsItemInline): 22 #fields = 13 #fields = 23 14 pass 24 15 … … 26 17 model = SubmissionFile 27 18 raw_id_fields = 'file', 'slot', 28 19 29 20 class SubmissionAdmin(admin.ModelAdmin): 30 raw_id_fields = ('assignmentedition', 'courseedition', 21 raw_id_fields = ('assignmentedition', 'courseedition', 31 22 'authors', 'created_by', 'submitted_by',) 32 23 inlines = SubmissionFileInline, SubmissionNewsItemInline, 33 24 34 25 list_display = 'assignmentedition','created','created_by', 35 26 admin.site.register(Submission, SubmissionAdmin) 36 37 27 38 28 39 class CheckResultStepInline(admin.TabularInline): 29 30 class CheckResultStepInline(admin.TabularInline): 40 31 model = CheckResultStep 41 32 42 33 class CheckResultAdmin(admin.ModelAdmin): 43 34 raw_id_fields = 'submission', 44 35 45 36 list_display = 'submission','created','checked','state', 46 37 inlines = CheckResultStepInline, … … 63 54 raw_id_fields = 'submission', 'created_by', 'authors', 64 55 inlines = CommentInline, 65 56 66 57 list_display = 'submission', 'created_by', 67 58 68 # def get_form(self, request, obj=None): 59 # def get_form(self, request, obj=None): 69 60 # # Sort user field on username 70 # f = super(ReviewAdmin, self).get_form(request, obj) 61 # f = super(ReviewAdmin, self).get_form(request, obj) 71 62 # 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 # 78 69 # return f 79 70 80 71 admin.site.register(Review, ReviewAdmin) 81 72 -
src/peach3/admin/user.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ 5 from __future__ import absolute_import 5 from django.contrib import admin 6 from peach3.models.user import Profile, VerificationCode 6 7 7 from django.contrib import admin8 from .models import Profile, VerificationCode9 10 8 class ProfileAdmin(admin.ModelAdmin): 11 9 raw_id_fields = 'user', 12 10 13 11 list_display = 'user', 14 12 ordering = 'user', 15 13 16 14 admin.site.register(Profile, ProfileAdmin) 17 15 18 16 class VerificationCodeAdmin(admin.ModelAdmin): 19 17 raw_id_fields = 'user', 20 18 21 19 list_display = 'user', 22 20 ordering = 'user', -
src/peach3/admin/wiki.py
r1436 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ … … 6 6 7 7 from django.contrib import admin 8 from .models import (Page, 9 PagePermission, 10 PageRevisionText, 11 PageRevision, 12 PageComment, 13 ) 8 from peach3.models.wiki import * #@UnusedWildImport 14 9 15 10 class PagePermissionInline(admin.TabularInline): 16 11 model = PagePermission 17 12 18 13 class PageAdmin(admin.ModelAdmin): 19 14 fieldsets = ( … … 34 29 35 30 admin.site.register(Page, PageAdmin) 36 31 37 32 38 33 class PageRevisionTextAdmin(admin.ModelAdmin): … … 53 48 54 49 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 1 1 """ This file is part of PEACH. 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology2 3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 from django.db import models5 #from django.db import models 6 6 from django.dispatch import Signal 7 8 APP_LABEL = 'peach3' 9 10 cleanup_signal = Signal() 11 12 from i18n import * 13 from grade import * 14 from course import * 15 from files import * 16 from proglang import * 17 from assignment import * 18 from checking import * # Move to seperate package? 19 from forum import * 20 from news import * 21 from pdf import * 22 from report import * 23 from submission import * 24 from wiki import * 25 from user import * 7 26 8 27 # Create your models here. 9 28 10 cleanup_signal = Signal() -
src/peach3/models/assignment.py
r1417 r1563 1 1 """ This file is part of PEACH. 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology2 3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 from django.conf import settings6 5 from django.core.cache import cache 7 6 from django.db import models 8 9 from peach3.course.models import Course, CourseEdition, Cluster10 from peach3.grade.models import GradingSystem11 from peach3.i18n.models import NamedModel, TranslatedName12 from peach3.files.models import FileType, FileValidator13 from peach3.programming_language.models import ProgrammingLanguage14 15 7 from django.utils.translation import ugettext_lazy as _ 16 from django.utils.translation import get_language17 18 8 from django.contrib.auth.models import User 19 9 10 from peach3.models.course import Course, CourseEdition, Cluster 11 from peach3.models.grade import GradingSystem 12 from peach3.models.i18n import NamedModel, TranslatedName 13 from peach3.models.files import FileType 14 from peach3.models.proglang import ProgrammingLanguage 15 from peach3.models.mixins import EditionMixin, OrderMixin 16 from peach3.models import APP_LABEL 17 from peach3.managers.assignment import TimeRangeAbstractManager 18 from peach3.utils.cache import cache_key 19 from peach3.utils.params import parse_parameters 20 from peach3.utils.dates import TimeRange 21 20 22 from datetime import datetime 21 from peach3.utils import EditionMixin, OrderMixin, cache_key, parse_parameters, TimeRange22 23 23 24 # Create your models here. … … 25 26 class AssignmentSet(NamedModel, OrderMixin): 26 27 courseedition = models.ForeignKey(CourseEdition) 27 28 28 29 order = models.PositiveIntegerField(null=True, blank=True) 29 30 class Meta: 30 31 class Meta(NamedModel.Meta): 32 app_label = APP_LABEL 31 33 ordering = 'order', 32 34 33 35 # ### Permission Interface 34 36 # def _get_permissions(self, user): 35 # return ( user.get_model_permissions(self) 37 # return ( user.get_model_permissions(self) 36 38 # | self.course.get_permissions(user) 37 39 # ) 38 # 40 # 39 41 def has_access(self, user, access=None): 40 42 return self.course.has_access(user, access) … … 42 44 def save(self, *arg, **kwargs): 43 45 super(AssignmentSet, self).save(*arg, **kwargs) 44 46 45 47 # If order is not set, set it to the primary key value 46 48 if self.pk and self.order is None: 47 49 self.order = self.pk 48 50 super(AssignmentSet, self).save() 49 51 50 52 def clone(self, courseedition): 51 53 # Return a clone of this set for a new courseedition 52 54 53 55 if courseedition==self.courseedition: 54 56 return self 55 57 56 58 newset = AssignmentSet.objects.create(courseedition = courseedition, 57 59 default_name = self.default_name, 58 60 default_language = self.default_language) 59 61 60 62 for translation in TranslatedName.objects.filter_by_object(self): 61 TranslatedName.objects.create_for_object(newset, 63 TranslatedName.objects.create_for_object(newset, 62 64 language=translation.language, 63 65 name=translation.name) 64 66 65 67 return newset 66 68 67 69 68 70 class Assignment(models.Model): 69 71 ### Model definition 70 72 course = models.ForeignKey(Course) 71 73 72 74 def has_access(self, user, access=None): 73 75 return self.course.has_access(user, access) 74 76 77 class Meta: 78 app_label = APP_LABEL 79 75 80 class AssignmentEdition(NamedModel, EditionMixin, OrderMixin): 76 81 ### Model definition 77 82 slug = models.SlugField(max_length=32, db_index=True) 78 83 79 84 assignment = models.ForeignKey(Assignment) 80 85 assignmentset = models.ForeignKey(AssignmentSet, blank=True, null=True) 81 86 82 87 courseedition = models.ForeignKey(CourseEdition) 83 88 84 89 created = models.DateTimeField() 85 90 … … 90 95 timeranges = models.ManyToManyField(Cluster, through="ClusterTimeRange") 91 96 individual_timeranges = models.ManyToManyField(User, through="IndividualTimeRange") 92 93 observelevel = models.PositiveSmallIntegerField(default=1, 97 98 observelevel = models.PositiveSmallIntegerField(default=1, 94 99 help_text="Minimum observelevel required to observe submissions for this assignment") 95 100 reviewlevel = models.PositiveSmallIntegerField(default=1, … … 97 102 reviewpublishlevel = models.PositiveSmallIntegerField(default=1, 98 103 help_text="Minimum reviewlevel required to publish a review for submissions for this assignment") 99 104 100 105 order = models.PositiveIntegerField(null=True, blank=True) 101 102 class Meta: 106 107 class Meta(NamedModel.Meta): 108 app_label = APP_LABEL 103 109 get_latest_by = 'created' 104 110 ordering = 'order', 105 111 unique_together = ('courseedition','slug'), 106 112 107 113 ### Model methods 108 114 109 115 # Updates tracking 110 116 # def mark_updated(self, when=None): … … 114 120 # cache.set(key, when, 60*60) 115 121 # return when 116 # 122 # 117 123 # def last_updated(self, user=None): 118 124 # now = datetime.utcnow().replace(microsecond=0) 119 # 125 # 120 126 # key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'updated') 121 127 # updated = cache.get(key) … … 129 135 # if isinstance(deadline, datetime) and date_inrange(deadline, updated, now): 130 136 # updated = self.mark_updated(deadline) 131 # 137 # 132 138 # # Check if submission updated 133 139 # if user: … … 139 145 # elif subupdated > updated: 140 146 # updated = self.mark_updated(subupdated) 141 # 147 # 142 148 # return updated 143 # 149 # 144 150 # def save(self, *arg, **kwarg): 145 151 # super(AssignmentEdition, self).save(*arg, **kwarg) 146 152 # self.mark_updated() 147 # 153 # 148 154 # def was_updated(self, stamp, user=None): 149 155 # updated = self.last_updated(user) # Do this even if stamp=None (to update cache) … … 155 161 # Return the number of submissions for this assignment 156 162 return self.submission_set.count() 157 163 158 164 # Timeranges 159 165 def get_creation_timerange(self): … … 161 167 self._creation_timerange = TimeRange(begin=self.created) 162 168 return self._creation_timerange 163 169 164 170 def _set_cluster_timerange(self, type, cluster, range): 165 171 """ Set a timerange of `type` for the given `cluster`. … … 169 175 """ 170 176 ClusterTimeRange.objects.set_timerange(assignmentedition=self, type=type, cluster=cluster, range=range) 171 177 172 178 def _get_cluster_timerange(self, type, cluster): 173 179 """ Get the timerange of given `type` for the given `cluster`. Returns a `TimeRange`. … … 177 183 if cluster=='*': 178 184 return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type) 179 185 180 186 if isinstance(cluster, (list, tuple, models.query.QuerySet)): 181 187 return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type, cluster__in=cluster) 182 188 183 189 return ClusterTimeRange.objects.get_timerange(assignmentedition=self, type=type, cluster=cluster) 184 190 185 191 def clone_timeranges(self, old_cluster, new_cluster): 186 192 for t in ['o', 'v']: 187 193 range = self._get_cluster_timerange(t, old_cluster) 188 194 self._set_cluster_timerange(t, new_cluster, range) 189 195 190 196 def _set_individual_timerange(self, type, user, range): 191 197 """ Set a timerange of `type` for the given `user`. … … 195 201 """ 196 202 IndividualTimeRange.objects.set_timerange(assignmentedition=self, type=type, user=user, range=range) 197 203 198 204 def _get_individual_timerange(self, type, user): 199 205 "Get the timerange of given `type` for the given `user`. Returns a `TimeRange`." 200 206 return IndividualTimeRange.objects.get_timerange(assignmentedition=self, type=type, user=user) 201 207 202 208 def _get_timerange(self, type, user=None, cluster=None): 203 209 """ Get a timerange of `type` for given `user` or `cluster`. 204 210 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`. 206 212 If `cluster` is '*', returns the union of the timeranges for all clusters 207 213 """ … … 215 221 individual = self._get_individual_timerange(type, user) 216 222 return (timerange + individual) 217 223 218 224 elif cluster: 219 225 return self._get_cluster_timerange(type, cluster) 220 226 221 227 else: 222 228 return TimeRange(closed=True) 223 229 224 230 def set_cluster_open_timerange(self, cluster, range): 225 231 self._set_cluster_timerange('o', cluster, range) 226 232 227 233 def set_individual_open_timerange(self, user, range): 228 234 self._set_individual_timerange('o', user, range) 229 235 230 236 def set_individual_deadline(self, user, deadline): 231 237 user_range = TimeRange(begin=datetime.utcnow(), end=deadline) 232 238 self._set_individual_timerange('o', user, TimeRange(begin=datetime.utcnow(), end=deadline)) 233 239 self._set_individual_timerange('v', user, TimeRange(begin=datetime.utcnow(), end=deadline)) 234 240 235 241 def get_open_timerange(self, user=None, cluster=None): 236 242 " Shortcut for `self.get_timerange('o', user, cluster)` " 237 243 return self._get_timerange('o', user=user, cluster=cluster) 238 244 239 245 def is_open(self, user=None, cluster=None, when=None): 240 246 """ Returns True if assignment is open at given date (or current date if when=None) … … 246 252 elif user: 247 253 # 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() 249 255 and (user.is_superuser 250 256 or self.courseedition.get_reviewlevel(user,'*') >= self.observelevel 251 257 )) 252 258 253 259 return False 254 260 255 261 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 258 264 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 260 266 None if assignment never opens 261 267 """ … … 264 270 return None 265 271 return cmp(range, when) 266 272 267 273 def get_deadline(self, user=None, cluster=None): 268 274 """ Get this assignment's deadline for given user or cluster. … … 276 282 return False 277 283 return range.end if range.has_end() else True 278 284 279 285 def set_cluster_visible_timerange(self, cluster, range): 280 286 self._set_cluster_timerange('v', cluster, range) 281 287 282 288 def set_individual_visible_timerange(self, user, range): 283 289 self._set_individual_timerange('v', user, range) 284 290 285 291 def get_visible_timerange(self, user=None, cluster=None): 286 292 " Shortcut for `self.get_timerange('v', user, cluster)` " 287 293 return self._get_timerange('v', user, cluster) 288 294 289 295 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 292 298 def is_visible(self, user=None, cluster=None, when=None): 293 299 if not cluster: 294 300 cluster = self.courseedition.get_user_cluster(user) 295 301 296 302 if when in self.get_visible_timerange(user, cluster): 297 303 return True … … 299 305 # Assignment is also visible if user's observer level exceeds self.observelevel 300 306 return when in self.get_creation_timerange() and self.can_observe(user) 301 307 302 308 return False 303 309 304 310 # Options 305 311 306 312 def _get_option(self, optioncode): 307 313 key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) … … 315 321 316 322 cache.set(key, result) 317 323 318 324 return result 319 325 320 326 def has_option(self, optioncode): 321 327 return self._get_option(optioncode)[0] 322 328 323 329 def get_option(self, optioncode): 324 330 return self._get_option(optioncode)[1] 325 331 326 332 def set_option(self, optioncode, **kwargs): 327 333 key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) 328 334 cache.delete(key) 329 335 330 336 parameters="\n".join((key if value is True else '%s=%s' % (key, value)) for key, value in kwargs.iteritems()) 331 337 332 338 obj, created = AssignmentEditionOption.objects.get_or_create(assignmentedition=self, 333 option=optioncode, 339 option=optioncode, 334 340 defaults={'parameters':parameters}) 335 341 if not created: 336 342 obj.parameters = parameters 337 343 obj.save() 338 344 339 345 def delete_option(self, optioncode): 340 346 key = cache_key('peach3.assignment.models.AssignmentEdition', self, 'option', optioncode) 341 347 cache.delete(key) 342 348 343 349 AssignmentEditionOption.objects.filter(assignmentedition=self, 344 350 option=optioncode).delete() 345 346 351 352 347 353 def has_option_parameter(self, optioncode, key): 348 354 return key in self.get_option(optioncode) 349 355 350 356 def get_option_parameter(self, optioncode, key, default=None): 351 357 return self.get_option(optioncode).get(key, default) 352 358 353 359 def set_option_parameter(self, optioncode, key, value): 354 360 option = self.get_option(optioncode) 355 361 option[key] = value 356 362 self.set_option(optioncode, **option) 357 363 358 364 def delete_option_parameter(self, optioncode, key): 359 365 option = self.get_option(optioncode) 360 366 del option[key] 361 367 self.set_option(optioncode, **option) 362 368 363 369 def __unicode__(self): 364 370 return u'%s (%s)' % (self.name, _('ed.%d') % self.edition) 365 371 366 372 # ### Permission Interface 367 373 # def _get_permissions(self, user): 368 # return ( user.get_model_permissions(self) 374 # return ( user.get_model_permissions(self) 369 375 # | self.assignment.get_permissions(user) 370 376 # | self.assignmentset.get_permissions(user) if self.assignmentset else frozenset() 371 377 # ) 372 378 # return perms 373 # 379 # 374 380 375 381 def has_access(self, user, access=None): … … 379 385 @classmethod 380 386 def get_uid_fields(cls): 381 return 'courseedition__period__slug', 'courseedition__code', 'slug', 382 387 return 'courseedition__period__slug', 'courseedition__code', 'slug', 388 383 389 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 386 392 ### Ordering mixin interface 387 393 @staticmethod 388 394 def get_grouping_field_name(): 389 395 return 'assignment' 390 396 391 397 def save(self, *arg, **kwargs): 392 398 super(AssignmentEdition, self).save(*arg, **kwargs) 393 399 394 400 # If order is not set, set it to the primary key value 395 401 if self.pk and self.order is None: 396 402 self.order = self.pk 397 403 super(AssignmentEdition, self).save() 398 404 399 405 class AssignmentEditionOption(models.Model): 400 406 OPTIONS = ( … … 402 408 ('COAUTHORS', _("Co-Authors")), # Parameters: min, max 403 409 ) 404 410 405 411 assignmentedition = models.ForeignKey(AssignmentEdition) 406 412 option = models.CharField(max_length=16, choices=OPTIONS) 407 413 parameters = models.TextField(blank=True, help_text=_("'key' or 'key=value' pairs; one per line")) 408 414 409 415 class Meta: 416 app_label = APP_LABEL 417 db_table = APP_LABEL+'_asgnedoption' 410 418 unique_together = (('assignmentedition', 'option',),) 411 db_table = 'assignment_asgnedoption' 412 419 413 420 class Admin: 414 421 list_display = ('assignmentedition', 'option',) … … 416 423 class AssignmentLayout(models.Model): 417 424 name = models.CharField(max_length=80, unique=True) 418 425 419 426 def __unicode__(self): 420 427 return self.name 421 428 429 class Meta: 430 app_label = APP_LABEL 431 422 432 class AssignmentSlot(NamedModel): 423 433 assignmentsubmlayout = models.ForeignKey(AssignmentLayout) … … 425 435 order = models.PositiveIntegerField() 426 436 427 validators = models.ManyToManyField(FileValidator, blank=True)437 #validators = models.ManyToManyField(FileValidator, blank=True) 428 438 allowedTypes = models.ManyToManyField(FileType, blank=True) 429 439 namePattern = models.CharField(max_length=200, blank=True) 430 440 431 441 required = models.BooleanField() 432 442 … … 434 444 def __unicode__(self): 435 445 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 473 450 class TimeRangeAbstractModel(models.Model): 474 451 TYPE_CHOICES = (('o', _('Open'),), … … 478 455 assignmentedition = models.ForeignKey(AssignmentEdition) 479 456 type = models.CharField(max_length=1, choices = TYPE_CHOICES) 480 457 481 458 state_from = models.DateTimeField(null=True, blank=True) 482 459 state_until = models.DateTimeField(null=True, blank=True) 483 460 484 461 objects = TimeRangeAbstractManager() 485 462 486 463 class Meta: 487 464 abstract = True 488 465 489 466 class ClusterTimeRange(TimeRangeAbstractModel): 490 467 cluster = models.ForeignKey(Cluster) 491 468 492 469 class Meta(TimeRangeAbstractModel.Meta): 470 app_label = APP_LABEL 493 471 unique_together = (('cluster', 'assignmentedition', 'type',), 494 472 ) 495 473 496 474 ### Naming interface 497 475 def __unicode__(self): … … 500 478 class IndividualTimeRange(TimeRangeAbstractModel): 501 479 user = models.ForeignKey(User) 502 480 503 481 class Meta(TimeRangeAbstractModel.Meta): 504 unique_together = (('user', 'assignmentedition', 'type',),505 )506 482 app_label = APP_LABEL 483 unique_together = (('user', 'assignmentedition', 'type',),) 484 507 485 ### Naming interface 508 486 def __unicode__(self): -
src/peach3/models/checking.py
r1247 r1563 1 1 """ This file is part of PEACH. 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology2 3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 5 6 6 from django.conf import settings 7 7 from django.db import models 8 from django.utils.translation import get_language9 8 10 from peach3.assignment.models import AssignmentEdition 11 from peach3.programming_language.models import ProgrammingLanguage 12 13 from peach3.utils import create_path 9 from peach3.models.assignment import AssignmentEdition 10 from peach3.models import APP_LABEL 11 from peach3.utils.files import create_path 14 12 15 13 import os.path … … 22 20 ce = ae.courseedition 23 21 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, 28 26 created.strftime('%Y%m%d%H%M%S')) 29 27 … … 32 30 filepath = models.CharField(max_length=300) 33 31 filename = models.CharField(max_length=100) 34 32 35 33 enabled = models.BooleanField(default=True) 36 34 created = models.DateTimeField(blank=False, null=False, default=datetime.utcnow) 37 35 38 36 class Meta: 37 app_label = APP_LABEL 39 38 get_latest_by = 'created' 40 39 … … 43 42 def get_store(): 44 43 return os.path.join(settings.STORE, 'legacy_checkers') 45 44 46 45 def get_subdir(self): 47 46 return os.path.join(self.get_store(), self.filepath) 48 47 49 48 def get_file_path(self): 50 49 return os.path.join(self.get_store(), self.filepath, self.filename) 51 50 52 51 @models.permalink 53 52 def get_absolute_url(self): … … 56 55 def delete(self, *args, **kwargs): 57 56 import os 58 57 59 58 try: 60 59 os.remove(self.get_file_path()) … … 66 65 except: 67 66 pass 68 67 69 68 super(LegacyChecker, self).delete(*args, **kwargs) 70 -
src/peach3/models/course.py
r1551 r1563 1 1 """ This file is part of PEACH. 2 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 from django.conf import settings6 5 from django.core.cache import cache 7 from django.contrib.auth.models import User , Permission6 from django.contrib.auth.models import User 8 7 from django.contrib.sites.models import Site 9 8 from django.contrib.sites.managers import CurrentSiteManager 10 9 from django.db import models, transaction 11 from django.db.models import Q12 10 13 11 from 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 13 from peach3.models.grade import Grade 14 from peach3.models.i18n import NamedModel, UniqueNamedModel, BlankNamedModel 15 from peach3.models.mixins import EditionMixin, OrderMixin 16 from peach3.models import APP_LABEL 17 from peach3.managers.course import PeriodManager, ClusterManager 18 from peach3.utils.cache import cache_key, unique_cache_hash 19 from peach3.utils.dates import TimeRange 21 20 22 21 # Create your models here. … … 29 28 objects = models.Manager() 30 29 30 class Meta(UniqueNamedModel.Meta): 31 app_label = APP_LABEL 32 31 33 def has_access(self, user, access=None): 32 34 return user.is_superuser or access is None 33 35 34 36 class Course(models.Model): 37 class Meta: 38 app_label = APP_LABEL 39 35 40 def has_access(self, user, access=None): 36 41 if user.is_superuser: … … 44 49 return False 45 50 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 used50 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 landed53 54 51 class Period(NamedModel): 55 52 slug = models.CharField(max_length=8, unique=True) … … 60 57 objects = PeriodManager() 61 58 59 class Meta(NamedModel.Meta): 60 app_label = APP_LABEL 61 ordering = '_begin', '-_end', 62 62 63 def get_range(self): 63 r ange= getattr(self, '_range', None)64 65 if not r ange:64 r = getattr(self, '_range', None) 65 66 if not r: 66 67 if self._begin is None and self._end is None: 67 r ange= TimeRange(open=True)68 r = TimeRange(open=True) 68 69 elif self._begin is None: 69 r ange= TimeRange(begin=None, end=self._end)70 r = TimeRange(begin=None, end=self._end) 70 71 elif self._end is None: 71 r ange= TimeRange(begin=self._begin, end=None)72 r = TimeRange(begin=self._begin, end=None) 72 73 elif self._end <= self._begin: 73 r ange= TimeRange(closed=True)74 r = TimeRange(closed=True) 74 75 else: 75 r ange= TimeRange(begin=self._begin, end=self._end)76 77 self._range = r ange78 79 return r ange80 81 def set_range(self, r ange):82 if r ange.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(): 83 84 self._begin, self._end = TimeRange.CLOSED 84 elif r ange.is_open():85 elif r.is_open(): 85 86 self._begin = None 86 87 self._end = None 87 88 else: 88 self._begin = r ange.begin89 self._end = r ange.end90 91 self._range = r ange92 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 94 95 95 96 def available(self, date=None): … … 99 100 self._range = None 100 101 super(Period, self).save(*arg, **kwarg) 101 102 class Meta:103 ordering = '_begin', '-_end',104 105 106 102 107 103 class CourseEdition(NamedModel, EditionMixin): … … 130 126 managers = models.ManyToManyField(User, related_name='courseedition_manager_set', blank=True) 131 127 132 class Meta: 128 class Meta(NamedModel.Meta): 129 app_label = APP_LABEL 133 130 get_latest_by = 'created' 134 131 unique_together = ('course', 'period'), ('code', 'period') … … 560 557 grade = models.ForeignKey(Grade) 561 558 559 class Meta: 560 app_label = APP_LABEL 561 562 562 ### Naming interface 563 563 def __unicode__(self): 564 564 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_range576 return active577 565 578 566 class Cluster(BlankNamedModel, OrderMixin): … … 600 588 objects = ClusterManager() 601 589 602 class Meta: 590 class Meta(BlankNamedModel.Meta): 591 app_label = APP_LABEL 603 592 unique_together = (('courseedition', 'default_name',),) 604 593 … … 926 915 927 916 class Meta: 917 app_label = APP_LABEL 928 918 unique_together = (('cluster', 'user',), 929 919 #('cluster__courseedition__course', 'user',), … … 1027 1017 1028 1018 class Meta: 1019 app_label = APP_LABEL 1029 1020 unique_together = (('cluster', 'user',),) -
src/peach3/models/files.py
r1552 r1563 1 1 """ This file is part of PEACH. 2 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 5 from __future__ import with_statement 6 6 7 7 from django.conf import settings 8 from django.core.cache import cache9 8 from django.db import models 10 from django.contrib.auth.models import User, Permission 11 12 from django.utils.translation import get_language 9 from django.contrib.auth.models import User 10 13 11 from django.utils.translation import ugettext_lazy as _ 14 12 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 13 from peach3.models.i18n import UniqueNamedModel 14 from peach3.models.mixins import RevisionMixin 15 from peach3.models import APP_LABEL 16 17 from peach3.managers.files import FileTypeManager, FileRevisionManager 18 19 from peach3.utils.params import parse_parameters, parse_patterns, match_patterns 20 21 import os 22 22 import codecs 23 23 … … 31 31 32 32 # 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',),) 133 138 134 139 class LEXER_CHOICES(object): … … 147 152 return iter(self.lexers) 148 153 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_type154 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_type159 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 matches176 177 def best_match(self, *arg, **kwarg):178 return self.all_matches(*arg, **kwarg)[0][0]179 180 154 class FileType(UniqueNamedModel): 181 155 ### Model definition … … 191 165 iconcls = models.CharField(max_length=30, blank=True) 192 166 193 validators = models.ManyToManyField(FileValidator, blank=True) # Remove after conversion completed167 #validators = models.ManyToManyField(FileValidator, blank=True) # Remove after conversion completed 194 168 195 169 objects = FileTypeManager() 170 171 class Meta(UniqueNamedModel.Meta): 172 app_label = APP_LABEL 196 173 197 174 ### … … 287 264 288 265 class 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 331 268 332 269 _codecs = {} … … 334 271 class FileRevision(models.Model, RevisionMixin): 335 272 ### Model definition 336 file = models.ForeignKey(File) 273 file = models.ForeignKey(File) #@ReservedAssignment 337 274 338 275 filepath = models.CharField(max_length=300) … … 350 287 351 288 objects = FileRevisionManager() 289 290 class Meta: 291 app_label = APP_LABEL 352 292 353 293 ### Model methods -
src/peach3/models/forum.py
r1554 r1563 8 8 from django.utils.translation import ugettext_lazy as _ 9 9 from django.utils.html import escape, linebreaks 10 11 from peach3.models import APP_LABEL 10 12 11 13 from django.db import models … … 22 24 23 25 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' 24 30 25 31 def get_header(self): … … 43 49 return u'Comment by %r for %r' % (self.author, self.parent) 44 50 45 class Meta:46 get_latest_by = 'created'47 48 51 MARKUP_CHOICES = ( 49 ('plain', _("Plain text")),50 ('rst' , _("Restructured text")),52 ('plain', _("Plain text")), 53 ('rst' , _("Restructured text")), 51 54 ) 52 55 … … 61 64 message = models.TextField(blank=True) 62 65 66 class Meta: 67 app_label = APP_LABEL 68 get_latest_by = 'created' 69 63 70 def get_formatted(self): 64 71 if self.markup=='plain': … … 67 74 return rst2html(self.message)['html_body'] 68 75 formatted = property(get_formatted) 69 70 class Meta:71 get_latest_by = 'created' -
src/peach3/models/grade.py
r1554 r1563 1 1 """ This file is part of PEACH. 2 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 5 6 6 from django.db import models 7 7 8 from peach3.i18n.models import UniqueNamedModel, NamedModel 8 from peach3.models.i18n import UniqueNamedModel, NamedModel 9 from peach3.models import APP_LABEL 10 11 from peach3.managers.grade import GradeManager 9 12 10 13 # Create your models here. 11 14 12 15 class 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 27 18 28 19 class Grade(NamedModel): … … 37 28 objects = GradeManager() 38 29 39 class Meta: 30 class Meta(NamedModel.Meta): 31 app_label = APP_LABEL 40 32 unique_together = (('system','default_name',),) 41 33 -
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 """ 1 5 from django.conf import settings 2 6 from django.contrib.contenttypes.models import ContentType … … 6 10 from django.utils.translation import get_language 7 11 12 from peach3.models import APP_LABEL 13 14 from peach3.managers.i18n import TranslatedNameManager 15 8 16 LANGCODES = [l[0] for l in settings.LANGUAGES] 9 17 … … 11 19 if isinstance(all_names, dict): 12 20 return all_names 13 21 14 22 default = None 15 23 names = {} … … 20 28 if ':' not in langname: 21 29 raise ValueError, 'Invalid format' 22 30 23 31 lang, name = langname.split(':', 1) 24 32 25 33 if lang not in LANGCODES: 26 34 raise ValueError, 'Invalid language code' 27 35 28 36 if default is None: 29 37 default = lang 30 38 31 39 names[lang] = name 32 40 33 41 return names, default 34 35 42 36 class _NamingMixin: 43 44 class I18NModel(models.Model): 37 45 def get_name(self, lang=None): 38 46 if lang is None: 39 47 lang = get_language() 40 48 41 49 try: 42 50 return TranslatedName.objects.get_by_object(object=self, language=lang).name 43 51 except TranslatedName.DoesNotExist: 44 52 return self.default_name 45 53 46 54 name = property(get_name) 47 55 48 56 def set_name(self, name, lang=None, set_default=False): 49 57 if lang is None: … … 51 59 elif lang not in LANGCODES: 52 60 raise ValueError, 'Invalid language code' 53 61 54 62 if lang==self.default_language or set_default: 55 63 # Save old default 56 64 old_default_name = self.default_name 57 65 old_default_language = self.default_language 58 66 59 67 # Set new default 60 68 TranslatedName.objects.filter_by_object(object=self, language=lang).delete() … … 62 70 self.default_language = lang 63 71 self.save() 64 72 65 73 # If language of old default differs from new default, save old default as translation 66 74 if old_default_language != lang: 67 75 self.set_name(old_default_name, old_default_language) 68 76 69 77 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, 71 79 language=lang, 72 80 defaults={ 73 81 'name':name, 74 82 }) 75 83 76 84 if not created: 77 85 object.name = name 78 86 object.save() 79 87 80 88 def get_all_names(self): 81 89 names = [(self.default_language, self.default_name)] \ 82 +[(o.language, o.name) 90 +[(o.language, o.name) 83 91 for o in TranslatedName.objects.filter_by_object(object=self)] 84 92 85 93 return "\n".join("%s:%s" % n for n in names) 86 94 87 95 def set_all_names(self, all_names): 88 96 names, default = parse_names(all_names) 89 97 90 98 # Update/add translations 91 99 for lang, name in names.iteritems(): 92 100 self.set_name(name, lang, lang==default) 93 101 94 102 # Delete removed translations 95 103 TranslatedName.objects.filter_by_object(object=self) \ 96 104 .exclude(language__in=names.keys()) \ 97 105 .delete() 98 106 99 107 all_names = property(get_all_names, set_all_names) 100 108 101 109 get_display_name = get_name 102 103 def __unicode__(self):104 return self.get_display_name()105 106 110 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 113 111 class Meta: 114 112 abstract = True 115 113 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 117 class NamedModel(I18NModel): 118 default_name = models.CharField(max_length=100, blank=False, unique=False) 118 119 default_language = models.CharField(max_length=16, 119 choices=settings.LANGUAGES, 120 choices=settings.LANGUAGES, 120 121 default=settings.LANGUAGE_CODE) 121 122 122 123 class Meta: 123 124 abstract = True 124 125 125 class BlankNamedModel(models.Model, _NamingMixin):126 default_name = models.CharField(max_length=100, blank= True, unique=False)126 class UniqueNamedModel(I18NModel): 127 default_name = models.CharField(max_length=100, blank=False, unique=True) 127 128 default_language = models.CharField(max_length=16, 128 choices=settings.LANGUAGES, 129 choices=settings.LANGUAGES, 129 130 default=settings.LANGUAGE_CODE) 130 131 131 132 class Meta: 132 133 abstract = True 133 134 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 135 class 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 155 144 class TranslatedName(models.Model): 156 145 content_type = models.ForeignKey(ContentType) … … 160 149 language = models.CharField(max_length=16, choices=settings.LANGUAGES) 161 150 name = models.CharField(max_length=100) 162 151 163 152 objects = TranslatedNameManager() 164 153 165 154 class Meta: 155 app_label = APP_LABEL 166 156 unique_together = (('content_type','object_id','language',),) 167 168 class TranslatedNameInline(generic.GenericTabularInline):169 model = TranslatedName -
src/peach3/models/news.py
r1552 r1563 1 1 """ This file is part of PEACH. 2 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 5 from django.contrib.auth.models import User … … 10 10 from django.utils.translation import ugettext_lazy as _ 11 11 12 from peach3.core.models import cleanup_signal 13 from peach3.course.models import CourseEdition, Cluster 12 from peach3.models.course import CourseEdition, Cluster 13 from peach3.models import APP_LABEL, cleanup_signal 14 from peach3.managers.news import NewsItemManager 15 14 16 from peach3.utils.rst import rst2html 15 17 16 from datetime import datetime , timedelta18 from datetime import datetime 17 19 18 20 # Create your models here. … … 22 24 ('rst' , _("Restructured text")), 23 25 ) 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 object33 expires = kwargs.get('expires', timedelta(days=365))34 if isinstance(expires, timedelta):35 kwargs['expires'] = kwargs['appears'] + expires36 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'] = id52 else:53 q['related_id'] = related.pk54 55 return self.filter(**q)56 26 57 27 class NewsItem(models.Model): … … 97 67 objects = NewsItemManager() 98 68 69 class Meta: 70 app_label = APP_LABEL 71 get_latest_by = 'appears' 72 99 73 def is_read(self, user): 100 74 return self.read.filter(pk=user.pk).count()>0 … … 128 102 return subj, body, link 129 103 130 class Meta:131 get_latest_by = 'appears'132 133 104 class NewsBody(models.Model): 134 105 subject = models.CharField(max_length=80) 135 106 message = models.TextField() 136 107 markup = models.SlugField(max_length=8, choices=MARKUP_CHOICES, default=MARKUP_CHOICES[0][0]) 108 109 class Meta: 110 app_label = APP_LABEL 137 111 138 112 def get_formatted(self): … … 145 119 def cleanup_news(sender, **kwargs): 146 120 from django.db import transaction 147 from datetime import datetime148 121 NewsItem.objects.filter(expires__lt=datetime.utcnow()).delete() 149 122 transaction.commit_unless_managed() 123 150 124 cleanup_signal.connect(cleanup_news) -
src/peach3/models/pdf.py
r1429 r1563 6 6 # Create your models here. 7 7 8 from peach3. core.models importcleanup_signal8 from peach3.models import APP_LABEL, cleanup_signal 9 9 10 10 from pdfviewer.models import PDFInfo, PDFInfoManager … … 15 15 ref_id = models.PositiveIntegerField(blank=True, null=True) 16 16 ref = generic.GenericForeignKey('ref_ct', 'ref_id') 17 17 18 18 objects = PDFInfoManager() 19 20 class Meta: 21 app_label = APP_LABEL 19 22 20 23 def cleanup_pdf(sender, **kwargs): 21 24 from pdfviewer.management.commands.clean_pdf import do_clean_pdf 22 25 23 26 # Clean pdf db entries after 1 week, clean cached pages after 1 hour 24 27 do_clean_pdf(7*24*3600, 3600) -
src/peach3/models/proglang.py
r1247 r1563 1 1 # -*- coding: utf-8 -*- 2 2 """ This file is part of PEACH. 3 4 Copyright (C) 2006-20 09Eindhoven University of Technology3 4 Copyright (C) 2006-2012 Eindhoven University of Technology 5 5 Copyright (C) 2009 Eljakim Information Technology bv. 6 6 """ 7 7 8 8 from django.db import models 9 from peach3.i18n.models import UniqueNamedModel 9 from peach3.models.i18n import UniqueNamedModel 10 from peach3.models import APP_LABEL 10 11 11 12 class ProgrammingLanguage(UniqueNamedModel): 12 13 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 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ … … 7 7 from django.db import models 8 8 9 from datetime import datetime 9 from peach3.models import APP_LABEL, cleanup_signal 10 10 11 import os 11 12 from peach3.core.models import cleanup_signal13 12 14 13 # Create your models here. … … 19 18 filename = models.CharField(max_length=256) 20 19 mimetype = models.CharField(max_length=50) 21 20 22 21 created = models.DateTimeField() 23 22 last_access = models.DateTimeField(null=True, blank=True) 24 23 25 24 users = models.ManyToManyField(User) 26 25 26 class Meta: 27 app_label = APP_LABEL 28 27 29 def get_subdir(self, autocreate=True): 28 30 dir = os.path.join(settings.TEMP, 'report%d' % self.id) … … 30 32 os.makedirs(dir) 31 33 return dir 32 34 33 35 def get_full_path(self, autocreate=True): 34 36 return os.path.join(self.get_subdir(autocreate), self.filename) 35 37 full_path = property(get_full_path) 36 38 37 39 def exists(self): 38 40 path = self.get_full_path(False) 39 41 return os.path.exists(path) 40 42 41 43 def get_hash(self): 42 44 if not hasattr(self, '_hash'): … … 46 48 return self._hash 47 49 hash = property(get_hash) 48 50 49 51 def get_filesize(self): 50 52 path = self.get_full_path() … … 54 56 return 0 55 57 filesize = property(get_filesize) 56 58 57 59 def delete(self, *args, **kwargs): 58 60 import os 59 61 60 62 try: 61 63 os.remove(self.get_full_path(False)) … … 67 69 except: 68 70 pass 69 71 70 72 super(Report, self).delete(*args, **kwargs) 71 73 72 74 @models.permalink 73 75 def get_absolute_url(self): 74 76 return ('peach3.report.views.download', [self.id, self.filename]) 75 77 absolute_url = property(get_absolute_url) 76 78 77 79 def get_download_html(self): 78 80 from django.template.loader import render_to_string 79 81 return render_to_string('report/download_link.html', {'report':self}) 80 82 download_html = property(get_download_html) 81 83 82 84 def __unicode__(self): 83 85 return self.filename 84 86 85 87 def cleanup_reports(sender, **kwargs): 86 88 from django.db import transaction -
src/peach3/models/submission.py
r1546 r1563 1 1 """ This file is part of PEACH. 2 2 3 3 Copyright (C) 2006-2009 Eindhoven University of Technology 4 4 """ … … 6 6 from django.core.cache import cache 7 7 from django.db import models, transaction 8 from django.db.models import Q9 8 from django.utils.translation import ugettext_lazy, ugettext as _ 10 9 from django.utils.translation import get_language … … 15 14 from django import dispatch 16 15 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 16 from peach3.models.assignment import AssignmentEdition, AssignmentSlot 17 from peach3.models.course import CourseEdition 18 from peach3.models.files import FileRevision 19 from peach3.models.forum import Comment 20 from peach3.models.grade import Grade 21 from peach3.models.news import NewsItem 22 from peach3.models.proglang import ProgrammingLanguage 23 from peach3.models.mixins import RevisionMixin 24 from peach3.models import APP_LABEL, cleanup_signal 25 from peach3.managers.submission import SubmissionManager, CheckResultManager, ReasonManager 26 27 from peach3.utils.cache import cache_key, unique_cache_hash 26 28 27 29 from datetime import datetime, timedelta 28 from string import Template29 30 30 31 # Create your models here. … … 32 33 submission_created = dispatch.Signal() 33 34 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 sb41 42 35 class Submission(models.Model): 43 36 assignmentedition = models.ForeignKey(AssignmentEdition) 44 37 courseedition = models.ForeignKey(CourseEdition) 45 38 46 39 authors = models.ManyToManyField(User) 47 40 comments = generic.GenericRelation(Comment, object_id_field='parent_id', … … 55 48 56 49 files = models.ManyToManyField(FileRevision, through='SubmissionFile') 57 50 58 51 modified = models.DateTimeField(null=True, blank=True, db_index=True) # Last modified time 59 52 … … 61 54 62 55 objects = SubmissionManager() 63 56 57 class Meta: 58 app_label = APP_LABEL 59 64 60 def mark_updated(self, skip_local_update=False): 65 61 now = datetime.utcnow().replace(microsecond=0) 66 62 ae = self.assignmentedition 67 63 68 64 self.flush_cache() 69 65 … … 84 80 key = cache_key('peach3.submission.ajax.list_all', au, ae) 85 81 cache.delete(key) 86 82 87 83 self._observelevel = {} 88 84 self._reviewlevel = {} 89 85 90 86 if not skip_local_update: 91 87 self.modified = now 92 88 self.save() 93 89 94 90 def last_updated(self): 95 91 key = cache_key('peach3.submission.models.Submission', self.author, self.assignmentedition, 'updated') … … 98 94 self.mark_updated() 99 95 return updated 100 96 101 97 def save(self, *arg, **kwarg): 102 98 self.modified = datetime.utcnow() 103 99 super(Submission, self).save(*arg, **kwarg) 104 100 self.mark_updated(True) 105 101 106 102 @transaction.commit_manually 107 103 def submit(self, stamp, user, comment=None, progLangId=None): 108 104 # User can be None (submitted by system) 109 105 assert user is None or self.can_submit(user) 110 106 111 107 try: 112 108 self.submitted = stamp … … 122 118 123 119 self.save() 124 120 125 121 if comment: 126 122 c=self.comments.create(author=user, created=stamp) … … 130 126 message=comment, 131 127 ) 132 128 133 129 if self.assignmentedition.legacychecker_set.filter(enabled=True).count()>0: 134 130 self.checkresult_set.create(created=stamp) 135 131 transaction.commit() # Must commit changes before sending signal 136 132 137 133 submission_created.send(sender=self.__class__, 138 134 instance=self) 139 135 else: 140 136 self.send_initial_news() 141 137 142 138 except: 143 139 transaction.rollback() 144 140 raise 145 141 146 142 finally: 147 143 transaction.commit() … … 151 147 152 148 return stamp is None or updated is None or updated>stamp 153 149 154 150 def _get_state(self, user): 155 151 if self.submitted is None: 156 152 return 'NEW' 157 153 158 154 # IF there is a review with a grade, return that state 159 155 try: … … 169 165 else: 170 166 return 'REVIEWING' 171 167 172 168 # IF there is a checkresult, return that state 173 169 try: … … 183 179 if not scr.grade.passing: 184 180 return 'REJECTED' 185 181 186 182 # OTHERWISE it's accepted 187 183 ae = self.assignmentedition … … 194 190 else: 195 191 return 'ACCEPTED' 196 192 197 193 def get_state(self, user): 198 194 """ Can return … … 208 204 """ 209 205 key = self._get_cachekey('state', user) 210 206 211 207 cached = cache.get(key) 212 208 if cached is None: 213 209 state = self._get_state(user) 214 210 cache.set(key, (state,)) 215 211 216 212 else: 217 213 state = cached[0] 218 214 219 215 return state 220 216 … … 224 220 except self.checkresult_set.model.DoesNotExist: 225 221 return None 226 222 227 223 def get_tests_count(self, user): 228 224 try: … … 230 226 except self.checkresult_set.model.DoesNotExist: 231 227 return None 232 228 233 229 def get_file(self, filename): 234 230 try: … … 238 234 239 235 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 241 237 # If user is None: for all authors, otherwise for requested user 242 238 243 239 authors = [user] if user else self.authors.all() 244 240 245 241 newersbs = Submission.objects.filter(authors__in=authors, 246 242 assignmentedition=self.assignmentedition, 247 243 created__gt=self.created) 248 244 249 245 return newersbs.count()>0 250 251 @transaction.commit_on_success 246 247 @transaction.commit_on_success 252 248 def send_initial_news(self): 253 249 # Send the initial news message after upload and accept/reject by the system 254 250 255 251 msg = None 256 252 try: … … 266 262 else: 267 263 return 268 264 269 265 authors = list(self.authors.all()) 270 266 271 267 self_ct = ContentType.objects.get_for_model(self) 272 268 now = datetime.utcnow() 273 269 exp = max(self.created + timedelta(days=365), 274 270 now + timedelta(days=30)) 275 271 276 272 # Mark any news item pertaining to an older submission for this assignment 277 273 # by this author as not-current … … 279 275 assignmentedition=self.assignmentedition): 280 276 NewsItem.objects.filter_related(sb).update(current=False) 281 277 282 278 admin_cluster = self.courseedition.get_admin_cluster() 283 279 if admin_cluster: 284 280 # Message to the course staff 285 281 286 282 existing_news = NewsItem.objects.filter( 287 283 courseedition = self.courseedition, … … 292 288 msgtype = msg+'-admin', 293 289 ).order_by('-created') 294 290 295 291 if msg=='received': 296 292 if len(existing_news)==0: … … 308 304 else: 309 305 news = existing_news[0] 310 306 311 307 if not news.current: 312 308 news.current = True 313 309 news.save() 314 310 315 311 else: 316 312 existing_news.delete() 317 313 318 314 # Message to the authors 319 315 for author in authors: 320 316 msg_end = '-author' if author==self.created_by else '-coauthor' 321 317 umsg = msg+msg_end 322 318 323 319 existing_news = NewsItem.objects.filter( 324 320 courseedition = self.courseedition, … … 329 325 msgtype__endswith = msg_end, 330 326 ).order_by('-created') 331 327 332 328 if len(existing_news)==0: 333 329 NewsItem.objects.create( … … 340 336 appears = self.created, 341 337 expires = exp, 342 msgtype = umsg, 338 msgtype = umsg, 343 339 ) 344 340 else: 345 341 news = existing_news[0] 346 342 347 343 if news.msgtype != umsg or news.expires < exp or not news.current: 348 344 news.current = True … … 350 346 news.expires = exp 351 347 news.save() 352 348 353 349 if news.msgtype != umsg: 354 350 news.mark_unread() 355 351 356 352 def get_news_text(self, newsitem): 357 353 type = newsitem.msgtype 358 354 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 360 356 # full submission and not just read a summary 361 357 texts = { … … 385 381 ), 386 382 } 387 383 388 384 args = { 389 385 'username' : self.created_by.username, … … 391 387 'assignmentname' : self.assignmentedition.name, 392 388 } 393 389 394 390 s,t = texts[type] 395 391 return s % args, t % args 396 392 397 393 def get_observelevel(self, user): 398 394 """ Returns the user's observelevel for this submission … … 402 398 if not hasattr(self, '_observelevel'): 403 399 self._observelevel = {} 404 400 405 401 if user not in self._observelevel: 406 402 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 410 406 def get_reviewlevel(self, user): 411 407 """ Returns the user's reviewlevel for this submission … … 418 414 if user not in self._reviewlevel: 419 415 clusters = self.get_clusters() 420 416 421 417 if len(clusters)==1 and clusters[0].admin_cluster: 422 418 # Special case: all authors are reviewers 423 419 clusters = '*' 424 420 425 421 self._reviewlevel[user] = self.courseedition.get_reviewlevel(user, clusters) 426 422 427 423 return self._reviewlevel[user] 428 424 429 425 def get_authors(self): 430 426 # Generator returning all authors; the author that submitted the submission 431 427 # is at the front 432 428 main_author = None 433 429 434 430 if self.submitted_by and self.is_author(self.submitted_by): 435 431 main_author = self.submitted_by 436 432 elif self.created_by and self.is_author(self.created_by): 437 433 main_author = self.created_by 438 434 439 435 if main_author: 440 436 yield main_author … … 442 438 else: 443 439 q = self.authors.all() 444 440 445 441 for author in q: 446 442 yield author 447 443 448 444 def get_clusters(self): 449 445 # Generator returning all author's clusters … … 459 455 result.append(cluster) 460 456 clusters[cluster.pk] = True 461 457 462 458 cache.set(key, result) 463 459 464 460 return result 465 461 466 462 def same_authors(self, other_submission): 467 463 # Returns the number of authors that are the same for both submissions 468 464 pks = [au.pk for au in other_submission.get_authors()] 469 465 return self.authors.filter(pk__in=pks).count() 470 466 471 467 def is_visible(self, user): 472 468 return self.is_author(user) or self.is_observer(user) 473 469 474 470 def is_author(self, user): 475 471 " Returns True if the given user is an author " 476 472 return self.authors.filter(pk=user.pk).count()>0 477 473 478 474 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 480 476 """ 481 477 return self.get_observelevel(user) >= self.assignmentedition.observelevel 482 478 483 479 def is_reviewer(self, user): 484 480 " Returns True if the user is a reviewer for this submission " 485 481 if not self.assignmentedition.has_option('REVIEW'): 486 482 return False 487 483 488 484 try: 489 485 review = self.review_set.latest() 490 486 except self.review_set.model.DoesNotExist: 491 487 review = None 492 488 493 489 if review: 494 490 return review.can_modify(user) 495 491 496 492 return self.get_reviewlevel(user) >= self.assignmentedition.reviewlevel 497 493 498 494 def can_publish_review(self, user, allow_republish=False, initial=False): 499 495 " Returns True if the user can publish the review for this submission " … … 504 500 return False 505 501 else: 506 if review.is_published() and not allow_republish: 502 if review.is_published() and not allow_republish: 507 503 # Cannot publish if it is already published 508 504 return False 509 505 510 506 return user.is_superuser \ 511 507 or self.courseedition.is_manager(user) \ 512 508 or (self.is_reviewer(user) \ 513 509 and self.get_reviewlevel(user) >= self.assignmentedition.reviewpublishlevel) 514 510 515 511 def can_submit(self, user): 516 return (self.submitted is None 512 return (self.submitted is None 517 513 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) 520 516 and self.assignmentedition.is_open(user, when=timedelta(minutes=-5)) 521 517 ) 522 518 ) 523 519 ) and self.has_access(user) 524 520 525 521 def has_access(self, user, access=None): 526 522 if user.is_superuser: … … 528 524 elif user.is_anonymous(): 529 525 return False 530 526 531 527 is_reviewer = self.is_reviewer(user) 532 528 if access=='review': … … 536 532 if access=='observe': 537 533 return is_observer 538 534 539 535 return access is None and (is_reviewer or is_observer or self.is_author(user)) 540 536 541 537 # def flush_cache(self): 542 538 # aeid = self.assignmentedition.id … … 544 540 # key = 'submissions/user:%d/assignment:%d' % (au.id, aeid) 545 541 # cache.delete(key) 546 542 547 543 def __unicode__(self): 548 544 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 550 546 @models.permalink 551 547 def get_absolute_url(self): … … 557 553 submission_cache_hash = unique_cache_hash(key, self) 558 554 return cache_key('peach3.submission.models.Submission', self, 'info', submission_cache_hash, *args) 559 555 560 556 def _del_cachekey(self): 561 557 key = cache_key('peach3.submission.models.Submission', self, 'key') … … 564 560 def flush_cache(self): 565 561 self._del_cachekey() 566 562 567 563 def delete(self, *args, **kwargs): 568 564 self.flush_cache() 569 565 super(Submission, self).delete(*args, **kwargs) 570 566 571 567 572 568 class SubmissionFile(models.Model): 573 569 submission = models.ForeignKey(Submission) 574 570 file = models.ForeignKey(FileRevision) 575 571 576 572 slot = models.ForeignKey(AssignmentSlot, null=True, blank=True) 577 573 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 585 576 586 577 class CheckResult(models.Model, RevisionMixin): … … 590 581 ('SYSFAIL', ugettext_lazy("System failure")), 591 582 ) 592 583 593 584 submission = models.ForeignKey(Submission) 594 585 created = models.DateTimeField() 595 586 checked = models.DateTimeField(null=True, blank=True) 596 587 checked_level = models.PositiveSmallIntegerField(null=True, blank=True) 597 588 598 589 state = models.CharField(max_length=8, blank=True, choices=STATES) 599 590 grade = models.ForeignKey(Grade, null=True, blank=True) … … 612 603 score_9 = models.IntegerField(null=True, blank=True) 613 604 score_10 = models.IntegerField(null=True, blank=True) 614 605 615 606 objects = CheckResultManager() 616 607 617 608 class Meta: 609 app_label = APP_LABEL 618 610 get_latest_by = 'created' 619 611 620 612 def mark_updated(self): 621 613 if self.submission: 622 614 self.submission.mark_updated() 623 615 624 616 def save(self, *arg, **kwarg): 625 617 super(CheckResult, self).save(*arg, **kwarg) 626 618 self.mark_updated() 627 619 628 620 def get_score(self, user): 629 621 if user: … … 632 624 level = 0 633 625 return getattr(self, 'score_%d' % level) 634 626 635 627 def get_tests_count(self, user): 636 628 " Returns a tuple (passed, total) with the number of test runs in this checkresult that are visible for user " 637 629 638 630 level = self.submission.get_observelevel(user) or 0 639 631 640 632 q = self.checkresultstep_set.exclude(level__gt=level).filter(stage='TESTRUN') 641 633 642 634 total = q.count() 643 635 passed = q.filter(passed=True).count() 644 636 645 637 return (passed, total) 646 638 … … 648 640 # super(CheckResult, self).save() 649 641 # self.submission.flush_cache() 650 642 651 643 ### Ordering mixin interface 652 644 @staticmethod 653 645 def get_grouping_field_name(): 654 646 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 = codeparts666 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 pass673 else:674 return reason, param675 676 if param[0]:677 try:678 reason = self.get(code=code, param1='', param2=param[1], **kwarg)679 except self.model.DoesNotExist:680 pass681 else:682 return reason, param683 684 if param[1]:685 try:686 reason = self.get(code=code, param1=param[0], param2='', **kwarg)687 except self.model.DoesNotExist:688 pass689 else:690 return reason, param691 692 reason = self.get(code=code, param1='', param2='', **kwarg)693 return reason, param694 695 def create(self, code, **kwarg):696 codeparts = code.split(':')697 code = codeparts.pop(0)698 699 param = codeparts700 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)716 647 717 648 class Reason(models.Model): … … 719 650 param1 = models.CharField(max_length=16, blank=True) 720 651 param2 = models.CharField(max_length=16, blank=True) 721 652 722 653 default_description = models.TextField() 723 654 default_language = models.CharField(max_length=16, 724 choices=settings.LANGUAGES, 655 choices=settings.LANGUAGES, 725 656 default=settings.LANGUAGE_CODE) 726 657 727 658 objects = ReasonManager() 659 660 class Meta: 661 app_label = APP_LABEL 728 662 729 663 def get_description(self, lang=None): 730 664 if lang is None: 731 665 lang = get_language() 732 666 733 667 key = cache_key('peach3.submission.models.Reason.get_description', self, lang) 734 668 735 669 cached = cache.get(key) 736 if cached is None: 670 if cached is None: 737 671 try: 738 672 description = self.reasonaltdescription_set.get(language=lang).description 739 673 except self.reasonaltdescription_set.model.DoesNotExist: 740 674 description = None 741 675 742 676 cache.set(key, (description,)) 743 677 744 678 else: 745 679 description = cached[0] 746 680 747 681 if description: 748 682 return description 749 683 else: 750 684 return self.default_description 751 685 752 686 description = property(get_description) 753 687 754 688 def __unicode__(self): 755 689 code = self.code … … 765 699 language = models.CharField(max_length=16, choices=settings.LANGUAGES) 766 700 description = models.CharField(max_length=80) 767 701 768 702 class Meta: 703 app_label = APP_LABEL 769 704 unique_together = (('reason','language',),) 770 705 … … 775 710 ('TESTRUN', ugettext_lazy("Test Run")), 776 711 ) 777 712 778 713 REPORTFORMATS = ( 779 714 ('2', ugettext_lazy("Peach2 legacy")), … … 782 717 ('r', ugettext_lazy("rST")), 783 718 ) 784 719 785 720 checkresult = models.ForeignKey(CheckResult) 786 721 stage = models.CharField(max_length=16, choices=STAGES, blank=True) 787 722 step = models.PositiveSmallIntegerField() 788 723 level = models.PositiveSmallIntegerField() 789 724 790 725 passed = models.BooleanField() 791 726 792 727 reason = models.CharField(max_length=32, blank=True) 793 728 794 729 report = models.TextField(blank=True) 795 730 report_format = models.CharField(max_length=1, choices=REPORTFORMATS) 796 731 score = models.IntegerField(null=True, blank=True) 797 732 798 733 runtime = models.PositiveIntegerField(null=True, blank=True, help_text=ugettext_lazy("Unit: miliseconds")) 799 734 800 735 generatedfiles = models.ManyToManyField(FileRevision, null=True, blank=True, editable=False) 801 736 737 class Meta: 738 app_label = APP_LABEL 739 802 740 def is_visible(self, user): 803 741 return self.level <= (self.checkresult.submission.get_observelevel(user) or 0) 804 742 805 743 def mark_updated(self): 806 744 if self.checkresult: 807 745 self.checkresult.mark_updated() 808 746 809 747 def save(self, *arg, **kwarg): 810 748 super(CheckResultStep, self).save(*arg, **kwarg) 811 749 self.mark_updated() 812 750 813 751 class CheckResultStepIO(models.Model): 814 752 STREAMS = ( … … 817 755 ('2', ugettext_lazy("stderr")), 818 756 ) 819 757 820 758 checkresultstep = models.ForeignKey(CheckResultStep) 821 759 stream = models.CharField(max_length=1, choices=STREAMS) … … 824 762 data = models.TextField() 825 763 764 class Meta: 765 app_label = APP_LABEL 766 826 767 class ReviewManager(models.Manager): 827 768 def create(self, *arg, **kwarg): … … 834 775 created = models.DateTimeField() 835 776 created_by = models.ForeignKey(User, related_name='review_creator') 836 777 837 778 reviewlevel = models.PositiveSmallIntegerField() 838 779 visibilitylevel = models.PositiveSmallIntegerField() 839 780 840 781 grade = models.ForeignKey(Grade, null=True, blank=True) 841 782 842 783 authors = models.ManyToManyField(User, related_name='review_author') 843 784 comments = generic.GenericRelation(Comment, object_id_field='parent_id', 844 785 content_type_field='parent_content_type') 845 786 846 787 objects = CheckResultManager() 847 788 848 789 class Meta: 790 app_label = APP_LABEL 849 791 get_latest_by = 'created' 850 792 851 793 def mark_updated(self): 852 794 if self.submission: 853 795 self.submission.mark_updated() 854 796 855 797 def save(self, *arg, **kwarg): 856 798 super(Review, self).save(*arg, **kwarg) 857 799 self.mark_updated() 858 800 859 801 @transaction.commit_on_success 860 802 def send_reviewed_news(self): 861 803 # Any older news about this submission is not current 862 804 assert self.is_published() 863 805 864 806 NewsItem.objects.filter_related(self.submission).update(current=False) 865 807 … … 874 816 msgtype = 'reviewed', 875 817 ) 876 818 877 819 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) 880 822 or self.reviewlevel < self.submission.get_reviewlevel(user)) 881 823 882 824 def is_visible(self, user): 883 825 return self.can_modify(user) or self.visibilitylevel <= (self.submission.get_observelevel(user) or 0) 884 826 885 827 def is_published(self): 886 828 return self.visibilitylevel==0 887 829 888 830 ### Ordering mixin interface 889 831 @staticmethod … … 891 833 return 'submission' 892 834 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 1 1 # -*- coding: utf-8 -*- 2 2 """ This file is part of PEACH. 3 3 4 4 Copyright (C) 2006-2009 Eindhoven University of Technology 5 5 """ … … 10 10 from django.utils.translation import ugettext_lazy as _ 11 11 12 from peach3.programming_language.models import ProgrammingLanguage 13 14 from altnames import AlternativeNames 12 from peach3.models.proglang import ProgrammingLanguage 13 from peach3.models import APP_LABEL 14 from peach3.managers.user import ProfileManager, VerificationCodeManager 15 16 from peach3.core.user import AlternativeNames 15 17 16 18 # 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)24 19 25 20 class Profile(models.Model): … … 36 31 ("Y/m/d",)*2, # 2007/01/09 37 32 ) 38 33 39 34 # Mapping from long dates (above) to short dates (without month and day names) 40 35 SHORT_DATES = { 41 36 "D j M Y" : "j-m-Y", 42 "j M Y" : "j-m-Y", 37 "j M Y" : "j-m-Y", 43 38 "D M j Y" : "n.j.Y", 44 39 "M j Y" : "n.j.Y", … … 50 45 "Y/m/d" : "Y/m/d", 51 46 } 52 47 53 48 TIME_CHOICES = ( 54 49 ("G:i",)*2, # 15:05 55 50 ("g:i a",)*2, # 3:05 pm 56 51 ) 57 52 58 53 STATE_VERIFIED = 'V' 59 54 STATE_UNVERIFIED = 'U' 60 55 STATE_BLOCKED = 'X' 61 56 62 57 STATE_CHOICES = ( 63 58 (STATE_UNVERIFIED, _("Unverified")), … … 65 60 (STATE_BLOCKED, _("Blocked")), 66 61 ) 67 62 68 63 user = models.ForeignKey(User) 69 64 initials = models.CharField(max_length=30, blank=True) … … 75 70 date_format = models.CharField(max_length=16, choices=DATE_CHOICES, default=DATE_CHOICES[0][0]) 76 71 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, 79 74 default='', blank=True) 80 75 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], 82 77 blank=False, null=False) 83 78 79 objects = ProfileManager() 80 81 class Meta: 82 app_label = APP_LABEL 83 84 84 def __unicode__(self): 85 85 return str(self.user) 86 86 87 87 def format_initials(self): 88 88 i = self.initials … … 91 91 else: 92 92 return '.'.join(list(i))+'.' 93 93 94 94 def get_username_sortable(self): 95 95 name = self.user.last_name … … 106 106 if self.last_name_prefix: 107 107 name += u' '+self.last_name_prefix 108 108 109 109 return name 110 110 111 111 def get_username_formal(self): 112 112 name = self.user.last_name … … 119 119 if self.last_name_prefix: 120 120 name += u' '+self.last_name_prefix 121 121 122 122 return name 123 123 124 124 def get_username(self): 125 125 name = self.user.last_name … … 134 134 name = self.format_initials() + u' ' + name 135 135 return name 136 136 137 137 def get_timezone(self): 138 138 if not hasattr(self, '_tz'): … … 144 144 self._tz = pytz.timezone(settings.TIME_ZONE) 145 145 return self._tz 146 146 147 147 def set_timezone(self, tz): 148 148 self._tz = tz 149 149 self.timezone_str = str(tz) 150 150 151 151 timezone = property(get_timezone, set_timezone) 152 152 153 153 def get_datetime_format(self): 154 154 return self.date_format+' '+self.time_format 155 155 datetime_format = property(get_datetime_format) 156 156 157 157 def get_userinfo(self): 158 158 df = self.date_format 159 159 160 160 info = { 161 'logged_in' : True, 161 'logged_in' : True, 162 162 'username' : self.user.username, 163 163 'date_format' : df, … … 167 167 'language' : self.language, 168 168 } 169 169 170 170 if self.user.is_staff: 171 171 info['staff'] = True 172 172 if self.user.is_superuser: 173 173 info['superuser'] = True 174 174 175 175 return info 176 176 177 177 def get_alternative_names(self): 178 178 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 datetime187 kwargs['created'] = datetime.utcnow()188 189 if 'expires' not in kwargs:190 from datetime import timedelta191 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 code200 179 201 180 class VerificationCode(models.Model): 202 181 EXPIRATION = 7 203 182 204 183 TYPE_REGISTRATION = 'R' 205 184 TYPE_PASSWORD_RESET = 'P' 206 185 TYPE_EMAIL_CHANGE = 'E' 207 186 208 187 TYPE_CHOICES = ( 209 188 (TYPE_REGISTRATION, _("Registration")), … … 211 190 (TYPE_EMAIL_CHANGE, _("Email change")), 212 191 ) 213 192 214 193 user = models.ForeignKey(User) 215 194 type = models.CharField(max_length=1, blank=False, null=False, choices=TYPE_CHOICES) … … 218 197 expires = models.DateTimeField(blank=True, null=True) 219 198 parameters = models.TextField(blank=True) 220 199 221 200 objects = VerificationCodeManager() 222 201 202 class Meta: 203 app_label = APP_LABEL 204 223 205 def get_challenge(self): 224 206 from hashlib import md5 225 207 226 208 hash = md5() 227 209 hash.update(settings.SECRET_KEY) … … 230 212 hash.update(self.code) 231 213 hash.update(self.parameters) 232 214 233 215 return hash.hexdigest() 234 216 235 217 def get_absolute_url(self): 236 218 from django.core.urlresolvers import reverse -
src/peach3/models/wiki.py
r1552 r1563 1 1 """ This file is part of PEACH. 2 2 3 Copyright (C) 2006-20 09Eindhoven University of Technology3 Copyright (C) 2006-2012 Eindhoven University of Technology 4 4 """ 5 5 from django.db import models 6 6 7 7 from django.conf import settings 8 from django.core import urlresolvers9 8 from django.core.cache import cache 10 from django.core.exceptions import PermissionDenied 11 from django.contrib.auth.models import User, Permission 9 from django.contrib.auth.models import User 12 10 from django.contrib.contenttypes.models import ContentType 13 11 from 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 13 from django.utils.translation import ugettext_lazy as _, get_language 14 15 from peach3.models.mixins import RevisionMixin 16 from peach3.models import APP_LABEL 17 from peach3.managers.wiki import PageManager, PageRevisionTextManager, PageRevisionManager 18 from peach3.utils.wiki import decode_full_path, user_can_create_page, construct_full_path 19 from peach3.utils.cache import cache_key 24 20 25 21 from 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 '/'+path32 else:33 if hasattr(parent, 'get_uid'):34 ouid = parent.get_uid()35 else:36 ouid = None37 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 exists49 try:50 page = Page.objects.get_by_path(path)51 except Page.DoesNotExist:52 return None53 54 return path55 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 = None69 pid = None70 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=False102 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=True111 elif value is None:112 kwargs['%s__isnull' % field] = True113 else:114 if field=='created':115 # Convert value to a datetime object116 # Format of value 'YYYYMMDDhhmmss'117 value = datetime.strptime(value, "%Y%m%d%H%M%S")118 kwargs[field] = value119 120 pid = None121 try:122 q = model.objects.filter(**kwargs)123 if latest_by_created:124 pid = q.latest().id125 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 pass133 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 = None146 147 result = parent, path148 149 cache.set(key, result)150 151 return result152 153 def user_can_create_page(user, parent, path):154 if user.is_anonymous():155 return False156 elif user.is_superuser:157 return True158 elif user.has_perm('wiki.add_page'):159 return True160 161 if parent and hasattr(parent, 'has_access') and parent.has_access(user, 'createwiki'):162 return True163 164 # Find a parent page165 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 True178 except Page.DoesNotExist:179 pass180 181 return False182 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/7551189 if parent:190 kwarg['parent'] = parent191 192 if not user_can_create_page(user, parent, path):193 raise PermissionDenied194 195 return super(PageManager, self).create(path = path,196 created = datetime.utcnow(),197 created_by = user,198 #parent = parent,199 **kwarg200 )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'] = parent209 else:210 query['parent__isnull'] = True211 212 return self.get(**query)213 214 def get(self, **kwarg):215 if 'parent' in kwarg:216 parent = kwarg.pop('parent')217 assert parent218 kwarg['parent_type'] = ContentType.objects.get_for_model(parent)219 kwarg['parent_id'] = parent.pk220 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)231 22 232 23 # The ordering here is important! … … 269 60 270 61 class Meta: 62 app_label = APP_LABEL 271 63 get_latest_by = 'created' 272 64 unique_together = (('parent_type','parent_id','path',),) … … 536 328 permission = models.PositiveSmallIntegerField(choices=PAGE_PERMISSION) 537 329 330 class Meta: 331 app_label = APP_LABEL 332 538 333 MARKUP_CHOICES = ( 539 334 ('plain', _("Plain text")), 540 335 ('rst' , _("Restructured text")), 541 336 ) 542 543 class PageRevisionTextManager(models.Manager):544 def __update_arg(self, kwarg):545 if 'content' in kwarg:546 kwarg['diff_base'] = None547 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'] = True558 kwarg['defaults'] = {'diff_base':None}559 560 return super(PageRevisionTextManager, self).get_or_create(**kwarg)561 562 337 563 338 class PageRevisionText(models.Model): … … 572 347 573 348 objects = PageRevisionTextManager() 349 350 class Meta: 351 app_label = APP_LABEL 574 352 575 353 ### Model methods … … 587 365 self.save() 588 366 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_success604 # 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_revision615 616 367 class PageRevision(models.Model, RevisionMixin): 617 368 ### Model definition … … 628 379 629 380 objects = PageRevisionManager() 381 382 class Meta: 383 app_label = APP_LABEL 384 get_latest_by = 'created' 630 385 631 386 ### Model methods … … 647 402 return u'%s (%s) [%d]' % (self.page, self.language, self.revision) 648 403 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.

