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
=== modified file 'src/webcatalog/forms.py'
--- src/webcatalog/forms.py 2012-03-16 00:37:44 +0000
+++ src/webcatalog/forms.py 2012-03-20 14:12:21 +0000
@@ -29,6 +29,7 @@
29from django import forms29from django import forms
30from django.conf import settings30from django.conf import settings
31from django.core.mail import EmailMultiAlternatives31from django.core.mail import EmailMultiAlternatives
32from django.core.validators import URLValidator
32from django.template.loader import render_to_string33from django.template.loader import render_to_string
3334
34from webcatalog.models import Application35from webcatalog.models import Application
@@ -55,6 +56,8 @@
5556
56class ApplicationForm(forms.ModelForm):57class ApplicationForm(forms.ModelForm):
5758
59 screenshot_url = forms.URLField(required=False)
60
58 class Meta:61 class Meta:
59 model = Application62 model = Application
60 exclude = ('distroseries', 'for_purchase', 'archive_id', 'price')63 exclude = ('distroseries', 'for_purchase', 'archive_id', 'price')
@@ -68,13 +71,11 @@
68 if desktop_key in data:71 if desktop_key in data:
69 data[app_key] = data[desktop_key]72 data[app_key] = data[desktop_key]
70 del(data[desktop_key])73 del(data[desktop_key])
71
72 try:74 try:
73 instance = Application.objects.get(75 instance = Application.objects.get(
74 package_name=data['package_name'], distroseries=distroseries)76 package_name=data['package_name'], distroseries=distroseries)
75 except Application.DoesNotExist:77 except Application.DoesNotExist:
76 instance = None78 instance = None
77
78 return cls(data=data, instance=instance)79 return cls(data=data, instance=instance)
7980
80 def clean(self):81 def clean(self):
@@ -86,12 +87,43 @@
86 for key, value in cleaned_data.items():87 for key, value in cleaned_data.items():
87 if not value:88 if not value:
88 instance_value = getattr(self.instance, key, False)89 instance_value = getattr(self.instance, key, False)
90 if hasattr(instance_value, 'all'):
91 instance_value = instance_value.all()
89 cleaned_data[key] = instance_value or value92 cleaned_data[key] = instance_value or value
9093
91 return cleaned_data94 return cleaned_data
9295
96 def save(self, commit=True):
97 app = super(ApplicationForm, self).save(commit)
98 screenshot_url = self.cleaned_data['screenshot_url']
99 if screenshot_url:
100 if screenshot_url not in app.screenshots:
101 app.applicationmedia_set.create(
102 media_type='screenshot', url=screenshot_url)
103 return app
104
105
106class MultiURLField(forms.Field):
107 def to_python(self, value):
108 "Normalize data to a list of strings."
109 # Return an empty list if no input was given.
110 if not value:
111 return []
112 return value.split(',')
113
114 def validate(self, value):
115 "Check if value consists only of valid urls."
116 # Use the parent's handling of required fields, etc.
117 super(MultiURLField, self).validate(value)
118
119 for email in value:
120 validator = URLValidator()
121 validator(email)
122
93123
94class ForPurchaseApplicationForm(forms.ModelForm):124class ForPurchaseApplicationForm(forms.ModelForm):
125 screenshot_urls = MultiURLField(required=False)
126
95 class Meta:127 class Meta:
96 model = Application128 model = Application
97 exclude = ('distroseries', 'section', 'popcon')129 exclude = ('distroseries', 'section', 'popcon')
@@ -103,6 +135,8 @@
103 tagline, _, description = app_data['description'].partition('\n')135 tagline, _, description = app_data['description'].partition('\n')
104 app_data['comment'] = tagline136 app_data['comment'] = tagline
105 app_data['description'] = description137 app_data['description'] = description
138 if 'screenshot_urls' in app_data:
139 app_data['screenshot_urls'] = ",".join(app_data['screenshot_urls'])
106 try:140 try:
107 instance = Application.objects.get(141 instance = Application.objects.get(
108 archive_id=app_data.get('archive_id'),142 archive_id=app_data.get('archive_id'),
@@ -116,6 +150,17 @@
116 value = self.cleaned_data['version']150 value = self.cleaned_data['version']
117 return apt.apt_pkg.upstream_version(value)151 return apt.apt_pkg.upstream_version(value)
118152
153 def save(self, commit=True):
154 app = super(ForPurchaseApplicationForm, self).save(commit)
155 if commit:
156 self.save_screenshot_urls()
157 return app
158
159 def save_screenshot_urls(self):
160 for screenshot_url in self.cleaned_data['screenshot_urls']:
161 self.instance.applicationmedia_set.get_or_create(
162 url=screenshot_url, media_type='screenshot')
163
119164
120class EmailDownloadLinkForm(forms.Form):165class EmailDownloadLinkForm(forms.Form):
121 email = forms.EmailField()166 email = forms.EmailField()
122167
=== modified file 'src/webcatalog/management/commands/import_for_purchase_apps.py'
--- src/webcatalog/management/commands/import_for_purchase_apps.py 2012-03-16 01:42:36 +0000
+++ src/webcatalog/management/commands/import_for_purchase_apps.py 2012-03-20 14:12:21 +0000
@@ -76,6 +76,7 @@
76 app.distroseries = distroseries76 app.distroseries = distroseries
77 app.for_purchase = True77 app.for_purchase = True
78 app.save()78 app.save()
79 form.save_screenshot_urls()
79 app.update_departments()80 app.update_departments()
80 self.add_icon_to_app(app, data=icon_data)81 self.add_icon_to_app(app, data=icon_data)
81 self.output(u"{0} created.\n".format(app.name).encode('utf-8'), 1)82 self.output(u"{0} created.\n".format(app.name).encode('utf-8'), 1)
8283
=== added file 'src/webcatalog/migrations/0015_add_applicationmedia.py'
--- src/webcatalog/migrations/0015_add_applicationmedia.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0015_add_applicationmedia.py 2012-03-20 14:12:21 +0000
@@ -0,0 +1,169 @@
1# encoding: utf-8
2from south.db import db
3from south.v2 import SchemaMigration
4
5
6class Migration(SchemaMigration):
7
8 def forwards(self, orm):
9 db.create_table('webcatalog_applicationmedia', (
10 ('id',
11 self.gf('django.db.models.fields.AutoField')(primary_key=True)),
12 ('application',
13 self.gf('django.db.models.fields.related.ForeignKey')(
14 to=orm['webcatalog.Application'])),
15 ('media_type',
16 self.gf('django.db.models.fields.CharField')(max_length=16)),
17 ('url',
18 self.gf('django.db.models.fields.URLField')(
19 max_length=200, verify_exists=False)),
20 ))
21 db.send_create_signal('webcatalog', ['ApplicationMedia'])
22
23 def backwards(self, orm):
24 db.delete_table('webcatalog_applicationmedia')
25
26 models = {
27 'auth.group': {
28 'Meta': {'object_name': 'Group'},
29 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
30 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
31 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
32 },
33 'auth.permission': {
34 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
35 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
36 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
37 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
39 },
40 'auth.user': {
41 'Meta': {'object_name': 'User'},
42 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
43 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
44 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
45 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
46 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
48 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
50 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
51 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
52 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
53 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
54 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
55 },
56 'contenttypes.contenttype': {
57 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
58 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
59 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
61 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
62 },
63 'webcatalog.applicationmedia': {
64 'Meta': {'object_name': 'ApplicationMedia'},
65 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
66 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
68 'url': ('django.db.models.fields.URLField', [],
69 {'max_length': '200', 'verify_exists': 'False'})
70 },
71 'webcatalog.application': {
72 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
73 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
74 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
75 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
76 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
77 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
78 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
79 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
80 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
81 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
82 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
83 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
84 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
85 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
87 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
88 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
89 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
90 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
91 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
92 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
93 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
94 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
95 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
96 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
97 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
98 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
99 },
100 'webcatalog.consumer': {
101 'Meta': {'object_name': 'Consumer'},
102 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
103 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
105 'secret': ('django.db.models.fields.CharField', [], {'default': "'TqnEDawbCrtYpntzTHKnAyIYkQxStI'", 'max_length': '255', 'blank': 'True'}),
106 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
107 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
108 },
109 'webcatalog.department': {
110 'Meta': {'object_name': 'Department'},
111 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
112 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
113 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
114 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
115 },
116 'webcatalog.distroseries': {
117 'Meta': {'object_name': 'DistroSeries'},
118 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
119 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
121 },
122 'webcatalog.exhibit': {
123 'Meta': {'object_name': 'Exhibit'},
124 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
125 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
126 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
127 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
128 'html': ('django.db.models.fields.TextField', [], {}),
129 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
130 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
131 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
132 'sca_id': ('django.db.models.fields.IntegerField', [], {})
133 },
134 'webcatalog.machine': {
135 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
136 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
137 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
138 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
139 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
140 'package_list': ('django.db.models.fields.TextField', [], {}),
141 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
142 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
143 },
144 'webcatalog.nonce': {
145 'Meta': {'object_name': 'Nonce'},
146 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
147 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
148 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
149 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
150 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
151 },
152 'webcatalog.reviewstatsimport': {
153 'Meta': {'object_name': 'ReviewStatsImport'},
154 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
155 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
156 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
157 },
158 'webcatalog.token': {
159 'Meta': {'object_name': 'Token'},
160 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
161 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
162 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
163 'token': ('django.db.models.fields.CharField', [], {'default': "'QwZbSFwQynUCbUyICBRGQlveynoyGnkAXPBQnTkHXomzhKkxAe'", 'max_length': '50', 'primary_key': 'True'}),
164 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'wTyHCVgrQHxgbdUzoeanRdVaDvtGAojXsYoPjZaoarXzyieOmh'", 'max_length': '50'}),
165 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
166 }
167 }
168
169 complete_apps = ['webcatalog']
0170
=== added file 'src/webcatalog/migrations/0016_populate_application_media.py'
--- src/webcatalog/migrations/0016_populate_application_media.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0016_populate_application_media.py 2012-03-20 14:12:21 +0000
@@ -0,0 +1,168 @@
1# encoding: utf-8
2from south.v2 import DataMigration
3
4
5class Migration(DataMigration):
6
7 def forwards(self, orm):
8 for app in orm.Application.objects.all():
9 if app.screenshot_url:
10 orm.ApplicationMedia.objects.create(
11 application=app,
12 media_type='screenshot',
13 url=app.screenshot_url,
14 )
15
16 def backwards(self, orm):
17 for app in orm.Application.objects.all():
18 # Set first found screenshot url to the screenshot_url attribute
19 screenshots = app.applicationmedia_set.filter(
20 media_type='screenshot')
21 if screenshots.count():
22 app.screenshot_url = screenshots[0].url
23 app.save()
24
25 models = {
26 'auth.group': {
27 'Meta': {'object_name': 'Group'},
28 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
29 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
30 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
31 },
32 'auth.permission': {
33 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
34 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
35 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
36 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
38 },
39 'auth.user': {
40 'Meta': {'object_name': 'User'},
41 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
42 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
43 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
44 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
45 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
47 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
48 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
50 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
51 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
52 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
53 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
54 },
55 'contenttypes.contenttype': {
56 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
57 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
58 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
60 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
61 },
62 'webcatalog.application': {
63 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
64 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
65 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
66 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
67 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
68 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
69 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
70 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
71 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
72 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
73 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
74 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
75 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
76 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
78 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
79 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
80 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
81 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
82 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
83 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
84 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
85 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
86 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
87 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
88 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
89 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
90 },
91 'webcatalog.applicationmedia': {
92 'Meta': {'object_name': 'ApplicationMedia'},
93 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
94 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
96 'url': ('django.db.models.fields.URLField', [],
97 {'max_length': '200', 'verify_exists': 'False'})
98 },
99 'webcatalog.consumer': {
100 'Meta': {'object_name': 'Consumer'},
101 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
102 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
103 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
104 'secret': ('django.db.models.fields.CharField', [], {'default': "'WpxqoTmAenfQszGjGvfbFISHJhThgo'", 'max_length': '255', 'blank': 'True'}),
105 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
106 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
107 },
108 'webcatalog.department': {
109 'Meta': {'object_name': 'Department'},
110 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
111 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
112 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
113 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
114 },
115 'webcatalog.distroseries': {
116 'Meta': {'object_name': 'DistroSeries'},
117 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
118 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
120 },
121 'webcatalog.exhibit': {
122 'Meta': {'object_name': 'Exhibit'},
123 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
124 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
125 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
126 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
127 'html': ('django.db.models.fields.TextField', [], {}),
128 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
129 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
130 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
131 'sca_id': ('django.db.models.fields.IntegerField', [], {})
132 },
133 'webcatalog.machine': {
134 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
135 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
136 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
138 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
139 'package_list': ('django.db.models.fields.TextField', [], {}),
140 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
141 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
142 },
143 'webcatalog.nonce': {
144 'Meta': {'object_name': 'Nonce'},
145 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
146 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
147 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
148 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
149 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
150 },
151 'webcatalog.reviewstatsimport': {
152 'Meta': {'object_name': 'ReviewStatsImport'},
153 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
154 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
155 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
156 },
157 'webcatalog.token': {
158 'Meta': {'object_name': 'Token'},
159 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
160 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
161 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
162 'token': ('django.db.models.fields.CharField', [], {'default': "'HVhkYAvgwlonjshHUvoeilzKvaqwvoowjADVxoonRifHvlnktE'", 'max_length': '50', 'primary_key': 'True'}),
163 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'xKXYwfpMJLvPvmayvjqlPqWvKrviDopKCXcnyUEkNTtWJoTXCE'", 'max_length': '50'}),
164 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
165 }
166 }
167
168 complete_apps = ['webcatalog']
0169
=== added file 'src/webcatalog/migrations/0017_delete_screenshot_url.py'
--- src/webcatalog/migrations/0017_delete_screenshot_url.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0017_delete_screenshot_url.py 2012-03-20 14:12:21 +0000
@@ -0,0 +1,158 @@
1# encoding: utf-8
2from south.db import db
3from south.v2 import SchemaMigration
4
5
6class Migration(SchemaMigration):
7
8 def forwards(self, orm):
9 db.delete_column('webcatalog_application', 'screenshot_url')
10
11 def backwards(self, orm):
12 db.add_column('webcatalog_application', 'screenshot_url',
13 self.gf('django.db.models.fields.URLField')(
14 default='', max_length=200, blank=True),
15 keep_default=False)
16
17 models = {
18 'auth.group': {
19 'Meta': {'object_name': 'Group'},
20 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
21 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
22 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
23 },
24 'auth.permission': {
25 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
26 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
27 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
28 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
29 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
30 },
31 'auth.user': {
32 'Meta': {'object_name': 'User'},
33 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
34 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
35 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
36 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
37 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
39 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
40 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
41 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
42 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
43 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
44 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
45 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
46 },
47 'contenttypes.contenttype': {
48 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
49 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
50 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
51 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
52 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
53 },
54 'webcatalog.application': {
55 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
56 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
57 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
58 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
59 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
60 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
61 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
62 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
63 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
64 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
65 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
66 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
67 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
68 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
69 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
70 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
71 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
72 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
73 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
74 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
75 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
76 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
77 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
78 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
79 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
80 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
81 },
82 'webcatalog.applicationmedia': {
83 'Meta': {'object_name': 'ApplicationMedia'},
84 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
85 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
86 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
87 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
88 },
89 'webcatalog.consumer': {
90 'Meta': {'object_name': 'Consumer'},
91 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
92 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
93 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
94 'secret': ('django.db.models.fields.CharField', [], {'default': "'DuVUqQPfeNRHRxrsetXaaCgCAsqCpl'", 'max_length': '255', 'blank': 'True'}),
95 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
96 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
97 },
98 'webcatalog.department': {
99 'Meta': {'object_name': 'Department'},
100 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
101 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
102 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
103 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
104 },
105 'webcatalog.distroseries': {
106 'Meta': {'object_name': 'DistroSeries'},
107 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
108 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
110 },
111 'webcatalog.exhibit': {
112 'Meta': {'object_name': 'Exhibit'},
113 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
114 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
115 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
116 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
117 'html': ('django.db.models.fields.TextField', [], {}),
118 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
120 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
121 'sca_id': ('django.db.models.fields.IntegerField', [], {})
122 },
123 'webcatalog.machine': {
124 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
125 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
126 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
127 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
128 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
129 'package_list': ('django.db.models.fields.TextField', [], {}),
130 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
131 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
132 },
133 'webcatalog.nonce': {
134 'Meta': {'object_name': 'Nonce'},
135 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
136 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
137 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
138 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
139 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
140 },
141 'webcatalog.reviewstatsimport': {
142 'Meta': {'object_name': 'ReviewStatsImport'},
143 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
144 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
145 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
146 },
147 'webcatalog.token': {
148 'Meta': {'object_name': 'Token'},
149 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
150 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
151 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
152 'token': ('django.db.models.fields.CharField', [], {'default': "'tKSJuAZIguDcEzJQNGqCebjlZUEosUkZtKYJcEHqtCHgipXtOs'", 'max_length': '50', 'primary_key': 'True'}),
153 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'gUFMuThoNMYOuRbIqLcmEiITrEcHWLibbLFOdDdaaICoVoZHyo'", 'max_length': '50'}),
154 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
155 }
156 }
157
158 complete_apps = ['webcatalog']
0159
=== added file 'src/webcatalog/migrations/0018_add_unique_together_to_application_media.py'
--- src/webcatalog/migrations/0018_add_unique_together_to_application_media.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0018_add_unique_together_to_application_media.py 2012-03-20 14:12:21 +0000
@@ -0,0 +1,157 @@
1# encoding: utf-8
2from south.db import db
3from south.v2 import SchemaMigration
4
5
6class Migration(SchemaMigration):
7
8 def forwards(self, orm):
9 db.create_unique('webcatalog_applicationmedia',
10 ['url', 'application_id'])
11
12 def backwards(self, orm):
13 db.delete_unique('webcatalog_applicationmedia',
14 ['url', 'application_id'])
15
16 models = {
17 'auth.group': {
18 'Meta': {'object_name': 'Group'},
19 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
20 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
21 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
22 },
23 'auth.permission': {
24 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
25 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
26 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
27 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
28 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
29 },
30 'auth.user': {
31 'Meta': {'object_name': 'User'},
32 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
33 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
34 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
35 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
36 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
38 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
39 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
40 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
41 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
42 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
43 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
44 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
45 },
46 'contenttypes.contenttype': {
47 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
48 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
49 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
50 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
51 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
52 },
53 'webcatalog.application': {
54 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
55 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
56 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
57 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
58 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
59 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
60 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
61 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
62 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
63 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
64 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
65 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
66 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
67 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
68 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
69 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
70 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
71 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
72 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
73 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
74 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
75 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
76 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
77 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
78 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
79 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'})
80 },
81 'webcatalog.applicationmedia': {
82 'Meta': {'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'},
83 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
84 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
85 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
86 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
87 },
88 'webcatalog.consumer': {
89 'Meta': {'object_name': 'Consumer'},
90 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
91 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
93 'secret': ('django.db.models.fields.CharField', [], {'default': "'RVPuZojYHnlWbiYQtZFBDrLEQFVKjR'", 'max_length': '255', 'blank': 'True'}),
94 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
95 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
96 },
97 'webcatalog.department': {
98 'Meta': {'object_name': 'Department'},
99 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
101 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
102 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
103 },
104 'webcatalog.distroseries': {
105 'Meta': {'object_name': 'DistroSeries'},
106 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
107 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
109 },
110 'webcatalog.exhibit': {
111 'Meta': {'object_name': 'Exhibit'},
112 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
113 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
114 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
115 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
116 'html': ('django.db.models.fields.TextField', [], {}),
117 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
118 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
119 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
120 'sca_id': ('django.db.models.fields.IntegerField', [], {})
121 },
122 'webcatalog.machine': {
123 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
124 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
125 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
126 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
127 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
128 'package_list': ('django.db.models.fields.TextField', [], {}),
129 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
130 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
131 },
132 'webcatalog.nonce': {
133 'Meta': {'object_name': 'Nonce'},
134 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
135 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
136 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
138 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
139 },
140 'webcatalog.reviewstatsimport': {
141 'Meta': {'object_name': 'ReviewStatsImport'},
142 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
143 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
144 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
145 },
146 'webcatalog.token': {
147 'Meta': {'object_name': 'Token'},
148 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
149 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
150 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
151 'token': ('django.db.models.fields.CharField', [], {'default': "'hxEWMjHsidgiAoDiyBOmdzzAynmFtUySRnVTtAQxLjXgNmkMgw'", 'max_length': '50', 'primary_key': 'True'}),
152 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'yNkBVzMQcOPQKuzNuVWHNTjQPQAdSXGYJToNQqpsuXyioPfmMw'", 'max_length': '50'}),
153 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
154 }
155 }
156
157 complete_apps = ['webcatalog']
0158
=== modified file 'src/webcatalog/models/__init__.py'
--- src/webcatalog/models/__init__.py 2012-03-01 21:38:31 +0000
+++ src/webcatalog/models/__init__.py 2012-03-20 14:12:21 +0000
@@ -25,6 +25,7 @@
25 # applications25 # applications
26 'DistroSeries',26 'DistroSeries',
27 'Application',27 'Application',
28 'ApplicationMedia',
28 'Department',29 'Department',
29 'Exhibit',30 'Exhibit',
30 'ReviewStatsImport',31 'ReviewStatsImport',
@@ -34,6 +35,7 @@
34from .oauthtoken import Token, Consumer, Nonce, DataStore35from .oauthtoken import Token, Consumer, Nonce, DataStore
35from .applications import (36from .applications import (
36 Application,37 Application,
38 ApplicationMedia,
37 Department,39 Department,
38 DistroSeries,40 DistroSeries,
39 Exhibit,41 Exhibit,
4042
=== modified file 'src/webcatalog/models/applications.py'
--- src/webcatalog/models/applications.py 2012-03-19 20:43:17 +0000
+++ src/webcatalog/models/applications.py 2012-03-20 14:12:21 +0000
@@ -37,6 +37,7 @@
37__metaclass__ = type37__metaclass__ = type
38__all__ = [38__all__ = [
39 'Application',39 'Application',
40 'ApplicationMedia',
40 'Department',41 'Department',
41 'DistroSeries',42 'DistroSeries',
42 'Exhibit',43 'Exhibit',
@@ -73,8 +74,6 @@
73 comment = models.CharField(max_length=255, blank=True)74 comment = models.CharField(max_length=255, blank=True)
74 popcon = models.IntegerField(null=True, blank=True)75 popcon = models.IntegerField(null=True, blank=True)
75 channel = models.CharField(max_length=255, blank=True)76 channel = models.CharField(max_length=255, blank=True)
76 screenshot_url = models.URLField(blank=True, verify_exists=False,
77 help_text="Only use this if it is other than the normal screenshot url.")
78 mimetype = models.CharField(max_length=2048, blank=True)77 mimetype = models.CharField(max_length=2048, blank=True)
79 architectures = models.CharField(max_length=255, blank=True)78 architectures = models.CharField(max_length=255, blank=True)
80 keywords = models.CharField(max_length=255, blank=True)79 keywords = models.CharField(max_length=255, blank=True)
@@ -114,6 +113,13 @@
114 return u"{0} ({1})".format(self.name, self.package_name)113 return u"{0} ({1})".format(self.name, self.package_name)
115114
116 @property115 @property
116 def screenshots(self):
117 if not hasattr(self, '_screenshots'):
118 qs = self.applicationmedia_set.filter(media_type='screenshot')
119 self._screenshots = [am.url for am in qs]
120 return self._screenshots
121
122 @property
117 def categories_set(self):123 def categories_set(self):
118 """Return the set of categories for this app"""124 """Return the set of categories for this app"""
119 stripped = [x.strip() for x in self.categories.split(';')]125 stripped = [x.strip() for x in self.categories.split(';')]
@@ -170,6 +176,28 @@
170 unique_together = ('distroseries', 'archive_id')176 unique_together = ('distroseries', 'archive_id')
171177
172178
179class ApplicationMedia(models.Model):
180 MEDIA_CHOICES = (
181 ('icon_16', 'Icon 16x16'),
182 ('icon_32', 'Icon 32x32'),
183 ('icon_64', 'Icon 64x64'),
184 ('icon_128', 'Icon 128x128'),
185 ('screenshot', 'Screenshot'),
186 ('video', 'Video'),
187 )
188
189 application = models.ForeignKey(Application)
190 media_type = models.CharField(max_length=16, choices=MEDIA_CHOICES)
191 url = models.URLField(verify_exists=False)
192
193 def __unicode__(self):
194 return "{0} for {1}".format(self.media_type, self.application)
195
196 class Meta:
197 app_label = 'webcatalog'
198 unique_together = (('application', 'url'),)
199
200
173class Department(models.Model):201class Department(models.Model):
174 parent = models.ForeignKey('self', blank=True, null=True)202 parent = models.ForeignKey('self', blank=True, null=True)
175 name = models.CharField(max_length=64)203 name = models.CharField(max_length=64)
176204
=== modified file 'src/webcatalog/static/css/carousel.css'
--- src/webcatalog/static/css/carousel.css 2012-03-19 20:43:17 +0000
+++ src/webcatalog/static/css/carousel.css 2012-03-20 14:12:21 +0000
@@ -187,6 +187,43 @@
187.featured-widget h4 {187.featured-widget h4 {
188 font-weight: bold;188 font-weight: bold;
189}189}
190
191/* Screenshots carousel */
192.screenshot .carousel-wrapper {
193 height: 190px;
194 width: 233px;
195}
196.screenshot .carousel .pagination li a.active {
197 background-color: #DD4814;
198}
199#screenshots-controls {
200 width: 70px;
201 height: 32px;
202 margin-left: 94px;
203}
204#screenshots-controls .next, #screenshots-controls .prev {
205 background: url(/assets/images/arrow-sliders.png) no-repeat 0 0;
206 float: left;
207 z-index: 20;
208 width: 32px;
209 height: 29px;
210}
211#screenshots-controls .next span, #screenshots-controls .prev span {
212 position:absolute;
213 left: -9999em;
214 height: 0;
215 width: 0;
216}
217#screenshots-controls .prev {
218 background-position:0 0;
219}
220#screenshots-controls .next {
221 background-position: -32px 0;
222}
223#screenshots-controls .prev:hover, #screenshots-controls .prev:focus,
224#screenshots-controls .next:hover, #screenshots-controls .next:focus {
225 outline: none;
226
190.top-rated-stars {227.top-rated-stars {
191 position: relative;228 position: relative;
192 left: -50px;229 left: -50px;
193230
=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
--- src/webcatalog/templates/webcatalog/application_detail.html 2012-03-16 17:18:41 +0000
+++ src/webcatalog/templates/webcatalog/application_detail.html 2012-03-20 14:12:21 +0000
@@ -6,7 +6,8 @@
6{% block header %}Details for {{ application.name }}{% endblock %}6{% block header %}Details for {{ application.name }}{% endblock %}
7{% block head_extra %}7{% block head_extra %}
8{{ block.super }}8{{ block.super }}
9<script src="{{ STATIC_URL }}yui/3.4.0/build/yui/yui-min.js"></script>9<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"/>
10<script src="{% url wc-combo %}?yui/3.4.0/build/yui/yui-min.js&js/carousel.js"></script>
10<script>11<script>
11YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'node-base', function (Y) {12YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('io-base', 'node-base', function (Y) {
12 function complete(id, obj){13 function complete(id, obj){
@@ -71,8 +72,8 @@
71 </div>72 </div>
72 <div class="description">73 <div class="description">
73 <div class="screenshot">74 <div class="screenshot">
74 {% if application.screenshot_url %}75 {% if application.screenshots %}
75 <img src="{{ application.screenshot_url }}" />76 {% include "webcatalog/screenshot_widget.html" %}
76 {% else %}77 {% else %}
77 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />78 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />
78 {% endif %}79 {% endif %}
7980
=== added file 'src/webcatalog/templates/webcatalog/screenshot_widget.html'
--- src/webcatalog/templates/webcatalog/screenshot_widget.html 1970-01-01 00:00:00 +0000
+++ src/webcatalog/templates/webcatalog/screenshot_widget.html 2012-03-20 14:12:21 +0000
@@ -0,0 +1,28 @@
1{% if application.screenshots|length_is:1 %}
2 <img src="{{ application.screenshots.0 }}" />
3{% else %}
4 <div class="carousel-wrapper">
5 <div id="screenshots-carousel" class="carousel">
6 <ol class="carouselol">
7 {% for screenshot in application.screenshots %}
8 <li class="slide{% if forloop.counter > 1 %} disabled{% endif %}">
9 <img src="{{ screenshot }}" />
10 </li>
11 {% endfor %}
12 </ol>
13 </div>
14 </div>
15 <div id="screenshots-controls"></div>
16 <script type="text/javascript">
17 YUI({combine: true, comboBase: '{% url wc-combo %}?', root: 'yui/3.4.0/build/'}).use('uwc-carousel', function(Y) {
18 var caro = new Y.uwc.Carousel({
19 nodeContainer: "#screenshots-carousel",
20 controlsContainer: "#screenshots-controls",
21 containerHeight: 170,
22 containerWidth: 225,
23 autoPlay: true
24 });
25 Y.all('.slide').removeClass('disabled');
26 });
27 </script>
28{% endif %}
029
=== modified file 'src/webcatalog/tests/factory.py'
--- src/webcatalog/tests/factory.py 2012-03-16 19:27:47 +0000
+++ src/webcatalog/tests/factory.py 2012-03-20 14:12:21 +0000
@@ -30,6 +30,7 @@
3030
31from webcatalog.models import (31from webcatalog.models import (
32 Application,32 Application,
33 ApplicationMedia,
33 Consumer,34 Consumer,
34 Department,35 Department,
35 DistroSeries,36 DistroSeries,
@@ -106,15 +107,22 @@
106 if distroseries is None:107 if distroseries is None:
107 distroseries = self.make_distroseries()108 distroseries = self.make_distroseries()
108109
109 return Application.objects.create(110 application = Application.objects.create(
110 package_name=package_name, name=name, comment=comment,111 package_name=package_name, name=name, comment=comment,
111 description=description, popcon=999, icon=icon,112 description=description, popcon=999, icon=icon,
112 icon_name=icon_name, distroseries=distroseries, architectures=arch,113 icon_name=icon_name, distroseries=distroseries, architectures=arch,
113 ratings_average=ratings_average, ratings_total=ratings_total,114 ratings_average=ratings_average, ratings_total=ratings_total,
114 ratings_histogram=ratings_histogram, screenshot_url=screenshot_url,115 ratings_histogram=ratings_histogram,
115 archive_id=archive_id, version=version, is_latest=is_latest,116 archive_id=archive_id, version=version, is_latest=is_latest,
116 wilson_score=wilson_score)117 wilson_score=wilson_score)
117118
119 if screenshot_url:
120 ApplicationMedia.objects.create(
121 application=application,
122 url=screenshot_url,
123 media_type='screenshot')
124 return application
125
118 def make_department(self, name, parent=None, slug=None):126 def make_department(self, name, parent=None, slug=None):
119 if slug is None:127 if slug is None:
120 slug = self.get_unique_string(prefix='slug-')128 slug = self.get_unique_string(prefix='slug-')
121129
=== modified file 'src/webcatalog/tests/test_commands.py'
--- src/webcatalog/tests/test_commands.py 2012-03-16 19:27:47 +0000
+++ src/webcatalog/tests/test_commands.py 2012-03-20 14:12:21 +0000
@@ -483,6 +483,14 @@
483 distroseries=self.natty)483 distroseries=self.natty)
484 self.assertTrue(app.is_latest)484 self.assertTrue(app.is_latest)
485485
486 def test_app_gets_screenshots(self):
487 call_command('import_for_purchase_apps')
488
489 app = Application.objects.get(package_name='hello',
490 distroseries=self.natty)
491 qs = app.applicationmedia_set.filter(media_type='screenshot')
492 self.assertEqual(2, qs.count())
493
486494
487class ImportRatingsTestCase(TestCaseWithFactory):495class ImportRatingsTestCase(TestCaseWithFactory):
488496
@@ -561,7 +569,7 @@
561 self.assertEqual(Decimal('4.00'), scribus.ratings_average)569 self.assertEqual(Decimal('4.00'), scribus.ratings_average)
562 self.assertEqual(4, scribus.ratings_total)570 self.assertEqual(4, scribus.ratings_total)
563 self.assertEqual('[0, 1, 0, 1, 2]', scribus.ratings_histogram)571 self.assertEqual('[0, 1, 0, 1, 2]', scribus.ratings_histogram)
564 self.assertEqual(3.36480032198111, scribus.wilson_score)572 self.assertAlmostEqual(3.36480032198111, scribus.wilson_score)
565 otherapp = Application.objects.get(id=otherapp.id)573 otherapp = Application.objects.get(id=otherapp.id)
566 self.assertEqual(None, otherapp.ratings_total)574 self.assertEqual(None, otherapp.ratings_total)
567575
568576
=== modified file 'src/webcatalog/tests/test_data/sca_apps.txt'
--- src/webcatalog/tests/test_data/sca_apps.txt 2012-03-13 14:09:29 +0000
+++ src/webcatalog/tests/test_data/sca_apps.txt 2012-03-20 14:12:21 +0000
@@ -10,6 +10,10 @@
10 "archive_id": "launchpad_zematynnad2/myppa",10 "archive_id": "launchpad_zematynnad2/myppa",
11 "icon_data": "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",11 "icon_data": "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",
12 "screenshot_url": "",12 "screenshot_url": "",
13 "screenshot_urls": [
14 "http://localhost:8000/screenshot_1.png",
15 "http://localhost:8000/screenshot_2.png"
16 ],
13 "archive_root": "",17 "archive_root": "",
14 "tos_url": "",18 "tos_url": "",
15 "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64.png",19 "icon_url": "http://localhost:8000/site_media/icons/2011/06/eg_64x64.png",
1620
=== modified file 'src/webcatalog/tests/test_forms.py'
--- src/webcatalog/tests/test_forms.py 2011-09-12 13:37:24 +0000
+++ src/webcatalog/tests/test_forms.py 2012-03-20 14:12:21 +0000
@@ -28,7 +28,7 @@
28 ApplicationForm,28 ApplicationForm,
29 desktop_field_mappings,29 desktop_field_mappings,
30 )30 )
31from webcatalog.models import Application31from webcatalog.models import Application, ApplicationMedia
32from webcatalog.tests.factory import TestCaseWithFactory32from webcatalog.tests.factory import TestCaseWithFactory
3333
34__metaclass__ = type34__metaclass__ = type
@@ -132,6 +132,26 @@
132 self.assertEqual(132 self.assertEqual(
133 extra_desktop_info[key], form.cleaned_data[form_key])133 extra_desktop_info[key], form.cleaned_data[form_key])
134134
135 def test_screenshot_url_is_properly_saved_as_application_media(self):
136 app = self.factory.make_application()
137 extra_desktop_info = {
138 'X-AppInstall-Screenshot-Url': 'http://example.com/screenshot',
139 'X-AppInstall-Package': app.package_name,
140 }
141 desktop_entry = self.get_desktop_entry(self.get_desktop_data(
142 extra_desktop_info))
143
144 form = ApplicationForm.get_form_from_desktop_data(desktop_entry,
145 distroseries=app.distroseries)
146 form.save()
147
148 app = Application.objects.get(pk=app.pk)
149
150 self.assertTrue('http://example.com/screenshot' in app.screenshots)
151 self.assertTrue(ApplicationMedia.objects.filter(
152 application=app, media_type='screenshot',
153 url='http://example.com/screenshot').exists())
154
135 def test_screenshot_url_isnt_verified(self):155 def test_screenshot_url_isnt_verified(self):
136 # Verifying if this URLField actually exists is a bad idea:156 # Verifying if this URLField actually exists is a bad idea:
137 # - It expects us to allow arbitrary outgoing connections from the DC157 # - It expects us to allow arbitrary outgoing connections from the DC
138158
=== modified file 'src/webcatalog/tests/test_views.py'
--- src/webcatalog/tests/test_views.py 2012-03-20 11:12:02 +0000
+++ src/webcatalog/tests/test_views.py 2012-03-20 14:12:21 +0000
@@ -60,6 +60,10 @@
6060
61class ApplicationDetailTestCase(TestCaseWithFactory):61class ApplicationDetailTestCase(TestCaseWithFactory):
6262
63 def get_app_details_url(self, app):
64 return reverse('wc-package-detail',
65 args=[app.distroseries.code_name, app.package_name])
66
63 def get_app_and_response(self, code_name='natty', version='11.04',67 def get_app_and_response(self, code_name='natty', version='11.04',
64 arch='x86_64', name=None, comment=None,68 arch='x86_64', name=None, comment=None,
65 description=None, detail_distro=None,69 description=None, detail_distro=None,
@@ -216,9 +220,7 @@
216220
217 app.for_purchase = True221 app.for_purchase = True
218 app.save()222 app.save()
219 url = reverse('wc-package-detail', args=[app.distroseries.code_name,223 response = self.client.get(self.get_app_details_url(app))
220 app.package_name])
221 response = self.client.get(url)
222 self.assertContains(response, '<td>Proprietary</td>')224 self.assertContains(response, '<td>Proprietary</td>')
223 self.assertNotContains(response, '<td>Open Source</td>')225 self.assertNotContains(response, '<td>Open Source</td>')
224226
@@ -241,8 +243,7 @@
241 def test_email_for_download_contents(self):243 def test_email_for_download_contents(self):
242 response, app = self.get_app_and_response(name="salamander")244 response, app = self.get_app_and_response(name="salamander")
243 data = {'email': 'joe@intestinal-flora.net'}245 data = {'email': 'joe@intestinal-flora.net'}
244 args = [app.distroseries.code_name, app.package_name]246 url = self.get_app_details_url(app)
245 url = reverse('wc-package-detail', args=args)
246247
247 self.client.post(url, data=data, follow=True)248 self.client.post(url, data=data, follow=True)
248249
@@ -254,8 +255,7 @@
254 def test_email_for_download_success_message(self):255 def test_email_for_download_success_message(self):
255 response, app = self.get_app_and_response(name="salamander")256 response, app = self.get_app_and_response(name="salamander")
256 data = {'email': 'joe@intestinal-flora.net'}257 data = {'email': 'joe@intestinal-flora.net'}
257 args = [app.distroseries.code_name, app.package_name]258 url = self.get_app_details_url(app)
258 url = reverse('wc-package-detail', args=args)
259259
260 response = self.client.post(url, data=data, follow=True)260 response = self.client.post(url, data=data, follow=True)
261261
@@ -265,8 +265,7 @@
265 def test_email_error_blank(self):265 def test_email_error_blank(self):
266 response, app = self.get_app_and_response(name="salamander")266 response, app = self.get_app_and_response(name="salamander")
267 data = {'email': ''}267 data = {'email': ''}
268 args = [app.distroseries.code_name, app.package_name]268 url = self.get_app_details_url(app)
269 url = reverse('wc-package-detail', args=args)
270269
271 response = self.client.post(url, data=data, follow=True)270 response = self.client.post(url, data=data, follow=True)
272271
@@ -277,8 +276,7 @@
277 def test_email_error_bogus(self):276 def test_email_error_bogus(self):
278 response, app = self.get_app_and_response(name="salamander")277 response, app = self.get_app_and_response(name="salamander")
279 data = {'email': 'bogus'}278 data = {'email': 'bogus'}
280 args = [app.distroseries.code_name, app.package_name]279 url = self.get_app_details_url(app)
281 url = reverse('wc-package-detail', args=args)
282280
283 response = self.client.post(url, data=data, follow=True)281 response = self.client.post(url, data=data, follow=True)
284282
@@ -290,9 +288,7 @@
290 app = self.factory.make_application(version='1.2.3',288 app = self.factory.make_application(version='1.2.3',
291 distroseries=self.factory.make_distroseries())289 distroseries=self.factory.make_distroseries())
292290
293 url = reverse('wc-package-detail', kwargs=dict(291 response = self.client.get(self.get_app_details_url(app))
294 distro=app.distroseries.code_name, package_name=app.package_name))
295 response = self.client.get(url)
296292
297 self.assertContains(response, '<th>Version:</th>')293 self.assertContains(response, '<th>Version:</th>')
298 self.assertContains(response, '<td>1.2.3</td>')294 self.assertContains(response, '<td>1.2.3</td>')
@@ -301,12 +297,27 @@
301 app = self.factory.make_application(version='',297 app = self.factory.make_application(version='',
302 distroseries=self.factory.make_distroseries())298 distroseries=self.factory.make_distroseries())
303299
304 url = reverse('wc-package-detail', kwargs=dict(300 response = self.client.get(self.get_app_details_url(app))
305 distro=app.distroseries.code_name, package_name=app.package_name))
306 response = self.client.get(url)
307301
308 self.assertNotContains(response, '<th>Version</th>')302 self.assertNotContains(response, '<th>Version</th>')
309303
304 def test_with_only_one_screenshot_there_is_no_carousel(self):
305 response, _ = self.get_app_and_response(
306 screenshot_url='http://example.com/screenshot.png')
307
308 self.assertNotContains(response, 'Y.uwc.Carousel')
309
310 def test_with_multiple_screenshots_there_is_js_carousel(self):
311 app = self.factory.make_application()
312 for i in range(3):
313 app.applicationmedia_set.create(
314 url='http://example.com/{}.png'.format(i),
315 media_type='screenshot')
316
317 response = self.client.get(self.get_app_details_url(app))
318
319 self.assertContains(response, 'id="screenshots-carousel"')
320
310321
311class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory):322class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory):
312 def test_renders_latest(self):323 def test_renders_latest(self):

Subscribers

People subscribed via source and target branches