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
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
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
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)
--- /dev/null
+ 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()
<%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>
<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)}