changeset 177:86129d185ddb

Add versioning to bundles Some other bundle-related changes were made, including: * Editing the snippetform CSS and HTML to allow bundle/form.djhtml to be reused for editing * Changing {% block title %} to {% block section %} in the base template for bundles to allow for more flexibility when creating breadcrumbs * Saved common URL patterns in variables in bundle/urls.py * Renamed explore.html to explore.djhtml for consistency You should now be able to upload new versions as well as view the files (or a particular file) for a bundle at a specific version. Coming soon: the ability to add a timestamp and a comment for each new uploaded version (if this feature is desirable).
author dellsystem <ilostwaldo@gmail.com>
date Sat, 20 Oct 2012 23:28:50 -0400
parents c042d26e6936
children df160069b769
files apps/bundle/forms.py apps/bundle/models.py apps/bundle/tasks.py apps/bundle/urls.py apps/bundle/views.py static/css/agora.less templates/bundle/base.djhtml templates/bundle/bundle.djhtml templates/bundle/edit.djhtml templates/bundle/explore.djhtml templates/bundle/explore.html templates/bundle/form.djhtml templates/bundle/index.djhtml templates/simple_field.djhtml
diffstat 14 files changed, 215 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/apps/bundle/forms.py
+++ b/apps/bundle/forms.py
@@ -6,7 +6,21 @@
 class BundleForm(forms.ModelForm):
     class Meta:
         model = Bundle
-        fields = ('uploader', 'name', 'description', 'free_license')
+        fields = ('name', 'description', 'free_license')
 
     file = forms.FileField(help_text=("Upload a plain text file or an \
         archive file."))
+
+
+class BundleEditForm(forms.ModelForm):
+    """
+    Like BundleForm, but for editing bundles. A new form is needed because
+    the name field should not be editable after creation, and because the
+    file field shouldn't be required in this case
+    """
+    class Meta:
+        model = Bundle
+        fields = ('description', 'free_license')
+
+    file = forms.FileField(help_text=("Upload a plain text file or an \
+        archive file to update the version."), required=False)
--- a/apps/bundle/models.py
+++ b/apps/bundle/models.py
@@ -28,6 +28,7 @@
     mod_date = models.DateTimeField('date last modified', auto_now=True)
     done_uploading = models.BooleanField(default=False)
     file_name = models.CharField(max_length=256) # the uploaded file
+    latest_version = models.IntegerField(default=1)
 
     def __unicode__(self):
         return self.name
@@ -37,11 +38,16 @@
         return ('bundle_details', [self.uploader.username, self.name])
 
     def get_temp_path(self):
-        return os.path.join('tmp', 'bundles', '%s' % self.id)
+        """
+        Returns the path to where the file is initially uploaded
+        """
+        return os.path.join('tmp', 'bundles',
+            "%d_%d" % (self.id, self.latest_version))
 
 
 class BundleFile(MPTTModel):
     bundle = models.ForeignKey(Bundle)
+    version = models.IntegerField()
     parent = TreeForeignKey('self', null=True, blank=True,
         related_name='children')
     name = models.CharField(max_length=256)
@@ -86,5 +92,6 @@
         return ('bundlefile_details', [
             self.bundle.uploader.username,
             self.bundle.name,
+            self.version,
             self.get_path()
         ])
--- a/apps/bundle/tasks.py
+++ b/apps/bundle/tasks.py
@@ -30,7 +30,8 @@
         filename = os.path.basename(file_path)
         full_path = file_path[len(bundle.get_temp_path()) + 1:]
         bundle_file = BundleFile(bundle=bundle, name=filename,
-            parent=parent_dir, full_path=full_path)
+            parent=parent_dir, full_path=full_path,
+            version=bundle.latest_version)
 
         if file_path in files:
             bundle_file.is_dir = False
@@ -79,7 +80,8 @@
     elif mime_type.startswith('text/'):
         # Should be a plain text file - create a CodeFile for it
         bundle_file = BundleFile(bundle=bundle, name=bundle.file_name,
-            full_path=bundle.file_name, file_size=os.path.getsize(file))
+            full_path=bundle.file_name, file_size=os.path.getsize(file),
+            version=bundle.latest_version)
         bundle_file.save_file_contents(open(file, 'rt'),
             original_filename=bundle.file_name)
 
