From: Nick Retallack Date: Tue, 1 Dec 2009 09:18:21 +0000 (-0800) Subject: merged. Oh no, we have two different user relationship models. Mine's in relations... X-Git-Url: http://git.veekun.com/zzz-floof.git/commitdiff_plain/f263f51648eef9739caa92e19ec45b32d7a1e49e?ds=sidebyside;hp=-c merged. Oh no, we have two different user relationship models. Mine's in relations.py, and is used for stuff like by/for/of, most of which isn't implemented yet --- f263f51648eef9739caa92e19ec45b32d7a1e49e diff --combined floof/config/routing.py index 32451e9,44462f8..f6906f4 --- a/floof/config/routing.py +++ b/floof/config/routing.py @@@ -42,9 -42,13 +42,13 @@@ def make_map() sub.connect('register', '/account/register', action='register') sub.connect('register_finish', '/account/register_finish', action='register_finish', **require_POST) - # with map.submapper() - map.connect('/users', controller='users', action='list') - map.connect('user_page', '/users/{name}', controller='users', action='view') + # Specific user stuff + with map.submapper(controller='users') as sub: + sub.connect( '/users', action='list') + sub.connect('user_page', '/users/{name}', action='view') + with map.submapper(controller='user_settings') as sub: + sub.connect( '/users/{name}/settings/relationships/toggle', + action='rel_toggle', **require_POST) # Comments with map.submapper(controller='comments') as sub: @@@ -55,20 -59,21 +59,25 @@@ sub.connect('/*owner_url/comments/{id}/reply', action='reply') sub.connect('/*owner_url/comments/{id}/reply_done', action='reply_done', **require_POST) + # Art with map.submapper(controller="art") as sub: sub.connect('new_art', '/art/new', action="new") sub.connect('create_art', '/art/create', action="create") sub.connect('rate_art', '/art/{id}/rate', action="rate") sub.connect('show_art', '/art/{id}', action="show") + # Some art pages pertain to a specific user, but still belong in the art + # controller + map.connect('/users/{name}/watchstream', controller='art', action='watchstream') + with map.submapper(controller='tag') as sub: sub.connect('delete_tag', '/art/{art_id}/tag/{id}') sub.connect('create_tag', '/art/{art_id}/tag') + with map.submapper(controller='relation') as sub: + sub.connect('create_relation', '/art/{art_id}/relations/{kind}/create', action="create") + # TODO: conditions: kind = by|for|of|character? + map.resource('tag','tags', controller="tag", parent_resource=dict(member_name='art', collection_name='art')) # Yeah, parent resources are specified kinda dumb-ly. Would be better if you could pass in the diff --combined floof/controllers/art.py index be75ee7,b8406ed..41298c8 --- a/floof/controllers/art.py +++ b/floof/controllers/art.py @@@ -8,58 -8,14 +8,61 @@@ from floof.lib.base import BaseControll log = logging.getLogger(__name__) import elixir -from floof.model.art import Art, Rating +from floof.model.users import User +from floof.model import Art, Rating, UserRelation from floof.model.comments import Discussion + from floof.model.users import User, UserRelationship + from sqlalchemy import func from sqlalchemy.exceptions import IntegrityError + from sqlalchemy.orm.exc import NoResultFound +from wtforms.validators import ValidationError +from wtforms import * + + +class ArtUploadForm(Form): + by = TextField('Artists') + file = FileField('Upload') + url = TextField('Link') + + # TODO: make this general purpose + def validate_file(self, field): + 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): @@@ -70,38 -26,23 +73,38 @@@ def new(self): """ New Art! """ + c.form = ArtUploadForm() return render("/art/new.mako") # TODO: login required def create(self): - c.art = Art(uploader=c.user, **request.params) - c.art.discussion = Discussion(count=0) - - try: - elixir.session.commit() - redirect(url('show_art', id=c.art.id)) - except IntegrityError: - # hurr, there must be a better way to do this but I am lazy right now - hash = c.art.hash - elixir.session.rollback() - duplicate_art = Art.get_by(hash=hash) - h.flash("We already have that one.") - redirect(url('show_art', id=duplicate_art.id)) + c.form = ArtUploadForm(request.params) + if c.form.validate(): + + c.art = Art(uploader=c.user, **request.params) + c.art.discussion = Discussion(count=0) + + for artist in c.form.by.data: + UserRelation(user=artist, kind="by", creator=c.user, art=c.art) + + file = request.params['file'] + + try: + elixir.session.commit() + redirect(url('show_art', id=c.art.id)) + except IntegrityError: + # hurr, there must be a better way to do this but I am lazy right now + hash = c.art.hash + elixir.session.rollback() + duplicate_art = Art.get_by(hash=hash) + h.flash("We already have that one.") + redirect(url('show_art', id=duplicate_art.id)) + + else: + ## TODO: JavaScript should be added to the upload form so that it is + ## impossible to submit the form when it contains any invalid users, + ## so this never happens. Only autocompled usernames should be allowed. + return render("/art/new.mako") def show(self, id): @@@ -124,3 -65,20 +127,20 @@@ elixir.session.commit() redirect(url('show_art', id=c.art.id)) + + + def watchstream(self, name): + """Watchstream for a certain user.""" + try: + c.watching_user = User.query.filter(func.lower(User.name) == name) \ + .one() + except NoResultFound: + abort(404) + + # This user has watches which are users which have art + # XXX use artist, not uploader + c.artwork = Art.query.join(Art.uploader, + User.target_of_relationships) \ + .filter(UserRelationship.user_id == c.watching_user.id) + + return render('/index.mako') diff --combined floof/model/users.py index 4f83a75,af4bfba..7601cf5 --- a/floof/model/users.py +++ b/floof/model/users.py @@@ -16,13 -16,12 +16,15 @@@ class User(Entity) # galleries = OneToMany('GalleryWidget') pages = OneToMany('UserPage', inverse="owner") primary_page = OneToOne('UserPage', inverse="owner") + relationships = OneToMany('UserRelationship', inverse='user') + target_of_relationships = OneToMany('UserRelationship', inverse='target_user') def __unicode__(self): return self.name + + def __str__(self): + return self.name def __init__(self, **kwargs): super(User, self).__init__(**kwargs) @@@ -48,17 -47,31 +50,31 @@@ class UserPage(Entity) This is so that they can keep some nice themed pages lying around for special occasions. Page templates that provide familiar interfaces will also be UserPage records. Users will see a panel full of them, and they can choose to clone those template pages to their own page list. - If more than one is set to visible, there would be tabs. - - """ - + If more than one is set to visible, there would be tabs. The primary page is indicated in the user model. + """ + owner = ManyToOne('User', inverse="pages") title = Field(String) visible = Field(Boolean) galleries = OneToMany('GalleryWidget') - - - - - # class ArtRelation(Entity): - # + + + class UserRelationshipTypes(object): + IS_WATCHING = 1 + + class UserRelationship(Entity): + """Represents some sort of connection between users. + + For the moment, this means "watching". Later, it may mean friending or + ignoring. + + XXX: Watching should be made more general than this; it should have the + power of an arbitrary query per watched artist without being unintelligible + to users. + """ + + user = ManyToOne('User') + target_user = ManyToOne('User') + type = Field(Integer) # UserRelationshipTypes above ++