Commit 4b1bc750875f2de02f0c6702ae925e5b9f0c492a
1 parent
f34a20a8
Exists in
master
and in
5 other branches
Biblioteca para gerar o slug automaticamente já no model
Showing
5 changed files
with
625 additions
and
0 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") | ... | ... |