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