Merge lp:~canonical-ca-hackers/ubuntu-webcatalog/938226-multiple-screenshots into lp:ubuntu-webcatalog

Proposed by Łukasz Czyżykowski
Status: Merged
Approved by: Anthony Lenton
Approved revision: 93
Merged at revision: 84
Proposed branch: lp:~canonical-ca-hackers/ubuntu-webcatalog/938226-multiple-screenshots
Merge into: lp:ubuntu-webcatalog
Diff against target: 1207 lines (+873/-28)
16 files modified
src/webcatalog/forms.py (+47/-2)
src/webcatalog/management/commands/import_for_purchase_apps.py (+1/-0)
src/webcatalog/migrations/0015_add_applicationmedia.py (+169/-0)
src/webcatalog/migrations/0016_populate_application_media.py (+168/-0)
src/webcatalog/migrations/0017_delete_screenshot_url.py (+158/-0)
src/webcatalog/migrations/0018_add_unique_together_to_application_media.py (+157/-0)
src/webcatalog/models/__init__.py (+2/-0)
src/webcatalog/models/applications.py (+30/-2)
src/webcatalog/static/css/carousel.css (+37/-0)
src/webcatalog/templates/webcatalog/application_detail.html (+4/-3)
src/webcatalog/templates/webcatalog/screenshot_widget.html (+28/-0)
src/webcatalog/tests/factory.py (+10/-2)
src/webcatalog/tests/test_commands.py (+9/-1)
src/webcatalog/tests/test_data/sca_apps.txt (+4/-0)
src/webcatalog/tests/test_forms.py (+21/-1)
src/webcatalog/tests/test_views.py (+28/-17)
To merge this branch: bzr merge lp:~canonical-ca-hackers/ubuntu-webcatalog/938226-multiple-screenshots
Reviewer Review Type Date Requested Status
Anthony Lenton (community) Approve
Review via email: mp+98389@code.launchpad.net

Commit message

Add multiple screenthots support.

Description of the change

Overview
========
This branch adds ability to display multiple screenshots for an application. End result looks this: http://ubuntuone.com/7k2Jn4I2Gu9N5QnZSGDQsq

Details
=======
- added new model, ApplicationMedia, which allows tracking unlimited number of screenshots, videos and other media for each application, this will be useful when implementing video support,
- with the new model there are appropriate migrations for moving data from application attribute to the new model (most of the code are from migrations),
- added code to the template, which uses carousel to display screenthots, but only in case when there's more than one

To post a comment you must log in.
92. By Łukasz Czyżykowski

Added unique_together index to the ApplicationMedia to restrict possibility of multiple same urls for the same application.

93. By Łukasz Czyżykowski

Merged from trunk.

Revision history for this message
Anthony Lenton (elachuni) wrote :

