Statistics
| Branch: | Revision:

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")