Artem Egorkine avatar Artem Egorkine committed e204263

Support for reordering pages in the "manage pages" admin page.
* zine/shared/js/jquery.ui.*:
The jQuery UI (1.7) components needed for the sortable functionality.
* zine/database.py,
zine/models.py,
zine/upgrades/versions/003_post_ordering.py:
Added the "order" column to the "posts" table.
* zine/templates/admin/_post_helpers.html:
Use "thead" and "tbody" elements in the posts table; added "id"
attribute to "tr"s representing individual posts.
* zine/views/admin.py,
zine/templates/admin/manage_pages.html:
Removed pagination from the "admin/mamage_pages" view as it conflicts
with the idea of by-hand reordering.
* zine/urls.py,
zine/views/__init__.py,
zine/views/admin.py:
Added "admin/order_pages" endpoint that updates the "order" field
on Post objects.

Comments (0)

Files changed (11)

 ^Makefile$
 ^env/
 ^.ropeproject/
+\.[^/]*\.sw.?$
     db.Column('pings_enabled', db.Boolean),
     db.Column('content_type', db.String(40), index=True),
     db.Column('status', db.Integer),
+    db.Column('order', db.Integer),
 )
 
 post_links = db.Table('post_links', metadata,
     'tags':             db.relation(Tag, secondary=post_tags, lazy=False,
                                     order_by=[tags.c.name]),
     '_comment_count':   posts.c.comment_count,
-    'comment_count':    db.synonym('_comment_count')
+    'comment_count':    db.synonym('_comment_count'),
+    'order':            posts.c.order
 }, order_by=posts.c.pub_date.desc(), primary_key=[posts.c.post_id])
 db.mapper(SummarizedPost, posts, properties={
     'id':               posts.c.post_id,

zine/shared/js/jquery.ui.core.min.js

+/*
+ * jQuery UI 1.7.3
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
+ */

zine/shared/js/jquery.ui.sortable.min.js

+/*
+ * jQuery UI Sortable 1.7.3
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Sortables
+ *
+ * Depends:
+ *	ui.core.js
+ */

zine/templates/admin/_post_helpers.html

 {% macro render_post_list(posts) %}
   <table class="postlist">
-    <tr>
-      <th class="title">{{ _("Title") }}
-      <th class="author">{{ _("Author") }}
-      <th class="categories">{{ _("Categories") }}
-      <th class="comments">{{ _("Comments") }}
-      <th class="date">{{ _("Date") }}
+    <thead>
+      <tr>
+        <th class="title">{{ _("Title") }}</th>
+        <th class="author">{{ _("Author") }}</th>
+        <th class="categories">{{ _("Categories") }}</th>
+        <th class="comments">{{ _("Comments") }}</th>
+        <th class="date">{{ _("Date") }}</th>
+      </tr>
+    </thead>
+    <tbody>
   {%- for post in posts %}
-    <tr class="{{ loop.cycle('odd', 'even') }}{% if post.is_draft %} draft{% endif %}">
+    <tr id="post-{{ post.id }}" class="post {{ loop.cycle('odd', 'even') }}{% if post.is_draft %} draft{% endif %}">
       <td class="title"><a href="{{ url_for('admin/edit_post', post_id=post.id)
         }}">{%- if post.title %}{{ post.title|e }}{%- else %}<em>#</em>{%- endif %}</a>
         <span class="meta">[<a href="{{ url_for(post) }}">{{ _('show') }}</a>]</span>
   {%- else %}
     <tr><td colspan="5">{{ _("No pages.") }}</td></tr>
   {%- endfor %}
+    </tbody>
   </table>
 {% endmacro %}
 

zine/templates/admin/manage_pages.html

 {% extends "admin/layout.html" %}
 {% from "admin/_post_helpers.html" import render_post_list, render_drafts_box %}
+{% block page_head %}
+  <script type="text/javascript" src="{{ url_for('core/shared', filename='js/jquery.ui.core.min.js') }}"></script>
+  <script type="text/javascript" src="{{ url_for('core/shared', filename='js/jquery.ui.sortable.min.js') }}"></script>
+{{ _("Manage Pages") }}{% endblock %}
 {% block title %}{{ _("Manage Pages") }}{% endblock %}
 {% block contents %}
   <h1>{{ _("Manage Pages") }}</h1>
       <input type="submit" value="{{ _('New Page') }}">
     </div>
   </form>
