merged suff, commented out some of my own
authorNick Retallack <nickretallack@gmail.com>
Mon, 7 Dec 2009 02:05:00 +0000 (18:05 -0800)
committerNick Retallack <nickretallack@gmail.com>
Mon, 7 Dec 2009 02:05:00 +0000 (18:05 -0800)
1  2 
floof/config/routing.py
floof/controllers/art.py
floof/lib/tags.py
floof/templates/art/new.mako
floof/templates/art/show.mako

diff --combined floof/config/routing.py
@@@ -22,8 -22,8 +22,8 @@@ def make_map()
      require_POST = dict(conditions={'method': ['POST']})
  
      # get rid of trailing slashes
--    map.redirect('/*(url)/', '/{url}',
--                 _redirect_code='301 Moved Permanently')
++    map.redirect('/*(url)/', '/{url}',
++                 _redirect_code='301 Moved Permanently')
  
  
      # The ErrorController route (handles 404/500 error pages); it should
diff --combined floof/controllers/art.py
@@@ -1,15 -1,15 +1,15 @@@
  import logging
  
- from pylons import config, request, response, session, tmpl_context as c, h
+ from pylons import config, request, response, session, tmpl_context as c
  from pylons.controllers.util import abort, redirect
  from pylons import url
  from floof.lib.base import BaseController, render
  
  log = logging.getLogger(__name__)
  
- from floof.lib import file_storage as storage
- from floof.model.users import User
- from floof.model import Art, Rating, UserRelation
+ from floof.lib import file_storage as storage, helpers as h
+ from floof.model import Art, Rating, ArtUser
+ from floof.model.art import ArtUserType
  from floof.model.comments import Discussion
  from floof.model.users import User, UserRelationship
  
@@@ -18,7 -18,6 +18,6 @@@ import elixi
  import os.path
  import PIL
  import PIL.Image
- from sqlalchemy import func
  from sqlalchemy.exceptions import IntegrityError
  from sqlalchemy.orm.exc import NoResultFound
  from wtforms.validators import ValidationError
@@@ -27,7 -26,6 +26,7 @@@ from wtforms import 
  
  class ArtUploadForm(Form):
      by = TextField('Artists')
 +    by_me = BooleanField('me')
      file = FileField('Upload')
      url = TextField('Link')
  
          if field.data == u'':
              raise ValidationError('File is required')
      
-     # Also make this into a general User List field validator
-     """ PLEASE NOTE!  I just realized that I need to have a __str__ method on User
-     to get it to write the usernames back in the form when it redisplays them, since
-     this validator turns them into user objects instead.  This fact actually sounds dangerous
-     to me in the future, since it means I proably shouldn't be changing the data input
-     by the user right here in the validator, or the user will see the post-mangled data instead
-     of what they actually typed.  Hm.
-     
-     One solution to this could be to only look up the users after normal validation is over, 
-     and then manually add validation errors to the form if that fails.  But I think that kind of
-     sucks.  Perhaps the ideology in Formish, where they keep Validation and Conversion as
-     separate tasks, is a better way of doing it?  That way there is less risk of changing the user's
-     input -- you do that at the conversiot stage -- yet it is still encapsulated in the form workflow.
-     Hm.  But that means I'd have to query for the users in the validation step and throw them away,
-     or something equally stupid.  Guess there's no perfect solution here, but I thought it was
-     worth discussing.
-     
-     Btw, this is meant to be used by a field with multi user autocompletion on it (like on stackoverflow tags),
-     so the user should never actually submit anything invalid unless they disable javascript and force it.
-     """
-     def validate_by(self, field):
-         if not field.data:
-             raise ValidationError("Needs at least one creator")
-         user_names = field.data.split()
-         users = []
-         # TODO: Could totally do a filter__in here instead of picking them out individually
-         for user_name in user_names:
-             user = User.get_by(name=user_name)
-             if not user:
-                 raise ValidationError("Couldn't find user %s" % user_name)
-             users.append(user)
-         field.data = users
  
  class ArtController(BaseController):
      def __before__(self, id=None):
          )
          c.art.discussion = Discussion(count=0)
  
