From 69d5189cc4fb2ca63418a0741bf744cf5fee8bc9 Mon Sep 17 00:00:00 2001 From: Nick Retallack Date: Tue, 6 Oct 2009 23:01:20 -0700 Subject: [PATCH] Added User Pages, which you can now display galleries on. Also detected duplicate submissions. Made an absolute mess of routes, but I think I've fixed them all now. Removed all url.current() calls because they don't seem to work, though I'm not sure if that's because I added explicit=True or not. I'm wary of them anyhow after reading http://pylonsbook.com/en/1.0/urls-routing-and-dispatch.html#route-memory --- floof/config/routing.py | 46 +++++++++++++++++------------------ floof/controllers/account.py | 14 +++++------ floof/controllers/art.py | 21 +++++++++++++--- floof/controllers/search.py | 2 +- floof/controllers/tag.py | 4 +-- floof/model/art.py | 5 ++-- floof/model/search.py | 21 ++++++++-------- floof/model/users.py | 35 +++++++++++++++++++++++++- floof/templates/account/register.mako | 2 +- floof/templates/art/new.mako | 2 +- floof/templates/base.mako | 11 +++++++++ floof/templates/macros.mako | 2 +- floof/templates/users/view.mako | 2 +- 13 files changed, 112 insertions(+), 55 deletions(-) diff --git a/floof/config/routing.py b/floof/config/routing.py index 7f46a2c..c9b4975 100644 --- a/floof/config/routing.py +++ b/floof/config/routing.py @@ -31,32 +31,32 @@ def make_map(): map.connect('/error/{action}', controller='error') map.connect('/error/{action}/{id}', controller='error') - map.connect('/', controller='main', action='index') - - # User stuff - map.connect('/account/login', controller='account', action='login') - map.connect('/account/login_begin', controller='account', action='login_begin', **require_POST) - map.connect('/account/login_finish', controller='account', action='login_finish') - map.connect('/account/logout', controller='account', action='logout', **require_POST) - map.connect('/account/register', controller='account', action='register') - map.connect('/account/register_finish', controller='account', action='register_finish', **require_POST) - + map.connect('home', '/', controller='main', action='index') + + # Account stuff + with map.submapper(controller="account") as sub: + sub.connect('login', '/account/login', action='login') + sub.connect('login_begin', '/account/login_begin', action='login_begin', **require_POST) + sub.connect('login_finish', '/account/login_finish', action='login_finish') + sub.connect('logout', '/account/logout', action='logout', **require_POST) + 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') - # Art stuff - art = map.resource('art','art', controller="art", member={'rate':'PUT'}) - # wow, it even works if you name the plural and singular the same thing. - # Resources documented here: http://routes.groovie.org/manual.html#restful-services - # It seems the first parameter (singular) is only ever used in route names, e.g. url('kitten', id=5). - # The second parameter, plural, is used everywhere else by default: in the url, controller name, - # and the route name for the collection. e.g. url('kittens') -> '/kittens' -> kittens.index(). - # Since our controllers have singular names, we'll have to override this every time with the 'controller' parameter. - # Even singular routes use the plural in urls. url('kitten', id=5) -> '/kittens/5'. - # And it appears that if the singular and plural are the same, either will match, so no harm done. - # It does mean, however, that if you have a None id accidentally, url('art', id=None) you'll get the same thing - # as url('art'). I mean, you might have wanted a singular but you got a plural route instead. - + + 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") + + with map.submapper(controller='tag') as sub: + sub.connect('delete_tag', '/art/{art_id}/tag/{id}') + sub.connect('create_tag', '/art/{art_id}/tag') + 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 --git a/floof/controllers/account.py b/floof/controllers/account.py index e4f39f3..7dabaa6 100644 --- a/floof/controllers/account.py +++ b/floof/controllers/account.py @@ -5,8 +5,8 @@ from openid.extensions.sreg import SRegRequest, SRegResponse from openid.store.filestore import FileOpenIDStore from sqlalchemy.orm.exc import NoResultFound -from pylons import request, response, session, tmpl_context as c, url -from pylons.controllers.util import abort, redirect_to +from pylons import request, response, session, tmpl_context as c, url, h +from pylons.controllers.util import abort, redirect from routes import url_for, request_config from floof.lib.base import BaseController, render @@ -35,7 +35,7 @@ class AccountController(BaseController): return_url = url_for(host=host, controller='account', action='login_finish') new_url = auth_request.redirectURL(return_to=return_url, realm=protocol + '://' + host) - redirect_to(new_url) + redirect(new_url) def login_finish(self): """Step two of logging in; the OpenID provider redirects back here.""" @@ -63,14 +63,14 @@ class AccountController(BaseController): session['register:nickname'] = sreg_res['nickname'] session.save() - redirect_to(url.current(action='register')) + redirect(url('register')) # Remember who's logged in, and we're good to go session['user_id'] = user.id session.save() # XXX send me where I came from - redirect_to('/') + redirect('/') def logout(self): """Log user out.""" @@ -81,7 +81,7 @@ class AccountController(BaseController): # XXX success message # XXX send me where I came from - redirect_to('/') + redirect('/') def register(self): """Logging in with an unrecognized identity URL redirects here.""" @@ -116,4 +116,4 @@ class AccountController(BaseController): # XXX how do we do success messages in some useful way? # XXX send me where I came from - redirect_to('/') + redirect('/') diff --git a/floof/controllers/art.py b/floof/controllers/art.py index 7ca1285..8dfc487 100644 --- a/floof/controllers/art.py +++ b/floof/controllers/art.py @@ -10,6 +10,9 @@ log = logging.getLogger(__name__) import elixir from floof.model.art import Art, Rating +from sqlalchemy.exceptions import IntegrityError + + class ArtController(BaseController): def __before__(self, id=None): super(ArtController, self).__before__() @@ -27,9 +30,19 @@ class ArtController(BaseController): # TODO: login required def create(self): - Art(uploaded_by=c.user, **request.params) - elixir.session.commit() - redirect_to(controller="main", action="index") + c.art = Art(uploader=c.user, **request.params) + + 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)) + def show(self, id): # c.art = h.get_object_or_404(Art, id=id) @@ -56,4 +69,4 @@ class ArtController(BaseController): c.art.rate(score, c.user) elixir.session.commit() - redirect(url('art', id=c.art.id)) + redirect(url('show_art', id=c.art.id)) diff --git a/floof/controllers/search.py b/floof/controllers/search.py index e27e5c8..57ff4d2 100644 --- a/floof/controllers/search.py +++ b/floof/controllers/search.py @@ -40,7 +40,7 @@ class SearchController(BaseController): # TODO: login required def display(self, id): c.search = h.get_object_or_404(SavedSearch, id=id) - c.gallery = GalleryWidget(search=c.search, displayer=c.user) + c.gallery = GalleryWidget(search=c.search, page=c.user.primary_page) elixir.session.commit() redirect(url(controller="users", action="view", name=c.user.name)) diff --git a/floof/controllers/tag.py b/floof/controllers/tag.py index ef3c7ce..c00eaf6 100644 --- a/floof/controllers/tag.py +++ b/floof/controllers/tag.py @@ -18,12 +18,12 @@ class TagController(BaseController): tag = h.get_object_or_404(Tag, id=id) elixir.session.delete(tag) elixir.session.commit() - redirect(url('art', id=art_id)) + redirect(url('show_art', id=art_id)) # TODO: login required def create(self, art_id): c.art = h.get_object_or_404(Art, id=art_id) c.art.add_tags(request.params["tags"], c.user) elixir.session.commit() - redirect(url('art', id=c.art.id)) + redirect(url('show_art', id=c.art.id)) diff --git a/floof/model/art.py b/floof/model/art.py index 9ff00c5..b709610 100644 --- a/floof/model/art.py +++ b/floof/model/art.py @@ -16,9 +16,9 @@ from floof.lib.dbhelpers import find_or_create, update_or_create class Art(Entity): title = Field(Unicode(120)) original_filename = Field(Unicode(120)) - hash = Field(String) + hash = Field(String, unique=True, required=True) - uploader = ManyToOne('User') + uploader = ManyToOne('User', required=True) tags = OneToMany('Tag') # def __init__(self, **kwargs): @@ -33,6 +33,7 @@ class Art(Entity): def set_file(self, file): self.hash = save_file("art", file) + self.original_filename = file.filename file = property(get_path, set_file) diff --git a/floof/model/search.py b/floof/model/search.py index e812afc..be2bb9e 100644 --- a/floof/model/search.py +++ b/floof/model/search.py @@ -17,9 +17,12 @@ class SavedSearch(Entity): class GalleryWidget(Entity): + page = ManyToOne('UserPage') search = ManyToOne(SavedSearch) - displayer = ManyToOne('User') # determines whose page should it should show up on - # Could be no-ones, if it's just a template. + + # NOTE: no longer needed now that we have pages, I guess. + # displayer = ManyToOne('User') # determines whose page should it should show up on + # # Could be no-ones, if it's just a template. # Needs some fields for position on your page @@ -30,13 +33,9 @@ class GalleryWidget(Entity): @string.setter def string(self, value): # TODO: should we delete the possibly orphaned saved search? - if not self.displayer: - # TODO: may have to refactor this into an init if the key ordering is inconvenienc - raise "Oh no! This gallery needs a displayer to set on the saved search." - - self.search = SavedSearch(author=self.displayer, string=value) - + # if not self.displayer: + # # TODO: may have to refactor this into an init if the key ordering is inconvenienc + # raise "Oh no! This gallery needs a displayer to set on the saved search." -# class UserPage(Entity): -# owner = ManyToOne('User') -# visible = Field(Boolean) \ No newline at end of file + self.search = SavedSearch(author=getattr(self,"author",None), string=value) + \ No newline at end of file diff --git a/floof/model/users.py b/floof/model/users.py index d4a679a..86c31d8 100644 --- a/floof/model/users.py +++ b/floof/model/users.py @@ -6,18 +6,51 @@ # from elixir import Entity, Field, Unicode, belongs_to, has_many from elixir import * +from search import GalleryWidget class User(Entity): name = Field(Unicode(20)) uploads = OneToMany('Art') has_many('identity_urls', of_kind='IdentityURL') searches = OneToMany('SavedSearch') - galleries = OneToMany('GalleryWidget') + # galleries = OneToMany('GalleryWidget') + pages = OneToMany('UserPage', inverse="owner") + primary_page = OneToOne('UserPage', inverse="owner") + def __unicode__(self): return self.name + def __init__(self, **kwargs): + super(User, self).__init__(**kwargs) + + + + # TODO: have this clone a standard starter page + self.primary_page = UserPage(owner=self, title="default", visible=True) + + # a starter gallery, just for fun + gallery = GalleryWidget(owner=self, string="awesome") + self.primary_page.galleries.append(gallery) + + class IdentityURL(Entity): url = Field(Unicode(255)) belongs_to('user', of_kind='User') + + +class UserPage(Entity): + """A user can have multiple pages, though by default they only have one visible. + 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. + + """ + + owner = ManyToOne('User', inverse="pages") + title = Field(String) + + visible = Field(Boolean) + galleries = OneToMany('GalleryWidget') \ No newline at end of file diff --git a/floof/templates/account/register.mako b/floof/templates/account/register.mako index 485012c..2de23a4 100644 --- a/floof/templates/account/register.mako +++ b/floof/templates/account/register.mako @@ -2,7 +2,7 @@

