aboutsummaryrefslogtreecommitdiff
path: root/tagging/fields.py
blob: 1e6645893d4245056ea35626bd90b118a0643265 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
from django.db.models import signals
from django.db.models.fields import CharField
from django.dispatch import dispatcher

from tagging import settings
from tagging.models import Tag
from tagging.validators import isTagList

class TagField(CharField):
    """
    A "special" character field that actually works as a relationship to tags
    "under the hood". This exposes a space-separated string of tags, but does
    the splitting/reordering/etc. under the hood.
    """
    def __init__(self, **kwargs):
        kwargs['max_length'] = kwargs.get('max_length', 255)
        kwargs['blank'] = kwargs.get('blank', True)
        kwargs['validator_list'] = [isTagList] + kwargs.get('validator_list', [])
        super(TagField, self).__init__(**kwargs)

    def contribute_to_class(self, cls, name):
        super(TagField, self).contribute_to_class(cls, name)

        # Make this object the descriptor for field access.
        setattr(cls, self.name, self)

        # Save tags back to the database post-save
        dispatcher.connect(self._save, signal=signals.post_save, sender=cls)

    def __get__(self, instance, owner=None):
        """
        Tag getter. Returns an instance's tags if accessed on an instance, and
        all of a model's tags if called on a class. That is, this model::

            class Link(models.Model):
                ...
                tags = TagField()

        Lets you do both of these::

            >>> l = Link.objects.get(...)
            >>> l.tags
            'tag1 tag2 tag3'

            >>> Link.tags
            'tag1 tag2 tag3 tag4'

        """
        # Handle access on the model (i.e. Link.tags)
        if instance is None:
            return tags2str(Tag.objects.usage_for_model(owner))

        tags = self._get_instance_tag_cache(instance)
        if tags is None:
            if instance._get_pk_val() is None:
                self._set_instance_tag_cache(instance, '')
            else:
                self._set_instance_tag_cache(instance, tags2str(Tag.objects.get_for_object(instance)))
        return self._get_instance_tag_cache(instance)

    def __set__(self, instance, value):
        """
        Set an object's tags.
        """
        if instance is None:
            raise AttributeError('%s can only be set on instances.' % self.name)
        if settings.FORCE_LOWERCASE_TAGS and value is not None:
            self._set_instance_tag_cache(instance, value.lower())
        else:
            self._set_instance_tag_cache(instance, value)

    def _save(self, signal, sender, instance):
        """
        Save tags back to the database
        """
        tags = self._get_instance_tag_cache(instance)
        if tags is not None :
            Tag.objects.update_tags(instance, tags)

    def __delete__(self, instance):
        """
        Clear all of an object's tags.
        """
        self._set_instance_tag_cache(instance, '')

    def _get_instance_tag_cache(self, instance):
        """
        Helper: get an instance's tag cache.
        """
        return getattr(instance, '_%s_cache' % self.attname, None)

    def _set_instance_tag_cache(self, instance, tags):
        """
        Helper: set an instance's tag cache.
        """
        setattr(instance, '_%s_cache' % self.attname, tags)

    def get_internal_type(self):
        return 'CharField'

    def formfield(self, **kwargs):
        from tagging import forms
        defaults = {'form_class': forms.TagField}
        defaults.update(kwargs)
        return super(TagField, self).formfield(**defaults)

# Helper
def tags2str(tagset):
    return u' '.join([t.name for t in tagset])