--- a/apps/bundle/urls.py
+++ b/apps/bundle/urls.py
@@ -1,10 +1,17 @@
 from django.conf.urls.defaults import *
 
 
+BUNDLE_PATTERN = r'^(?P<user>[^/]*)/(?P<bundle>[^/]+)'
+VERSION_PATTERN = '(?P<version>\d+)'
+
+
 urlpatterns = patterns('apps.bundle.views',
-    url(r'^(?P<user>[^/]+)/(?P<bundle>[^/]+)/(?P<path>.+)/$', 'file_detail',
-        name='bundlefile_details'),
-    url(r'^(?P<user>.*)/(?P<bundle>.*)/$', 'detail', name='bundle_details'),
+    url(BUNDLE_PATTERN + '/?$', 'detail', name='bundle_details'),
+    url(BUNDLE_PATTERN + '/' + VERSION_PATTERN + '/?$', 'detail',
+        name='bundle_version'),
+    url(BUNDLE_PATTERN + '/edit', 'edit', name='bundle_edit'),
+    url(BUNDLE_PATTERN + '/' + VERSION_PATTERN + '/(?P<path>.+)/?$',
+        'file_detail', name='bundlefile_details'),
     url(r'^$', 'index', name='bundle_new'),
     url(r'^explore$', 'explore', name='bundle_explore'),
 )
--- a/apps/bundle/views.py
+++ b/apps/bundle/views.py
@@ -7,14 +7,16 @@
 from django.http import HttpResponse
 
 from apps.bundle.models import Bundle, BundleFile
-from apps.bundle.forms import BundleForm
+from apps.bundle.forms import BundleForm, BundleEditForm
 from apps.bundle.tasks import handle_bundle_upload
 from apps.pygments_style.models import PygmentsStyle
 
 
-def detail(request, user, bundle, file=None):
+def detail(request, user, bundle, file=None, version=0):
     bundle = get_object_or_404(Bundle, uploader__username=user, name=bundle)
-    files = bundle.bundlefile_set.all()
+    # If the version is not set, use the latest version
+    version = int(version) or bundle.latest_version
+    files = bundle.bundlefile_set.filter(version=version)
 
     if request.user.is_authenticated():
         pygments_style = request.user.get_profile().pygments_style
@@ -27,28 +29,31 @@
         'bundle': bundle,
         'files': files,
         'file': file,
+        'previous_versions': xrange(1, bundle.latest_version + 1),
+        'this_version': version,
     }
 
     return render(request, 'bundle/bundle.djhtml', context)
 
 
-def file_detail(request, user, bundle, path):
+def file_detail(request, user, bundle, version, path):
+    print version
     bundle_file = get_object_or_404(BundleFile, bundle__uploader__username=user,
-        bundle__name=bundle, full_path=path, is_dir=False)
+        bundle__name=bundle, full_path=path, is_dir=False, version=version)
 
-    return detail(request, user, bundle, file=bundle_file)
+    return detail(request, user, bundle, file=bundle_file, version=version)
+
 
 @login_required
 def index(request):
     if request.method == 'POST':
         post_data = request.POST.copy()
-        post_data['uploader'] = request.user.id
-        form = BundleForm(post_data,
-                          request.FILES)
+        bundle = Bundle(uploader=request.user)
+        form = BundleForm(post_data, request.FILES, instance=bundle)
 
         if form.is_valid():
             file = request.FILES.get('file')
-            bundle = form.save()
+            form.save()
 
             bundle.file_name = file.name
             bundle.save()
@@ -76,4 +81,45 @@
         'recent_bundles': Bundle.objects.all()[:20]
     }
 
