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