aboutsummaryrefslogtreecommitdiff
path: root/tagging/utils.py
blob: af50bacc955e6c27ac06e476762fdc83c7f863f6 (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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import math
import re
import types

from django.db.models.query import QuerySet
from django.utils.encoding import force_unicode, smart_unicode

# Python 2.3 compatibility
if not hasattr(__builtins__, 'set'):
    from sets import Set as set

find_tag_re = re.compile(r'[-\w]+', re.U)

def get_tag_name_list(tag_names):
    """
    Finds tag names in the given string and return them as a list.
    """
    if tag_names is not None:
        tag_names = force_unicode(tag_names)
    results = find_tag_re.findall(tag_names or '')
    return results

def get_tag_list(tags):
    """
    Utility function for accepting tag input in a flexible manner.

    If a ``Tag`` object is given, it will be returned in a list as
    its single occupant.

    If given, the tag names in the following will be used to create a
    ``Tag`` ``QuerySet``:

        * A string, which may contain multiple tag names.
        * A list or tuple of strings corresponding to tag names.
        * A list or tuple of integers corresponding to tag ids.

    If given, the following will be returned as-is:

        * A list or tuple of ``Tag`` objects.
        * A ``Tag`` ``QuerySet``.
    """
    from tagging.models import Tag
    if isinstance(tags, Tag):
        return [tags]
    elif isinstance(tags, QuerySet) and tags.model is Tag:
        return tags
    elif isinstance(tags, types.StringTypes):
        return Tag.objects.filter(name__in=get_tag_name_list(tags))
    elif isinstance(tags, (types.ListType, types.TupleType)):
        if len(tags) == 0:
            return tags
        contents = set()
        for item in tags:
            if isinstance(item, types.StringTypes):
                contents.add('string')
            elif isinstance(item, Tag):
                contents.add('tag')
            elif isinstance(item, (types.IntType, types.LongType)):
                contents.add('int')
        if len(contents) == 1:
            if 'string' in contents:
                return Tag.objects.filter(name__in=[smart_unicode(tag) \
                                                    for tag in tags])
            elif 'tag' in contents:
                return tags
            elif 'int' in contents:
                return Tag.objects.filter(id__in=tags)
        else:
            raise ValueError(u'If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.')
    else:
        raise ValueError(u'The tag input given was invalid.')

def get_tag(tag):
    """
    Utility function for accepting single tag input in a flexible
    manner.

    If a ``Tag`` object is given it will be returned as-is; if a
    string or integer are given, they will be used to lookup the
    appropriate ``Tag``.

    If no matching tag can be found, ``None`` will be returned.
    """
    from tagging.models import Tag
    if isinstance(tag, Tag):
        return tag

    try:
        if isinstance(tag, types.StringTypes):
            return Tag.objects.get(name=tag)
        elif isinstance(tag, (types.IntType, types.LongType)):
            return Tag.objects.get(id=tag)
    except Tag.DoesNotExist:
        pass

    return None

# Font size distribution algorithms
LOGARITHMIC, LINEAR = 1, 2

def calculate_cloud(tags, steps=4, distribution=LOGARITHMIC):
    """
    Add a ``font_size`` attribute to each tag according to the
    frequency of its use, as indicated by its ``count``
    attribute.

    ``steps`` defines the range of font sizes - ``font_size`` will
    be an integer between 1 and ``steps`` (inclusive).

    ``distribution`` defines the type of font size distribution
    algorithm which will be used - logarithmic or linear. It must be
    either ``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``.

    The algorithm to scale the tags logarithmically is from a
    blog post by Anders Pearson, 'Scaling tag clouds':
    http://thraxil.com/users/anders/posts/2005/12/13/scaling-tag-clouds/
    """
    if len(tags) > 0:
        thresholds = []
        counts = [tag.count for tag in tags]
        max_weight = float(max(counts))
        min_weight = float(min(counts))

        # Set up the appropriate thresholds
        if distribution == LOGARITHMIC:
            thresholds = [math.pow(max_weight - min_weight + 1, float(i) / float(steps)) \
                          for i in range(1, steps + 1)]
        elif distribution == LINEAR:
            delta = (max_weight - min_weight) / float(steps)
            thresholds = [min_weight + i * delta for i in range(1, steps + 1)]
        else:
            raise ValueError(u'Invalid font size distribution algorithm specified: %s.' % distribution)

        for tag in tags:
            font_set = False
            for i in range(steps):
                if not font_set and tag.count <= thresholds[i]:
                    tag.font_size = i + 1
                    font_set = True
    return tags