]> git.ozlabs.org Git - patchwork/commitdiff
Bundle reordering support
authorJeremy Kerr <jk@ozlabs.org>
Sun, 8 Feb 2009 10:40:17 +0000 (21:40 +1100)
committerJeremy Kerr <jk@ozlabs.org>
Sun, 8 Feb 2009 10:44:25 +0000 (21:44 +1100)
Bundles can now be reordered and saved.

Add dependency on jquery in INSTALL.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
apps/patchwork/models.py
apps/patchwork/utils.py
apps/patchwork/views/bundle.py
apps/patchwork/views/patch.py
docs/INSTALL
htdocs/css/style.css
htdocs/js/bundle.js
htdocs/js/jquery-1.2.6.js [deleted symlink]
templates/patchwork/bundle.html
templates/patchwork/patch-list.html

index d0c2a6ed5d4c467fe289aa2ea1eddc64096c6786..a672f9ad41ebe5f16879db45a655a42b2c5643c8 100644 (file)
@@ -295,12 +295,25 @@ class Bundle(models.Model):
     def n_patches(self):
         return self.patches.all().count()
 
+    def ordered_patches(self):
+        return self.patches.order_by('bundlepatch__order');
+
     def append_patch(self, patch):
         # todo: use the aggregate queries in django 1.1
-        orders = BundlePatch.objects.filter(bundle = self).values('order')
-        max_order = max([ v for (k, v) in orders])
+        orders = BundlePatch.objects.filter(bundle = self).order_by('-order') \
+                 .values('order')
+
+        if len(orders) > 0:
+            max_order = orders[0]['order']
+        else:
+            max_order = 0
+
+        # see if the patch is already in this bundle
+        if BundlePatch.objects.filter(bundle = self, patch = patch).count():
+            raise Exception("patch is already in bundle")
 
-        bp = BundlePatch.objects.create(bundle = self, patch = patch, order = max_order + 1)
+        bp = BundlePatch.objects.create(bundle = self, patch = patch,
+                order = max_order + 1)
         bp.save()
 
     class Meta:
@@ -327,7 +340,8 @@ class BundlePatch(models.Model):
     order = models.IntegerField()
 
     class Meta:
-        unique_together = [('bundle', 'patch'), ('bundle', 'order')]
+        unique_together = [('bundle', 'patch')]
+        ordering = ['order']
 
 class UserPersonConfirmation(models.Model):
     user = models.ForeignKey(User)
index 63daa85c541fd77a845a3e9a952222cd827e5742..5bd6925b6bf0065ee2beaf30b60f354c2d4de5c0 100644 (file)
@@ -19,7 +19,7 @@
 
 
 from patchwork.forms import MultiplePatchForm
-from patchwork.models import Bundle, Project, State, UserProfile
+from patchwork.models import Bundle, Project, BundlePatch, State, UserProfile
 from django.conf import settings
 from django.shortcuts import render_to_response, get_object_or_404
 
@@ -100,35 +100,35 @@ def set_bundle(user, project, action, data, patches, context):
         bundle = Bundle(owner = user, project = project,
                 name = data['bundle_name'])
         bundle.save()
-        str = 'added to new bundle "%s"' % bundle.name
-        auth_required = False
+        context.add_message("Bundle %s created" % bundle.name)
 
     elif action =='add':
         bundle = get_object_or_404(Bundle, id = data['bundle_id'])
-        str = 'added to bundle "%s"' % bundle.name
-        auth_required = False
 
     elif action =='remove':
         bundle = get_object_or_404(Bundle, id = data['removed_bundle_id'])
-        str = 'removed from bundle "%s"' % bundle.name
-        auth_required = False
 
     if not bundle:
         return ['no such bundle']
 
     for patch in patches:
         if action == 'create' or action == 'add':
-            bundle.append_patch(patch)
+            try:
+                bundle.append_patch(patch)
+                context.add_message("Patch '%s' added to bundle %s" % \
+                        (patch.name, bundle.name))
+            except Exception, ex:
+                context.add_message("Couldn't add patch '%s' to bundle: %s" % \
+                        (patch.name, ex.message))
 
         elif action == 'remove':
