[extensions-web] upload: Rework the upload code
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [extensions-web] upload: Rework the upload code
- Date: Sat, 3 Mar 2012 02:46:11 +0000 (UTC)
commit 8d44e7e22282aa40cac63fb1cefd36d3633830c7
Author: Jasper St. Pierre <jstpierre mecheye net>
Date: Mon Feb 27 17:43:56 2012 -0500
upload: Rework the upload code
First of all, use transactions. Second, refactor and split out the big
mess that is parse_metadata_json, so we don't have to do any extraneous
saving. Thirdly, fix and add new tests so that we're sure that this code
is working. Fourthly, remove the dedicated "Upload a new version" page,
as this just clutters the code up. Looking up an existing version by
UUID should be more than enough.
sweettooth/extensions/models.py | 51 +++++-----
.../templates/extensions/detail_edit.html | 2 +-
sweettooth/extensions/tests.py | 98 +++++++-------------
sweettooth/extensions/urls.py | 7 +-
sweettooth/extensions/views.py | 62 ++++---------
5 files changed, 80 insertions(+), 140 deletions(-)
---
diff --git a/sweettooth/extensions/models.py b/sweettooth/extensions/models.py
index 63042ef..b2c084b 100644
--- a/sweettooth/extensions/models.py
+++ b/sweettooth/extensions/models.py
@@ -45,6 +45,12 @@ class ExtensionManager(models.Manager):
def visible(self):
return self.filter(versions__status__in=VISIBLE_STATUSES).distinct()
+ def create_from_metadata(self, metadata, **kwargs):
+ instance = self.model(**kwargs)
+ instance.parse_metadata_json(metadata)
+ instance.save()
+ return instance
+
def build_shell_version_map(versions):
shell_version_map = {}
for version in versions:
@@ -122,6 +128,12 @@ class Extension(models.Model):
if not validate_uuid(self.uuid):
raise ValidationError("Invalid UUID")
+ def parse_metadata_json(self, metadata):
+ self.name = metadata.pop('name', "")
+ self.description = metadata.pop('description', "")
+ self.url = metadata.pop('url', "")
+ self.uuid = metadata['uuid']
+
def save(self, replace_metadata_json=True, *args, **kwargs):
super(Extension, self).save(*args, **kwargs)
if replace_metadata_json:
@@ -364,30 +376,9 @@ class ExtensionVersion(models.Model):
zipfile.writestr("metadata.json", self.make_metadata_json_string())
zipfile.close()
- def parse_metadata_json(self, metadata):
- """
- Given the contents of a metadata.json file, fill in the fields
- of the version and associated extension.
- """
+ def save(self, *args, **kwargs):
assert self.extension is not None
- # Only parse the standard data for a new extension
- if self.extension.pk is None:
- self.extension.name = metadata.pop('name', "")
- self.extension.description = metadata.pop('description', "")
- self.extension.url = metadata.pop('url', "")
- self.extension.uuid = metadata['uuid']
- self.extension.save()
-
- # Due to Django ORM magic and stupidity, this is unfortunately necessary
- self.extension = self.extension
-
- # FIXME: We shouldn't do this, but Django saving requires it.
- if self.status is None:
- self.status = STATUS_NEW
-
- self.extra_json_fields = json.dumps(metadata)
-
# get version number
ver_ids = self.extension.versions.order_by('-version')
try:
@@ -398,8 +389,18 @@ class ExtensionVersion(models.Model):
self.version = ver_id
- # ManyToManyField requires a PK, so we need to save.
- self.save()
+ super(ExtensionVersion, self).save(*args, **kwargs)
+
+ def parse_metadata_json(self, metadata):
+ """
+ Given the contents of a metadata.json file, fill in the fields
+ of the version and associated extension.
+
+ NOTE: This needs to be called after this has been saved, as we
+ need a PK to be able to add ourselves to a PK.
+ """
+
+ self.extra_json_fields = json.dumps(metadata)
for sv_string in metadata.pop('shell-version', []):
try:
@@ -411,8 +412,6 @@ class ExtensionVersion(models.Model):
self.shell_versions.add(sv)
- self.save()
-
def get_status_class(self):
return STATUSES[self.status].lower()
diff --git a/sweettooth/extensions/templates/extensions/detail_edit.html b/sweettooth/extensions/templates/extensions/detail_edit.html
index 2c9774b..ea20319 100644
--- a/sweettooth/extensions/templates/extensions/detail_edit.html
+++ b/sweettooth/extensions/templates/extensions/detail_edit.html
@@ -45,7 +45,7 @@
{% if not is_preview %}
<h2 class="expandy_header expanded"> Admin </h2>
<ul>
- <li><a href="{% url extensions-upload-file pk=extension.pk %}">Upload a new version</a></li>
+ <li><a href="{% url extensions-upload-file %}">Upload a new version</a></li>
{% if version %}
<li><a href="{% url review-version pk=version.pk %}">View review and source</a></li>
{% endif %}
diff --git a/sweettooth/extensions/tests.py b/sweettooth/extensions/tests.py
index 8262f17..30c9fb2 100644
--- a/sweettooth/extensions/tests.py
+++ b/sweettooth/extensions/tests.py
@@ -33,13 +33,13 @@ class BasicUserTestCase(object):
class ParseZipfileTest(BasicUserTestCase, TestCase):
def test_simple_metadata(self):
- metadata = {"name": "Test Metadata",
+ metadata = {"uuid": "test-metadata mecheye net",
+ "name": "Test Metadata",
"description": "Simple test metadata",
"url": "http://test-metadata.gnome.org"}
- extension = models.Extension(creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion(extension=extension)
version.parse_metadata_json(metadata)
self.assertEquals(extension.name, "Test Metadata")
@@ -47,12 +47,11 @@ class ParseZipfileTest(BasicUserTestCase, TestCase):
self.assertEquals(extension.url, "http://test-metadata.gnome.org")
def test_simple_zipdata_data(self):
- extension = models.Extension(creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
-
with get_test_zipfile('SimpleExtension') as f:
metadata = models.parse_zipfile_metadata(f)
+
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion(extension=extension)
version.parse_metadata_json(metadata)
self.assertEquals(extension.uuid, "test-extension mecheye net")
@@ -61,12 +60,11 @@ class ParseZipfileTest(BasicUserTestCase, TestCase):
self.assertEquals(extension.url, "http://test-metadata.gnome.org")
def test_extra_metadata(self):
- extension = models.Extension(creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
-
with get_test_zipfile('ExtraMetadata') as f:
metadata = models.parse_zipfile_metadata(f)
+
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion(extension=extension)
version.parse_metadata_json(metadata)
extra = json.loads(version.extra_json_fields)
@@ -77,19 +75,17 @@ class ParseZipfileTest(BasicUserTestCase, TestCase):
class ReplaceMetadataTest(BasicUserTestCase, TestCase):
def test_replace_metadata(self):
- extension = models.Extension(creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
-
old_zip_file = get_test_zipfile('LotsOfFiles')
metadata = models.parse_zipfile_metadata(old_zip_file)
old_zip_file.seek(0)
- version.source = File(old_zip_file)
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion(extension=extension,
+ source=File(old_zip_file))
version.parse_metadata_json(metadata)
- version.replace_metadata_json()
+
new_zip = version.get_zipfile('r')
old_zip = ZipFile(File(old_zip_file), 'r')
@@ -166,13 +162,9 @@ class ExtensionVersionTest(BasicUserTestCase, TestCase):
"description": "Simple test metadata",
"url": "http://test-metadata.gnome.org"}
- extension = models.Extension(creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
- version.parse_metadata_json(metadata)
-
- version.status = models.STATUS_ACTIVE
- version.save()
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
self.assertEquals(version.version, 1)
self.assertEquals(extension.latest_version, version)
@@ -183,20 +175,14 @@ class ExtensionVersionTest(BasicUserTestCase, TestCase):
"description": "Simple test metadata",
"url": "http://test-metadata.gnome.org"}
- extension = models.Extension(creator=self.user)
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
- v1 = models.ExtensionVersion()
- v1.extension = extension
- v1.status = models.STATUS_ACTIVE
- v1.parse_metadata_json(metadata)
- v1.save()
+ v1 = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
self.assertEquals(v1.version, 1)
- v2 = models.ExtensionVersion()
- v2.extension = extension
- v2.status = models.STATUS_ACTIVE
- v2.parse_metadata_json(metadata)
- v2.save()
+ v2 = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
self.assertEquals(v2.version, 2)
self.assertEquals(list(extension.visible_versions.order_by('version')), [v1, v2])
@@ -208,30 +194,21 @@ class ExtensionVersionTest(BasicUserTestCase, TestCase):
"description": "Simple test metadata",
"url": "http://test-metadata.gnome.org"}
- extension = models.Extension(creator=self.user)
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
- v1 = models.ExtensionVersion()
- v1.extension = extension
- v1.parse_metadata_json(metadata)
- v1.status = models.STATUS_ACTIVE
- v1.save()
+ v1 = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
self.assertEquals(v1.version, 1)
- v2 = models.ExtensionVersion()
- v2.extension = extension
- v2.status = models.STATUS_NEW
- v2.parse_metadata_json(metadata)
- v2.save()
+ v2 = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_NEW)
self.assertEquals(v2.version, 2)
self.assertEquals(list(extension.visible_versions.order_by('version')), [v1])
self.assertEquals(extension.latest_version, v1)
- v3 = models.ExtensionVersion()
- v3.extension = extension
- v3.status = models.STATUS_ACTIVE
- v3.parse_metadata_json(metadata)
- v3.save()
+ v3 = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
self.assertEquals(v3.version, 3)
self.assertEquals(list(extension.visible_versions.order_by('version')), [v1, v3])
@@ -244,14 +221,11 @@ class ExtensionVersionTest(BasicUserTestCase, TestCase):
"url": "http://test-metadata.gnome.org",
"shell-version": ["3.0.0", "3.0.1", "3.0.2"]}
- extension = models.Extension(creator=self.user)
-
- version = models.ExtensionVersion()
- version.extension = extension
- version.status = models.STATUS_ACTIVE
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
+ version = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
version.parse_metadata_json(metadata)
- version.save()
shell_versions = sorted(sv.version_string for sv in version.shell_versions.all())
self.assertEquals(shell_versions, ["3.0.0", "3.0.1", "3.0.2"])
@@ -262,14 +236,12 @@ class ExtensionVersionTest(BasicUserTestCase, TestCase):
"url": "http://test-metadata.gnome.org",
"shell-version": ["3.0", "3.2"]}
- extension = models.Extension(creator=self.user)
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=self.user)
- version = models.ExtensionVersion()
- version.extension = extension
- version.status = models.STATUS_ACTIVE
+ version = models.ExtensionVersion.objects.create(extension=extension,
+ status=models.STATUS_ACTIVE)
version.parse_metadata_json(metadata)
- version.save()
shell_versions = sorted(sv.version_string for sv in version.shell_versions.all())
self.assertEquals(shell_versions, ["3.0", "3.2"])
diff --git a/sweettooth/extensions/urls.py b/sweettooth/extensions/urls.py
index 6dc7214..88111b8 100644
--- a/sweettooth/extensions/urls.py
+++ b/sweettooth/extensions/urls.py
@@ -4,11 +4,6 @@ from django.views.generic.simple import direct_to_template
from extensions import views, models, feeds
-upload_patterns = patterns('',
- url(r'^$', views.upload_file, dict(pk=None), name='extensions-upload-file'),
- url(r'^new-version/(?P<pk>\d+)/$', views.upload_file, name='extensions-upload-file'),
-)
-
ajax_patterns = patterns('',
url(r'^edit/(?P<pk>\d+)', views.ajax_inline_edit_view, name='extensions-ajax-inline'),
url(r'^submit/(?P<pk>\d+)', views.ajax_submit_and_lock_view, name='extensions-ajax-submit'),
@@ -53,7 +48,7 @@ urlpatterns = patterns('',
url(r'^rss/', feeds.LatestExtensionsFeed(), name='extensions-rss-feed'),
- url(r'^upload/', include(upload_patterns)),
+ url(r'^upload/', views.upload_file, name='extensions-upload-file'),
url(r'^ajax/', include(ajax_patterns)),
url(r'', include(shell_patterns)),
)
diff --git a/sweettooth/extensions/views.py b/sweettooth/extensions/views.py
index 8db311b..524a2b2 100644
--- a/sweettooth/extensions/views.py
+++ b/sweettooth/extensions/views.py
@@ -4,6 +4,7 @@ from django.core.paginator import Paginator, InvalidPage
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.contrib import messages
+from django.db import transaction
from django.http import HttpResponseForbidden, HttpResponseServerError, Http404
from django.shortcuts import get_object_or_404, redirect
from django.template.loader import render_to_string
@@ -341,16 +342,8 @@ def ajax_set_status_view(request, newstatus):
mvs=render_to_string('extensions/multiversion_status.html', context))
@login_required
-def upload_file(request, pk):
- if pk is None:
- extension = models.Extension(creator=request.user)
- extension_is_new = True
- else:
- extension = models.Extension.objects.get(pk=pk)
- extension_is_new = False
- if extension.creator != request.user:
- return HttpResponseForbidden()
-
+ transaction commit_manually
+def upload_file(request):
errors = []
extra_debug = None
@@ -359,39 +352,27 @@ def upload_file(request, pk):
if form.is_valid():
file_source = form.cleaned_data['source']
+ sid = transaction.savepoint()
+
try:
metadata = models.parse_zipfile_metadata(file_source)
uuid = metadata['uuid']
except (models.InvalidExtensionData, KeyError), e:
messages.error(request, "Invalid extension data: %s" % (e.message,))
+ return redirect('extensions-upload-file')
- if pk is not None:
- return redirect('extensions-upload-file', pk=pk)
- else:
- return redirect('extensions-upload-file')
-
- existing = models.Extension.objects.filter(uuid=uuid)
- if pk is None and existing.exists():
- # Error out if we already have an extension with the same
- # uuid -- or correct their mistake if they're the same user.
- ext = existing.get()
- if request.user == ext.creator:
- return upload_file(request, ext.pk)
- else:
+ try:
+ extension = models.Extension.objects.get(uuid=uuid)
+ except models.Extension.DoesNotExist:
+ extension = models.Extension.objects.create_from_metadata(metadata, creator=request.user)
+ else:
+ if request.user != extension.creator:
messages.error(request, "An extension with that UUID has already been added.")
return redirect('extensions-upload-file')
- version = models.ExtensionVersion()
- version.extension = extension
- version.parse_metadata_json(metadata)
-
- extension.creator = request.user
-
try:
extension.full_clean()
except ValidationError, e:
- is_valid = False
-
# Output a specialized error message for a common mistake:
if getattr(e, 'message_dict', None) and 'url' in e.message_dict:
errors = [mark_safe("You have an invalid URL. Make sure your URL "
@@ -399,22 +380,15 @@ def upload_file(request, pk):
else:
errors = e.messages
- version.delete()
- if extension_is_new:
- extension.delete()
-
extra_debug = repr(e)
+ transaction.savepoint_rollback(sid)
else:
- is_valid = True
-
- if is_valid:
- extension.save()
-
- version.extension = extension
- version.source = file_source
- version.status = models.STATUS_NEW
- version.save()
+ transaction.savepoint_commit(sid)
+ version = models.ExtensionVersion.objects.create(extension=extension,
+ source=file_source,
+ status=models.STATUS_NEW)
+ version.parse_metadata_json(metadata)
version.replace_metadata_json()
return redirect('extensions-version-detail',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]