Merge lp:~michael.nelson/ubuntu-webcatalog/1015515-only-one-app into lp:ubuntu-webcatalog

Proposed by Michael Nelson
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
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.OperationalError: FATAL: role "michael" does not exist" when trying to run tests here with psql (which looks like it's trying to use ident, but I've set local privs in pg_hba to be trust), so
 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.

To post a comment you must log in.
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.

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

Looks fine!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py'
--- src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0025_only_one_app_per_series_pkg_name.py 2012-06-28 14:07:20 +0000
@@ -0,0 +1,186 @@
1# encoding: utf-8
2from south.v2 import DataMigration
3from django.db.models import Count
4
5class Migration(DataMigration):
6
7 def forwards(self, orm):
8 """Delete duplicate apps per distroseries.
9
10 Prefer first an sca_imported app, then the most recent.
11 """
12 Application = orm['webcatalog.Application']
13 dups = Application.objects.values(
14 'package_name', 'distroseries__code_name').order_by().annotate(
15 num_dups=Count('distroseries')).filter(num_dups__gt=1)
16 for dup in dups:
17 sca_version = Application.objects.filter(
18 package_name=dup['package_name'],
19 distroseries__code_name=dup['distroseries__code_name'],
20 imported_from_sca=True)
21 if sca_version:
22 sca_version = sca_version[0]
23 dups_to_delete = Application.objects.filter(
24 package_name=dup['package_name'],
25 distroseries__code_name=dup['distroseries__code_name'])
26 dups_to_delete = dups_to_delete.exclude(id=sca_version.id)
27 dups_to_delete.delete()
28 else:
29 dup_apps = Application.objects.filter(
30 package_name=dup['package_name'],
31 distroseries__code_name=dup['distroseries__code_name']
32 ).order_by('-id')
33 most_recent = dup_apps[0]
34 dup_apps.exclude(id=most_recent.id).delete()
35
36 def backwards(self, orm):
37 pass
38
39
40 models = {
41 'auth.group': {
42 'Meta': {'object_name': 'Group'},
43 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
44 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
45 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
46 },
47 'auth.permission': {
48 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
49 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
50 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
51 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
52 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
53 },
54 'auth.user': {
55 'Meta': {'object_name': 'User'},
56 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
57 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
58 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
59 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
60 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
62 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
63 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
64 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
65 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
66 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
67 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
68 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
69 },
70 'contenttypes.contenttype': {
71 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
72 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
73 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
75 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
76 },
77 'webcatalog.application': {
78 'Meta': {'ordering': "('-wilson_score', 'name')", 'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'},
79 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
80 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
81 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
82 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
83 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
84 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
85 'debtags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
86 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
87 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
88 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
89 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
90 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
91 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'imported_from_sca': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
93 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
94 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
95 'license': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
96 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
97 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
98 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
99 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
100 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
101 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
102 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
103 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
104 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
105 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
106 'wilson_score': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
107 },
108 'webcatalog.applicationmedia': {
109 'Meta': {'ordering': "('url',)", 'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'},
110 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
111 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
112 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
113 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
114 },
115 'webcatalog.consumer': {
116 'Meta': {'object_name': 'Consumer'},
117 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
118 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
119 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
120 'secret': ('django.db.models.fields.CharField', [], {'default': "'HQTqmfphaOwnrnRfWIzLsPUUqqthuh'", 'max_length': '255', 'blank': 'True'}),
121 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
122 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
123 },
124 'webcatalog.department': {
125 'Meta': {'object_name': 'Department'},
126 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
127 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
128 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
129 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
130 },
131 'webcatalog.distroseries': {
132 'Meta': {'object_name': 'DistroSeries'},
133 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
134 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
135 'prerelease': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
136 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
137 },
138 'webcatalog.exhibit': {
139 'Meta': {'object_name': 'Exhibit'},
140 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
141 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
142 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
143 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
144 'html': ('django.db.models.fields.TextField', [], {}),
145 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
147 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
148 'sca_id': ('django.db.models.fields.IntegerField', [], {}),
149 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'})
150 },
151 'webcatalog.machine': {
152 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
153 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
154 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
155 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
156 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
157 'package_list': ('django.db.models.fields.TextField', [], {}),
158 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
159 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
160 },
161 'webcatalog.nonce': {
162 'Meta': {'object_name': 'Nonce'},
163 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
164 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
165 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
166 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
167 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
168 },
169 'webcatalog.reviewstatsimport': {
170 'Meta': {'object_name': 'ReviewStatsImport'},
171 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
172 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
173 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
174 },
175 'webcatalog.token': {
176 'Meta': {'object_name': 'Token'},
177 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
178 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
179 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
180 'token': ('django.db.models.fields.CharField', [], {'default': "'sZsoIBDSOGiMtIjLpqHhOsnLNFoJrpvXhCUaZHktQQKzdYMMPU'", 'max_length': '50', 'primary_key': 'True'}),
181 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'bpTvhxNJwAHXkVNvtBgcEhuhXeTvySnFZtIQxjgENGFnrrHjMM'", 'max_length': '50'}),
182 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
183 }
184 }
185
186 complete_apps = ['webcatalog']
0187
=== added file 'src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py'
--- src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/migrations/0026_unique_pkg_name_distroseries.py 2012-06-28 14:07:20 +0000
@@ -0,0 +1,171 @@
1# encoding: utf-8
2from south.db import db
3from south.v2 import SchemaMigration
4
5
6class Migration(SchemaMigration):
7
8 def forwards(self, orm):
9
10 # Removing unique constraint on 'Application', fields ['archive_id', 'distroseries']
11 db.delete_unique('webcatalog_application', ['archive_id', 'distroseries_id'])
12
13 # Adding unique constraint on 'Application', fields ['distroseries', 'package_name']
14 db.create_unique('webcatalog_application', ['distroseries_id', 'package_name'])
15
16
17 def backwards(self, orm):
18
19 # Removing unique constraint on 'Application', fields ['distroseries', 'package_name']
20 db.delete_unique('webcatalog_application', ['distroseries_id', 'package_name'])
21
22 # Adding unique constraint on 'Application', fields ['archive_id', 'distroseries']
23 db.create_unique('webcatalog_application', ['archive_id', 'distroseries_id'])
24
25 models = {
26 'auth.group': {
27 'Meta': {'object_name': 'Group'},
28 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
29 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
30 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
31 },
32 'auth.permission': {
33 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
34 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
35 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
36 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
38 },
39 'auth.user': {
40 'Meta': {'object_name': 'User'},
41 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
42 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
43 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
44 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
45 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
47 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
48 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
49 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
50 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
51 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
52 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
53 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
54 },
55 'contenttypes.contenttype': {
56 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
57 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
58 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
60 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
61 },
62 'webcatalog.application': {
63 'Meta': {'ordering': "('-wilson_score', 'name')", 'unique_together': "(('distroseries', 'package_name'),)", 'object_name': 'Application'},
64 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
65 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
66 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}),
67 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
68 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
69 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
70 'debtags': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
71 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}),
72 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
73 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}),
74 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
75 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
76 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 'imported_from_sca': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
78 'is_latest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
79 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
80 'license': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
81 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}),
82 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
83 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
84 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
85 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2', 'blank': 'True'}),
86 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}),
87 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
88 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
89 'section': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}),
90 'version': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
91 'wilson_score': ('django.db.models.fields.FloatField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
92 },
93 'webcatalog.applicationmedia': {
94 'Meta': {'ordering': "('url',)", 'unique_together': "(('application', 'url'),)", 'object_name': 'ApplicationMedia'},
95 'application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Application']"}),
96 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
97 'media_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
98 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
99 },
100 'webcatalog.consumer': {
101 'Meta': {'object_name': 'Consumer'},
102 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
103 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
105 'secret': ('django.db.models.fields.CharField', [], {'default': "'CkaJaMaVaYjiDEVByNnJWMmblaoFop'", 'max_length': '255', 'blank': 'True'}),
106 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
107 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
108 },
109 'webcatalog.department': {
110 'Meta': {'object_name': 'Department'},
111 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
112 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
113 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}),
114 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
115 },
116 'webcatalog.distroseries': {
117 'Meta': {'object_name': 'DistroSeries'},
118 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
119 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
120 'prerelease': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
121 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'})
122 },
123 'webcatalog.exhibit': {
124 'Meta': {'object_name': 'Exhibit'},
125 'banner_url': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
126 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
127 'display': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
128 'distroseries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.DistroSeries']", 'symmetrical': 'False'}),
129 'html': ('django.db.models.fields.TextField', [], {}),
130 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
131 'package_names': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
132 'published': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
133 'sca_id': ('django.db.models.fields.IntegerField', [], {}),
134 'weight': ('django.db.models.fields.IntegerField', [], {'default': '0'})
135 },
136 'webcatalog.machine': {
137 'Meta': {'unique_together': "(('owner', 'uuid'),)", 'object_name': 'Machine'},
138 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
139 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140 'logo_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56', 'blank': 'True'}),
141 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
142 'package_list': ('django.db.models.fields.TextField', [], {}),
143 'packages_checksum': ('django.db.models.fields.CharField', [], {'max_length': '56'}),
144 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'})
145 },
146 'webcatalog.nonce': {
147 'Meta': {'object_name': 'Nonce'},
148 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
149 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
150 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
151 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
152 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Token']"})
153 },
154 'webcatalog.reviewstatsimport': {
155 'Meta': {'object_name': 'ReviewStatsImport'},
156 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']", 'unique': 'True'}),
157 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
158 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'})
159 },
160 'webcatalog.token': {
161 'Meta': {'object_name': 'Token'},
162 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Consumer']"}),
163 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
164 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
165 'token': ('django.db.models.fields.CharField', [], {'default': "'XTzTOPjxjOzhqRWOpdmAPcbaAZqIKoiJKPnfuAVUpuYhCwgxhm'", 'max_length': '50', 'primary_key': 'True'}),
166 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'uZmUdXqaSKXXaCeeLbiwQjLzDPixijEGysvlXSiSpVatcifOxX'", 'max_length': '50'}),
167 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
168 }
169 }
170
171 complete_apps = ['webcatalog']
0172
=== modified file 'src/webcatalog/models/applications.py'
--- src/webcatalog/models/applications.py 2012-06-28 14:07:20 +0000
+++ src/webcatalog/models/applications.py 2012-06-28 14:07:20 +0000
@@ -191,7 +191,7 @@
191191
192 class Meta:192 class Meta:
193 app_label = 'webcatalog'193 app_label = 'webcatalog'
194 unique_together = ('distroseries', 'archive_id')194 unique_together = ('distroseries', 'package_name')
195 ordering = ('-wilson_score', 'name')195 ordering = ('-wilson_score', 'name')
196196
197197
198198
=== modified file 'src/webcatalog/tests/__init__.py'
--- src/webcatalog/tests/__init__.py 2012-05-30 21:57:52 +0000
+++ src/webcatalog/tests/__init__.py 2012-06-28 14:07:20 +0000
@@ -24,6 +24,7 @@
24from .test_handlers import *24from .test_handlers import *
25from .test_models import *25from .test_models import *
26from .test_managers import *26from .test_managers import *
27from .test_migrations import *
27from .test_pep8 import *28from .test_pep8 import *
28from .test_preflight import *29from .test_preflight import *
29from .test_templatetags import *30from .test_templatetags import *
3031
=== modified file 'src/webcatalog/tests/factory.py'
--- src/webcatalog/tests/factory.py 2012-06-28 14:07:20 +0000
+++ src/webcatalog/tests/factory.py 2012-06-28 14:07:20 +0000
@@ -29,7 +29,10 @@
29from itertools import count29from itertools import count
30from django.contrib.auth.models import User30from django.contrib.auth.models import User
31from django.contrib.sessions.models import Session31from django.contrib.sessions.models import Session
32from django.test import TestCase32from django.test import (
33 TestCase,
34 TransactionTestCase,
35)
33from django_openid_auth.models import UserOpenID36from django_openid_auth.models import UserOpenID
3437
35from webcatalog.models import (38from webcatalog.models import (
@@ -104,7 +107,7 @@
104 ratings_histogram='', screenshot_url='',107 ratings_histogram='', screenshot_url='',
105 archive_id=None, version='', is_latest=False,108 archive_id=None, version='', is_latest=False,
106 wilson_score=0.0, debtags=[], departments=None,109 wilson_score=0.0, debtags=[], departments=None,
107 license='', price=None):110 license='', price=None, imported_from_sca=False):
108 if name is None:111 if name is None:
109 name = self.get_unique_string(prefix='Readable Name')112 name = self.get_unique_string(prefix='Readable Name')
110 if package_name is None:113 if package_name is None:
@@ -124,7 +127,7 @@
124 ratings_histogram=ratings_histogram,127 ratings_histogram=ratings_histogram,
125 archive_id=archive_id, version=version, is_latest=is_latest,128 archive_id=archive_id, version=version, is_latest=is_latest,
126 wilson_score=wilson_score, debtags=debtags,129 wilson_score=wilson_score, debtags=debtags,
127 license=license, price=price)130 license=license, price=price, imported_from_sca=imported_from_sca)
128131
129 if departments is not None:132 if departments is not None:
130 for d in departments:133 for d in departments:
@@ -237,3 +240,10 @@
237 def setUp(self):240 def setUp(self):
238 super(TestCaseWithFactory, self).setUp()241 super(TestCaseWithFactory, self).setUp()
239 self.factory = WebCatalogObjectFactory()242 self.factory = WebCatalogObjectFactory()
243
244
245class TxTestCaseWithFactory(TransactionTestCase):
246
247 def setUp(self):
248 super(TxTestCaseWithFactory, self).setUp()
249 self.factory = WebCatalogObjectFactory()
240250
=== modified file 'src/webcatalog/tests/helpers.py'
--- src/webcatalog/tests/helpers.py 2012-06-06 18:00:26 +0000
+++ src/webcatalog/tests/helpers.py 2012-06-28 14:07:20 +0000
@@ -30,6 +30,10 @@
3030
31from contextlib import contextmanager31from contextlib import contextmanager
32from django.conf import settings32from django.conf import settings
33from django.core.management import call_command
34from south.migration import Migrations
35
36from webcatalog.tests.factory import TxTestCaseWithFactory
3337
3438
35# Original snippet from http://djangosnippets.org/snippets/2156/39# Original snippet from http://djangosnippets.org/snippets/2156/
@@ -60,3 +64,36 @@
60 finally:64 finally:
61 switch_settings(**old_settings)65 switch_settings(**old_settings)
62# end snippet66# end snippet
67
68
69class MigrationTestCase(TxTestCaseWithFactory):
70 """A Test case for testing migrations."""
71
72 # These must be defined by subclasses.
73 start_migration = None
74 dest_migration = None
75 django_application = None
76
77 def setUp(self):
78 super(MigrationTestCase, self).setUp()
79 migrations = Migrations(self.django_application)
80 self.start_orm = migrations[self.start_migration].orm()
81 self.dest_orm = migrations[self.dest_migration].orm()
82
83 # Ensure the migration history is up-to-date with a fake migration.
84 # The other option would be to use the south setting for these tests
85 # so that the migrations are used to setup the test db.
86 call_command('migrate', self.django_application, fake=True,
87 verbosity=0)
88 # Then migrate back to the start migration.
89 call_command('migrate', self.django_application, self.start_migration,
90 verbosity=0)
91
92 def tearDown(self):
93 # Leave the db in the final state so that the test runner doesn't
94 # error when truncating the database.
95 call_command('migrate', self.django_application, verbosity=0)
96
97 def migrate_to_dest(self):
98 call_command('migrate', self.django_application, self.dest_migration,
99 verbosity=0)
63100
=== added file 'src/webcatalog/tests/test_migrations.py'
--- src/webcatalog/tests/test_migrations.py 1970-01-01 00:00:00 +0000
+++ src/webcatalog/tests/test_migrations.py 2012-06-28 14:07:20 +0000
@@ -0,0 +1,57 @@
1# -*- coding: utf-8 -*-
2# This file is part of the Apps Directory
3# Copyright (C) 2011 Canonical Ltd.
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Affero General Public License as
7# published by the Free Software Foundation, either version 3 of the
8# License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Affero General Public License for more details.
14#
15# You should have received a copy of the GNU Affero General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18"""Temporary test cases for developing and reviewing migrations."""
19
20from webcatalog.tests.helpers import MigrationTestCase
21from webcatalog.models import Application
22
23
24class OnlyOneAppPerSeriesPkgNameTestCase(MigrationTestCase):
25 start_migration = '0024_remove_application_id_and_rename_for_purchase'
26 dest_migration = '0025_only_one_app_per_series_pkg_name'
27 django_application = 'webcatalog'
28
29 def test_latest_app_remains_when_neither_sca_imported(self):
30 oneiric = self.factory.make_distroseries(code_name='oneiric')
31 precise = self.factory.make_distroseries(code_name='precise')
32 self.factory.make_application(
33 package_name='foo', distroseries=oneiric)
34 foo_oneiric_2 = self.factory.make_application(
35 package_name='foo', distroseries=oneiric)
36 foo_precise = self.factory.make_application(
37 package_name='foo', distroseries=precise)
38
39 self.migrate_to_dest()
40
41 all_foo_apps = Application.objects.filter(package_name='foo')
42 self.assertEqual([foo_oneiric_2, foo_precise], list(all_foo_apps))
43
44 def test_sca_imported_app_wins(self):
45 oneiric = self.factory.make_distroseries(code_name='oneiric')
46 precise = self.factory.make_distroseries(code_name='precise')
47 foo_oneiric_1 = self.factory.make_application(
48 package_name='foo', distroseries=oneiric, imported_from_sca=True)
49 self.factory.make_application(
50 package_name='foo', distroseries=oneiric)
51 foo_precise = self.factory.make_application(
52 package_name='foo', distroseries=precise)
53
54 self.migrate_to_dest()
55
56 all_foo_apps = Application.objects.filter(package_name='foo')
57 self.assertEqual([foo_oneiric_1, foo_precise], list(all_foo_apps))
058
=== modified file 'src/webcatalog/tests/test_views.py'
--- src/webcatalog/tests/test_views.py 2012-06-28 14:07:20 +0000
+++ src/webcatalog/tests/test_views.py 2012-06-28 14:07:20 +0000
@@ -419,29 +419,6 @@
419 response,419 response,
420 '<iframe src="http://example.com/video_iframe.html"')420 '<iframe src="http://example.com/video_iframe.html"')
421421
422 def test_double_entry_in_the_db_is_displayed_sainely(self):
423 """Make sure that bug:1006442 is fixed"""
424 app = self.factory.make_application()
425 self.factory.make_application(
426 package_name=app.package_name,
427 distroseries=app.distroseries
428 )
429 response = self.client.get(self.get_app_details_url(app))
430
431 self.assertEquals(response.status_code, 200)
432
433 def test_make_sure_doubled_entry_displays_only_one_distroseries(self):
434 app = self.factory.make_application()
435 self.factory.make_application(
436 package_name=app.package_name,
437 distroseries=app.distroseries
438 )
439 response = self.client.get(
440 reverse('wc-package-detail', args=[app.package_name]))
441
442 ds = "Ubuntu {0}".format(app.distroseries.version)
443 self.assertContains(response, ds, 1)
444
445422
446class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory):423class ApplicationDetailNoSeriesTestCase(TestCaseWithFactory):
447 def test_renders_latest(self):424 def test_renders_latest(self):
@@ -1162,16 +1139,6 @@
11621139
1163 self.assertEqual(200, response.status_code)1140 self.assertEqual(200, response.status_code)
11641141
1165 def test_handles_multiple_apps_for_same_distroseries(self):
1166 ds = self.factory.make_distroseries()
1167 self.factory.make_application(package_name='skype', distroseries=ds)
1168 self.factory.make_application(package_name='skype', distroseries=ds)
1169
1170 response = self.client.get(reverse('wc-package-reviews',
1171 args=[ds.code_name, 'skype']))
1172
1173 self.assertEqual(200, response.status_code)
1174
1175 def test_handles_api_error(self):1142 def test_handles_api_error(self):
1176 app = self.factory.make_application()1143 app = self.factory.make_application()
1177 self.mock_get_reviews.side_effect = APIError('500', 'error')1144 self.mock_get_reviews.side_effect = APIError('500', 'error')
11781145
=== modified file 'src/webcatalog/views.py'
--- src/webcatalog/views.py 2012-06-21 09:21:42 +0000
+++ src/webcatalog/views.py 2012-06-28 14:07:20 +0000
@@ -231,14 +231,8 @@
231231
232232
233def application_reviews(request, package_name, distro, ajax=False, page=1):233def application_reviews(request, package_name, distro, ajax=False, page=1):
234 # XXX 2012-05-25 bug=1002262 michaeln Multiple apps for distroseries.234 app = get_object_or_404(Application, package_name=package_name,
235 # It seems that we have a few applications which are present in
236 # multiple archives for the same distroseries (skype,
237 # unity-lens-utilities, probably via a myapps PPA as well as extras?).
238 # Don't break when this happens.
239 apps = get_list_or_404(Application, package_name=package_name,
240 distroseries__code_name=distro)235 distroseries__code_name=distro)
241 app = apps[0]
242236
243 # XXX michaeln 2011-09-15 bug=851662 Better review language options.237 # XXX michaeln 2011-09-15 bug=851662 Better review language options.
244 reviews = WebServices().get_reviews_for_package(238 reviews = WebServices().get_reviews_for_package(

Subscribers

People subscribed via source and target branches