-            bundle.patches.remove(patch)
-
-    if len(patches) > 0:
-        if len(patches) == 1:
-            str = 'patch ' + str
-        else:
-            str = 'patches ' + str
-        context.add_message(str)
+            try:
+                bp = BundlePatch.objects.get(bundle = bundle, patch = patch)
+                bp.delete()
+                context.add_message("Patch '%s' removed from bundle %s\n" % \
+                        (patch.name, bundle.name))
+            except Exception:
+                pass
 
     bundle.save()
 
index d8e4e2fc82ca65282bd78037cec9b5719d03a62f..9995fc6c4b3a76c1ac6ae0d14cf3f9c3a8fa464f 100644 (file)
@@ -23,7 +23,7 @@ from django.shortcuts import render_to_response, get_object_or_404
 from patchwork.requestcontext import PatchworkRequestContext
 from django.http import HttpResponse, HttpResponseRedirect
 import django.core.urlresolvers
-from patchwork.models import Patch, Bundle, Project
+from patchwork.models import Patch, Bundle, BundlePatch, Project
 from patchwork.utils import get_patch_ids
 from patchwork.forms import BundleForm, DeleteBundleForm
 from patchwork.views import generic_list
@@ -49,7 +49,10 @@ def setbundle(request):
             patch_id = request.POST.get('patch_id', None)
             if patch_id:
                 patch = get_object_or_404(Patch, id = patch_id)
-                bundle.patches.add(patch)
+                try:
+                    bundle.append_patch(patch)
+                except Exception:
+                    pass
             bundle.save()
         elif action == 'add':
             bundle = get_object_or_404(Bundle,
@@ -65,7 +68,7 @@ def setbundle(request):
             for id in patch_ids:
                 try:
                     patch = Patch.objects.get(id = id)
-                    bundle.patches.add(patch)
+                    bundle.append_patch(patch)
                 except ex:
                     pass
 
@@ -143,11 +146,23 @@ def bundle(request, bundle_id):
     else:
         form = BundleForm(instance = bundle)
 
+    if request.method == 'POST' and request.POST.get('form') == 'reorderform':
+        order = get_object_or_404(BundlePatch, bundle = bundle,
+                        patch__id = request.POST.get('order_start')).order
+
+        for patch_id in request.POST.getlist('neworder'):
+            bundlepatch = get_object_or_404(BundlePatch,
+                        bundle = bundle, patch__id = patch_id)
+            bundlepatch.order = order
+            bundlepatch.save()
+            order += 1
+
     context = generic_list(request, bundle.project,
             'patchwork.views.bundle.bundle',
             view_args = {'bundle_id': bundle_id},
             filter_settings = filter_settings,
-            patches = bundle.patches.all())
+            patches = bundle.ordered_patches(),
+            editable_order = True)
 
     context['bundle'] = bundle
     context['bundleform'] = form
index 72472cac646925e6e4f4d70a6955d27fdabcc935..49843eb2cfad3c99c5dfc62707d829ddd66f793c 100644 (file)
@@ -59,7 +59,7 @@ def patch(request, patch_id):
                     data = request.POST)
             if createbundleform.is_valid():
                 createbundleform.save()
-                bundle.patches.add(patch)
+                bundle.append_patch(patch)
                 bundle.save()
                 createbundleform = CreateBundleForm()
                 context.add_message('Bundle %s created' % bundle.name)
@@ -67,9 +67,13 @@ def patch(request, patch_id):
         elif action == 'addtobundle':
             bundle = get_object_or_404(Bundle, id = \
                         request.POST.get('bundle_id'))
-            bundle.patches.add(patch)
-            bundle.save()
-            context.add_message('Patch added to bundle "%s"' % bundle.name)
+            try:
+                bundle.append_patch(patch)
+                bundle.save()
+                context.add_message('Patch added to bundle "%s"' % bundle.name)
+            except Exception, ex:
+                context.add_message("Couldn't add patch '%s' to bundle %s: %s" \
+                        % (patch.name, bundle.name, ex.message))
 
         # all other actions require edit privs
         elif not editable:
