web/lib/tagging/utils.py
author ymh <ymh.work@gmail.com>
Fri, 22 Jan 2010 18:23:34 +0100
changeset 11 f236caaceb43
permissions -rw-r--r--
add pois
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
11
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     1
"""
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     2
Tagging utilities - from user tag input parsing to tag cloud
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     3
calculation.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     4
"""
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     5
import math
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     6
import types
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     7
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     8
from django.db.models.query import QuerySet
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
     9
from django.utils.encoding import force_unicode
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    10
from django.utils.translation import ugettext as _
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    11
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    12
# Python 2.3 compatibility
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    13
try:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    14
    set
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    15
except NameError:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    16
    from sets import Set as set
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    17
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    18
def parse_tag_input(input):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    19
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    20
    Parses tag input, with multiple word input being activated and
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    21
    delineated by commas and double quotes. Quotes take precedence, so
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    22
    they may contain commas.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    23
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    24
    Returns a sorted list of unique tag names.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    25
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    26
    if not input:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    27
        return []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    28
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    29
    input = force_unicode(input)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    30
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    31
    # Special case - if there are no commas or double quotes in the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    32
    # input, we don't *do* a recall... I mean, we know we only need to
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    33
    # split on spaces.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    34
    if u',' not in input and u'"' not in input:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    35
        words = list(set(split_strip(input, u' ')))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    36
        words.sort()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    37
        return words
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    38
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    39
    words = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    40
    buffer = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    41
    # Defer splitting of non-quoted sections until we know if there are
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    42
    # any unquoted commas.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    43
    to_be_split = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    44
    saw_loose_comma = False
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    45
    open_quote = False
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    46
    i = iter(input)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    47
    try:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    48
        while 1:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    49
            c = i.next()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    50
            if c == u'"':
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    51
                if buffer:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    52
                    to_be_split.append(u''.join(buffer))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    53
                    buffer = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    54
                # Find the matching quote
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    55
                open_quote = True
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    56
                c = i.next()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    57
                while c != u'"':
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    58
                    buffer.append(c)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    59
                    c = i.next()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    60
                if buffer:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    61
                    word = u''.join(buffer).strip()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    62
                    if word:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    63
                        words.append(word)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    64
                    buffer = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    65
                open_quote = False
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    66
            else:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    67
                if not saw_loose_comma and c == u',':
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    68
                    saw_loose_comma = True
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    69
                buffer.append(c)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    70
    except StopIteration:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    71
        # If we were parsing an open quote which was never closed treat
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    72
        # the buffer as unquoted.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    73
        if buffer:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    74
            if open_quote and u',' in buffer:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    75
                saw_loose_comma = True
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    76
            to_be_split.append(u''.join(buffer))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    77
    if to_be_split:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    78
        if saw_loose_comma:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    79
            delimiter = u','
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    80
        else:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    81
            delimiter = u' '
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    82
        for chunk in to_be_split:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    83
            words.extend(split_strip(chunk, delimiter))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    84
    words = list(set(words))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    85
    words.sort()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    86
    return words
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    87
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    88
def split_strip(input, delimiter=u','):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    89
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    90
    Splits ``input`` on ``delimiter``, stripping each resulting string
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    91
    and returning a list of non-empty strings.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    92
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    93
    if not input:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    94
        return []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    95
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    96
    words = [w.strip() for w in input.split(delimiter)]
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    97
    return [w for w in words if w]
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    98
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
    99