-         if c.form.by_me and c.user not in c.form.by.data:
-             UserRelation(user=c.user, creator=c.user, kind="by", art=c.art)
-         for artist in c.form.by.data:
-             UserRelation(user=artist, creator=c.user, kind="by", art=c.art)
++        # <<<<<<< HEAD
++        #         if c.form.by_me and c.user not in c.form.by.data:
++        #             UserRelation(user=c.user, creator=c.user, kind="by", art=c.art)
++        # 
++        #         for artist in c.form.by.data:
++        #             UserRelation(user=artist, creator=c.user, kind="by", art=c.art)
++        # =======
+         # For the moment, cheerfully assume that people are uploading their own
+         # art
+         ArtUser(art=c.art, user=c.user, type=ArtUserType.BY)
++        # >>>>>>> origin/master
  
  
          try:
      def watchstream(self, name):
          """Watchstream for a certain user."""
          try:
-             c.watching_user = User.query.filter(func.lower(User.name) == name) \
-                                   .one()
+             c.watching_user = User.get_by(name=name)
          except NoResultFound:
              abort(404)
  
diff --combined floof/lib/tags.py
index 0000000,104fe65..6186048
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,126 +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()
@@@ -1,44 -1,26 +1,42 @@@
  <%inherit file="/base.mako" />
  
  <h1>Add New Art</h1>
- <p>Now: Upload a file.  Later: Supply a link?  Not exclusive to uploading.</p>
- ## Todo: write some macros to make outputting form fields easier.
  ${h.form(h.url('create_art'), multipart=True)}
  
- <div>
-     ${normal_field(c.form.by)}
-     ${checkbox_field(c.form.by_me)}
- </div>
- <div>${normal_field(c.form.file)}</div>
++##<<<<<<< HEAD
++##<div>
++##    ${normal_field(c.form.by)}
++##    ${checkbox_field(c.form.by_me)}
++##</div>
++##
++##<div>${normal_field(c.form.file)}</div>
++##=======
+ ## Todo: write some macros to make outputting form fields easier.
+ ${normal_field(c.form.file)}
++##>>>>>>> origin/master
  
- ##Artist: ${h.text('artist')}
- ##${h.file('file')}
  ${h.submit(None, 'Upload!')}
  ${h.end_form()}
  
  
 +<%def name="field_errors(errors)">
 +    %if errors:
 +    <ul class="errors">
 +        %for error in errors:
 +            <li>${error}
 +        %endfor
 +    </ul>
 +    %endif
 +</%def>
 +
  
  <%def name="normal_field(field)">
 -<div>
 -${field.label()|n}
 -${field()|n} 
 -%if field.errors:
 -<ul class="errors">
 -%for error in field.errors:
 -<li>${error}
 -%endfor
 -</ul>
 -%endif
 -</div>
 +    ${field.label()|n}
 +    ${field()|n}
 +    ${field_errors(field.errors)}
  </%def>
 +
 +<%def name="checkbox_field(field)">
 +    ${field()|n} ${field.label()|n}
 +    ${field_errors(field.errors)}
 +</%def>
@@@ -34,17 -34,21 +34,15 @@@ ${h.end_form()
  
  <h2>Relations</h2>
  <ul>
- % for relation in c.art.user_relations:
- <li>${relation.kind}: ${relation.user}
+ % for label, relations in (('Artist', c.art.artists), \
+                                ('Recipient', c.art.recipients), \
+                                ('Participant', c.art.participants)):
+ % for user in relations:
+ <li>${label}: ${user.name}
+ % endfor
  % endfor
  </ul>
  
--<h2>Add Relations</h2>
--${h.form (h.url("create_relation", kind="by", art_id=c.art.id))}
--By: ${h.text('username')}
--${h.submit('add','Add')}
--${h.end_form()}
--
  <img class="full" src="${h.storage_url('art/medium', c.art.hash)}">
  
  ${comments.comment_block(c.art.discussion.comments)}