-    return render(request, "bundle/explore.html", context)
+    return render(request, "bundle/explore.djhtml", context)
+
+
+@login_required
+def edit(request, user, bundle):
+    bundle = get_object_or_404(Bundle, name=bundle,
+        uploader__username=request.user.username)
+
+    # If the username specified in the URL is someone else's, show that page
+    if user != request.user.username:
+        # The bundle must exist, otherwise it would 404
+        return redirect(bundle)
+
+    if request.method == 'POST':
+        form = BundleEditForm(request.POST, instance=bundle)
+
+        if form.is_valid():
+            form.save()
+
+            file = request.FILES.get('file')
+            if file is not None:
+                bundle.done_uploading = False
+                bundle.file_name = file.name
+                bundle.latest_version += 1
+                bundle.save()
+                bundle_path = bundle.get_temp_path()
+
+                with open(bundle_path, 'wb+') as destination:
+                    for chunk in request.FILES.get('file', []):
+                        destination.write(chunk)
+
+                handle_bundle_upload.delay(bundle.id)
+            return redirect(bundle)
+    else:
+        form = BundleEditForm(instance=bundle)
+
+    context = {
+        'bundle': bundle,
+        'form': form,
+    }
+
+    return render(request, "bundle/edit.djhtml", context)
--- a/static/css/agora.less
+++ b/static/css/agora.less
@@ -190,6 +190,7 @@
 
 .snippetform {
     input[type=text], textarea {
+        display: block;
         width: @nonSidebarWidth - @inputPadding * 2 - 2;
         padding: 5px;
         margin: 10px 0;
@@ -415,3 +416,7 @@
     margin-left: 0;
     padding-left: 18px;
 }
+
+.form-field {
+    margin-bottom: 10px;
+}
--- a/templates/bundle/base.djhtml
+++ b/templates/bundle/base.djhtml
@@ -1,5 +1,5 @@
 {% extends "base.djhtml" %}
 
 {% block breadcrumbs %}
-<a href="{% url bundle_new %}">Bundles</a> &raquo; {% block title %}{% endblock %}
+<a href="{% url bundle_new %}">Bundles</a> &raquo; {% block section %}{% endblock %}
 {% endblock %}
--- a/templates/bundle/bundle.djhtml
+++ b/templates/bundle/bundle.djhtml
@@ -5,16 +5,33 @@
 {% load sizefieldtags %}
 
 
+{% block section %}
+<a href="{{ bundle.get_absolute_url }}">{{ bundle }}</a>
+by <a href="{{ bundle.uploader.get_absolute_url }}">{{ bundle.uploader }}</a>
+(version {{ this_version }})
+{% if file %}
+&raquo;
+{{ file }}
+{% endif %}
+{% endblock %}
+
 {% block title %}
-{{ bundle.name }} by {{ bundle.uploader }}
-{% endblock title %}
+{% if file %}
+{{ file }} in
+{% endif %}
+{{ bundle }} by {{ bundle.uploader }}
+{% endblock %}
 
 
 {% block content %}
+<div class="right-float">
+<h2><a href="{% url bundle_edit request.user.username bundle.name %}">Edit this bundle</a></h2>
+</div>
 <h1>
 <a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a>
 by
 <a href="{{ bundle.uploader.get_absolute_url }}">{{ bundle.uploader }}</a>
+(version {{ this_version }})
 </h1>
 
 {% if not bundle.done_uploading %}
@@ -57,6 +74,21 @@
     {% else %}
     <p><strong>Description:</strong> {{ bundle.description|default:"N/A" }}</p>
     <p><strong>License:</strong> {{ bundle.free_license }}</p>
+    <p><strong>Latest version number:</strong> {{ bundle.latest_version }}</p>
+    {% if previous_versions %}
+    <h3>Versions</h3>
+    <ul>
+    {% for version in previous_versions %}
+        <li>
+            <a href="{{ bundle.get_absolute_url }}/{{ version }}">
+                {% if version == this_version %}<strong>{% endif %}
+                Version {{ version }}
+                {% if version == this_version %}</strong>{% endif %}
+            </a>
+        </li>
+    {% endfor %}
+    </ul>
+    {% endif %}
     {% endif %}
 </div>
 {% else %}
new file mode 100644
--- /dev/null
+++ b/templates/bundle/edit.djhtml
@@ -0,0 +1,23 @@
+{% extends "bundle/base.djhtml" %}
+
+
+{% block title %}
+Editing {{ bundle }}
+{% endblock %}
+
+{% block section %}
+<a href="{{ bundle.get_absolute_url }}">{{ bundle }}</a>
+&raquo;
+Edit
+{% endblock %}
+
+{% block content %}
+<h1>Editing {{ bundle }}</h1>
+
+<p class="hint">
+You can't change a bundle's name after creating it. If you need to change
+the name, simply create a new bundle using the desired name.
+</p>
+
+{% include "bundle/form.djhtml" %}
+{% endblock %}
new file mode 100644
--- /dev/null
+++ b/templates/bundle/explore.djhtml
@@ -0,0 +1,41 @@
+{% extends "bundle/base.djhtml" %}
+
+
+{% load i18n %}
+
+
+{% block title %}
+{% trans "Explore" %}
+{% endblock %}
+
+
+{% block content %}
+<h1>{% trans "Recent bundles" %}</h1>
+
+{% if recent_bundles %}
+<table class="default">
+    <thead>
+        <tr>
+            <th>{% trans "Bundle name" %}</th>
+            <th>{% trans "Created on" %}</th>
+            <th>{% trans "User" %}</th>
+        </tr>
+    </thead>
+    <tbody>
+        {% for bundle in recent_bundles %}
+        <tr>
+            <td><a href="{{ bundle.get_absolute_url }}">{{ bundle }}</a></td>
+            <td>{{ bundle.pub_date }}</td>
+            <td>
+                <a href="{{ bundle.uploader.get_absolute_url }}">
+                    {{ bundle.uploader }}
+                </a>
+            </td>
+        </tr>
+        {% endfor %}
+    </tbody>
+</table>
+{% else %}
+<p>{% trans "No bundles have been created yet!" %}</p>
+{% endif %}
+{% endblock %}
deleted file mode 100644
--- a/templates/bundle/explore.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "bundle/base.djhtml" %}
-
-
-{% load i18n %}
-
-
-{% block title %}
-{% trans "Explore" %}
-{% endblock %}
-
-
-{% block content %}
-<h1>{% trans "Recent bundles" %}</h1>
-
-{% if recent_bundles %}
-<table class="default">
-    <thead>
-        <tr>
-            <th>{% trans "Bundle name" %}</th>
-            <th>{% trans "Created on" %}</th>
-            <th>{% trans "User" %}</th>
-        </tr>
-    </thead>
-    <tbody>
-        {% for bundle in recent_bundles %}
-        <tr>
-            <td><a href="{{ bundle.get_absolute_url }}">{{ bundle }}</a></td>
-            <td>{{ bundle.pub_date }}</td>
-            <td>
-                <a href="{{ bundle.uploader.get_absolute_url }}">
-                    {{ bundle.uploader }}
-                </a>
-            </td>
-        </tr>
-        {% endfor %}
-    </tbody>
-</table>
-{% else %}
-<p>{% trans "No bundles have been created yet!" %}</p>
-{% endif %}
-{% endblock %}
--- a/templates/bundle/form.djhtml
+++ b/templates/bundle/form.djhtml
@@ -10,23 +10,9 @@
 
     {% csrf_token %}
 