def edit_string_for_tags(tags):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   100
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   101
    Given list of ``Tag`` instances, creates a string representation of
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   102
    the list suitable for editing by the user, such that submitting the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   103
    given string representation back without changing it will give the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   104
    same list of tags.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   105
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   106
    Tag names which contain commas will be double quoted.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   107
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   108
    If any tag name which isn't being quoted contains whitespace, the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   109
    resulting string of tag names will be comma-delimited, otherwise
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   110
    it will be space-delimited.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   111
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   112
    names = []
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   113
    use_commas = False
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   114
    for tag in tags:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   115
        name = tag.name
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   116
        if u',' in name:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   117
            names.append('"%s"' % name)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   118
            continue
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   119
        elif u' ' in name:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   120
            if not use_commas:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   121
                use_commas = True
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   122
        names.append(name)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   123
    if use_commas:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   124
        glue = u', '
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   125
    else:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   126
        glue = u' '
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   127
    return glue.join(names)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   128
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   129
def get_queryset_and_model(queryset_or_model):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   130
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   131
    Given a ``QuerySet`` or a ``Model``, returns a two-tuple of
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   132
    (queryset, model).
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   133
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   134
    If a ``Model`` is given, the ``QuerySet`` returned will be created
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   135
    using its default manager.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   136
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   137
    try:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   138
        return queryset_or_model, queryset_or_model.model
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   139
    except AttributeError:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   140
        return queryset_or_model._default_manager.all(), queryset_or_model
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   141
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   142
def get_tag_list(tags):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   143
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   144
    Utility function for accepting tag input in a flexible manner.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   145
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   146
    If a ``Tag`` object is given, it will be returned in a list as
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   147
    its single occupant.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   148
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   149
    If given, the tag names in the following will be used to create a
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   150
    ``Tag`` ``QuerySet``:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   151
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   152
       * A string, which may contain multiple tag names.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   153
       * A list or tuple of strings corresponding to tag names.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   154
       * A list or tuple of integers corresponding to tag ids.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   155
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   156
    If given, the following will be returned as-is:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   157
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   158
       * A list or tuple of ``Tag`` objects.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   159
       * A ``Tag`` ``QuerySet``.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   160
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   161
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   162
    from tagging.models import Tag
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   163
    if isinstance(tags, Tag):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   164
        return [tags]
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   165
    elif isinstance(tags, QuerySet) and tags.model is Tag:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   166
        return tags
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   167
    elif isinstance(tags, types.StringTypes):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   168
        return Tag.objects.filter(name__in=parse_tag_input(tags))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   169
    elif isinstance(tags, (types.ListType, types.TupleType)):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   170
        if len(tags) == 0:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   171
            return tags
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   172
        contents = set()
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   173
        for item in tags:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   174
            if isinstance(item, types.StringTypes):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   175
                contents.add('string')
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   176
            elif isinstance(item, Tag):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   177
                contents.add('tag')
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   178
            elif isinstance(item, (types.IntType, types.LongType)):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   179
                contents.add('int')
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   180
        if len(contents) == 1:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   181
            if 'string' in contents:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   182
                return Tag.objects.filter(name__in=[force_unicode(tag) \
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   183
                                                    for tag in tags])
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   184
            elif 'tag' in contents:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   185
                return tags
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   186
            elif 'int' in contents:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   187
                return Tag.objects.filter(id__in=tags)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   188
        else:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   189
            raise ValueError(_('If a list or tuple of tags is provided, they must all be tag names, Tag objects or Tag ids.'))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   190
    else:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   191
        raise ValueError(_('The tag input given was invalid.'))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   192
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   193
def get_tag(tag):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   194
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   195
    Utility function for accepting single tag input in a flexible
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   196
    manner.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   197
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   198
    If a ``Tag`` object is given it will be returned as-is; if a
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   199
    string or integer are given, they will be used to lookup the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   200
    appropriate ``Tag``.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   201
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   202
    If no matching tag can be found, ``None`` will be returned.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   203
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   204
    from tagging.models import Tag
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   205
    if isinstance(tag, Tag):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   206
        return tag
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   207
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   208
    try:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   209
        if isinstance(tag, types.StringTypes):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   210
            return Tag.objects.get(name=tag)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   211
        elif isinstance(tag, (types.IntType, types.LongType)):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   212
            return Tag.objects.get(id=tag)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   213
    except Tag.DoesNotExist:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   214
        pass
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   215
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   216
    return None
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   217
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   218
# Font size distribution algorithms
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   219
LOGARITHMIC, LINEAR = 1, 2
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   220
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   221
def _calculate_thresholds(min_weight, max_weight, steps):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   222
    delta = (max_weight - min_weight) / float(steps)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   223
    return [min_weight + i * delta for i in range(1, steps + 1)]
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   224
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   225
def _calculate_tag_weight(weight, max_weight, distribution):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   226
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   227
    Logarithmic tag weight calculation is based on code from the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   228
    `Tag Cloud`_ plugin for Mephisto, by Sven Fuchs.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   229
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   230
    .. _`Tag Cloud`: http://www.artweb-design.de/projects/mephisto-plugin-tag-cloud
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   231
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   232
    if distribution == LINEAR or max_weight == 1:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   233
        return weight
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   234
    elif distribution == LOGARITHMIC:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   235
        return math.log(weight) * max_weight / math.log(max_weight)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   236
    raise ValueError(_('Invalid distribution algorithm specified: %s.') % distribution)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   237
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   238
def calculate_cloud(tags, steps=4, distribution=LOGARITHMIC):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   239
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   240
    Add a ``font_size`` attribute to each tag according to the
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   241
    frequency of its use, as indicated by its ``count``
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   242
    attribute.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   243
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   244
    ``steps`` defines the range of font sizes - ``font_size`` will
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   245
    be an integer between 1 and ``steps`` (inclusive).
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   246
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   247
    ``distribution`` defines the type of font size distribution
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   248
    algorithm which will be used - logarithmic or linear. It must be
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   249
    one of ``tagging.utils.LOGARITHMIC`` or ``tagging.utils.LINEAR``.
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   250
    """
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   251
    if len(tags) > 0:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   252
        counts = [tag.count for tag in tags]
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   253
        min_weight = float(min(counts))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   254
        max_weight = float(max(counts))
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   255
        thresholds = _calculate_thresholds(min_weight, max_weight, steps)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   256
        for tag in tags:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   257
            font_set = False
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   258
            tag_weight = _calculate_tag_weight(tag.count, max_weight, distribution)
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   259
            for i in range(steps):
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   260
                if not font_set and tag_weight <= thresholds[i]:
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   261
                    tag.font_size = i + 1
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   262
                    font_set = True
f236caaceb43 add pois
ymh <ymh.work@gmail.com>
parents:
diff changeset
   263
    return tags