merged suff, commented out some of my own
[zzz-floof.git] / floof / lib / tags.py
diff --git a/floof/lib/tags.py b/floof/lib/tags.py
new file mode 100644 (file)
index 0000000..6186048
--- /dev/null
@@ -0,0 +1,128 @@
+from floof.model import Art, ArtUser, ArtUserType, Tag, TagText, User
+
+from dbhelpers import find_or_create
+import re
+def parse(search_string):
+    """Parses a search query, and returns a query object on Art.
+
+    Queries can contain:
+    - Regular tags: foo
+    - User relations: by:kalu, of:eevee, for:ootachi
+
+    Later:
+    - Negative versions of anything above: -by:eevee, -dongs
+    """
+
+    # XXX doesn't do negative querying yet.
+    # XXX could use some sane limits.
+
+    # We'll be building this as we go.
+    q = Art.query
+
+    terms = search_string.split()
+    for tag in terms:
+        if ':' in tag:
+            # This is a special tag; at the moment, by/for/of to indicate
+            # related users
+            prefix, tag = tag.split(':', 1)
+
+            # XXX what to do if this fails?  abort?  return empty query?
+            target_user = User.get_by(name=tag)
+
+            if prefix == 'by':
+                rel = ArtUserType.BY
+            elif prefix == 'for':
+                rel = ArtUserType.FOR
+            elif prefix == 'of':
+                rel = ArtUserType.OF
+            else:
+                # Bogus tag.  Not sure what to do here, so for the moment,
+                # ignore it
+                continue
+
+            # Inner join to the ArtUser table
+            q = q.join(ArtUser, aliased=True) \
+                 .filter(ArtUser.user == target_user) \
+                 .filter(ArtUser.type == rel)
+
+        else:
+            # Regular ol' tag
+            q = q.join(Tag, TagText, aliased=True) \
+                 .filter(TagText.text == tag)
+
+    return q
+
+def add_tags(art, tag_string, user):
+    """Takes a string that looks like a tag query, and effectively modifies the
+    art's tags to match it.
+    """
+
+    # XXX what to do with invalid tags?  just return them and let caller fix?
+    bad_tags = []
+    for tag_text in tag_string.split():
+        original_tag_text = tag_text
+        tag_text = tag_text.lower()
+
+        # Adding or removing a tag?
+        if tag_text[0] == '-':
+            add = False
+            tag_text = tag_text[1:]
+        else:
+            # Allow "+foo" to mean "add foo"
+            if tag_text[0] == '+':
+                tag_text = tag_text[1:]
+            add = True
+
+        # Check for special namespaces
+        prefix = None
+        if ':' in tag_text:
+            prefix, tag_text = tag_text.split(':', 1)
+            if prefix not in ['by', 'for', 'of']:
+                # This is bogus.  Skip it.
+                bad_tags.append(original_tag_text)
+                continue
+
+            if prefix == 'by':
+                # XXX this needs supporting.  silently ignore for now
+                continue
+
+        # Must be 3-50 alphanumeric characters
+        if not re.match('^[a-z0-9]{3,50}$', tag_text):
+            bad_tags.append(original_tag_text)
+            continue
+
+        # Do work!
+        if prefix:
+            target_user = User.get_by(name=tag_text)
+
+            # Special tag; at the moment, just a relationship
+            if prefix == 'by':
+                rel = ArtUserType.BY
+            elif prefix == 'for':
+                rel = ArtUserType.FOR
+            elif prefix == 'of':
+                rel = ArtUserType.OF
+
+            user_assoc_data = dict(art=art, user=target_user, type=rel)
+            if add:
+                find_or_create(ArtUser, **user_assoc_data)
+
+            else:
+                # XXX this will die for nonassociations
+                user_assoc = ArtUser.get_by(art=art, **user_assoc_data)
+                user_assoc.delete()
+
+        else:
+            # Regular tag
+            if add:
+                tag = find_or_create(TagText, text=tag_text)
+                find_or_create(Tag, art=art, tagger=user, tagtext=tag)
+
+            else:
+                tag = TagText.get_by(text=tag_text)
+                if tag:
+                    # XXX this will die
+                    tag_assoc = Tag.get_by(art=art, tagger=user, tagtext=tag)
+                    tag_assoc.delete()
+
+    elixir.session.commit()