-  {%- if pagination.necessary %}
-  <div class="pagination">
-    {{ pagination.generate() }}
-  </div>
-  {%- endif %}
+
+  <p>Pages can be rearranged by dragging.</p>
+
+  <form id="order" action="{{ url_for('admin/order_pages') }}" method="post" style="display: none;">
+      <p>
+        The order of the pages changed. It will only be updated to the server
+        after you press the "Save page order" button.
+      </p>
+    <div class="actions">
+      <input type="button" id="save" value="{{ _('Save page order') }}">
+      <input type="button" id="reset" value="{{ _('Reset page order') }}">
+      <input type="hidden" id="action" name="action" value=""/>
+      <input type="hidden" id="orig" value=""/>
+      <input type="hidden" id="cur" name="order" value=""/>
+    </div>
+  </form>
+  <script>
+    function post_order(element) {
+        return String( $(element).sortable("toArray") ).replace(/post-/g, '');
+    };
+
+    $(function () {
+        $('table').sortable({
+            items: 'tbody > tr',
+            axis: 'y',
+            containment: 'parent',
+            tolerance: 'pointer',
+            placeholder: 'ui-sortable-placeholder',
+            forcePlaceholderSize: true,
+
+            stop: function(event, ui) {
+                $('#order #cur')[0].value = post_order(this);
+
+                if( $('#order #cur')[0].value != $('#order #orig')[0].value ) {
+                    $('#order').fadeIn();
+                } else {
+                    $('#order').fadeOut();
+                }
+            }
+        });
+
+        $('#order #orig')[0].value = post_order('table');
+
+        /* Before starting to drag a "tr", explicidly set the size on the
+           contained "td" elements so that they keep alignment! When releasing
+           a row, return things to the normal state.
+        */
+        var dims;
+
+        $('tr.post').bind('mousedown', function () {
+            // Collect cell dimensions from the selected row
+            dims = Array();
+            $(this).children().each(function () {
+                dims.push( Array($(this).height(), $(this).width()) );
+            });
+
+            // Set cell dimensions on all cells in the table
+            $(this).parent().children().each(function () {
+                n = 0;
+                $(this).children().each(function () {
+                    this.height = dims[n][0];
+                    this.width = dims[n][1];
+                    n++;
+                });
+            });
+        }).bind('mouseup', function () {
+            // Reset cell dimension on all cells in the table
+            $(this).parent().children().each(function () {
+                $(this).children().each(function () {
+                    this.height = "";
+                    this.width = "";
+                });
+            });
+        });
+
+        $('#order input[type="button"]').bind('click', function () {
+            $('#order #action')[0].value = this.id;
+            this.form.submit();
+        });
+    });
+  </script>
 {% endblock %}

zine/upgrades/versions/003_post_ordering.py

