e75629ead70235bd8ba1e7165c7b95249b3b0d54
[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 from pylons import c
7
8 def parse(search_string):
9 """Parses a search query, and returns a query object on Art.
10
11 Queries can contain:
12 - Regular tags: foo
13 - User relations: by:kalu, of:eevee, for:ootachi
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 if tag == 'me':
32 tag = c.user.name
33
34 # XXX what to do if this fails? abort? return empty query?
35 target_user = User.get_by(name=tag)
36
37 if prefix == 'by':
38 rel = ArtUserType.BY
39 elif prefix == 'for':
40 rel = ArtUserType.FOR
41 elif prefix == 'of':
42 rel = ArtUserType.OF
43 else:
44 # Bogus tag. Not sure what to do here, so for the moment,
45 # ignore it
46 continue
47
48 # Inner join to the ArtUser table
49 q = q.join(ArtUser, aliased=True) \
50 .filter(ArtUser.user == target_user) \
51 .filter(ArtUser.type == rel)
52
53 else:
54 # Regular ol' tag
55 q = q.join(Tag, TagText, aliased=True) \
56 .filter(TagText.text == tag)
57
58 return q
59
60 def add_tags(art, tag_string, user):
61 """Takes a string that looks like a tag query, and effectively modifies the
62 art's tags to match it.
63 """
64
65 # XXX what to do with invalid tags? just return them and let caller fix?
66 bad_tags = []
67 for tag_text in tag_string.split():
68 original_tag_text = tag_text
69 tag_text = tag_text.lower()
70
71 # Adding or removing a tag?
72 if tag_text[0] == '-':
73 add = False
74 tag_text = tag_text[1:]
75 else:
76 # Allow "+foo" to mean "add foo"
77 if tag_text[0] == '+':
78 tag_text = tag_text[1:]
79 add = True
80
81 # Check for special namespaces
82 prefix = None
83 if ':' in tag_text:
84 prefix, tag_text = tag_text.split(':', 1)
85 if prefix not in ['by', 'for', 'of']:
86 # This is bogus. Skip it.
87 bad_tags.append(original_tag_text)
88 continue
89
90 if prefix == 'by':
91 # XXX this needs supporting. silently ignore for now
92 continue
93
94 if tag_text == 'me':
95 tag_text = c.user.name
96
97 # Must be 3-50 alphanumeric characters
98 if not re.match('^[a-z0-9]{3,50}$', tag_text):
99 bad_tags.append(original_tag_text)
100 continue
101
102 # Do work!
103 if prefix:
104 target_user = User.get_by(name=tag_text)
105
106 # Special tag; at the moment, just a relationship
107 if prefix == 'by':
108 rel = ArtUserType.BY
109 elif prefix == 'for':
110 rel = ArtUserType.FOR
111 elif prefix == 'of':
112 rel = ArtUserType.OF
113
114 user_assoc_data = dict(art=art, user=target_user, type=rel)
115 if add:
116 find_or_create(ArtUser, **user_assoc_data)
117
118 else:
119 # XXX this will die for nonassociations
120 user_assoc = ArtUser.get_by(art=art, **user_assoc_data)
121 user_assoc.delete()
122
123 else:
124 # Regular tag
125 if add:
126 tag = find_or_create(TagText, text=tag_text)
127 find_or_create(Tag, art=art, tagger=user, tagtext=tag)
128
129 else:
130 tag = TagText.get_by(text=tag_text)
131 if tag:
132 # XXX this will die
133 tag_assoc = Tag.get_by(art=art, tagger=user, tagtext=tag)
134 tag_assoc.delete()
135
136 elixir.session.commit()