index 42822e3650aac51c9b788c15a73dbecb51174b07..5075f9e0721b6cb1cfaacbb7e6bf73b1e86b4acc 100644 (file)
@@ -70,6 +70,18 @@ in brackets):
          cd ../../apps
          ln -s ../lib/packages/django-registration ./registration
 
+               We also use some Javascript libraries:
+
+                cd lib/packages
+                mkdir jquery
+                cd jquery
+                wget http://jqueryjs.googlecode.com/files/jquery-1.3.min.js
+                wget http://www.isocra.com/articles/jquery.tablednd_0_5.js.zip
+                unzip jquery.tablednd_0_5.js.zip jquery.tablednd_0_5.js
+                cd ../../../htdocs/js/
+                ln -s ../../lib/packages/jquery/jquery-1.3.min.js ./
+                ln -s ../../lib/packages/jquery/jquery.tablednd_0_5.js ./
+
        The settings.py file contains default settings for patchwork, you'll
        need to configure settings for your own setup.
 
index 4d1e4406bbe5c6611d28c4d019705fa23b55a47d..1813c2014e49d5f67427228740f0abff23e195b4 100644 (file)
@@ -152,7 +152,13 @@ table.patchlist td.patchlistfilters {
        border-top: thin solid gray;
        border-bottom: thin solid black;
        font-size: smaller;
-
+}
+table.patchlist td.patchlistreorder {
+       background: #c0c0ff;
+       border-top: thin solid gray;
+       border-bottom: thin solid black;
+       font-size: smaller;
+       text-align: right;
 }
 table.patchlist tr.odd {
        background: #ffffff;
@@ -178,6 +184,25 @@ div.patchforms {
        margin-top: 2em;
 }
 
+/* list order manipulation */
+
+table.patchlist tr.draghover {
+       background: #e8e8e8 !important;
+}
+
+.dragging {
+       border: thin solid black;
+       background: #e8e8e8 !important;
+}
+
+input#reorder-cancel {
+       display: none;
+       color: #505050;
+}
+
+input#reorder-change {
+}
+
 /* list pagination */
 .paginator { padding-bottom: 1em; padding-top: 1em; font-size: 80%; }
 
index dc4fb9c5958da97b3d1dbfcfba6dee694d624944..0bdf41aeade11c6ac9ab57b73ca68e8d892195c8 100644 (file)
@@ -1,41 +1,82 @@
-function parse_patch_id(id_str)
+
+var editing_order = false;
+var dragging = false;
+
+function order_button_click(node)
 {
-    var i;
+    var rows, form;
 
-    i = id_str.indexOf(':');
-    if (i == -1)
-        return null;
+    form = $("#reorderform");
+    rows = $("#patchlist").get(0).tBodies[0].rows;
 
-    return id_str.substring(i + 1);
-}
+    if (rows.length < 1)
+        return;
 
