root / taggit / models.py @ 04733cdb
History | View | Annotate | Download (5 kB)
1 |
import django |
---|---|
2 |
from django.contrib.contenttypes.models import ContentType |
3 |
from django.contrib.contenttypes.generic import GenericForeignKey |
4 |
from django.db import models, IntegrityError, transaction |
5 |
from django.template.defaultfilters import slugify as default_slugify |
6 |
from django.utils.translation import ugettext_lazy as _, ugettext |
7 |
|
8 |
|
9 |
class TagBase(models.Model): |
10 |
name = models.CharField(verbose_name=_('Name'), max_length=100) |
11 |
slug = models.SlugField(verbose_name=_('Slug'), unique=True, max_length=100) |
12 |
|
13 |
def __unicode__(self): |
14 |
return self.name |
15 |
|
16 |
class Meta: |
17 |
abstract = True
|
18 |
|
19 |
def save(self, *args, **kwargs): |
20 |
if not self.pk and not self.slug: |
21 |
self.slug = self.slugify(self.name) |
22 |
if django.VERSION >= (1, 2): |
23 |
from django.db import router |
24 |
using = kwargs.get("using") or router.db_for_write( |
25 |
type(self), instance=self) |
26 |
# Make sure we write to the same db for all attempted writes,
|
27 |
# with a multi-master setup, theoretically we could try to
|
28 |
# write and rollback on different DBs
|
29 |
kwargs["using"] = using
|
30 |
trans_kwargs = {"using": using}
|
31 |
else:
|
32 |
trans_kwargs = {} |
33 |
i = 0
|
34 |
while True: |
35 |
i += 1
|
36 |
try:
|
37 |
sid = transaction.savepoint(**trans_kwargs) |
38 |
res = super(TagBase, self).save(*args, **kwargs) |
39 |
transaction.savepoint_commit(sid, **trans_kwargs) |
40 |
return res
|
41 |
except IntegrityError:
|
42 |
transaction.savepoint_rollback(sid, **trans_kwargs) |
43 |
self.slug = self.slugify(self.name, i) |
44 |
else:
|
45 |
return super(TagBase, self).save(*args, **kwargs) |
46 |
|
47 |
def slugify(self, tag, i=None): |
48 |
slug = default_slugify(tag) |
49 |
if i is not None: |
50 |
slug += "_%d" % i
|
51 |
return slug
|
52 |
|
53 |
|
54 |
class Tag(TagBase): |
55 |
class Meta: |
56 |
verbose_name = _("Tag")
|
57 |
verbose_name_plural = _("Tags")
|
58 |
|
59 |
|
60 |
|
61 |
class ItemBase(models.Model): |
62 |
def __unicode__(self): |
63 |
return ugettext("%(object)s tagged with %(tag)s") % { |
64 |
"object": self.content_object, |
65 |
"tag": self.tag |
66 |
} |
67 |
|
68 |
class Meta: |
69 |
abstract = True
|
70 |
|
71 |
@classmethod
|
72 |
def tag_model(cls): |
73 |
return cls._meta.get_field_by_name("tag")[0].rel.to |
74 |
|
75 |
@classmethod
|
76 |
def tag_relname(cls): |
77 |
return cls._meta.get_field_by_name('tag')[0].rel.related_name |
78 |
|
79 |
@classmethod
|
80 |
def lookup_kwargs(cls, instance): |
81 |
return {
|
82 |
'content_object': instance
|
83 |
} |
84 |
|
85 |
@classmethod
|
86 |
def bulk_lookup_kwargs(cls, instances): |
87 |
return {
|
88 |
"content_object__in": instances,
|
89 |
} |
90 |
|
91 |
|
92 |
class TaggedItemBase(ItemBase): |
93 |
if django.VERSION < (1, 2): |
94 |
tag = models.ForeignKey(Tag, related_name="%(class)s_items")
|
95 |
else:
|
96 |
tag = models.ForeignKey(Tag, related_name="%(app_label)s_%(class)s_items")
|
97 |
|
98 |
class Meta: |
99 |
abstract = True
|
100 |
|
101 |
@classmethod
|
102 |
def tags_for(cls, model, instance=None): |
103 |
if instance is not None: |
104 |
return cls.tag_model().objects.filter(**{
|
105 |
'%s__content_object' % cls.tag_relname(): instance
|
106 |
}) |
107 |
return cls.tag_model().objects.filter(**{
|
108 |
'%s__content_object__isnull' % cls.tag_relname(): False |
109 |
}).distinct() |
110 |
|
111 |
|
112 |
class GenericTaggedItemBase(ItemBase): |
113 |
object_id = models.IntegerField(verbose_name=_('Object id'), db_index=True) |
114 |
if django.VERSION < (1, 2): |
115 |
content_type = models.ForeignKey( |
116 |
ContentType, |
117 |
verbose_name=_('Content type'),
|
118 |
related_name="%(class)s_tagged_items"
|
119 |
) |
120 |
else:
|
121 |
content_type = models.ForeignKey( |
122 |
ContentType, |
123 |
verbose_name=_('Content type'),
|
124 |
related_name="%(app_label)s_%(class)s_tagged_items"
|
125 |
) |
126 |
content_object = GenericForeignKey() |
127 |
|
128 |
class Meta: |
129 |
abstract=True
|
130 |
|
131 |
@classmethod
|
132 |
def lookup_kwargs(cls, instance): |
133 |
return {
|
134 |
'object_id': instance.pk,
|
135 |
'content_type': ContentType.objects.get_for_model(instance)
|
136 |
} |
137 |
|
138 |
@classmethod
|
139 |
def bulk_lookup_kwargs(cls, instances): |
140 |
# TODO: instances[0], can we assume there are instances.
|
141 |
return {
|
142 |
"object_id__in": [instance.pk for instance in instances], |
143 |
"content_type": ContentType.objects.get_for_model(instances[0]), |
144 |
} |
145 |
|
146 |
@classmethod
|
147 |
def tags_for(cls, model, instance=None): |
148 |
ct = ContentType.objects.get_for_model(model) |
149 |
kwargs = { |
150 |
"%s__content_type" % cls.tag_relname(): ct
|
151 |
} |
152 |
if instance is not None: |
153 |
kwargs["%s__object_id" % cls.tag_relname()] = instance.pk
|
154 |
return cls.tag_model().objects.filter(**kwargs).distinct()
|
155 |
|
156 |
|
157 |
class TaggedItem(GenericTaggedItemBase, TaggedItemBase): |
158 |
class Meta: |
159 |
verbose_name = _("Tagged Item")
|
160 |
verbose_name_plural = _("Tagged Items")
|