+"""Post ordering support"""
+# Keep __doc__ to a single line
+from zine.upgrades.versions import *
+
+metadata1 = db.MetaData()
+metadata2 = db.MetaData()
+
+# Define tables here
+posts_old = db.Table('posts', metadata1,
+    db.Column('post_id', db.Integer, primary_key=True),
+    db.Column('pub_date', db.DateTime),
+    db.Column('last_update', db.DateTime),
+    db.Column('slug', db.String(200), index=True, nullable=False),
+    db.Column('uid', db.String(250)),
+    db.Column('title', db.String(150)),
+    db.Column('text_id', db.Integer, db.ForeignKey('texts.text_id')),
+    db.Column('author_id', db.Integer, db.ForeignKey('users.user_id')),
+    db.Column('comments_enabled', db.Boolean),
+    db.Column('comment_count', db.Integer, nullable=False, default=0),
+    db.Column('pings_enabled', db.Boolean),
+    db.Column('content_type', db.String(40), index=True),
+    db.Column('status', db.Integer),
+)
+
+posts_new = db.Table('posts', metadata2,
+    db.Column('post_id', db.Integer, primary_key=True),
+    db.Column('pub_date', db.DateTime),
+    db.Column('last_update', db.DateTime),
+    db.Column('slug', db.String(200), index=True, nullable=False),
+    db.Column('uid', db.String(250)),
+    db.Column('title', db.String(150)),
+    db.Column('text_id', db.Integer, db.ForeignKey('texts.text_id')),
+    db.Column('author_id', db.Integer, db.ForeignKey('users.user_id')),
+    db.Column('comments_enabled', db.Boolean),
+    db.Column('comment_count', db.Integer, nullable=False, default=0),
+    db.Column('pings_enabled', db.Boolean),
+    db.Column('content_type', db.String(40), index=True),
+    db.Column('status', db.Integer),
+    db.Column('order', db.Integer),
+)
+
+# Define the objects here
+
+
+def map_tables(mapper):
+    clear_mappers()
+    # Map tables to the python objects here
+
+
+def upgrade(migrate_engine):
+    # Upgrade operations go here. Don't create your own engine
+    # bind migrate_engine to your metadata
+    session = scoped_session(lambda: create_session(migrate_engine,
+                                                    autoflush=True,
+                                                    autocommit=False))
+    map_tables(session.mapper)
+
+    metadata1.bind = migrate_engine
+    metadata2.bind = migrate_engine
+
+    yield '<ul>'
+    yield '  <li>Update the posts table adding "order" column</li>\n'
+    yield '</ul>'
+
+    posts_new.create_column('order')
+
+
+
+def downgrade(migrate_engine):
+    # Operations to reverse the above upgrade go here.
+    session = scoped_session(lambda: create_session(migrate_engine,
+                                                    autoflush=True,
+                                                    autocommit=False))
+    map_tables(session.mapper)
+
+    metadata1.bind = migrate_engine
+    metadata2.bind = migrate_engine
+
+    yield '<ul>'
+    yield '  <li>Update the posts table removing "order" column</li>\n'
+    yield '</ul>'
+
+    posts_new.drop_column('order')
+
         Rule('/pages/', endpoint='admin/manage_pages', defaults={'page': 1}),
         Rule('/pages/page/<int:page>', endpoint='admin/manage_pages'),
         Rule('/pages/new', endpoint='admin/new_page'),
+        Rule('/pages/order', endpoint='admin/order_pages'),
         Rule('/p/<int:post_id>', endpoint='admin/edit_post'),
         Rule('/p/<int:post_id>/delete', endpoint='admin/delete_post'),
         Rule('/p/<int:post_id>/comments', defaults={'page': 1, 'per_page': 20},

zine/views/__init__.py

     'admin/manage_entries':     admin.manage_entries,
     'admin/new_page':           admin.edit_page,
     'admin/manage_pages':       admin.manage_pages,
+    'admin/order_pages':        admin.order_pages,
     'admin/edit_post':          admin.dispatch_post_edit,
     'admin/delete_post':        admin.dispatch_post_delete,
     'admin/manage_comments':    admin.manage_comments,

zine/views/admin.py

 def manage_pages(request, page):
     """Show a list of pages."""
     page_query = Post.query.type('page')
-    pages = page_query.limit(PER_PAGE).offset(PER_PAGE * (page - 1)).all()
-    pagination = AdminPagination('admin/manage_pages', page, PER_PAGE,
-                                 page_query.count())
+    pages = page_query.order_by('"order"').all()
     if not pages and page != 1:
         raise NotFound()
     return render_admin_response('admin/manage_pages.html', 'manage.pages',
-                                 pages=pages, pagination=pagination)
+                                 pages=pages)
 
+@require_admin_privilege(CREATE_PAGES | EDIT_OWN_PAGES | EDIT_OTHER_PAGES)
+def order_pages(request):
+    """Order pages and redirect to ``manage_pages``"""
+
+    if request.method == 'POST':
+        if 'action' in request.form and request.form['action'] == 'save':
+            order = map( int, request.form['order'].split(',') )
+            print order
+
+            for index,page_id in enumerate(order):
+                post = Post.query.get(page_id)
+                if post:
+                    post.order = index
+
+            flash(u'Page order saved.')
+            db.commit()
+    
+    return redirect_to('admin/manage_pages')
 
 @require_admin_privilege(EDIT_OWN_PAGES | EDIT_OTHER_PAGES)
 def edit_page(request, post=None):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.