Registering from ${c.identity_url}.

-${h.form(url.current(action='register_finish'), method='POST')} +${h.form(url('register_finish'), method='POST')}
Username
${h.text('username', value=c.username)}
diff --git a/floof/templates/art/new.mako b/floof/templates/art/new.mako index e2c00fb..3eda527 100644 --- a/floof/templates/art/new.mako +++ b/floof/templates/art/new.mako @@ -3,7 +3,7 @@

Add New Art

Now: Upload a file. Later: Supply a link? Not exclusive to uploading.

-${h.form(h.url_for(controller='art', action='upload'), multipart=True)} +${h.form(h.url('create_art'), multipart=True)} ${h.file('file')} ${h.submit(None, 'Upload!')} ${h.end_form()} diff --git a/floof/templates/base.mako b/floof/templates/base.mako index 7290a11..73f069c 100644 --- a/floof/templates/base.mako +++ b/floof/templates/base.mako @@ -46,6 +46,17 @@ ${h.end_form()} + +<% messages = h.flash.pop_messages() %> +% if messages: + +% endif + +
${next.body()}
diff --git a/floof/templates/macros.mako b/floof/templates/macros.mako index 5633e5b..061b7b9 100644 --- a/floof/templates/macros.mako +++ b/floof/templates/macros.mako @@ -2,7 +2,7 @@