Nice!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/webcatalog/forms.py'
2--- src/webcatalog/forms.py 2012-03-16 00:37:44 +0000
3+++ src/webcatalog/forms.py 2012-03-20 14:12:21 +0000
4@@ -29,6 +29,7 @@
5 from django import forms
6 from django.conf import settings
7 from django.core.mail import EmailMultiAlternatives
8+from django.core.validators import URLValidator
9 from django.template.loader import render_to_string
10
11 from webcatalog.models import Application
12@@ -55,6 +56,8 @@
13
14 class ApplicationForm(forms.ModelForm):
15
16+ screenshot_url = forms.URLField(required=False)
17+
18 class Meta:
19 model = Application
20 exclude = ('distroseries', 'for_purchase', 'archive_id', 'price')
21@@ -68,13 +71,11 @@
22 if desktop_key in data:
23 data[app_key] = data[desktop_key]
24 del(data[desktop_key])
25-
26 try:
27 instance = Application.objects.get(
28 package_name=data['package_name'], distroseries=distroseries)
29 except Application.DoesNotExist:
30 instance = None
31-
32 return cls(data=data, instance=instance)
33
34 def clean(self):
35@@ -86,12 +87,43 @@
36 for key, value in cleaned_data.items():
37 if not value:
38 instance_value = getattr(self.instance, key, False)
39+ if hasattr(instance_value, 'all'):
40+ instance_value = instance_value.all()
41 cleaned_data[key] = instance_value or value
42
43 return cleaned_data
44
45+ def save(self, commit=True):
46+ app = super(ApplicationForm, self).save(commit)
47+ screenshot_url = self.cleaned_data['screenshot_url']
48+ if screenshot_url:
49+ if screenshot_url not in app.screenshots:
50+ app.applicationmedia_set.create(
51+ media_type='screenshot', url=screenshot_url)
52+ return app
53+
54+
55+class MultiURLField(forms.Field):
56+ def to_python(self, value):
57+ "Normalize data to a list of strings."
58+ # Return an empty list if no input was given.
59+ if not value:
60+ return []
61+ return value.split(',')
62+
63+ def validate(self, value):
64+ "Check if value consists only of valid urls."
65+ # Use the parent's handling of required fields, etc.
66+ super(MultiURLField, self).validate(value)
67+
68+ for email in value:
69+ validator = URLValidator()
70+ validator(email)
71+
72
73 class ForPurchaseApplicationForm(forms.ModelForm):
74+ screenshot_urls = MultiURLField(required=False)
75+
76 class Meta:
77 model = Application
78 exclude = ('distroseries', 'section', 'popcon')
79@@ -103,6 +135,8 @@
80 tagline, _, description = app_data['description'].partition('\n')
81 app_data['comment'] = tagline
82 app_data['description'] = description
83+ if 'screenshot_urls' in app_data:
84+ app_data['screenshot_urls'] = ",".join(app_data['screenshot_urls'])
85 try:
86 instance = Application.objects.get(
87 archive_id=app_data.get('archive_id'),
88@@ -116,6 +150,17 @@
89 value = self.cleaned_data['version']
90 return apt.apt_pkg.upstream_version(value)
91
92+ def save(self, commit=True):
93+ app = super(ForPurchaseApplicationForm, self).save(commit)
94+ if commit:
95+ self.save_screenshot_urls()
96+ return app
97+
98+ def save_screenshot_urls(self):
99+ for screenshot_url in self.cleaned_data['screenshot_urls']:
100+ self.instance.applicationmedia_set.get_or_create(
101+ url=screenshot_url, media_type='screenshot')
102+
103
104 class EmailDownloadLinkForm(forms.Form):
105 email = forms.EmailField()
106
107=== modified file 'src/webcatalog/management/commands/import_for_purchase_apps.py'
108--- src/webcatalog/management/commands/import_for_purchase_apps.py 2012-03-16 01:42:36 +0000
109+++ src/webcatalog/management/commands/import_for_purchase_apps.py 2012-03-20 14:12:21 +0000
110@@ -76,6 +76,7 @@
111 app.distroseries = distroseries
112 app.for_purchase = True
113 app.save()
114+ form.save_screenshot_urls()
115 app.update_departments()
116 self.add_icon_to_app(app, data=icon_data)
117 self.output(u"{0} created.\n".format(app.name).encode('utf-8'), 1)
118
119=== added file 'src/webcatalog/migrations/0015_add_applicationmedia.py'
120--- src/webcatalog/migrations/0015_add_applicationmedia.py 1970-01-01 00:00:00 +0000
121+++ src/webcatalog/migrations/0015_add_applicationmedia.py 2012-03-20 14:12:21 +0000
122@@ -0,0 +1,169 @@
123+# encoding: utf-8
124+from south.db import db
125+from south.v2 import SchemaMigration
126+
127+
128+class Migration(SchemaMigration):
129+
130+ def forwards(self, orm):
131+ db.create_table('webcatalog_applicationmedia', (
132+ ('id',
133+ self.gf('django.db.models.fields.AutoField')(primary_key=True)),
134+ ('application',
135+ self.gf('django.db.models.fields.related.ForeignKey')(
136+ to=orm['webcatalog.Application'])),
137+ ('media_type',
138+ self.gf('django.db.models.fields.CharField')(max_length=16)),
139+ ('url',
140+ self.gf('django.db.models.fields.URLField')(
141+ max_length=200, verify_exists=False)),
142+ ))
143+ db.send_create_signal('webcatalog', ['ApplicationMedia'])
144+
145+ def backwards(self, orm):
146+ db.delete_table('webcatalog_applicationmedia')
147+
148+ models = {
149+ 'auth.group': {
150+ 'Meta': {'object_name': 'Group'},
151+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
152+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
153+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
154+ },
155+ 'auth.permission': {
156+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
157+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
158+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
159+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
160+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
161+ },
162+ 'auth.user': {
163+ 'Meta': {'object_name': 'User'},
164+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
165+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
166+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
167+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
168+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
169+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
170+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
171+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
172+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
173+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
174+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
175+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
176+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
177+ },
178+ 'contenttypes.contenttype': {
179+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
180+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
181+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
182+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
183+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
184+ },
185+ 'webcatalog.applicationmedia': {
186+ 'Meta': {'object_name': 'ApplicationMedia'},
187+ 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
188+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
189+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
190+ 'url': ('django.db.models.fields.URLField', [],
191+ {'max_length': '200', 'verify_exists': 'False'})
192+ },
193+ 'webcatalog.application': {
194+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
195+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
196+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
197+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
198+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
199+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
200+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
201+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
202+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
203+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
204+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
205+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
206+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
207+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
208+ 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
209+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
210+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
211+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
212+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
213+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
214+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
215+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
216+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
217+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
218+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
219+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
220+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
221+ },
222+ 'webcatalog.consumer': {
223+ 'Meta': {'object_name': 'Consumer'},
224+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
225+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
226+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
227+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'TqnEDawbCrtYpntzTHKnAyIYkQxStI'", 'max_length': '255', 'blank': 'True'}),
228+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
229+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
230+ },
231+ 'webcatalog.department': {
232+ 'Meta': {'object_name': 'Department'},
233+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
234+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
235+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
236+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
237+ },
238+ 'webcatalog.distroseries': {
239+ 'Meta': {'object_name': 'DistroSeries'},
240+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
241+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
242+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
243+ },
244+ 'webcatalog.exhibit': {
245+ 'Meta': {'object_name': 'Exhibit'},
246+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
247+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
248+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
249+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
250+ 'html': ('django.db.models.fields.TextField', [], {}),
251+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
252+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
253+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
254+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
255+ },
256+ 'webcatalog.machine': {
257+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
258+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
259+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
260+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
261+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
262+ 'package_list': ('django.db.models.fields.TextField', [], {}),
263+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
264+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
265+ },
266+ 'webcatalog.nonce': {
267+ 'Meta': {'object_name': 'Nonce'},
268+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
269+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
270+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
271+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
272+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
273+ },
274+ 'webcatalog.reviewstatsimport': {
275+ 'Meta': {'object_name': 'ReviewStatsImport'},
276+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
277+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
278+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
279+ },
280+ 'webcatalog.token': {
281+ 'Meta': {'object_name': 'Token'},
282+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
283+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
284+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
285+ 'token': ('django.db.models.fields.CharField', [], {'default': "'QwZbSFwQynUCbUyICBRGQlveynoyGnkAXPBQnTkHXomzhKkxAe'", 'max_length': '50', 'primary_key': 'True'}),
286+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'wTyHCVgrQHxgbdUzoeanRdVaDvtGAojXsYoPjZaoarXzyieOmh'", 'max_length': '50'}),
287+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
288+ }
289+ }
290+
291+ complete_apps = ['webcatalog']
292
293=== added file 'src/webcatalog/migrations/0016_populate_application_media.py'
294--- src/webcatalog/migrations/0016_populate_application_media.py 1970-01-01 00:00:00 +0000
295+++ src/webcatalog/migrations/0016_populate_application_media.py 2012-03-20 14:12:21 +0000
296@@ -0,0 +1,168 @@
297+# encoding: utf-8
298+from south.v2 import DataMigration
299+
300+
301+class Migration(DataMigration):
302+
303+ def forwards(self, orm):
304+ for app in orm.Application.objects.all():
305+ if app.screenshot_url:
306+ orm.ApplicationMedia.objects.create(
307+ application=app,
308+ media_type='screenshot',
309+ url=app.screenshot_url,
310+ )
311+
312+ def backwards(self, orm):
313+ for app in orm.Application.objects.all():
314+ # Set first found screenshot url to the screenshot_url attribute
315+ screenshots = app.applicationmedia_set.filter(
316+ media_type='screenshot')
317+ if screenshots.count():
318+ app.screenshot_url = screenshots[0].url
319+ app.save()
320+
321+ models = {
322+ 'auth.group': {
323+ 'Meta': {'object_name': 'Group'},
324+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
325+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
326+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
327+ },
328+ 'auth.permission': {
329+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
330+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
331+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
332+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
333+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
334+ },
335+ 'auth.user': {
336+ 'Meta': {'object_name': 'User'},
337+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
338+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
339+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
340+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
341+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
342+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
343+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
344+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
345+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
346+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
347+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
348+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
349+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
350+ },
351+ 'contenttypes.contenttype': {
352+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
353+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
354+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
355+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
356+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
357+ },
358+ 'webcatalog.application': {
359+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
360+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
361+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
362+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
363+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
364+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
365+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
366+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
367+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
368+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
369+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
370+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
371+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
372+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
373+ 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
374+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
375+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
376+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
377+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
378+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
379+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
380+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
381+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
382+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
383+ 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
384+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
385+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
386+ },
387+ 'webcatalog.applicationmedia': {
388+ 'Meta': {'object_name': 'ApplicationMedia'},
389+ 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
390+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
391+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
392+ 'url': ('django.db.models.fields.URLField', [],
393+ {'max_length': '200', 'verify_exists': 'False'})
394+ },
395+ 'webcatalog.consumer': {
396+ 'Meta': {'object_name': 'Consumer'},
397+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
398+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
399+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
400+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'WpxqoTmAenfQszGjGvfbFISHJhThgo'", 'max_length': '255', 'blank': 'True'}),
401+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
402+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
403+ },
404+ 'webcatalog.department': {
405+ 'Meta': {'object_name': 'Department'},
406+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
407+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
408+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
409+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
410+ },
411+ 'webcatalog.distroseries': {
412+ 'Meta': {'object_name': 'DistroSeries'},
413+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
414+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
415+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
416+ },
417+ 'webcatalog.exhibit': {
418+ 'Meta': {'object_name': 'Exhibit'},
419+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
420+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
421+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
422+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
423+ 'html': ('django.db.models.fields.TextField', [], {}),
424+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
425+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
426+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
427+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
428+ },
429+ 'webcatalog.machine': {
430+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
431+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
432+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
433+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
434+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
435+ 'package_list': ('django.db.models.fields.TextField', [], {}),
436+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
437+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
438+ },
439+ 'webcatalog.nonce': {
440+ 'Meta': {'object_name': 'Nonce'},
441+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
442+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
443+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
444+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
445+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
446+ },
447+ 'webcatalog.reviewstatsimport': {
448+ 'Meta': {'object_name': 'ReviewStatsImport'},
449+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
450+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
451+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
452+ },
453+ 'webcatalog.token': {
454+ 'Meta': {'object_name': 'Token'},
455+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
456+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
457+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
458+ 'token': ('django.db.models.fields.CharField', [], {'default': "'HVhkYAvgwlonjshHUvoeilzKvaqwvoowjADVxoonRifHvlnktE'", 'max_length': '50', 'primary_key': 'True'}),
459+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'xKXYwfpMJLvPvmayvjqlPqWvKrviDopKCXcnyUEkNTtWJoTXCE'", 'max_length': '50'}),
460+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
461+ }
462+ }
463+
464+ complete_apps = ['webcatalog']
465
466=== added file 'src/webcatalog/migrations/0017_delete_screenshot_url.py'
467--- src/webcatalog/migrations/0017_delete_screenshot_url.py 1970-01-01 00:00:00 +0000
468+++ src/webcatalog/migrations/0017_delete_screenshot_url.py 2012-03-20 14:12:21 +0000
469@@ -0,0 +1,158 @@
470+# encoding: utf-8
471+from south.db import db
472+from south.v2 import SchemaMigration
473+
474+
475+class Migration(SchemaMigration):
476+
477+ def forwards(self, orm):
478+ db.delete_column('webcatalog_application', 'screenshot_url')
479+
480+ def backwards(self, orm):
481+ db.add_column('webcatalog_application', 'screenshot_url',
482+ self.gf('django.db.models.fields.URLField')(
483+ default='', max_length=200, blank=True),
484+ keep_default=False)
485+
486+ models = {
487+ 'auth.group': {
488+ 'Meta': {'object_name': 'Group'},
489+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
490+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
491+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
492+ },
493+ 'auth.permission': {
494+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
495+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
496+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
497+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
498+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
499+ },
500+ 'auth.user': {
501+ 'Meta': {'object_name': 'User'},
502+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
503+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
504+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
505+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
506+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
507+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
508+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
509+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
510+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
511+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
512+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
513+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
514+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
515+ },
516+ 'contenttypes.contenttype': {
517+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
518+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
519+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
520+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
521+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
522+ },
523+ 'webcatalog.application': {
524+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
525+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
526+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
527+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
528+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
529+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
530+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
531+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
532+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
533+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
534+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
535+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
536+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
537+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
538+ 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
539+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
540+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
541+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
542+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
543+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
544+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
545+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
546+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
547+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
548+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
549+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
550+ },
551+ 'webcatalog.applicationmedia': {
552+ 'Meta': {'object_name': 'ApplicationMedia'},
553+ 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
554+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
555+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
556+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
557+ },
558+ 'webcatalog.consumer': {
559+ 'Meta': {'object_name': 'Consumer'},
560+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
561+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
562+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
563+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'DuVUqQPfeNRHRxrsetXaaCgCAsqCpl'", 'max_length': '255', 'blank': 'True'}),
564+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
565+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
566+ },
567+ 'webcatalog.department': {
568+ 'Meta': {'object_name': 'Department'},
569+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
570+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
571+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
572+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
573+ },
574+ 'webcatalog.distroseries': {
575+ 'Meta': {'object_name': 'DistroSeries'},
576+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
577+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
578+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
579+ },
580+ 'webcatalog.exhibit': {
581+ 'Meta': {'object_name': 'Exhibit'},
582+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
583+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
584+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
585+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
586+ 'html': ('django.db.models.fields.TextField', [], {}),
587+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
588+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
589+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
590+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
591+ },
592+ 'webcatalog.machine': {
593+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
594+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
595+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
596+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
597+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
598+ 'package_list': ('django.db.models.fields.TextField', [], {}),
599+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
600+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
601+ },
602+ 'webcatalog.nonce': {
603+ 'Meta': {'object_name': 'Nonce'},
604+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
605+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
606+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
607+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
608+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
609+ },
610+ 'webcatalog.reviewstatsimport': {
611+ 'Meta': {'object_name': 'ReviewStatsImport'},
612+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
613+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
614+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
615+ },
616+ 'webcatalog.token': {
617+ 'Meta': {'object_name': 'Token'},
618+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
619+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
620+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
621+ 'token': ('django.db.models.fields.CharField', [], {'default': "'tKSJuAZIguDcEzJQNGqCebjlZUEosUkZtKYJcEHqtCHgipXtOs'", 'max_length': '50', 'primary_key': 'True'}),
622+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'gUFMuThoNMYOuRbIqLcmEiITrEcHWLibbLFOdDdaaICoVoZHyo'", 'max_length': '50'}),
623+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
624+ }
625+ }
626+
627+ complete_apps = ['webcatalog']
628
629=== added file 'src/webcatalog/migrations/0018_add_unique_together_to_application_media.py'
630--- src/webcatalog/migrations/0018_add_unique_together_to_application_media.py 1970-01-01 00:00:00 +0000
631+++ src/webcatalog/migrations/0018_add_unique_together_to_application_media.py 2012-03-20 14:12:21 +0000
632@@ -0,0 +1,157 @@
633+# encoding: utf-8
634+from south.db import db
635+from south.v2 import SchemaMigration
636+
637+
638+class Migration(SchemaMigration):
639+
640+ def forwards(self, orm):
641+ db.create_unique('webcatalog_applicationmedia',
642+ ['url', 'application_id'])
643+
644+ def backwards(self, orm):
645+ db.delete_unique('webcatalog_applicationmedia',
646+ ['url', 'application_id'])
647+
648+ models = {
649+ 'auth.group': {
650+ 'Meta': {'object_name': 'Group'},
651+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
652+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
653+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
654+ },
655+ 'auth.permission': {
656+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
657+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
658+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
659+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
660+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
661+ },
662+ 'auth.user': {
663+ 'Meta': {'object_name': 'User'},
664+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
665+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
666+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
667+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
668+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
669+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
670+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
671+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
672+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
673+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
674+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
675+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
676+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
677+ },
678+ 'contenttypes.contenttype': {
679+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
680+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
681+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
682+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
683+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
684+ },
685+ 'webcatalog.application': {
686+ 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
687+ 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
688+ 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
689+ 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
690+ 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
691+ 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
692+ 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
693+ 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
694+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
695+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
696+ 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
697+ 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
698+ 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
699+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
700+ 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
701+ 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
702+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
703+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
704+ 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
705+ 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
706+ 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
707+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
708+ 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
709+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
710+ 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
711+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
712+ },
713+ 'webcatalog.applicationmedia': {
714+ 'Meta': {'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'},
715+ 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
716+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
717+ 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
718+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
719+ },
720+ 'webcatalog.consumer': {
721+ 'Meta': {'object_name': 'Consumer'},
722+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
723+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
724+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
725+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'RVPuZojYHnlWbiYQtZFBDrLEQFVKjR'", 'max_length': '255', 'blank': 'True'}),
726+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
727+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
728+ },
729+ 'webcatalog.department': {
730+ 'Meta': {'object_name': 'Department'},
731+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
732+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
733+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
734+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
735+ },
736+ 'webcatalog.distroseries': {
737+ 'Meta': {'object_name': 'DistroSeries'},
738+ 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
739+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
740+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
741+ },
742+ 'webcatalog.exhibit': {
743+ 'Meta': {'object_name': 'Exhibit'},
744+ 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
745+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
746+ 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
747+ 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
748+ 'html': ('django.db.models.fields.TextField', [], {}),
749+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
750+ 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
751+ 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
752+ 'sca_id': ('django.db.models.fields.IntegerField', [], {})
753+ },
754+ 'webcatalog.machine': {
755+ 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
756+ 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
757+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
758+ 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
759+ 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
760+ 'package_list': ('django.db.models.fields.TextField', [], {}),
761+ 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
762+ 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
763+ },
764+ 'webcatalog.nonce': {
765+ 'Meta': {'object_name': 'Nonce'},
766+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
767+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
768+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
769+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
770+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
771+ },
772+ 'webcatalog.reviewstatsimport': {
773+ 'Meta': {'object_name': 'ReviewStatsImport'},
774+ 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
775+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
776+ 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
777+ },
778+ 'webcatalog.token': {
779+ 'Meta': {'object_name': 'Token'},
780+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
781+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
782+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
783+ 'token': ('django.db.models.fields.CharField', [], {'default': "'hxEWMjHsidgiAoDiyBOmdzzAynmFtUySRnVTtAQxLjXgNmkMgw'", 'max_length': '50', 'primary_key': 'True'}),
784+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'yNkBVzMQcOPQKuzNuVWHNTjQPQAdSXGYJToNQqpsuXyioPfmMw'", 'max_length': '50'}),
785+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
786+ }
787+ }
788+
789+ complete_apps = ['webcatalog']
790
791=== modified file 'src/webcatalog/models/__init__.py'
792--- src/webcatalog/models/__init__.py 2012-03-01 21:38:31 +0000
793+++ src/webcatalog/models/__init__.py 2012-03-20 14:12:21 +0000
794@@ -25,6 +25,7 @@
795 # applications
796 'DistroSeries',
797 'Application',
798+ 'ApplicationMedia',
799 'Department',
800 'Exhibit',
801 'ReviewStatsImport',
802@@ -34,6 +35,7 @@
803 from .oauthtoken import Token, Consumer, Nonce, DataStore
804 from .applications import (
805 Application,
806+ ApplicationMedia,
807 Department,
808 DistroSeries,
809 Exhibit,
810
811=== modified file 'src/webcatalog/models/applications.py'
812--- src/webcatalog/models/applications.py 2012-03-19 20:43:17 +0000
813+++ src/webcatalog/models/applications.py 2012-03-20 14:12:21 +0000
814@@ -37,6 +37,7 @@
815 __metaclass__ = type
816 __all__ = [
817 'Application',
818+ 'ApplicationMedia',
819 'Department',
820 'DistroSeries',
821 'Exhibit',
822@@ -73,8 +74,6 @@
823 comment = models.CharField(max_length=255, blank=True)
824 popcon = models.IntegerField(null=True, blank=True)
825 channel = models.CharField(max_length=255, blank=True)
826- screenshot_url = models.URLField(blank=True, verify_exists=False,
827- help_text="Only use this if it is other than the normal screenshot url.")
828 mimetype = models.CharField(max_length=2048, blank=True)
829 architectures = models.CharField(max_length=255, blank=True)
830 keywords = models.CharField(max_length=255, blank=True)
831@@ -114,6 +113,13 @@
832 return u"{0} ({1})".format(self.name, self.package_name)
833
834 @property
835+ def screenshots(self):
836+ if not hasattr(self, '_screenshots'):
837+ qs = self.applicationmedia_set.filter(media_type='screenshot')
838+ self._screenshots = [am.url for am in qs]
839+ return self._screenshots
840+
841+ @property
842 def categories_set(self):
843 """Return the set of categories for this app"""
844 stripped = [x.strip() for x in self.categories.split(';')]
845@@ -170,6 +176,28 @@
846 unique_together = ('distroseries', 'archive_id')
847
848
849+class ApplicationMedia(models.Model):
850+ MEDIA_CHOICES = (
851+ ('icon_16', 'Icon 16x16'),
852+ ('icon_32', 'Icon 32x32'),
853+ ('icon_64', 'Icon 64x64'),
854+ ('icon_128', 'Icon 128x128'),
855+ ('screenshot', 'Screenshot'),
856+ ('video', 'Video'),
857+ )
858+
859+ application = models.ForeignKey(Application)
860+ media_type = models.CharField(max_length=16, choices=MEDIA_CHOICES)
861+ url = models.URLField(verify_exists=False)
862+
863+ def __unicode__(self):
864+ return "{0} for {1}".format(self.media_type, self.application)
865+
866+ class Meta:
867+ app_label = 'webcatalog'
868+ unique_together = (('application', 'url'),)
869+
870+
871 class Department(models.Model):
872 parent = models.ForeignKey('self', blank=True, null=True)
873 name = models.CharField(max_length=64)
874
875=== modified file 'src/webcatalog/static/css/carousel.css'
876--- src/webcatalog/static/css/carousel.css 2012-03-19 20:43:17 +0000
877+++ src/webcatalog/static/css/carousel.css 2012-03-20 14:12:21 +0000
878@@ -187,6 +187,43 @@
879 .featured-widget h4 {
880 font-weight: bold;
881 }
882+
883+/* Screenshots carousel */
884+.screenshot .carousel-wrapper {
885+ height: 190px;
886+ width: 233px;
887+}
888+.screenshot .carousel .pagination li a.active {
889+ background-color: #DD4814;
890+}
891+#screenshots-controls {
892+ width: 70px;
893+ height: 32px;
894+ margin-left: 94px;
895+}
896+#screenshots-controls .next, #screenshots-controls .prev {
897+ background: url(/assets/images/arrow-sliders.png) no-repeat 0 0;
898+ float: left;
899+ z-index: 20;
900+ width: 32px;
901+ height: 29px;
902+}
903+#screenshots-controls .next span, #screenshots-controls .prev span {
904+ position:absolute;
905+ left: -9999em;
906+ height: 0;
907+ width: 0;
908+}
909+#screenshots-controls .prev {
910+ background-position:0 0;
911+}
912+#screenshots-controls .next {
913+ background-position: -32px 0;
914+}
915+#screenshots-controls .prev:hover, #screenshots-controls .prev:focus,
916+#screenshots-controls .next:hover, #screenshots-controls .next:focus {
917+ outline: none;
918+
919 .top-rated-stars {
920 position: relative;
921 left: -50px;
922
923=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
924--- src/webcatalog/templates/webcatalog/application_detail.html 2012-03-16 17:18:41 +0000
925+++ src/webcatalog/templates/webcatalog/application_detail.html 2012-03-20 14:12:21 +0000
926@@ -6,7 +6,8 @@
927 {% block header %}Details for {{ application.name }}{% endblock %}
928 {% block head_extra %}
929 {{ block.super }}
930-<script src="{{ STATIC_URL }}yui/3.4.0/build/yui/yui-min.js"></script>
931+<link rel="stylesheet" type="text/css" href="{% url wc-combo %}?light/css/reset.css&light/css/styles.css&css/webcatalog.css&light/css/forms.css&css/carousel.css"/>
932+<script src="{% url wc-combo %}?yui/3.4.0/build/yui/yui-min.js&js/carousel.js"></script>
933 <script>
934 YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'node-base', function (Y) {
935 function complete(id, obj){
936@@ -71,8 +72,8 @@
937 </div>
938 <div class="description">
939 <div class="screenshot">
940- {% if application.screenshot_url %}
941- <img src="{{ application.screenshot_url }}" />
942+ {% if application.screenshots %}
943+ {% include "webcatalog/screenshot_widget.html" %}
944 {% else %}
945 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />
946 {% endif %}
947
948=== added file 'src/webcatalog/templates/webcatalog/screenshot_widget.html'
949--- src/webcatalog/templates/webcatalog/screenshot_widget.html 1970-01-01 00:00:00 +0000
950+++ src/webcatalog/templates/webcatalog/screenshot_widget.html 2012-03-20 14:12:21 +0000
951@@ -0,0 +1,28 @@
952+{% if application.screenshots|length_is:1 %}
953+ <img src="{{ application.screenshots.0 }}" />
954+{% else %}
955+ <div class="carousel-wrapper">
956+ <div id="screenshots-carousel" class="carousel">
957+ <ol class="carouselol">
958+ {% for screenshot in application.screenshots %}
959+ <li class="slide{% if forloop.counter > 1 %} disabled{% endif %}">
960+ <img src="{{ screenshot }}" />
961+ </li>
962+ {% endfor %}
963+ </ol>
964+ </div>
965+ </div>
966+ <div id="screenshots-controls"></div>
967+ <script type="text/javascript">
968+ YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {
969+ var caro = new Y.uwc.Carousel({
970+ nodeContainer: "#screenshots-carousel",
971+ controlsContainer: "#screenshots-controls",
972+ containerHeight: 170,
973+ containerWidth: 225,
974+ autoPlay: true
975+ });
976+ Y.all('.slide').removeClass('disabled');
977+ });
978+ </script>
979+{% endif %}
980
981=== modified file 'src/webcatalog/tests/factory.py'
982--- src/webcatalog/tests/factory.py 2012-03-16 19:27:47 +0000
983+++ src/webcatalog/tests/factory.py 2012-03-20 14:12:21 +0000
984@@ -30,6 +30,7 @@
985
986 from webcatalog.models import (
987 Application,
988+ ApplicationMedia,
989 Consumer,
990 Department,
991 DistroSeries,
992@@ -106,15 +107,22 @@
993 if distroseries is None:
994 distroseries = self.make_distroseries()
995
996- return Application.objects.create(
997+ application = Application.objects.create(
998 package_name=package_name, name=name, comment=comment,
999 description=description, popcon=999, icon=icon,
1000 icon_name=icon_name, distroseries=distroseries, architectures=arch,
1001 ratings_average=ratings_average, ratings_total=ratings_total,
1002- ratings_histogram=ratings_histogram, screenshot_url=screenshot_url,
1003+ ratings_histogram=ratings_histogram,
1004 archive_id=archive_id, version=version, is_latest=is_latest,
1005 wilson_score=wilson_score)
1006
1007+ if screenshot_url:
1008+ ApplicationMedia.objects.create(
1009+ application=application,
1010+ url=screenshot_url,
1011+ media_type='screenshot')
1012+ return application
1013+
1014 def make_department(self, name, parent=None, slug=None):
1015 if slug is None:
1016 slug = self.get_unique_string(prefix='slug-')
1017
1018=== modified file 'src/webcatalog/tests/test_commands.py'
1019--- src/webcatalog/tests/test_commands.py 2012-03-16 19:27:47 +0000
1020+++ src/webcatalog/tests/test_commands.py 2012-03-20 14:12:21 +0000
1021@@ -483,6 +483,14 @@
1022 distroseries=self.natty)
1023 self.assertTrue(app.is_latest)
1024
1025+ def test_app_gets_screenshots(self):
1026+ call_command('import_for_purchase_apps')
1027+
1028+ app = Application.objects.get(package_name='hello',
1029+ distroseries=self.natty)
1030+ qs = app.applicationmedia_set.filter(media_type='screenshot')
1031+ self.assertEqual(2, qs.count())
1032+
1033
1034 class ImportRatingsTestCase(TestCaseWithFactory):
1035
1036@@ -561,7 +569,7 @@
1037 self.assertEqual(Decimal('4.00'), scribus.ratings_average)
1038 self.assertEqual(4, scribus.ratings_total)
1039 self.assertEqual('[0, 1, 0, 1, 2]', scribus.ratings_histogram)
1040- self.assertEqual(3.36480032198111, scribus.wilson_score)
1041+ self.assertAlmostEqual(3.36480032198111, scribus.wilson_score)
1042 otherapp = Application.objects.get(id=otherapp.id)
1043 self.assertEqual(None, otherapp.ratings_total)
1044
1045
1046=== modified file 'src/webcatalog/tests/test_data/sca_apps.txt'
1047--- src/webcatalog/tests/test_data/sca_apps.txt 2012-03-13 14:09:29 +0000
1048+++ src/webcatalog/tests/test_data/sca_apps.txt 2012-03-20 14:12:21 +0000
1049@@ -10,6 +10,10 @@
1050 "archive_id": "launchpad_zematynnad2/myppa",
1051 "icon_data": "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",
1052 "screenshot_url": "",
1053+ "screenshot_urls": [
1054+ "http://localhost:8000/screenshot_1.png",
1055+ "http://localhost:8000/screenshot_2.png"
1056+ ],
1057 "archive_root": "",
1058 "tos_url": "",
1059 "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64.png",
1060
1061=== modified file 'src/webcatalog/tests/test_forms.py'
1062--- src/webcatalog/tests/test_forms.py 2011-09-12 13:37:24 +0000
1063+++ src/webcatalog/tests/test_forms.py 2012-03-20 14:12:21 +0000
1064@@ -28,7 +28,7 @@
1065 ApplicationForm,
1066 desktop_field_mappings,
1067 )
1068-from webcatalog.models import Application
1069+from webcatalog.models import Application, ApplicationMedia
1070 from webcatalog.tests.factory import TestCaseWithFactory
1071
1072 __metaclass__ = type
1073@@ -132,6 +132,26 @@
1074 self.assertEqual(
1075 extra_desktop_info[key], form.cleaned_data[form_key])
1076
1077+ def test_screenshot_url_is_properly_saved_as_application_media(self):
1078+ app = self.factory.make_application()
1079+ extra_desktop_info = {
1080+ 'X-AppInstall-Screenshot-Url': 'http://example.com/screenshot',
1081+ 'X-AppInstall-Package': app.package_name,
1082+ }
1083+ desktop_entry = self.get_desktop_entry(self.get_desktop_data(
1084+ extra_desktop_info))
1085+
1086+ form = ApplicationForm.get_form_from_desktop_data(desktop_entry,
1087+ distroseries=app.distroseries)
1088+ form.save()
1089+
1090+ app = Application.objects.get(pk=app.pk)
1091+
1092+ self.assertTrue('http://example.com/screenshot' in app.screenshots)
1093+ self.assertTrue(ApplicationMedia.objects.filter(
1094+ application=app, media_type='screenshot',
1095+ url='http://example.com/screenshot').exists())
1096+
1097 def test_screenshot_url_isnt_verified(self):
1098 # Verifying if this URLField actually exists is a bad idea:
1099 # - It expects us to allow arbitrary outgoing connections from the DC
1100
1101=== modified file 'src/webcatalog/tests/test_views.py'
1102--- src/webcatalog/tests/test_views.py 2012-03-20 11:12:02 +0000
1103+++ src/webcatalog/tests/test_views.py 2012-03-20 14:12:21 +0000
1104@@ -60,6 +60,10 @@
1105
1106 class ApplicationDetailTestCase(TestCaseWithFactory):
1107
1108+ def get_app_details_url(self, app):
1109+ return reverse('wc-package-detail',
1110+ args=[app.distroseries.code_name, app.package_name])
1111+
1112 def get_app_and_response(self, code_name='natty', version='11.04',
1113 arch='x86_64', name=None, comment=None,
1114 description=None, detail_distro=None,
1115@@ -216,9 +220,7 @@
1116
1117 app.for_purchase = True
1118 app.save()
1119- url = reverse('wc-package-detail', args=[app.distroseries.code_name,
1120- app.package_name])
1121- response = self.client.get(url)
1122+ response = self.client.get(self.get_app_details_url(app))
1123 self.assertContains(response, '<td>Proprietary</td>')
1124 self.assertNotContains(response, '<td>Open Source</td>')
1125
1126@@ -241,8 +243,7 @@
1127 def test_email_for_download_contents(self):
1128 response, app = self.get_app_and_response(name="salamander")
1129 data = {'email': 'joe@intestinal-flora.net'}
1130- args = [app.distroseries.code_name, app.package_name]
1131- url = reverse('wc-package-detail', args=args)
1132+ url = self.get_app_details_url(app)
1133
1134 self.client.post(url, data=data, follow=True)
1135
1136@@ -254,8 +255,7 @@
1137 def test_email_for_download_success_message(self):
1138 response, app = self.get_app_and_response(name="salamander")
1139 data = {'email': 'joe@intestinal-flora.net'}
1140- args = [app.distroseries.code_name, app.package_name]
1141- url = reverse('wc-package-detail', args=args)
1142+ url = self.get_app_details_url(app)
1143
1144 response = self.client.post(url, data=data, follow=True)
1145
1146@@ -265,8 +265,7 @@
1147 def test_email_error_blank(self):
1148 response, app = self.get_app_and_response(name="salamander")
1149 data = {'email': ''}
1150- args = [app.distroseries.code_name, app.package_name]
1151- url = reverse('wc-package-detail', args=args)
1152+ url = self.get_app_details_url(app)
1153
1154 response = self.client.post(url, data=data, follow=True)
1155
1156@@ -277,8 +276,7 @@
1157 def test_email_error_bogus(self):
1158 response, app = self.get_app_and_response(name="salamander")
1159 data = {'email': 'bogus'}
1160- args = [app.distroseries.code_name, app.package_name]
1161- url = reverse('wc-package-detail', args=args)
1162+ url = self.get_app_details_url(app)
1163
1164 response = self.client.post(url, data=data, follow=True)
1165
1166@@ -290,9 +288,7 @@
1167 app = self.factory.make_application(version='1.2.3',
1168 distroseries=self.factory.make_distroseries())
1169
1170- url = reverse('wc-package-detail', kwargs=dict(
1171- distro=app.distroseries.code_name, package_name=app.package_name))
1172- response = self.client.get(url)
1173+ response = self.client.get(self.get_app_details_url(app))
1174
1175 self.assertContains(response, '<th>Version:</th>')
1176 self.assertContains(response, '<td>1.2.3</td>')
1177@@ -301,12 +297,27 @@
1178 app = self.factory.make_application(version='',
1179 distroseries=self.factory.make_distroseries())
1180
1181- url = reverse('wc-package-detail', kwargs=dict(
1182- distro=app.distroseries.code_name, package_name=app.package_name))
1183- response = self.client.get(url)
1184+ response = self.client.get(self.get_app_details_url(app))
1185
1186 self.assertNotContains(response, '<th>Version</th>')
1187
1188+ def test_with_only_one_screenshot_there_is_no_carousel(self):
1189+ response, _ = self.get_app_and_response(
1190+ screenshot_url='http://example.com/screenshot.png')
1191+
1192+ self.assertNotContains(response, 'Y.uwc.Carousel')
1193+
1194+ def test_with_multiple_screenshots_there_is_js_carousel(self):
1195+ app = self.factory.make_application()
1196+ for i in range(3):
1197+ app.applicationmedia_set.create(
1198+ url='http://example.com/{}.png'.format(i),
1199+ media_type='screenshot')
1200+
1201+ response = self.client.get(self.get_app_details_url(app))
1202+
1203+ self.assertContains(response, 'id="screenshots-carousel"')
1204+
1205
1206 class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory):
1207 def test_renders_latest(self):

Subscribers

People subscribed via source and target branches