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