-    {% with field=form.name %}
+    {% for field in form %}
     {% include "simple_field.djhtml" %}
-    {% endwith %}
-
-    {% with field=form.description %}
-    {% include "simple_field.djhtml" %}
-    {% endwith %}
-
-    {% with field=form.free_license %}
-    {% include "simple_field.djhtml" %}
-    {% endwith %}
-
-    <br /><br />
-
-    {% with field=form.file %}
-    {% include "simple_field.djhtml" %}
-    {% endwith %}
+    {% endfor %}
 
     <br />
     <div class="center-align">
--- a/templates/bundle/index.djhtml
+++ b/templates/bundle/index.djhtml
@@ -5,6 +5,10 @@
 New bundle
 {% endblock title %}
 
+{% block section %}
+New bundle
+{% endblock %}
+
 {% block content %}
 <div id="non-sidebar">
     <h1>{% trans "Upload a new bundle" %}</h1>
--- a/templates/simple_field.djhtml
+++ b/templates/simple_field.djhtml
@@ -1,10 +1,13 @@
-<strong>{{ field.label_tag }}</strong>
-{{ field }}
+<div class="form-field">
+    <strong>{{ field.label_tag }}</strong>
 
-{% if field.errors %}
-<div class="errors">{{ field.errors }}</div>
-{% endif %}
+    {% if field.errors %}
+    <div class="errors">{{ field.errors }}</div>
+    {% endif %}
 
-{% if field.help_text %}
-<p>{{ field.help_text }}</p>
-{% endif %}
+    {{ field }}
+
+    {% if field.help_text %}
+    <p>{{ field.help_text }}</p>
+    {% endif %}
+</div>