]> 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 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
     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:
         bp.save()
 
     class Meta:
@@ -327,7 +340,8 @@ class BundlePatch(models.Model):
     order = models.IntegerField()
 
     class Meta:
     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)
 
 class UserPersonConfirmation(models.Model):
     user = models.ForeignKey(User)
index 63daa85c541fd77a845a3e9a952222cd827e5742..5bd6925b6bf0065ee2beaf30b60f354c2d4de5c0 100644 (file)
@@ -19,7 +19,7 @@
 
 
 from patchwork.forms import MultiplePatchForm
 
 
 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
 
 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()
         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'])
 
     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'])
 
     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':
 
     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':
 
         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()
 
 
     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.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
 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)
             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,
             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)
             for id in patch_ids:
                 try:
                     patch = Patch.objects.get(id = id)
-                    bundle.patches.add(patch)
+                    bundle.append_patch(patch)
                 except ex:
                     pass
 
                 except ex:
                     pass
 
@@ -143,11 +146,23 @@ def bundle(request, bundle_id):
     else:
         form = BundleForm(instance = bundle)
 
     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,
     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
 
     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()
                     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)
                 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'))
         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:
 
         # 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
 
          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.
 
        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;
        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;
 }
 table.patchlist tr.odd {
        background: #ffffff;
@@ -178,6 +184,25 @@ div.patchforms {
        margin-top: 2em;
 }
 
        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%; }
 
 /* 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"
 
 {% 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">
   </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>
   <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>
 
  </tr>
 </table>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Patch</a>
     {% else %}
      ></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>
      <a class="colinactive" href="{% listurl order="name" %}">Patch</a>
+     {% else %}
+     <span class="colinactive">Patch</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Date</a>
     {% else %}
      ></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>
      <a class="colinactive" href="{% listurl order="date" %}">Date</a>
+     {% else %}
+     <span class="colinactive">Date</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Submitter</a>
     {% else %}
      ></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>
      <a class="colinactive" href="{% listurl order="submitter" %}">Submitter</a>
+     {% else %}
+     <span class="colinactive">Submitter</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">Delegate</a>
     {% else %}
      ></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>
      <a class="colinactive" href="{% listurl order="delegate" %}">Delegate</a>
+     {% else %}
+     <span class="colinactive">Delegate</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
     {% endifequal %}
    </th>
 
      ></a> <a class="colactive"
       href="{% listurl order=order.reversed_name %}">State</a>
     {% else %}
      ></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>
      <a class="colinactive" href="{% listurl order="state" %}">State</a>
+     {% else %}
+     <span class="colinactive">State</span>
+     {% endif %}
     {% endifequal %}
    </th>
 
     {% endifequal %}
    </th>