Merge lp:~michael.nelson/ubuntu-webcatalog/1015515-only-one-app into lp:ubuntu-webcatalog
- 1015515-only-one-app
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Anthony Lenton | ||||
Approved revision: | 175 | ||||
Merged at revision: | 152 | ||||
Proposed branch: | lp:~michael.nelson/ubuntu-webcatalog/1015515-only-one-app | ||||
Merge into: | lp:ubuntu-webcatalog | ||||
Prerequisite: | lp:~michael.nelson/ubuntu-webcatalog/1015515-dont-create-new-app | ||||
Diff against target: |
621 lines (+467/-44) 9 files modified
src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py (+186/-0) src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py (+171/-0) src/webcatalog/models/applications.py (+1/-1) src/webcatalog/tests/__init__.py (+1/-0) src/webcatalog/tests/factory.py (+13/-3) src/webcatalog/tests/helpers.py (+37/-0) src/webcatalog/tests/test_migrations.py (+57/-0) src/webcatalog/tests/test_views.py (+0/-33) src/webcatalog/views.py (+1/-7) |
||||
To merge this branch: | bzr merge lp:~michael.nelson/ubuntu-webcatalog/1015515-only-one-app | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Anthony Lenton (community) | Approve | ||
Review via email: mp+112553@code.launchpad.net |
Commit message
package_name and distroseries are unique together on Application.
Description of the change
Overview
========
Continues on from the prereq for bug 1015515, adding a migration to ensure that duplicates are removed, before updating the unique constraint for Application.
`fab test:pg=true`
Note:
1) I'm getting "psycopg2.
2) I'm not sure whether I should comment out the migration test as per usual. If it does pass locally with psql, I'm keen to see if it fails on the vps or passes. If it passes, yay.
- 174. By Michael Nelson
-
Merged 1015515-
dont-create- new-app into 1015515- only-one- app. - 175. By Michael Nelson
-
REFACTOR: removed outdated comment and switched get_list_or_404 to get_object now that we have db restriction.
Preview Diff
1 | === added file 'src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py' |
2 | --- src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py 1970-01-01 00:00:00 +0000 |
3 | +++ src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py 2012-06-28 14:07:20 +0000 |
4 | @@ -0,0 +1,186 @@ |
5 | +# encoding: utf-8 |
6 | +from south.v2 import DataMigration |
7 | +from django.db.models import Count |
8 | + |
9 | +class Migration(DataMigration): |
10 | + |
11 | + def forwards(self, orm): |
12 | + """Delete duplicate apps per distroseries. |
13 | + |
14 | + Prefer first an sca_imported app, then the most recent. |
15 | + """ |
16 | + Application = orm['webcatalog.Application'] |
17 | + dups = Application.objects.values( |
18 | + 'package_name', 'distroseries__code_name').order_by().annotate( |
19 | + num_dups=Count('distroseries')).filter(num_dups__gt=1) |
20 | + for dup in dups: |
21 | + sca_version = Application.objects.filter( |
22 | + package_name=dup['package_name'], |
23 | + distroseries__code_name=dup['distroseries__code_name'], |
24 | + imported_from_sca=True) |
25 | + if sca_version: |
26 | + sca_version = sca_version[0] |
27 | + dups_to_delete = Application.objects.filter( |
28 | + package_name=dup['package_name'], |
29 | + distroseries__code_name=dup['distroseries__code_name']) |
30 | + dups_to_delete = dups_to_delete.exclude(id=sca_version.id) |
31 | + dups_to_delete.delete() |
32 | + else: |
33 | + dup_apps = Application.objects.filter( |
34 | + package_name=dup['package_name'], |
35 | + distroseries__code_name=dup['distroseries__code_name'] |
36 | + ).order_by('-id') |
37 | + most_recent = dup_apps[0] |
38 | + dup_apps.exclude(id=most_recent.id).delete() |
39 | + |
40 | + def backwards(self, orm): |
41 | + pass |
42 | + |
43 | + |
44 | + models = { |
45 | + 'auth.group': { |
46 | + 'Meta': {'object_name': 'Group'}, |
47 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
48 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
49 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
50 | + }, |
51 | + 'auth.permission': { |
52 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
53 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
54 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
55 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
56 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
57 | + }, |
58 | + 'auth.user': { |
59 | + 'Meta': {'object_name': 'User'}, |
60 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
61 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
62 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
63 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
64 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
65 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
66 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
67 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
68 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
69 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
70 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
71 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
72 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
73 | + }, |
74 | + 'contenttypes.contenttype': { |
75 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
76 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
77 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
78 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
79 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
80 | + }, |
81 | + 'webcatalog.application': { |
82 | + 'Meta': {'ordering': "('-wilson_score', 'name')", 'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'}, |
83 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
84 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
85 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
86 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
87 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
88 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
89 | + 'debtags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
90 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
91 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
92 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
93 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
94 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
95 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
96 | + 'imported_from_sca': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
97 | + 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
98 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
99 | + 'license': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
100 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
101 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
102 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
103 | + 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
104 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}), |
105 | + 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}), |
106 | + 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), |
107 | + 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
108 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
109 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
110 | + 'wilson_score': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) |
111 | + }, |
112 | + 'webcatalog.applicationmedia': { |
113 | + 'Meta': {'ordering': "('url',)", 'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'}, |
114 | + 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}), |
115 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
116 | + 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), |
117 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
118 | + }, |
119 | + 'webcatalog.consumer': { |
120 | + 'Meta': {'object_name': 'Consumer'}, |
121 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
122 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
123 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
124 | + 'secret': ('django.db.models.fields.CharField', [], {'default': "'HQTqmfphaOwnrnRfWIzLsPUUqqthuh'", 'max_length': '255', 'blank': 'True'}), |
125 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
126 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"}) |
127 | + }, |
128 | + 'webcatalog.department': { |
129 | + 'Meta': {'object_name': 'Department'}, |
130 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
131 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
132 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}), |
133 | + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) |
134 | + }, |
135 | + 'webcatalog.distroseries': { |
136 | + 'Meta': {'object_name': 'DistroSeries'}, |
137 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
138 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
139 | + 'prerelease': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
140 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
141 | + }, |
142 | + 'webcatalog.exhibit': { |
143 | + 'Meta': {'object_name': 'Exhibit'}, |
144 | + 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
145 | + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
146 | + 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), |
147 | + 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}), |
148 | + 'html': ('django.db.models.fields.TextField', [], {}), |
149 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
150 | + 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
151 | + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
152 | + 'sca_id': ('django.db.models.fields.IntegerField', [], {}), |
153 | + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}) |
154 | + }, |
155 | + 'webcatalog.machine': { |
156 | + 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'}, |
157 | + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
158 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
159 | + 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}), |
160 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
161 | + 'package_list': ('django.db.models.fields.TextField', [], {}), |
162 | + 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}), |
163 | + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}) |
164 | + }, |
165 | + 'webcatalog.nonce': { |
166 | + 'Meta': {'object_name': 'Nonce'}, |
167 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
168 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
169 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
170 | + 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
171 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"}) |
172 | + }, |
173 | + 'webcatalog.reviewstatsimport': { |
174 | + 'Meta': {'object_name': 'ReviewStatsImport'}, |
175 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}), |
176 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
177 | + 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
178 | + }, |
179 | + 'webcatalog.token': { |
180 | + 'Meta': {'object_name': 'Token'}, |
181 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
182 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
183 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
184 | + 'token': ('django.db.models.fields.CharField', [], {'default': "'sZsoIBDSOGiMtIjLpqHhOsnLNFoJrpvXhCUaZHktQQKzdYMMPU'", 'max_length': '50', 'primary_key': 'True'}), |
185 | + 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'bpTvhxNJwAHXkVNvtBgcEhuhXeTvySnFZtIQxjgENGFnrrHjMM'", 'max_length': '50'}), |
186 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
187 | + } |
188 | + } |
189 | + |
190 | + complete_apps = ['webcatalog'] |
191 | |
192 | === added file 'src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py' |
193 | --- src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py 1970-01-01 00:00:00 +0000 |
194 | +++ src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py 2012-06-28 14:07:20 +0000 |
195 | @@ -0,0 +1,171 @@ |
196 | +# encoding: utf-8 |
197 | +from south.db import db |
198 | +from south.v2 import SchemaMigration |
199 | + |
200 | + |
201 | +class Migration(SchemaMigration): |
202 | + |
203 | + def forwards(self, orm): |
204 | + |
205 | + # Removing unique constraint on 'Application', fields ['archive_id', 'distroseries'] |
206 | + db.delete_unique('webcatalog_application', ['archive_id', 'distroseries_id']) |
207 | + |
208 | + # Adding unique constraint on 'Application', fields ['distroseries', 'package_name'] |
209 | + db.create_unique('webcatalog_application', ['distroseries_id', 'package_name']) |
210 | + |
211 | + |
212 | + def backwards(self, orm): |
213 | + |
214 | + # Removing unique constraint on 'Application', fields ['distroseries', 'package_name'] |
215 | + db.delete_unique('webcatalog_application', ['distroseries_id', 'package_name']) |
216 | + |
217 | + # Adding unique constraint on 'Application', fields ['archive_id', 'distroseries'] |
218 | + db.create_unique('webcatalog_application', ['archive_id', 'distroseries_id']) |
219 | + |
220 | + models = { |
221 | + 'auth.group': { |
222 | + 'Meta': {'object_name': 'Group'}, |
223 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
224 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
225 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
226 | + }, |
227 | + 'auth.permission': { |
228 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
229 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
230 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
231 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
232 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
233 | + }, |
234 | + 'auth.user': { |
235 | + 'Meta': {'object_name': 'User'}, |
236 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
237 | + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
238 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
239 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
240 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
241 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
242 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
243 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
244 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
245 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
246 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
247 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
248 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
249 | + }, |
250 | + 'contenttypes.contenttype': { |
251 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
252 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
253 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
254 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
255 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
256 | + }, |
257 | + 'webcatalog.application': { |
258 | + 'Meta': {'ordering': "('-wilson_score', 'name')", 'unique_together': "(('distroseries', 'package_name'),)", 'object_name': 'Application'}, |
259 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
260 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
261 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
262 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
263 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
264 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
265 | + 'debtags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
266 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
267 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
268 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
269 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
270 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
271 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
272 | + 'imported_from_sca': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
273 | + 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
274 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
275 | + 'license': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
276 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
277 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
278 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
279 | + 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
280 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}), |
281 | + 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}), |
282 | + 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), |
283 | + 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
284 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
285 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), |
286 | + 'wilson_score': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}) |
287 | + }, |
288 | + 'webcatalog.applicationmedia': { |
289 | + 'Meta': {'ordering': "('url',)", 'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'}, |
290 | + 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}), |
291 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
292 | + 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), |
293 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
294 | + }, |
295 | + 'webcatalog.consumer': { |
296 | + 'Meta': {'object_name': 'Consumer'}, |
297 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
298 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
299 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
300 | + 'secret': ('django.db.models.fields.CharField', [], {'default': "'CkaJaMaVaYjiDEVByNnJWMmblaoFop'", 'max_length': '255', 'blank': 'True'}), |
301 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
302 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"}) |
303 | + }, |
304 | + 'webcatalog.department': { |
305 | + 'Meta': {'object_name': 'Department'}, |
306 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
307 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
308 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}), |
309 | + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}) |
310 | + }, |
311 | + 'webcatalog.distroseries': { |
312 | + 'Meta': {'object_name': 'DistroSeries'}, |
313 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
314 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
315 | + 'prerelease': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
316 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
317 | + }, |
318 | + 'webcatalog.exhibit': { |
319 | + 'Meta': {'object_name': 'Exhibit'}, |
320 | + 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
321 | + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
322 | + 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), |
323 | + 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}), |
324 | + 'html': ('django.db.models.fields.TextField', [], {}), |
325 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
326 | + 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), |
327 | + 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
328 | + 'sca_id': ('django.db.models.fields.IntegerField', [], {}), |
329 | + 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'}) |
330 | + }, |
331 | + 'webcatalog.machine': { |
332 | + 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'}, |
333 | + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
334 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
335 | + 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}), |
336 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), |
337 | + 'package_list': ('django.db.models.fields.TextField', [], {}), |
338 | + 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}), |
339 | + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}) |
340 | + }, |
341 | + 'webcatalog.nonce': { |
342 | + 'Meta': {'object_name': 'Nonce'}, |
343 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
344 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
345 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
346 | + 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
347 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"}) |
348 | + }, |
349 | + 'webcatalog.reviewstatsimport': { |
350 | + 'Meta': {'object_name': 'ReviewStatsImport'}, |
351 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}), |
352 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
353 | + 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
354 | + }, |
355 | + 'webcatalog.token': { |
356 | + 'Meta': {'object_name': 'Token'}, |
357 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}), |
358 | + 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
359 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
360 | + 'token': ('django.db.models.fields.CharField', [], {'default': "'XTzTOPjxjOzhqRWOpdmAPcbaAZqIKoiJKPnfuAVUpuYhCwgxhm'", 'max_length': '50', 'primary_key': 'True'}), |
361 | + 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'uZmUdXqaSKXXaCeeLbiwQjLzDPixijEGysvlXSiSpVatcifOxX'", 'max_length': '50'}), |
362 | + 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) |
363 | + } |
364 | + } |
365 | + |
366 | + complete_apps = ['webcatalog'] |
367 | |
368 | === modified file 'src/webcatalog/models/applications.py' |
369 | --- src/webcatalog/models/applications.py 2012-06-28 14:07:20 +0000 |
370 | +++ src/webcatalog/models/applications.py 2012-06-28 14:07:20 +0000 |
371 | @@ -191,7 +191,7 @@ |
372 | |
373 | class Meta: |
374 | app_label = 'webcatalog' |
375 | - unique_together = ('distroseries', 'archive_id') |
376 | + unique_together = ('distroseries', 'package_name') |
377 | ordering = ('-wilson_score', 'name') |
378 | |
379 | |
380 | |
381 | === modified file 'src/webcatalog/tests/__init__.py' |
382 | --- src/webcatalog/tests/__init__.py 2012-05-30 21:57:52 +0000 |
383 | +++ src/webcatalog/tests/__init__.py 2012-06-28 14:07:20 +0000 |
384 | @@ -24,6 +24,7 @@ |
385 | from .test_handlers import * |
386 | from .test_models import * |
387 | from .test_managers import * |
388 | +from .test_migrations import * |
389 | from .test_pep8 import * |
390 | from .test_preflight import * |
391 | from .test_templatetags import * |
392 | |
393 | === modified file 'src/webcatalog/tests/factory.py' |
394 | --- src/webcatalog/tests/factory.py 2012-06-28 14:07:20 +0000 |
395 | +++ src/webcatalog/tests/factory.py 2012-06-28 14:07:20 +0000 |
396 | @@ -29,7 +29,10 @@ |
397 | from itertools import count |
398 | from django.contrib.auth.models import User |
399 | from django.contrib.sessions.models import Session |
400 | -from django.test import TestCase |
401 | +from django.test import ( |
402 | + TestCase, |
403 | + TransactionTestCase, |
404 | +) |
405 | from django_openid_auth.models import UserOpenID |
406 | |
407 | from webcatalog.models import ( |
408 | @@ -104,7 +107,7 @@ |
409 | ratings_histogram='', screenshot_url='', |
410 | archive_id=None, version='', is_latest=False, |
411 | wilson_score=0.0, debtags=[], departments=None, |
412 | - license='', price=None): |
413 | + license='', price=None, imported_from_sca=False): |
414 | if name is None: |
415 | name = self.get_unique_string(prefix='Readable Name') |
416 | if package_name is None: |
417 | @@ -124,7 +127,7 @@ |
418 | ratings_histogram=ratings_histogram, |
419 | archive_id=archive_id, version=version, is_latest=is_latest, |
420 | wilson_score=wilson_score, debtags=debtags, |
421 | - license=license, price=price) |
422 | + license=license, price=price, imported_from_sca=imported_from_sca) |
423 | |
424 | if departments is not None: |
425 | for d in departments: |
426 | @@ -237,3 +240,10 @@ |
427 | def setUp(self): |
428 | super(TestCaseWithFactory, self).setUp() |
429 | self.factory = WebCatalogObjectFactory() |
430 | + |
431 | + |
432 | +class TxTestCaseWithFactory(TransactionTestCase): |
433 | + |
434 | + def setUp(self): |
435 | + super(TxTestCaseWithFactory, self).setUp() |
436 | + self.factory = WebCatalogObjectFactory() |
437 | |
438 | === modified file 'src/webcatalog/tests/helpers.py' |
439 | --- src/webcatalog/tests/helpers.py 2012-06-06 18:00:26 +0000 |
440 | +++ src/webcatalog/tests/helpers.py 2012-06-28 14:07:20 +0000 |
441 | @@ -30,6 +30,10 @@ |
442 | |
443 | from contextlib import contextmanager |
444 | from django.conf import settings |
445 | +from django.core.management import call_command |
446 | +from south.migration import Migrations |
447 | + |
448 | +from webcatalog.tests.factory import TxTestCaseWithFactory |
449 | |
450 | |
451 | # Original snippet from http://djangosnippets.org/snippets/2156/ |
452 | @@ -60,3 +64,36 @@ |
453 | finally: |
454 | switch_settings(**old_settings) |
455 | # end snippet |
456 | + |
457 | + |
458 | +class MigrationTestCase(TxTestCaseWithFactory): |
459 | + """A Test case for testing migrations.""" |
460 | + |
461 | + # These must be defined by subclasses. |
462 | + start_migration = None |
463 | + dest_migration = None |
464 | + django_application = None |
465 | + |
466 | + def setUp(self): |
467 | + super(MigrationTestCase, self).setUp() |
468 | + migrations = Migrations(self.django_application) |
469 | + self.start_orm = migrations[self.start_migration].orm() |
470 | + self.dest_orm = migrations[self.dest_migration].orm() |
471 | + |
472 | + # Ensure the migration history is up-to-date with a fake migration. |
473 | + # The other option would be to use the south setting for these tests |
474 | + # so that the migrations are used to setup the test db. |
475 | + call_command('migrate', self.django_application, fake=True, |
476 | + verbosity=0) |
477 | + # Then migrate back to the start migration. |
478 | + call_command('migrate', self.django_application, self.start_migration, |
479 | + verbosity=0) |
480 | + |
481 | + def tearDown(self): |
482 | + # Leave the db in the final state so that the test runner doesn't |
483 | + # error when truncating the database. |
484 | + call_command('migrate', self.django_application, verbosity=0) |
485 | + |
486 | + def migrate_to_dest(self): |
487 | + call_command('migrate', self.django_application, self.dest_migration, |
488 | + verbosity=0) |
489 | |
490 | === added file 'src/webcatalog/tests/test_migrations.py' |
491 | --- src/webcatalog/tests/test_migrations.py 1970-01-01 00:00:00 +0000 |
492 | +++ src/webcatalog/tests/test_migrations.py 2012-06-28 14:07:20 +0000 |
493 | @@ -0,0 +1,57 @@ |
494 | +# -*- coding: utf-8 -*- |
495 | +# This file is part of the Apps Directory |
496 | +# Copyright (C) 2011 Canonical Ltd. |
497 | +# |
498 | +# This program is free software: you can redistribute it and/or modify |
499 | +# it under the terms of the GNU Affero General Public License as |
500 | +# published by the Free Software Foundation, either version 3 of the |
501 | +# License, or (at your option) any later version. |
502 | +# |
503 | +# This program is distributed in the hope that it will be useful, |
504 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
505 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
506 | +# GNU Affero General Public License for more details. |
507 | +# |
508 | +# You should have received a copy of the GNU Affero General Public License |
509 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
510 | + |
511 | +"""Temporary test cases for developing and reviewing migrations.""" |
512 | + |
513 | +from webcatalog.tests.helpers import MigrationTestCase |
514 | +from webcatalog.models import Application |
515 | + |
516 | + |
517 | +class OnlyOneAppPerSeriesPkgNameTestCase(MigrationTestCase): |
518 | + start_migration = '0024_remove_application_id_and_rename_for_purchase' |
519 | + dest_migration = '0025_only_one_app_per_series_pkg_name' |
520 | + django_application = 'webcatalog' |
521 | + |
522 | + def test_latest_app_remains_when_neither_sca_imported(self): |
523 | + oneiric = self.factory.make_distroseries(code_name='oneiric') |
524 | + precise = self.factory.make_distroseries(code_name='precise') |
525 | + self.factory.make_application( |
526 | + package_name='foo', distroseries=oneiric) |
527 | + foo_oneiric_2 = self.factory.make_application( |
528 | + package_name='foo', distroseries=oneiric) |
529 | + foo_precise = self.factory.make_application( |
530 | + package_name='foo', distroseries=precise) |
531 | + |
532 | + self.migrate_to_dest() |
533 | + |
534 | + all_foo_apps = Application.objects.filter(package_name='foo') |
535 | + self.assertEqual([foo_oneiric_2, foo_precise], list(all_foo_apps)) |
536 | + |
537 | + def test_sca_imported_app_wins(self): |
538 | + oneiric = self.factory.make_distroseries(code_name='oneiric') |
539 | + precise = self.factory.make_distroseries(code_name='precise') |
540 | + foo_oneiric_1 = self.factory.make_application( |
541 | + package_name='foo', distroseries=oneiric, imported_from_sca=True) |
542 | + self.factory.make_application( |
543 | + package_name='foo', distroseries=oneiric) |
544 | + foo_precise = self.factory.make_application( |
545 | + package_name='foo', distroseries=precise) |
546 | + |
547 | + self.migrate_to_dest() |
548 | + |
549 | + all_foo_apps = Application.objects.filter(package_name='foo') |
550 | + self.assertEqual([foo_oneiric_1, foo_precise], list(all_foo_apps)) |
551 | |
552 | === modified file 'src/webcatalog/tests/test_views.py' |
553 | --- src/webcatalog/tests/test_views.py 2012-06-28 14:07:20 +0000 |
554 | +++ src/webcatalog/tests/test_views.py 2012-06-28 14:07:20 +0000 |
555 | @@ -419,29 +419,6 @@ |
556 | response, |
557 | '<iframe src="http://example.com/video_iframe.html"') |
558 | |
559 | - def test_double_entry_in_the_db_is_displayed_sainely(self): |
560 | - """Make sure that bug:1006442 is fixed""" |
561 | - app = self.factory.make_application() |
562 | - self.factory.make_application( |
563 | - package_name=app.package_name, |
564 | - distroseries=app.distroseries |
565 | - ) |
566 | - response = self.client.get(self.get_app_details_url(app)) |
567 | - |
568 | - self.assertEquals(response.status_code, 200) |
569 | - |
570 | - def test_make_sure_doubled_entry_displays_only_one_distroseries(self): |
571 | - app = self.factory.make_application() |
572 | - self.factory.make_application( |
573 | - package_name=app.package_name, |
574 | - distroseries=app.distroseries |
575 | - ) |
576 | - response = self.client.get( |
577 | - reverse('wc-package-detail', args=[app.package_name])) |
578 | - |
579 | - ds = "Ubuntu {0}".format(app.distroseries.version) |
580 | - self.assertContains(response, ds, 1) |
581 | - |
582 | |
583 | class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory): |
584 | def test_renders_latest(self): |
585 | @@ -1162,16 +1139,6 @@ |
586 | |
587 | self.assertEqual(200, response.status_code) |
588 | |
589 | - def test_handles_multiple_apps_for_same_distroseries(self): |
590 | - ds = self.factory.make_distroseries() |
591 | - self.factory.make_application(package_name='skype', distroseries=ds) |
592 | - self.factory.make_application(package_name='skype', distroseries=ds) |
593 | - |
594 | - response = self.client.get(reverse('wc-package-reviews', |
595 | - args=[ds.code_name, 'skype'])) |
596 | - |
597 | - self.assertEqual(200, response.status_code) |
598 | - |
599 | def test_handles_api_error(self): |
600 | app = self.factory.make_application() |
601 | self.mock_get_reviews.side_effect = APIError('500', 'error') |
602 | |
603 | === modified file 'src/webcatalog/views.py' |
604 | --- src/webcatalog/views.py 2012-06-21 09:21:42 +0000 |
605 | +++ src/webcatalog/views.py 2012-06-28 14:07:20 +0000 |
606 | @@ -231,14 +231,8 @@ |
607 | |
608 | |
609 | def application_reviews(request, package_name, distro, ajax=False, page=1): |
610 | - # XXX 2012-05-25 bug=1002262 michaeln Multiple apps for distroseries. |
611 | - # It seems that we have a few applications which are present in |
612 | - # multiple archives for the same distroseries (skype, |
613 | - # unity-lens-utilities, probably via a myapps PPA as well as extras?). |
614 | - # Don't break when this happens. |
615 | - apps = get_list_or_404(Application, package_name=package_name, |
616 | + app = get_object_or_404(Application, package_name=package_name, |
617 | distroseries__code_name=distro) |
618 | - app = apps[0] |
619 | |
620 | # XXX michaeln 2011-09-15 bug=851662 Better review language options. |
621 | reviews = WebServices().get_reviews_for_package( |
Looks fine!