Fixed two minor crashes in lib.tags.
[zzz-floof.git] / floof / lib / tags.py
1 from floof.model import Art, ArtUser, ArtUserType, Tag, TagText, User
2 import elixir
3 from dbhelpers import find_or_create
4 import re
5
6 def parse(search_string, me):
7 """Parses a search query, and returns a query object on Art.
8
9 Queries can contain:
10 - Regular tags: foo
11 - User relations: by:kalu, of:eevee, for:ootachi
12 - User relations relative to some user: by:me
13 Note that this necessitates a `me` parameter.
14
15 Later:
16 - Negative versions of anything above: -by:eevee, -dongs
17 """
18
19 # XXX doesn't do negative querying yet.
20 # XXX could use some sane limits.
21
22 # We'll be building this as we go.
23 q = Art.query
24
25 terms = search_string.split()
26 for tag in terms:
27 if ':' in tag:
28 # This is a special tag; at the moment, by/for/of to indicate
29 # related users
30 prefix, tag = tag.split(':', 1)
31
32 if tag == 'me':
33 target_user = me
34 else:
35 # XXX what to do if this fails? abort? return empty query?
36 target_user = User.get_by(name=tag)
37
38 if prefix == 'by':
39 rel = ArtUserType.BY
40 elif prefix == 'for':
41 rel = ArtUserType.FOR
42 elif prefix == 'of':
43 rel = ArtUserType.OF
44 else:
45 # Bogus tag. Not sure what to do here, so for the moment,
46 # ignore it
47 continue
48
49 # Inner join to the ArtUser table
50 q = q.join(ArtUser, aliased=True) \
51 .filter(ArtUser.user == target_user) \
52 .filter(ArtUser.type == rel)
53
54 else:
55 # Regular ol' tag
56 q = q.join(Tag, TagText, aliased=True) \
57 .filter(TagText.text == tag)
58
59 return q
60
61 def add_tags(art, tag_string, adding_user, me):
62 """Takes a string that looks like a tag query, and effectively modifies the
63 art's tags to match it.
64 """
65
66 # XXX what to do with invalid tags? just return them and let caller fix?
67 bad_tags = []
68 for tag_text in tag_string.split():
69 original_tag_text = tag_text
70 tag_text = tag_text.lower()
71
72 # Adding or removing a tag?
73 if tag_text[0] == '-':
74 add = False
75 tag_text = tag_text[1:]
76 else:
77 # Allow "+foo" to mean "add foo"
78 if tag_text[0] == '+':
79 tag_text = tag_text[1:]
80 add = True
81
82 # Check for special namespaces
83 prefix = None
84 if ':' in tag_text:
85 prefix, tag_text = tag_text.split(':', 1)
86 if prefix not in ['by', 'for', 'of']:
87 # This is bogus. Skip it.
88 bad_tags.append(original_tag_text)
89 continue
90
91 if prefix == 'by':
92 # XXX this needs supporting. silently ignore for now
93 continue
94
95 # Must be 3-50 alphanumeric characters
96 if not re.match('^[a-z0-9]{3,50}$', tag_text):
97 bad_tags.append(original_tag_text)
98 continue
99
100 # Do work!
101 if prefix:
102 if tag_text == 'me':
103 target_user = me
104 else:
105 target_user = User.get_by(name=tag_text)
106
107 # Special tag; at the moment, just a relationship
108 if prefix == 'by':
109 rel = ArtUserType.BY
110 elif prefix == 'for':
111 rel = ArtUserType.FOR
112 elif prefix == 'of':
113 rel = ArtUserType.OF
114
115 user_assoc_data = dict(art=art, user=target_user, type=rel)
116 if add:
117 find_or_create(ArtUser, **user_assoc_data)
118
119 else:
120 # XXX this will die for nonassociations
121 user_assoc = ArtUser.get_by(**user_assoc_data)
122 user_assoc.delete()
123
124 else:
125 # Regular tag
126 if add:
127 tag = find_or_create(TagText, text=tag_text)
128 find_or_create(Tag, art=art, tagger=user, tagtext=tag)
129
130 else:
131 tag = TagText.get_by(text=tag_text)
132 if tag:
133 # XXX this will die
134 tag_assoc = Tag.get_by(art=art, tagger=user, tagtext=tag)
135 tag_assoc.delete()
136
137 elixir.session.commit()