details: http://www.bx.psu.edu/hg/galaxy/rev/a9426ddbe468 changeset: 2929:a9426ddbe468 user: James Taylor <james@jamestaylor.org> date: Wed Oct 28 15:09:10 2009 -0400 description: Pages can now be deleted. Pages have a 'published' attribute that determines whether they are publicly accessible. There is now a list of all published pages from all users diffstat: lib/galaxy/model/mapping.py | 2 + lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py | 35 ++ lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py | 34 ++ lib/galaxy/web/controllers/page.py | 141 +++++++- lib/galaxy/web/framework/helpers/grids.py | 2 +- templates/base_panels.mako | 3 + 6 files changed, 194 insertions(+), 23 deletions(-) diffs (332 lines): diff -r fabb2f6abc45 -r a9426ddbe468 lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Wed Oct 28 15:09:05 2009 -0400 +++ b/lib/galaxy/model/mapping.py Wed Oct 28 15:09:10 2009 -0400 @@ -545,6 +545,8 @@ ForeignKey( "page_revision.id", use_alter=True, name='page_latest_revision_id_fk' ), index=True ), Column( "title", TEXT ), Column( "slug", TEXT, unique=True, index=True ), + Column( "published", Boolean, index=True, default=False ), + Column( "deleted", Boolean, index=True, default=False ), ) PageRevision.table = Table( "page_revision", metadata, diff -r fabb2f6abc45 -r a9426ddbe468 lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/model/migrate/versions/0023_page_published_and_deleted_columns.py Wed Oct 28 15:09:10 2009 -0400 @@ -0,0 +1,35 @@ +""" +Migration script to add columns for tracking whether pages are deleted and +publicly accessible. +""" + +from sqlalchemy import * +from migrate import * +from migrate.changeset import * + +import logging +log = logging.getLogger( __name__ ) + +metadata = MetaData( migrate_engine ) + +def upgrade(): + + print __doc__ + metadata.reflect() + + Page_table = Table( "page", metadata, autoload=True ) + + c = Column( "published", Boolean, index=True, default=False ) + c.create( Page_table ) + assert c is Page_table.c.published + + c = Column( "deleted", Boolean, index=True, default=False ) + c.create( Page_table ) + assert c is Page_table.c.deleted + +def downgrade(): + metadata.reflect() + + Page_table = Table( "page", metadata, autoload=True ) + Page_table.c.published.drop() + Page_table.c.deleted.drop() diff -r fabb2f6abc45 -r a9426ddbe468 lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py Wed Oct 28 15:09:10 2009 -0400 @@ -0,0 +1,34 @@ +""" +Remove unique constraint from page slugs to allow creating a page with +the same slug as a deleted page. +""" + +from sqlalchemy import * +from migrate import * +from migrate.changeset import * + +import datetime +now = datetime.datetime.utcnow + +import logging +log = logging.getLogger( __name__ ) + +metadata = MetaData( migrate_engine ) + +def upgrade(): + print __doc__ + metadata.reflect() + + Page_table = Table( "page", metadata, autoload=True ) + + i = Index( "ix_page_slug", Page_table.c.slug ) + i.drop() + + i = Index( "ix_page_slug", Page_table.c.slug, unique=False ) + i.create() + + +def downgrade(): + metadata.reflect() + #Page_table = Table( "page", metadata, autoload=True ) + #Page_table.c.slug.alter( unique=True ) diff -r fabb2f6abc45 -r a9426ddbe468 lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Wed Oct 28 15:09:05 2009 -0400 +++ b/lib/galaxy/web/controllers/page.py Wed Oct 28 15:09:10 2009 -0400 @@ -6,16 +6,27 @@ VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" ) +def format_bool( b ): + if b: + return "yes" + else: + return "" + +class PublicURLColumn( grids.GridColumn ): + def get_value( self, trans, grid, item ): + username = trans.user.username or "???" + return username + "/" + item.slug + def get_link( self, trans, grid, item ): + if trans.user.username: + return dict( action='display_by_username_and_slug', username=item.user.username, slug=item.slug ) + else: + return None + +class OwnerColumn( grids.GridColumn ): + def get_value( self, trans, grid, item ): + return item.user.username + class PageListGrid( grids.Grid ): - class URLColumn( grids.GridColumn ): - def get_value( self, trans, grid, item ): - username = trans.user.username or "???" - return username + "/" + item.slug - def get_link( self, trans, grid, item ): - if trans.user.username: - return dict( action='display_by_username_and_slug', username=item.user.username, slug=item.slug ) - else: - return None # Grid definition use_panels = True title = "Your pages" @@ -23,7 +34,8 @@ default_sort_key = "-create_time" columns = [ grids.GridColumn( "Title", key="title", attach_popup=True ), - URLColumn( "Public URL" ), + PublicURLColumn( "Public URL" ), + grids.GridColumn( "Published", key="published", format=format_bool ), grids.GridColumn( "Created", key="create_time", format=time_ago ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), ] @@ -32,25 +44,67 @@ ] operations = [ grids.GridOperation( "View", allow_multiple=False, url_args=dict( action='display') ), - grids.GridOperation( "Edit", allow_multiple=False, url_args=dict( action='edit') ) + grids.GridOperation( "Edit name/id", allow_multiple=False, url_args=dict( action='edit') ), + grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( action='edit_content') ), + grids.GridOperation( "Delete" ), + grids.GridOperation( "Publish", condition=( lambda item: not item.published ) ), + grids.GridOperation( "Unpublish", condition=( lambda item: item.published ) ), ] def apply_default_filter( self, trans, query, **kwargs ): - return query.filter_by( user=trans.user ) + return query.filter_by( user=trans.user, deleted=False ) + +class PageAllPublishedGrid( grids.Grid ): + # Grid definition + use_panels = True + title = "Published pages from all users" + model_class = model.Page + default_sort_key = "-create_time" + columns = [ + grids.GridColumn( "Title", key="title" ), + PublicURLColumn( "Public URL" ), + OwnerColumn( "Published by" ), + grids.GridColumn( "Created", key="create_time", format=time_ago ), + grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), + ] + def apply_default_filter( self, trans, query, **kwargs ): + return query.filter_by( deleted=False, published=True ) class PageController( BaseController ): - list = PageListGrid() + _page_list = PageListGrid() + _all_published_list = PageAllPublishedGrid() @web.expose - @web.require_admin + @web.require_login() def index( self, trans, *args, **kwargs ): + # Handle operation + if 'operation' in kwargs and 'id' in kwargs: + session = trans.sa_session + operation = kwargs['operation'] + ids = util.listify( kwargs['id'] ) + for id in ids: + item = session.query( model.Page ).get( trans.security.decode_id( id ) ) + if operation == "Delete": + item.deleted = True + elif operation == "Publish": + item.published = True + elif operation == "Unpublish": + item.published = False + session.flush() # Build grid - grid = self.list( trans, *args, **kwargs ) + grid = self._page_list( trans, *args, **kwargs ) # Render grid wrapped in panels return trans.fill_template( "page/index.mako", grid=grid ) @web.expose - @web.require_admin + @web.require_login() + def list_published( self, trans, *args, **kwargs ): + grid = self._all_published_list( trans, *args, **kwargs ) + # Render grid wrapped in panels + return trans.fill_template( "page/index.mako", grid=grid ) + + + @web.expose @web.require_login( "create pages" ) def create( self, trans, page_title="", page_slug="" ): """ @@ -65,7 +119,7 @@ page_slug_err = "Page id is required" elif not VALID_SLUG_RE.match( page_slug ): page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character" - elif trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug ).first(): + elif trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first(): page_slug_err = "Page id must be unique" else: # Create the new stored workflow @@ -98,9 +152,50 @@ template="page/create.mako" ) @web.expose - @web.require_admin + @web.require_login( "create pages" ) + def edit( self, trans, id, page_title="", page_slug="" ): + """ + Create a new page + """ + encoded_id = id + id = trans.security.decode_id( id ) + session = trans.sa_session + page = session.query( model.Page ).get( id ) + user = trans.user + assert page.user == user + page_title_err = page_slug_err = "" + if trans.request.method == "POST": + if not page_title: + page_title_err = "Page name is required" + elif not page_slug: + page_slug_err = "Page id is required" + elif not VALID_SLUG_RE.match( page_slug ): + page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character" + elif page_slug == page.slug or trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first(): + page_slug_err = "Page id must be unique" + else: + page.title = page_title + page.slug = page_slug + session.flush() + # Display the management page + return trans.response.send_redirect( web.url_for( action='index' ) ) + else: + page_title = page.title + page_slug = page.slug + return trans.show_form( + web.FormBuilder( web.url_for( id=encoded_id ), "Edit page attributes", submit_text="Submit" ) + .add_text( "page_title", "Page title", value=page_title, error=page_title_err ) + .add_text( "page_slug", "Page identifier", value=page_slug, error=page_slug_err, + help="""A unique identifier that will be used for + public links to this page. A default is generated + from the page title, but can be edited. This field + must contain only lowercase letters, numbers, and + the '-' character.""" ), + template="page/create.mako" ) + + @web.expose @web.require_login( "edit pages" ) - def edit( self, trans, id ): + def edit_content( self, trans, id ): """ Render the main page editor interface. """ @@ -110,7 +205,7 @@ return trans.fill_template( "page/editor.mako", page=page ) @web.expose - @web.require_admin + @web.require_login() def save( self, trans, id, content ): id = trans.security.decode_id( id ) page = trans.sa_session.query( model.Page ).get( id ) @@ -126,10 +221,12 @@ trans.sa_session.flush() @web.expose - @web.require_admin + @web.require_login() def display( self, trans, id ): id = trans.security.decode_id( id ) page = trans.sa_session.query( model.Page ).get( id ) + if page.user is not trans.user: + error( "Page is not owned by current user" ) return trans.fill_template( "page/display.mako", page=page ) @web.expose @@ -138,7 +235,7 @@ user = session.query( model.User ).filter_by( username=username ).first() if user is None: raise web.httpexceptions.HTTPNotFound() - page = trans.sa_session.query( model.Page ).filter_by( user=user, slug=slug ).first() + page = trans.sa_session.query( model.Page ).filter_by( user=user, slug=slug, deleted=False, published=True ).first() if page is None: raise web.httpexceptions.HTTPNotFound() return trans.fill_template( "page/display.mako", page=page ) diff -r fabb2f6abc45 -r a9426ddbe468 lib/galaxy/web/framework/helpers/grids.py --- a/lib/galaxy/web/framework/helpers/grids.py Wed Oct 28 15:09:05 2009 -0400 +++ b/lib/galaxy/web/framework/helpers/grids.py Wed Oct 28 15:09:10 2009 -0400 @@ -279,7 +279,7 @@ temp['id'] = item.id return temp else: - return dict( operation=operation.label, id=item.id ) + return dict( operation=self.label, id=item.id ) def allowed( self, item ): if self.condition: diff -r fabb2f6abc45 -r a9426ddbe468 templates/base_panels.mako --- a/templates/base_panels.mako Wed Oct 28 15:09:05 2009 -0400 +++ b/templates/base_panels.mako Wed Oct 28 15:09:10 2009 -0400 @@ -243,6 +243,9 @@ <li><hr style="color: inherit; background-color: gray"/></li> <li><a target="galaxy_main" href="${h.url_for( controller='history', action='list' )}">Histories</a></li> <li><a target="galaxy_main" href="${h.url_for( controller='dataset', action='list' )}">Datasets</a></li> + %if app.config.get_bool( 'enable_pages', False ): + <li><a href="${h.url_for( controller='page' )}">Pages</a></li> + %endif %endif </ul> </div>