-function bundle_handle_drop(table, row)
-{
-    var relative, relation, current;
-    var relative_id, current_id;
+    if (editing_order) {
 
-    current = $(row);
-    relative = $(current).prev();
-    relation = 'after';
+        /* disable the save button */
+        node.disabled = true;
 
-    /* if we have no previous row, position ourselves before the next
-     * row instead */
-    if (!relative.length) {
-        relative = current.next();
-        relation = 'before';
+        /* add input elements as the sequence of patches */
+        for (var i = 0; i < rows.length; i++) {
+            form.append('<input type="hidden" name="neworder" value="' +
+                    row_to_patch_id(rows[i]) + '"/>');
+        }
 
-        if (!relative)
-            return;
+        form.get(0).submit();
+    } else {
+
+        /* store the first order value */
+        start_order = row_to_patch_id(rows[0]);
+        $("input[name='order_start']").attr("value", start_order);
+
+        /* update buttons */
+        node.setAttribute("value", "Save order");
+        $("#reorder\\-cancel").css("display", "inline");
+
+        /* show help text */
+        $("#reorderhelp").text('Drag & drop rows to reorder');
+
+        /* enable drag & drop on the patches list */
+        $("#patchlist").tableDnD({
+            onDragClass: 'dragging',
+            onDragStart: function() { dragging = true; },
+            onDrop: function() { dragging = false; }
+        });
+
+        /* replace zebra striping with hover */
+        $("#patchlist tbody tr").css("background", "inherit");
+        $("#patchlist tbody tr").hover(drag_hover_in, drag_hover_out);
     }
 
-    current_id = parse_patch_id(current.attr('id'));
-    relative_id = parse_patch_id(relative.attr('id'));
+    editing_order = !editing_order;
+}
 
-    alert("put patch " + current_id + " " + relation + " " + relative_id);
+function order_cancel_click(node)
+{
+    node.form.submit();
 }
 
-$(document).ready(function() {
-    $("#patchlist").tableDnD({
-        onDrop: bundle_handle_drop
-    });
-});
+/* dragging helper functions */
+function drag_hover_in()
+{
+    if (!dragging)
+        $(this).addClass("draghover");
+}
+function drag_hover_out()
+{
+    $(this).removeClass("draghover");
+}
+
+function row_to_patch_id(node)
+{
+    var id_str, i;
+
+    id_str = node.getAttribute("id");
+
+    i = id_str.indexOf(':');
+    if (i == -1)
+        return null;
+
+    return id_str.substring(i + 1);
+}
diff --git a/htdocs/js/jquery-1.2.6.js b/htdocs/js/jquery-1.2.6.js
deleted file mode 120000 (symlink)
index cb24de6..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../lib/packages/jquery/jquery-1.2.6.js
\ No newline at end of file
index d9a2785c52e67c7f52e75653289f7e589aadba2c..616a62ee93551c30b2ae13eaeb2b2814b4e57dae 100644 (file)
@@ -4,7 +4,7 @@
 
 {% block headers %}
   <script language="JavaScript" type="text/javascript"
-   src="/js/jquery-1.2.6.js">
+   src="/js/jquery-1.3.min.js">
   </script>
   <script language="JavaScript" type="text/javascript"
    src="/js/jquery.tablednd_0_5.js">
index 5518805b036b52434637f0c02792003e8a6b0863..d4dd325c61fc4899548dd3cb00edc4495f2c7480 100644 (file)
@@ -9,6 +9,19 @@
   <td class="patchlistfilters">
  {% include "patchwork/filters.html" %}
   </td>
+ {% if order.editable %}
+  <td class="patchlistreorder">
+   <form method="post" id="reorderform">
+    <input type="hidden" name="form" value="reorderform"/>
+    <input type="hidden" name="order_start" value="0"/>
+    <span id="reorderhelp"></span>
+    <input id="reorder-cancel" type="button" value="Cancel"
+     onClick="order_cancel_click(this)"/>
+    <input id="reorder-change" type="button" value="Change order"
+     onClick="order_button_click(this)"/>
+    </form>
+  </td>
+ {% endif %}
  </tr>
 </table>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Patch</a>
     {% else %}
+     {% if not order.editable %}
      <a class="colinactive" href="{% listurl order="name" %}">Patch</a>
+     {% else %}
+     <span class="colinactive">Patch</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Date</a>
     {% else %}
+     {% if not order.editable %}
      <a class="colinactive" href="{% listurl order="date" %}">Date</a>
+     {% else %}
+     <span class="colinactive">Date</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Submitter</a>
     {% else %}
+     {% if not order.editable %}
      <a class="colinactive" href="{% listurl order="submitter" %}">Submitter</a>
+     {% else %}
+     <span class="colinactive">Submitter</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Delegate</a>
     {% else %}
+     {% if not order.editable %}
      <a class="colinactive" href="{% listurl order="delegate" %}">Delegate</a>
+     {% else %}
+     <span class="colinactive">Delegate</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">State</a>
     {% else %}
+     {% if not order.editable %}
      <a class="colinactive" href="{% listurl order="state" %}">State</a>
+     {% else %}
+     <span class="colinactive">State</span>
+     {% endif %}
     {% endifequal %}
    </th>