Commit 4f9d56208becd4f93d82ad9dc3ecb0054f35d8a5
Exists in
master
and in
5 other branches
Merge branch 'master' of https://github.com/amadeusproject/amadeuslms
Showing
15 changed files
with
953 additions
and
107 deletions
Show diff stats
... | ... | @@ -0,0 +1,15 @@ |
1 | +# coding: utf-8 | |
2 | +# | |
3 | +# Copyright (c) 2008—2015 Andy Mikhailenko | |
4 | +# | |
5 | +# This file is part of django-autoslug. | |
6 | +# | |
7 | +# django-autoslug is free software under terms of the GNU Lesser | |
8 | +# General Public License version 3 (LGPLv3) as published by the Free | |
9 | +# Software Foundation. See the file README for copying conditions. | |
10 | +# | |
11 | +from autoslug.fields import AutoSlugField | |
12 | + | |
13 | + | |
14 | +__version__ = '1.9.3' | |
15 | +__all__ = ['AutoSlugField'] | ... | ... |
... | ... | @@ -0,0 +1,343 @@ |
1 | +# coding: utf-8 | |
2 | +# | |
3 | +# Copyright (c) 2008—2015 Andy Mikhailenko | |
4 | +# | |
5 | +# This file is part of django-autoslug. | |
6 | +# | |
7 | +# django-autoslug is free software under terms of the GNU Lesser | |
8 | +# General Public License version 3 (LGPLv3) as published by the Free | |
9 | +# Software Foundation. See the file README for copying conditions. | |
10 | +# | |
11 | + | |
12 | +# django | |
13 | +from django.conf import settings | |
14 | +from django.db.models.fields import SlugField | |
15 | +from django.db.models.signals import post_save | |
16 | + | |
17 | +# 3rd-party | |
18 | +try: | |
19 | + from south.modelsinspector import introspector | |
20 | +except ImportError: | |
21 | + introspector = lambda self: [], {} | |
22 | + | |
23 | +try: | |
24 | + from modeltranslation import utils as modeltranslation_utils | |
25 | +except ImportError: | |
26 | + modeltranslation_utils = None | |
27 | + | |
28 | +# this app | |
29 | +from autoslug.settings import slugify, autoslug_modeltranslation_enable | |
30 | +from autoslug import utils | |
31 | + | |
32 | +__all__ = ['AutoSlugField'] | |
33 | + | |
34 | +SLUG_INDEX_SEPARATOR = '-' # the "-" in "foo-2" | |
35 | + | |
36 | +try: # pragma: nocover | |
37 | + # Python 2.x | |
38 | + basestring | |
39 | +except NameError: # pragma: nocover | |
40 | + # Python 3.x | |
41 | + basestring = str | |
42 | + | |
43 | + | |
44 | +class AutoSlugField(SlugField): | |
45 | + """ | |
46 | + AutoSlugField is an extended SlugField able to automatically resolve name | |
47 | + clashes. | |
48 | + | |
49 | + AutoSlugField can also perform the following tasks on save: | |
50 | + | |
51 | + - populate itself from another field (using `populate_from`), | |
52 | + - use custom `slugify` function (using `slugify` or :doc:`settings`), and | |
53 | + - preserve uniqueness of the value (using `unique` or `unique_with`). | |
54 | + | |
55 | + None of the tasks is mandatory, i.e. you can have auto-populated non-unique | |
56 | + fields, manually entered unique ones (absolutely unique or within a given | |
57 | + date) or both. | |
58 | + | |
59 | + Uniqueness is preserved by checking if the slug is unique with given constraints | |
60 | + (`unique_with`) or globally (`unique`) and adding a number to the slug to make | |
61 | + it unique. | |
62 | + | |
63 | + :param always_update: boolean: if True, the slug is updated each time the | |
64 | + model instance is saved. Use with care because `cool URIs don't | |
65 | + change`_ (and the slug is usually a part of object's URI). Note that | |
66 | + even if the field is editable, any manual changes will be lost when | |
67 | + this option is activated. | |
68 | + :param populate_from: string or callable: if string is given, it is considered | |
69 | + as the name of attribute from which to fill the slug. If callable is given, | |
70 | + it should accept `instance` parameter and return a value to fill the slug | |
71 | + with. | |
72 | + :param sep: string: if defined, overrides default separator for automatically | |
73 | + incremented slug index (i.e. the "-" in "foo-2"). | |
74 | + :param slugify: callable: if defined, overrides `AUTOSLUG_SLUGIFY_FUNCTION` | |
75 | + defined in :doc:`settings`. | |
76 | + :param unique: boolean: ensure total slug uniqueness (unless more precise | |
77 | + `unique_with` is defined). | |
78 | + :param unique_with: string or tuple of strings: name or names of attributes | |
79 | + to check for "partial uniqueness", i.e. there will not be two objects | |
80 | + with identical slugs if these objects share the same values of given | |
81 | + attributes. For instance, ``unique_with='pub_date'`` tells AutoSlugField | |
82 | + to enforce slug uniqueness of all items published on given date. The | |
83 | + slug, however, may reappear on another date. If more than one field is | |
84 | + given, e.g. ``unique_with=('pub_date', 'author')``, then the same slug may | |
85 | + reappear within a day or within some author's articles but never within | |
86 | + a day for the same author. Foreign keys are also supported, i.e. not only | |
87 | + `unique_with='author'` will do, but also `unique_with='author__name'`. | |
88 | + | |
89 | + .. _cool URIs don't change: http://w3.org/Provider/Style/URI.html | |
90 | + | |
91 | + .. note:: always place any slug attribute *after* attributes referenced | |
92 | + by it (i.e. those from which you wish to `populate_from` or check | |
93 | + `unique_with`). The reasoning is that autosaved dates and other such | |
94 | + fields must be already processed before using them in the AutoSlugField. | |
95 | + | |
96 | + Example usage: | |
97 | + | |
98 | + .. code-block:: python | |
99 | + | |
100 | + from django.db import models | |
101 | + from autoslug import AutoSlugField | |
102 | + | |
103 | + class Article(models.Model): | |
104 | + '''An article with title, date and slug. The slug is not totally | |
105 | + unique but there will be no two articles with the same slug within | |
106 | + any month. | |
107 | + ''' | |
108 | + title = models.CharField(max_length=200) | |
109 | + pub_date = models.DateField(auto_now_add=True) | |
110 | + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month') | |
111 | + | |
112 | + | |
113 | + More options: | |
114 | + | |
115 | + .. code-block:: python | |
116 | + | |
117 | + # slugify but allow non-unique slugs | |
118 | + slug = AutoSlugField() | |
119 | + | |
120 | + # globally unique, silently fix on conflict ("foo" --> "foo-1".."foo-n") | |
121 | + slug = AutoSlugField(unique=True) | |
122 | + | |
123 | + # autoslugify value from attribute named "title"; editable defaults to False | |
124 | + slug = AutoSlugField(populate_from='title') | |
125 | + | |
126 | + # same as above but force editable=True | |
127 | + slug = AutoSlugField(populate_from='title', editable=True) | |
128 | + | |
129 | + # ensure that slug is unique with given date (not globally) | |
130 | + slug = AutoSlugField(unique_with='pub_date') | |
131 | + | |
132 | + # ensure that slug is unique with given date AND category | |
133 | + slug = AutoSlugField(unique_with=('pub_date','category')) | |
134 | + | |
135 | + # ensure that slug in unique with an external object | |
136 | + # assuming that author=ForeignKey(Author) | |
137 | + slug = AutoSlugField(unique_with='author') | |
138 | + | |
139 | + # ensure that slug in unique with a subset of external objects (by lookups) | |
140 | + # assuming that author=ForeignKey(Author) | |
141 | + slug = AutoSlugField(unique_with='author__name') | |
142 | + | |
143 | + # mix above-mentioned behaviour bits | |
144 | + slug = AutoSlugField(populate_from='title', unique_with='pub_date') | |
145 | + | |
146 | + # minimum date granularity is shifted from day to month | |
147 | + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month') | |
148 | + | |
149 | + # autoslugify value from a dynamic attribute (i.e. a method) | |
150 | + slug = AutoSlugField(populate_from='get_full_name') | |
151 | + | |
152 | + # autoslugify value from a custom callable | |
153 | + # (ex. usage: user profile models) | |
154 | + slug = AutoSlugField(populate_from=lambda instance: instance.user.get_full_name()) | |
155 | + | |
156 | + # specify model manager for looking up slugs shared by subclasses | |
157 | + | |
158 | + class Article(models.Model): | |
159 | + '''An article with title, date and slug. The slug is not totally | |
160 | + unique but there will be no two articles with the same slug within | |
161 | + any month. | |
162 | + ''' | |
163 | + objects = models.Manager() | |
164 | + title = models.CharField(max_length=200) | |
165 | + slug = AutoSlugField(populate_from='title', unique_with='pub_date__month', manager=objects) | |
166 | + | |
167 | + class NewsArticle(Article): | |
168 | + pass | |
169 | + | |
170 | + # autoslugify value using custom `slugify` function | |
171 | + from autoslug.settings import slugify as default_slugify | |
172 | + def custom_slugify(value): | |
173 | + return default_slugify(value).replace('-', '_') | |
174 | + slug = AutoSlugField(slugify=custom_slugify) | |
175 | + | |
176 | + """ | |
177 | + def __init__(self, *args, **kwargs): | |
178 | + kwargs['max_length'] = kwargs.get('max_length', 50) | |
179 | + | |
180 | + # autopopulated slug is not editable unless told so | |
181 | + self.populate_from = kwargs.pop('populate_from', None) | |
182 | + if self.populate_from: | |
183 | + kwargs.setdefault('editable', False) | |
184 | + | |
185 | + # unique_with value can be string or tuple | |
186 | + self.unique_with = kwargs.pop('unique_with', ()) | |
187 | + if isinstance(self.unique_with, basestring): | |
188 | + self.unique_with = (self.unique_with,) | |
189 | + | |
190 | + self.slugify = kwargs.pop('slugify', slugify) | |
191 | + assert hasattr(self.slugify, '__call__') | |
192 | + | |
193 | + self.index_sep = kwargs.pop('sep', SLUG_INDEX_SEPARATOR) | |
194 | + | |
195 | + if self.unique_with: | |
196 | + # we will do "manual" granular check below | |
197 | + kwargs['unique'] = False | |
198 | + | |
199 | + # Set db_index=True unless it's been set manually. | |
200 | + if 'db_index' not in kwargs: | |
201 | + kwargs['db_index'] = True | |
202 | + | |
203 | + # A boolean instructing the field to accept Unicode letters in | |
204 | + # addition to ASCII letters. Defaults to False. | |
205 | + self.allow_unicode = kwargs.pop('allow_unicode', False) | |
206 | + | |
207 | + # When using model inheritence, set manager to search for matching | |
208 | + # slug values | |
209 | + self.manager = kwargs.pop('manager', None) | |
210 | + | |
211 | + self.always_update = kwargs.pop('always_update', False) | |
212 | + super(SlugField, self).__init__(*args, **kwargs) | |
213 | + | |
214 | + def deconstruct(self): | |
215 | + name, path, args, kwargs = super(AutoSlugField, self).deconstruct() | |
216 | + | |
217 | + if self.max_length == 50: | |
218 | + kwargs.pop('max_length', None) | |
219 | + | |
220 | + if self.populate_from is not None: | |
221 | + kwargs['populate_from'] = self.populate_from | |
222 | + if self.editable is not False: | |
223 | + kwargs['editable'] = self.editable | |
224 | + | |
225 | + if self.unique_with != (): | |
226 | + kwargs['unique_with'] = self.unique_with | |
227 | + kwargs.pop('unique', None) | |
228 | + | |
229 | + if self.slugify != slugify: | |
230 | + kwargs['slugify'] = self.slugify | |
231 | + | |
232 | + if self.index_sep != SLUG_INDEX_SEPARATOR: | |
233 | + kwargs['sep'] = self.index_sep | |
234 | + | |
235 | + kwargs.pop('db_index', None) | |
236 | + | |
237 | + if self.manager is not None: | |
238 | + kwargs['manager'] = self.manager | |
239 | + | |
240 | + if self.always_update: | |
241 | + kwargs['always_update'] = self.always_update | |
242 | + | |
243 | + return name, path, args, kwargs | |
244 | + | |
245 | + def pre_save(self, instance, add): | |
246 | + | |
247 | + # get currently entered slug | |
248 | + value = self.value_from_object(instance) | |
249 | + | |
250 | + manager = self.manager | |
251 | + | |
252 | + # autopopulate | |
253 | + if self.always_update or (self.populate_from and not value): | |
254 | + value = utils.get_prepopulated_value(self, instance) | |
255 | + | |
256 | + # pragma: nocover | |
257 | + if __debug__ and not value and not self.blank: | |
258 | + print('Failed to populate slug %s.%s from %s' % \ | |
259 | + (instance._meta.object_name, self.name, self.populate_from)) | |
260 | + | |
261 | + if value: | |
262 | + slug = self.slugify(value) | |
263 | + else: | |
264 | + slug = None | |
265 | + | |
266 | + if not self.blank: | |
267 | + slug = instance._meta.model_name | |
268 | + elif not self.null: | |
269 | + slug = '' | |
270 | + | |
271 | + if not self.blank: | |
272 | + assert slug, 'slug is defined before trying to ensure uniqueness' | |
273 | + | |
274 | + if slug: | |
275 | + slug = utils.crop_slug(self, slug) | |
276 | + | |
277 | + # ensure the slug is unique (if required) | |
278 | + if self.unique or self.unique_with: | |
279 | + slug = utils.generate_unique_slug(self, instance, slug, manager) | |
280 | + | |
281 | + assert slug, 'value is filled before saving' | |
282 | + | |
283 | + # make the updated slug available as instance attribute | |
284 | + setattr(instance, self.name, slug) | |
285 | + | |
286 | + # modeltranslation support | |
287 | + if 'modeltranslation' in settings.INSTALLED_APPS \ | |
288 | + and not hasattr(self.populate_from, '__call__') \ | |
289 | + and autoslug_modeltranslation_enable: | |
290 | + post_save.connect(modeltranslation_update_slugs, sender=type(instance)) | |
291 | + | |
292 | + return slug | |
293 | + | |
294 | + | |
295 | + def south_field_triple(self): | |
296 | + "Returns a suitable description of this field for South." | |
297 | + args, kwargs = introspector(self) | |
298 | + kwargs.update({ | |
299 | + 'populate_from': 'None' if callable(self.populate_from) else repr(self.populate_from), | |
300 | + 'unique_with': repr(self.unique_with) | |
301 | + }) | |
302 | + return ('autoslug.fields.AutoSlugField', args, kwargs) | |
303 | + | |
304 | + | |
305 | +def modeltranslation_update_slugs(sender, **kwargs): | |
306 | + # https://bitbucket.org/neithere/django-autoslug/pull-request/11/modeltranslation-support-fix-issue-19/ | |
307 | + # http://django-modeltranslation.readthedocs.org | |
308 | + # | |
309 | + # TODO: tests | |
310 | + # | |
311 | + if not modeltranslation_utils: | |
312 | + return | |
313 | + | |
314 | + instance = kwargs['instance'] | |
315 | + slugs = {} | |
316 | + | |
317 | + for field in instance._meta.fields: | |
318 | + if type(field) != AutoSlugField: | |
319 | + continue | |
320 | + if not field.populate_from: | |
321 | + continue | |
322 | + for lang in settings.LANGUAGES: | |
323 | + lang_code, _ = lang | |
324 | + lang_code = lang_code.replace('-', '_') | |
325 | + | |
326 | + populate_from_localized = modeltranslation_utils.build_localized_fieldname(field.populate_from, lang_code) | |
327 | + field_name_localized = modeltranslation_utils.build_localized_fieldname(field.name, lang_code) | |
328 | + | |
329 | + # The source field or the slug field itself may not be registered | |
330 | + # with translator | |
331 | + if not hasattr(instance, populate_from_localized): | |
332 | + continue | |
333 | + if not hasattr(instance, field_name_localized): | |
334 | + continue | |
335 | + | |
336 | + populate_from_value = getattr(instance, populate_from_localized) | |
337 | + field_value = getattr(instance, field_name_localized) | |
338 | + | |
339 | + if not field_value or field.always_update: | |
340 | + slug = field.slugify(populate_from_value) | |
341 | + slugs[field_name_localized] = slug | |
342 | + | |
343 | + sender.objects.filter(pk=instance.pk).update(**slugs) | ... | ... |
... | ... | @@ -0,0 +1,68 @@ |
1 | +# coding: utf-8 | |
2 | +# | |
3 | +# Copyright (c) 2008—2015 Andy Mikhailenko | |
4 | +# | |
5 | +# This file is part of django-autoslug. | |
6 | +# | |
7 | +# django-autoslug is free software under terms of the GNU Lesser | |
8 | +# General Public License version 3 (LGPLv3) as published by the Free | |
9 | +# Software Foundation. See the file README for copying conditions. | |
10 | +# | |
11 | +""" | |
12 | +Django settings that affect django-autoslug: | |
13 | + | |
14 | +`AUTOSLUG_SLUGIFY_FUNCTION` | |
15 | + Allows to define a custom slugifying function. | |
16 | + | |
17 | + The function can be repsesented as string or callable, e.g.:: | |
18 | + | |
19 | + # custom function, path as string: | |
20 | + AUTOSLUG_SLUGIFY_FUNCTION = 'some_app.slugify_func' | |
21 | + | |
22 | + # custom function, callable: | |
23 | + AUTOSLUG_SLUGIFY_FUNCTION = some_app.slugify_func | |
24 | + | |
25 | + # custom function, defined inline: | |
26 | + AUTOSLUG_SLUGIFY_FUNCTION = lambda slug: 'can i haz %s?' % slug | |
27 | + | |
28 | + If no value is given, default value is used. | |
29 | + | |
30 | + Default value is one of these depending on availability in given order: | |
31 | + | |
32 | + * `unidecode.unidecode()` if Unidecode_ is available; | |
33 | + * `pytils.translit.slugify()` if pytils_ is available; | |
34 | + * `django.template.defaultfilters.slugify()` bundled with Django. | |
35 | + | |
36 | + django-autoslug also ships a couple of slugify functions that use | |
37 | + the translitcodec_ Python library, e.g.:: | |
38 | + | |
39 | + # using as many characters as needed to make a natural replacement | |
40 | + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_long' | |
41 | + | |
42 | + # using the minimum number of characters to make a replacement | |
43 | + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_short' | |
44 | + | |
45 | + # only performing single character replacements | |
46 | + AUTOSLUG_SLUGIFY_FUNCTION = 'autoslug.utils.translit_one' | |
47 | + | |
48 | +.. _Unidecode: http://pypi.python.org/pypi/Unidecode | |
49 | +.. _pytils: http://pypi.python.org/pypi/pytils | |
50 | +.. _translitcodec: http://pypi.python.org/pypi/translitcodec | |
51 | + | |
52 | +`AUTOSLUG_MODELTRANSLATION_ENABLE` | |
53 | + Django-autoslug support of modeltranslation_ is still experimental. | |
54 | + If you wish to enable it, please set this option to `True` in your project | |
55 | + settings. Default is `False`. | |
56 | + | |
57 | +.. _modeltranslation: http://django-modeltranslation.readthedocs.org | |
58 | + | |
59 | +""" | |
60 | +from django.conf import settings | |
61 | +from django.core.urlresolvers import get_callable | |
62 | + | |
63 | +# use custom slugifying function if any | |
64 | +slugify_function_path = getattr(settings, 'AUTOSLUG_SLUGIFY_FUNCTION', 'autoslug.utils.slugify') | |
65 | +slugify = get_callable(slugify_function_path) | |
66 | + | |
67 | +# enable/disable modeltranslation support | |
68 | +autoslug_modeltranslation_enable = getattr(settings, 'AUTOSLUG_MODELTRANSLATION_ENABLE', False) | ... | ... |
... | ... | @@ -0,0 +1,199 @@ |
1 | +# coding: utf-8 | |
2 | +# | |
3 | +# Copyright (c) 2008—2015 Andy Mikhailenko | |
4 | +# | |
5 | +# This file is part of django-autoslug. | |
6 | +# | |
7 | +# django-autoslug is free software under terms of the GNU Lesser | |
8 | +# General Public License version 3 (LGPLv3) as published by the Free | |
9 | +# Software Foundation. See the file README for copying conditions. | |
10 | +# | |
11 | + | |
12 | +# django | |
13 | +from django.core.exceptions import ImproperlyConfigured | |
14 | +from django.db.models import ForeignKey | |
15 | +from django.db.models.fields import FieldDoesNotExist, DateField | |
16 | +from django.template.defaultfilters import slugify as django_slugify | |
17 | + | |
18 | +try: | |
19 | + # i18n-friendly approach | |
20 | + from unidecode import unidecode | |
21 | +except ImportError: | |
22 | + try: | |
23 | + # Cyrillic transliteration (primarily Russian) | |
24 | + from pytils.translit import slugify | |
25 | + except ImportError: | |
26 | + # fall back to Django's default method | |
27 | + slugify = django_slugify | |
28 | +else: | |
29 | + # Use Django's default method over decoded string | |
30 | + def slugify(value): | |
31 | + return django_slugify(unidecode(value)) | |
32 | + | |
33 | + | |
34 | +def get_prepopulated_value(field, instance): | |
35 | + """ | |
36 | + Returns preliminary value based on `populate_from`. | |
37 | + """ | |
38 | + if hasattr(field.populate_from, '__call__'): | |
39 | + # AutoSlugField(populate_from=lambda instance: ...) | |
40 | + return field.populate_from(instance) | |
41 | + else: | |
42 | + # AutoSlugField(populate_from='foo') | |
43 | + attr = getattr(instance, field.populate_from) | |
44 | + return callable(attr) and attr() or attr | |
45 | + | |
46 | + | |
47 | +def generate_unique_slug(field, instance, slug, manager): | |
48 | + """ | |
49 | + Generates unique slug by adding a number to given value until no model | |
50 | + instance can be found with such slug. If ``unique_with`` (a tuple of field | |
51 | + names) was specified for the field, all these fields are included together | |
52 | + in the query when looking for a "rival" model instance. | |
53 | + """ | |
54 | + | |
55 | + original_slug = slug = crop_slug(field, slug) | |
56 | + | |
57 | + default_lookups = tuple(get_uniqueness_lookups(field, instance, field.unique_with)) | |
58 | + | |
59 | + index = 1 | |
60 | + | |
61 | + if not manager: | |
62 | + manager = field.model._default_manager | |
63 | + | |
64 | + | |
65 | + # keep changing the slug until it is unique | |
66 | + while True: | |
67 | + # find instances with same slug | |
68 | + lookups = dict(default_lookups, **{field.name: slug}) | |
69 | + rivals = manager.filter(**lookups) | |
70 | + if instance.pk: | |
71 | + rivals = rivals.exclude(pk=instance.pk) | |
72 | + | |
73 | + if not rivals: | |
74 | + # the slug is unique, no model uses it | |
75 | + return slug | |
76 | + | |
77 | + # the slug is not unique; change once more | |
78 | + index += 1 | |
79 | + | |
80 | + # ensure the resulting string is not too long | |
81 | + tail_length = len(field.index_sep) + len(str(index)) | |
82 | + combined_length = len(original_slug) + tail_length | |
83 | + if field.max_length < combined_length: | |
84 | + original_slug = original_slug[:field.max_length - tail_length] | |
85 | + | |
86 | + # re-generate the slug | |
87 | + data = dict(slug=original_slug, sep=field.index_sep, index=index) | |
88 | + slug = '%(slug)s%(sep)s%(index)d' % data | |
89 | + | |
90 | + # ...next iteration... | |
91 | + | |
92 | + | |
93 | +def get_uniqueness_lookups(field, instance, unique_with): | |
94 | + """ | |
95 | + Returns a dict'able tuple of lookups to ensure uniqueness of a slug. | |
96 | + """ | |
97 | + for original_lookup_name in unique_with: | |
98 | + if '__' in original_lookup_name: | |
99 | + field_name, inner_lookup = original_lookup_name.split('__', 1) | |
100 | + else: | |
101 | + field_name, inner_lookup = original_lookup_name, None | |
102 | + | |
103 | + try: | |
104 | + other_field = instance._meta.get_field(field_name) | |
105 | + except FieldDoesNotExist: | |
106 | + raise ValueError('Could not find attribute %s.%s referenced' | |
107 | + ' by %s.%s (see constraint `unique_with`)' | |
108 | + % (instance._meta.object_name, field_name, | |
109 | + instance._meta.object_name, field.name)) | |
110 | + | |
111 | + if field == other_field: | |
112 | + raise ValueError('Attribute %s.%s references itself in `unique_with`.' | |
113 | + ' Please use "unique=True" for this case.' | |
114 | + % (instance._meta.object_name, field_name)) | |
115 | + | |
116 | + value = getattr(instance, field_name) | |
117 | + if not value: | |
118 | + if other_field.blank: | |
119 | + field_object, model, direct, m2m = instance._meta.get_field_by_name(field_name) | |
120 | + if isinstance(field_object, ForeignKey): | |
121 | + lookup = '%s__isnull' % field_name | |
122 | + yield lookup, True | |
123 | + break | |
124 | + raise ValueError('Could not check uniqueness of %s.%s with' | |
125 | + ' respect to %s.%s because the latter is empty.' | |
126 | + ' Please ensure that "%s" is declared *after*' | |
127 | + ' all fields listed in unique_with.' | |
128 | + % (instance._meta.object_name, field.name, | |
129 | + instance._meta.object_name, field_name, | |
130 | + field.name)) | |
131 | + if isinstance(other_field, DateField): # DateTimeField is a DateField subclass | |
132 | + inner_lookup = inner_lookup or 'day' | |
133 | + | |
134 | + if '__' in inner_lookup: | |
135 | + raise ValueError('The `unique_with` constraint in %s.%s' | |
136 | + ' is set to "%s", but AutoSlugField only' | |
137 | + ' accepts one level of nesting for dates' | |
138 | + ' (e.g. "date__month").' | |
139 | + % (instance._meta.object_name, field.name, | |
140 | + original_lookup_name)) | |
141 | + | |
142 | + parts = ['year', 'month', 'day'] | |
143 | + try: | |
144 | + granularity = parts.index(inner_lookup) + 1 | |
145 | + except ValueError: | |
146 | + raise ValueError('expected one of %s, got "%s" in "%s"' | |
147 | + % (parts, inner_lookup, original_lookup_name)) | |
148 | + else: | |
149 | + for part in parts[:granularity]: | |
150 | + lookup = '%s__%s' % (field_name, part) | |
151 | + yield lookup, getattr(value, part) | |
152 | + else: | |
153 | + # TODO: this part should be documented as it involves recursion | |
154 | + if inner_lookup: | |
155 | + if not hasattr(value, '_meta'): | |
156 | + raise ValueError('Could not resolve lookup "%s" in `unique_with` of %s.%s' | |
157 | + % (original_lookup_name, instance._meta.object_name, field.name)) | |
158 | + for inner_name, inner_value in get_uniqueness_lookups(field, value, [inner_lookup]): | |
159 | + yield original_lookup_name, inner_value | |
160 | + else: | |
161 | + yield field_name, value | |
162 | + | |
163 | + | |
164 | +def crop_slug(field, slug): | |
165 | + if field.max_length < len(slug): | |
166 | + return slug[:field.max_length] | |
167 | + return slug | |
168 | + | |
169 | + | |
170 | +try: | |
171 | + import translitcodec | |
172 | +except ImportError: | |
173 | + pass | |
174 | +else: | |
175 | + import re | |
176 | + PUNCT_RE = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+') | |
177 | + | |
178 | + def translitcodec_slugify(codec): | |
179 | + def _slugify(value, delim='-', encoding=''): | |
180 | + """ | |
181 | + Generates an ASCII-only slug. | |
182 | + | |
183 | + Borrowed from http://flask.pocoo.org/snippets/5/ | |
184 | + """ | |
185 | + if encoding: | |
186 | + encoder = "%s/%s" % (codec, encoding) | |
187 | + else: | |
188 | + encoder = codec | |
189 | + result = [] | |
190 | + for word in PUNCT_RE.split(value.lower()): | |
191 | + word = word.encode(encoder) | |
192 | + if word: | |
193 | + result.append(word) | |
194 | + return unicode(delim.join(result)) | |
195 | + return _slugify | |
196 | + | |
197 | + translit_long = translitcodec_slugify("translit/long") | |
198 | + translit_short = translitcodec_slugify("translit/short") | |
199 | + translit_one = translitcodec_slugify("translit/one") | ... | ... |
courses/admin.py
1 | 1 | from django.contrib import admin |
2 | 2 | |
3 | -from .models import Category, Course, Module | |
3 | +from .models import Category, Course, Subject,Topic | |
4 | 4 | |
5 | 5 | class CategoryAdmin(admin.ModelAdmin): |
6 | 6 | list_display = ['name', 'slug'] |
... | ... | @@ -10,10 +10,15 @@ class CourseAdmin(admin.ModelAdmin): |
10 | 10 | list_display = ['name', 'slug'] |
11 | 11 | search_fields = ['name', 'slug'] |
12 | 12 | |
13 | -class ModuleAdmin(admin.ModelAdmin): | |
13 | +class SubjectAdmin(admin.ModelAdmin): | |
14 | + list_display = ['name', 'slug'] | |
15 | + search_fields = ['name', 'slug'] | |
16 | + | |
17 | +class TopicAdmin(admin.ModelAdmin): | |
14 | 18 | list_display = ['name', 'slug'] |
15 | 19 | search_fields = ['name', 'slug'] |
16 | 20 | |
17 | 21 | admin.site.register(Category, CategoryAdmin) |
18 | 22 | admin.site.register(Course, CourseAdmin) |
19 | -admin.site.register(Module, ModuleAdmin) | |
20 | 23 | \ No newline at end of file |
24 | +admin.site.register(Subject, SubjectAdmin) | |
25 | +admin.site.register(Topic, TopicAdmin) | ... | ... |
courses/forms.py
1 | 1 | from django import forms |
2 | 2 | from django.utils.translation import ugettext_lazy as _ |
3 | -from .models import Category, Course, Subject | |
3 | +from .models import Category, Course, Subject, Topic | |
4 | 4 | |
5 | 5 | class CategoryForm(forms.ModelForm): |
6 | 6 | |
... | ... | @@ -62,5 +62,19 @@ class SubjectForm(forms.ModelForm): |
62 | 62 | help_texts = { |
63 | 63 | 'name': _("Subjects's name"), |
64 | 64 | 'description': _("Subjects's description"), |
65 | - 'visible': _('Is the subject visible?'), | |
65 | + 'visible': _('Is the subject visible?'), | |
66 | + } | |
67 | + | |
68 | +class TopicForm(forms.ModelForm): | |
69 | + | |
70 | + class Meta: | |
71 | + model = Topic | |
72 | + fields = ('name', 'description',) | |
73 | + labels = { | |
74 | + 'name': _('Name'), | |
75 | + 'description': _('Description'), | |
76 | + } | |
77 | + help_texts = { | |
78 | + 'name': _("Topic's name"), | |
79 | + 'description': _("Topic's description"), | |
66 | 80 | } | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +# -*- coding: utf-8 -*- | |
2 | +# Generated by Django 1.10 on 2016-09-08 01:59 | |
3 | +from __future__ import unicode_literals | |
4 | + | |
5 | +import autoslug.fields | |
6 | +from django.db import migrations, models | |
7 | +import django.db.models.deletion | |
8 | + | |
9 | + | |
10 | +class Migration(migrations.Migration): | |
11 | + | |
12 | + dependencies = [ | |
13 | + ('courses', '0005_auto_20160815_0922'), | |
14 | + ] | |
15 | + | |
16 | + operations = [ | |
17 | + migrations.CreateModel( | |
18 | + name='Subject', | |
19 | + fields=[ | |
20 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
21 | + ('name', models.CharField(max_length=100, verbose_name='Name')), | |
22 | + ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True, verbose_name='Slug')), | |
23 | + ('description', models.TextField(blank=True, verbose_name='Description')), | |
24 | + ('visible', models.BooleanField(default=True, verbose_name='Visible')), | |
25 | + ('create_date', models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')), | |
26 | + ('update_date', models.DateTimeField(auto_now=True, verbose_name='Date of last update')), | |
27 | + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subjects', to='courses.Course', verbose_name='Course')), | |
28 | + ], | |
29 | + options={ | |
30 | + 'verbose_name': 'Subject', | |
31 | + 'verbose_name_plural': 'Subjects', | |
32 | + }, | |
33 | + ), | |
34 | + migrations.RemoveField( | |
35 | + model_name='module', | |
36 | + name='course', | |
37 | + ), | |
38 | + migrations.DeleteModel( | |
39 | + name='Module', | |
40 | + ), | |
41 | + ] | ... | ... |
... | ... | @@ -0,0 +1,33 @@ |
1 | +# -*- coding: utf-8 -*- | |
2 | +# Generated by Django 1.10 on 2016-09-08 02:51 | |
3 | +from __future__ import unicode_literals | |
4 | + | |
5 | +import autoslug.fields | |
6 | +from django.db import migrations, models | |
7 | +import django.db.models.deletion | |
8 | + | |
9 | + | |
10 | +class Migration(migrations.Migration): | |
11 | + | |
12 | + dependencies = [ | |
13 | + ('courses', '0006_auto_20160907_2259'), | |
14 | + ] | |
15 | + | |
16 | + operations = [ | |
17 | + migrations.CreateModel( | |
18 | + name='Topic', | |
19 | + fields=[ | |
20 | + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |
21 | + ('name', models.CharField(max_length=100, verbose_name='Name')), | |
22 | + ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True, verbose_name='Slug')), | |
23 | + ('description', models.TextField(blank=True, verbose_name='Description')), | |
24 | + ('create_date', models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')), | |
25 | + ('update_date', models.DateTimeField(auto_now=True, verbose_name='Date of last update')), | |
26 | + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topics', to='courses.Subject', verbose_name='Subject')), | |
27 | + ], | |
28 | + options={ | |
29 | + 'verbose_name': 'Topic', | |
30 | + 'verbose_name_plural': 'Topics', | |
31 | + }, | |
32 | + ), | |
33 | + ] | ... | ... |
courses/models.py
... | ... | @@ -50,6 +50,7 @@ class Subject(models.Model): |
50 | 50 | update_date = models.DateTimeField(_('Date of last update'), auto_now=True) |
51 | 51 | course = models.ForeignKey(Course, verbose_name = _('Course'), related_name="subjects") |
52 | 52 | |
53 | + | |
53 | 54 | class Meta: |
54 | 55 | |
55 | 56 | verbose_name = _('Subject') |
... | ... | @@ -57,3 +58,21 @@ class Subject(models.Model): |
57 | 58 | |
58 | 59 | def __str__(self): |
59 | 60 | return self.name |
61 | + | |
62 | +class Topic(models.Model): | |
63 | + | |
64 | + name = models.CharField(_('Name'), max_length = 100) | |
65 | + slug = AutoSlugField(_("Slug"),populate_from='name',unique=True) | |
66 | + description = models.TextField(_('Description'), blank = True) | |
67 | + create_date = models.DateTimeField(_('Creation Date'), auto_now_add = True) | |
68 | + update_date = models.DateTimeField(_('Date of last update'), auto_now=True) | |
69 | + subject = models.ForeignKey(Subject, verbose_name = _('Subject'), related_name="topics") | |
70 | + | |
71 | + | |
72 | + class Meta: | |
73 | + | |
74 | + verbose_name = _('Topic') | |
75 | + verbose_name_plural = _('Topics') | |
76 | + | |
77 | + def __str__(self): | |
78 | + return self.name | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +{% load i18n %} | |
2 | + | |
3 | +<div class="panel panel-default"> | |
4 | + <div class="panel-heading"> | |
5 | + <div class="row"> | |
6 | + <div class="col-md-9 col-sm-9"> | |
7 | + <h3>{{topic}}</h3> | |
8 | + </div> | |
9 | + </div> | |
10 | + </div> | |
11 | + <div class="panel-body"> | |
12 | + <p>{{topic.description}}</p> | |
13 | + </div> | |
14 | +</div> | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +{% load i18n %} | |
2 | + | |
3 | +<div class="panel panel-default"> | |
4 | + <div class="panel-heading"> | |
5 | + <div class="row"> | |
6 | + <div class="col-md-9 col-sm-9"> | |
7 | + <h3>{{topic}}</h3> | |
8 | + </div> | |
9 | + <div class="col-md-3 col-sm-3"> | |
10 | + <a href="#" class="btn">{% trans "edit" %}</a> | |
11 | + </div> | |
12 | + </div> | |
13 | + </div> | |
14 | + <div class="panel-body"> | |
15 | + <p>{{topic.description}}</p> | |
16 | + </div> | |
17 | +</div> | ... | ... |
... | ... | @@ -0,0 +1,62 @@ |
1 | +{% extends 'base.html' %} | |
2 | + | |
3 | +{% load static i18n permission_tags %} | |
4 | + | |
5 | +{% block breadcrumbs %} | |
6 | + | |
7 | + <ol class="breadcrumb"> | |
8 | + <li><a href="{% url 'app:index' %}">{% trans 'Home' %}</a></li> | |
9 | + <li><a href="{% url 'course:view' course.slug %}">{{ course }}</a></li> | |
10 | + <li class="active">{% trans 'Manage Subjects' %}</li> | |
11 | + </ol> | |
12 | +{% endblock %} | |
13 | + | |
14 | +{% block sidebar %} | |
15 | + | |
16 | + <div class="panel panel-primary"> | |
17 | + | |
18 | + <div class="panel-heading"> | |
19 | + <h3 class="panel-title">{{course}}</h3> | |
20 | + </div> | |
21 | + | |
22 | + <div class="panel-body"> | |
23 | + {% for subject in subjects %} | |
24 | + <a href="{% url 'course:view_subject' subject.slug%}" class="btn btn-default">{{subject}}</a> | |
25 | + {% endfor %} | |
26 | + </div> | |
27 | + | |
28 | + </div> | |
29 | +{% endblock %} | |
30 | + | |
31 | +{% block content %} | |
32 | + <div class="panel panel-info"> | |
33 | + <div class="panel-heading"> | |
34 | + <h3 class="panel-title">{% trans "Presentation Subject" %}</h3> | |
35 | + </div> | |
36 | + <div class="panel-body"> | |
37 | + <p> | |
38 | + {{subject.description}} | |
39 | + </p> | |
40 | + </div> | |
41 | + </div> | |
42 | +{% for topic in topics %} | |
43 | + {% if user|has_role:'professor' or user|has_role:'system_admin'%} | |
44 | + {% include "subject/form_view_teacher.html" %} | |
45 | + {% else %} | |
46 | + {% include "subject/form_view_student.html" %} | |
47 | + {% endif %} | |
48 | +{% endfor %} | |
49 | + | |
50 | +{% endblock %} | |
51 | + | |
52 | +{% block rightbar %} | |
53 | + | |
54 | + <div class="panel panel-warning"> | |
55 | + <div class="panel-heading"> | |
56 | + <h3 class="panel-title">Pending Stuffs</h3> | |
57 | + </div> | |
58 | + <div class="panel-body"> | |
59 | + | |
60 | + </div> | |
61 | + </div> | |
62 | +{% endblock rightbar %} | ... | ... |
courses/urls.py
... | ... | @@ -14,8 +14,9 @@ urlpatterns = [ |
14 | 14 | url(r'^categories/edit/(?P<slug>[\w_-]+)/$', views.UpdateCatView.as_view(), name='update_cat'), |
15 | 15 | url(r'^categories/(?P<slug>[\w_-]+)/$', views.ViewCat.as_view(), name='view_cat'), |
16 | 16 | url(r'^categories/delete/(?P<slug>[\w_-]+)/$', views.DeleteCatView.as_view(), name='delete_cat'), |
17 | - url(r'^course/(?P<slug>[\w_-]+)/modules/$', views.ModulesView.as_view(), name='manage_mods'), | |
18 | - url(r'^course/(?P<slug>[\w_-]+)/modules/create/$', views.CreateModView.as_view(), name='create_mods'), | |
19 | - url(r'^course/(?P<slug_course>[\w_-]+)/modules/edit/(?P<slug>[\w_-]+)/$', views.UpdateModView.as_view(), name='update_mods'), | |
20 | - url(r'^course/(?P<slug_course>[\w_-]+)/modules/delete/(?P<slug>[\w_-]+)/$', views.DeleteModView.as_view(), name='delete_mods'), | |
17 | + url(r'^course/(?P<slug>[\w_-]+)/subjects/$', views.SubjectsView.as_view(), name='view_subject'), | |
18 | + # url(r'^course/(?P<slug>[\w_-]+)/modules/create/$', views.CreateModView.as_view(), name='create_mods'), | |
19 | + # url(r'^course/(?P<slug_course>[\w_-]+)/modules/edit/(?P<slug>[\w_-]+)/$', views.UpdateModView.as_view(), name='update_mods'), | |
20 | + # url(r'^course/(?P<slug_course>[\w_-]+)/modules/delete/(?P<slug>[\w_-]+)/$', views.DeleteModView.as_view(), name='delete_mods'), | |
21 | + # url(r'^course/(?P<slug>[\w_-]+)/subject$', views.ViewSubject.as_view(), name='view_subject'), | |
21 | 22 | ] | ... | ... |
courses/views.py
... | ... | @@ -9,8 +9,8 @@ from django.core.urlresolvers import reverse_lazy |
9 | 9 | from django.utils.translation import ugettext_lazy as _ |
10 | 10 | from slugify import slugify |
11 | 11 | |
12 | -from .forms import CourseForm, CategoryForm, ModuleForm | |
13 | -from .models import Course, Module, Category | |
12 | +from .forms import CourseForm, CategoryForm, SubjectForm | |
13 | +from .models import Course, Subject, Category | |
14 | 14 | |
15 | 15 | |
16 | 16 | class IndexView(LoginRequiredMixin, generic.ListView): |
... | ... | @@ -186,108 +186,123 @@ class DeleteCatView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView): |
186 | 186 | |
187 | 187 | return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) |
188 | 188 | |
189 | -class ModulesView(LoginRequiredMixin, generic.ListView): | |
189 | +class SubjectsView(LoginRequiredMixin, generic.ListView): | |
190 | 190 | |
191 | 191 | login_url = reverse_lazy("core:home") |
192 | 192 | redirect_field_name = 'next' |
193 | - template_name = 'module/index.html' | |
194 | - context_object_name = 'modules' | |
195 | - paginate_by = 1 | |
193 | + template_name = 'subject/index.html' | |
194 | + context_object_name = 'subjects' | |
195 | + model = Subject | |
196 | + # paginate_by = 5 | |
196 | 197 | |
197 | 198 | def get_queryset(self): |
198 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
199 | - return Module.objects.filter(course = course) | |
199 | + subject = get_object_or_404(Subject, slug = self.kwargs.get('slug')) | |
200 | + course = subject.course | |
201 | + return course.subjects.filter(visible=True) | |
200 | 202 | |
201 | 203 | def get_context_data(self, **kwargs): |
202 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
203 | - context = super(ModulesView, self).get_context_data(**kwargs) | |
204 | - context['course'] = course | |
205 | - | |
206 | - return context | |
207 | - | |
208 | -class CreateModView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView): | |
209 | - | |
210 | - allowed_roles = ['professor', 'system_admin'] | |
211 | - login_url = reverse_lazy("core:home") | |
212 | - redirect_field_name = 'next' | |
213 | - template_name = 'module/create.html' | |
214 | - form_class = ModuleForm | |
215 | - | |
216 | - def get_success_url(self): | |
217 | - return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
218 | - | |
219 | - def get_context_data(self, **kwargs): | |
220 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
221 | - context = super(CreateModView, self).get_context_data(**kwargs) | |
222 | - context['course'] = course | |
223 | - | |
204 | + # print ("Deu Certo") | |
205 | + subject = get_object_or_404(Subject, slug = self.kwargs.get('slug')) | |
206 | + # print (course) | |
207 | + # print (course.slug) | |
208 | + # print (course.subjects.filter(visible=True)) | |
209 | + context = super(SubjectsView, self).get_context_data(**kwargs) | |
210 | + context['course'] = subject.course | |
211 | + context['subject'] = subject | |
212 | + context['topics'] = subject.topics.all() | |
213 | + # print (context) | |
224 | 214 | return context |
225 | 215 | |
226 | - def form_valid(self, form): | |
227 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
228 | - | |
229 | - self.object = form.save(commit = False) | |
230 | - self.object.slug = slugify(self.object.name) | |
231 | - self.object.course = course | |
232 | - self.object.save() | |
233 | - | |
234 | - return super(CreateModView, self).form_valid(form) | |
235 | - | |
236 | - def render_to_response(self, context, **response_kwargs): | |
237 | - messages.success(self.request, _('Module created successfully!')) | |
238 | - | |
239 | - return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
240 | - | |
241 | -class UpdateModView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView): | |
242 | - | |
243 | - allowed_roles = ['professor', 'system_admin'] | |
244 | - login_url = reverse_lazy("core:home") | |
245 | - redirect_field_name = 'next' | |
246 | - template_name = 'module/update.html' | |
247 | - model = Module | |
248 | - form_class = ModuleForm | |
249 | - | |
250 | - def get_success_url(self): | |
251 | - return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
252 | - | |
253 | - def get_context_data(self, **kwargs): | |
254 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug_course')) | |
255 | - context = super(UpdateModView, self).get_context_data(**kwargs) | |
256 | - context['course'] = course | |
257 | - | |
258 | - return context | |
259 | - | |
260 | - def form_valid(self, form): | |
261 | - self.object = form.save(commit = False) | |
262 | - self.object.slug = slugify(self.object.name) | |
263 | - self.object.save() | |
264 | - | |
265 | - return super(UpdateModView, self).form_valid(form) | |
266 | - | |
267 | - def render_to_response(self, context, **response_kwargs): | |
268 | - messages.success(self.request, _('Module edited successfully!')) | |
269 | - | |
270 | - return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
271 | - | |
272 | -class DeleteModView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView): | |
273 | - | |
274 | - allowed_roles = ['professor', 'system_admin'] | |
275 | - login_url = reverse_lazy("core:home") | |
276 | - redirect_field_name = 'next' | |
277 | - model = Module | |
278 | - template_name = 'module/delete.html' | |
279 | - | |
280 | - def get_success_url(self): | |
281 | - return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
282 | - | |
283 | - def get_context_data(self, **kwargs): | |
284 | - course = get_object_or_404(Course, slug = self.kwargs.get('slug_course')) | |
285 | - context = super(DeleteModView, self).get_context_data(**kwargs) | |
286 | - context['course'] = course | |
287 | - | |
288 | - return context | |
289 | - | |
290 | - def render_to_response(self, context, **response_kwargs): | |
291 | - messages.success(self.request, _('Module deleted successfully!')) | |
292 | - | |
293 | - return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
216 | +# class CreateSubjectView(LoginRequiredMixin, HasRoleMixin, generic.edit.CreateView): | |
217 | +# | |
218 | +# allowed_roles = ['professor', 'system_admin'] | |
219 | +# login_url = reverse_lazy("core:home") | |
220 | +# redirect_field_name = 'next' | |
221 | +# template_name = 'module/create.html' | |
222 | +# form_class = SubjectForm | |
223 | +# | |
224 | +# def get_success_url(self): | |
225 | +# return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
226 | +# | |
227 | +# def get_context_data(self, **kwargs): | |
228 | +# course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
229 | +# context = super(CreateModView, self).get_context_data(**kwargs) | |
230 | +# context['course'] = course | |
231 | +# | |
232 | +# return context | |
233 | +# | |
234 | +# def form_valid(self, form): | |
235 | +# course = get_object_or_404(Course, slug = self.kwargs.get('slug')) | |
236 | +# | |
237 | +# self.object = form.save(commit = False) | |
238 | +# self.object.slug = slugify(self.object.name) | |
239 | +# self.object.course = course | |
240 | +# self.object.save() | |
241 | +# | |
242 | +# return super(CreateModView, self).form_valid(form) | |
243 | +# | |
244 | +# def render_to_response(self, context, **response_kwargs): | |
245 | +# messages.success(self.request, _('Module created successfully!')) | |
246 | +# | |
247 | +# return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
248 | +# | |
249 | +# class UpdateModView(LoginRequiredMixin, HasRoleMixin, generic.UpdateView): | |
250 | +# | |
251 | +# allowed_roles = ['professor', 'system_admin'] | |
252 | +# login_url = reverse_lazy("core:home") | |
253 | +# redirect_field_name = 'next' | |
254 | +# template_name = 'module/update.html' | |
255 | +# model = Module | |
256 | +# form_class = ModuleForm | |
257 | +# | |
258 | +# def get_success_url(self): | |
259 | +# return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
260 | +# | |
261 | +# def get_context_data(self, **kwargs): | |
262 | +# course = get_object_or_404(Course, slug = self.kwargs.get('slug_course')) | |
263 | +# context = super(UpdateModView, self).get_context_data(**kwargs) | |
264 | +# context['course'] = course | |
265 | +# | |
266 | +# return context | |
267 | +# | |
268 | +# def form_valid(self, form): | |
269 | +# self.object = form.save(commit = False) | |
270 | +# self.object.slug = slugify(self.object.name) | |
271 | +# self.object.save() | |
272 | +# | |
273 | +# return super(UpdateModView, self).form_valid(form) | |
274 | +# | |
275 | +# def render_to_response(self, context, **response_kwargs): | |
276 | +# messages.success(self.request, _('Module edited successfully!')) | |
277 | +# | |
278 | +# return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
279 | +# | |
280 | +# class DeleteModView(LoginRequiredMixin, HasRoleMixin, generic.DeleteView): | |
281 | +# | |
282 | +# allowed_roles = ['professor', 'system_admin'] | |
283 | +# login_url = reverse_lazy("core:home") | |
284 | +# redirect_field_name = 'next' | |
285 | +# model = Module | |
286 | +# template_name = 'module/delete.html' | |
287 | +# | |
288 | +# def get_success_url(self): | |
289 | +# return reverse_lazy('course:manage_mods', kwargs={'slug' : self.object.course.slug}) | |
290 | +# | |
291 | +# def get_context_data(self, **kwargs): | |
292 | +# course = get_object_or_404(Course, slug = self.kwargs.get('slug_course')) | |
293 | +# context = super(DeleteModView, self).get_context_data(**kwargs) | |
294 | +# context['course'] = course | |
295 | +# | |
296 | +# return context | |
297 | +# | |
298 | +# def render_to_response(self, context, **response_kwargs): | |
299 | +# messages.success(self.request, _('Module deleted successfully!')) | |
300 | +# | |
301 | +# return self.response_class(request=self.request, template=self.get_template_names(), context=context, using=self.template_engine) | |
302 | + | |
303 | +# class ViewSubject(LoginRequiredMixin, generic.DetailView): | |
304 | +# login_url = reverse_lazy("core:home") | |
305 | +# redirect_field_name = 'next' | |
306 | +# model = Course | |
307 | +# template_name = 'subject/index.html' | |
308 | +# context_object_name = 'course' | ... | ... |