Merge lp:~michael.nelson/rnr-server/dont-depend-on-appname into lp:rnr-server

Proposed by Michael Nelson
Status: Merged
Approved by: Anthony Lenton
Approved revision: 173
Merged at revision: 162
Proposed branch: lp:~michael.nelson/rnr-server/dont-depend-on-appname
Merge into: lp:rnr-server
Diff against target: 583 lines (+417/-20)
8 files modified
src/reviewsapp/api/handlers.py (+1/-1)
src/reviewsapp/management/commands/populate.py (+2/-2)
src/reviewsapp/migrations/0006_add_review_app_name.py (+160/-0)
src/reviewsapp/migrations/0007_populate_review_app_name_and_remove_redundant_software_items.py (+234/-0)
src/reviewsapp/models/reviews.py (+6/-4)
src/reviewsapp/tests/factory.py (+3/-2)
src/reviewsapp/tests/test_handlers.py (+8/-8)
src/reviewsapp/tests/test_rnrclient.py (+3/-3)
To merge this branch: bzr merge lp:~michael.nelson/rnr-server/dont-depend-on-appname
Reviewer Review Type Date Requested Status
Anthony Lenton (community) Approve
Review via email: mp+54013@code.launchpad.net

Commit message

[r=achuni][bug=737649] Data migration from SoftwareItem.app_name -> Review.app_name including removing/creating relevant software items (forwards/backwards respectively).

Description of the change

Overview
========

This branch adds a schema migration (add Review.app_name column) and a data migration (copy app_name from software items, then remove now-redundant software items).

Unfortunately, testing the migrations sux. The best I could come up with is:
http://pastebin.ubuntu.com/582130/

which ensures that for a small subset of data at least, the migration works both forwards and backwards without losing info.

The next branch:
https://code.edge.launchpad.net/~michael.nelson/rnr-server/dont-depend-on-appname-2/+merge/54042
removes the actual SoftwareItem.app_name.

To post a comment you must log in.
Revision history for this message
Anthony Lenton (elachuni) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/reviewsapp/api/handlers.py'
2--- src/reviewsapp/api/handlers.py 2011-03-17 14:29:54 +0000
3+++ src/reviewsapp/api/handlers.py 2011-03-18 17:09:28 +0000
4@@ -191,7 +191,7 @@
5 kwargs = dict(item for item in kwargs.items() if item[1] != 'any')
6 return Review.objects.filter(
7 softwareitem__package_name=package_name,
8- softwareitem__app_name=appname,
9+ app_name=appname,
10 hide=False,
11 **kwargs).order_by(
12 '-usefulness_percentage')[batch_start:batch_end]
13
14=== modified file 'src/reviewsapp/management/commands/populate.py'
15--- src/reviewsapp/management/commands/populate.py 2011-03-15 14:08:02 +0000
16+++ src/reviewsapp/management/commands/populate.py 2011-03-18 17:09:28 +0000
17@@ -182,7 +182,7 @@
18 Currently (id, softwareitem_id, version, architecture_id,
19 repository_id, reviewer_id, date_created, rating, summary,
20 review_text, language, hide, usefulness_total,
21- usefulness_favorable, usefulness_percentage)
22+ usefulness_favorable, usefulness_percentage, app_name)
23 """
24 softwareitem_id = randint(*self.ranges['reviewsapp_softwareitem'])
25 version = '%.1f' % random()
26@@ -195,5 +195,5 @@
27 review_text = self.random_comment(wordcount=20)
28 false = self.booleans[0]
29 return [id, softwareitem_id, version, arch_id, repo_id, reviewer_id,
30- date_created, rating, summary, review_text, 'en', false, 0, 0, 0]
31+ date_created, rating, summary, review_text, 'en', false, 0, 0, 0, '']
32
33
34=== added file 'src/reviewsapp/migrations/0006_add_review_app_name.py'
35--- src/reviewsapp/migrations/0006_add_review_app_name.py 1970-01-01 00:00:00 +0000
36+++ src/reviewsapp/migrations/0006_add_review_app_name.py 2011-03-18 17:09:28 +0000
37@@ -0,0 +1,160 @@
38+
39+from south.db import db
40+from django.db import models
41+from reviewsapp.models import *
42+
43+class Migration:
44+
45+ def forwards(self, orm):
46+
47+ # Adding field 'Review.app_name'
48+ db.add_column('reviewsapp_review', 'app_name', orm['reviewsapp.review:app_name'])
49+
50+ def backwards(self, orm):
51+
52+ # Deleting field 'Review.app_name'
53+ db.delete_column('reviewsapp_review', 'app_name')
54+
55+
56+ models = {
57+ 'auth.group': {
58+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
60+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
61+ },
62+ 'auth.permission': {
63+ 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
64+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
65+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
66+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
68+ },
69+ 'auth.user': {
70+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
71+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
72+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
73+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
74+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
76+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
77+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
78+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
79+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
80+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
81+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
82+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
83+ },
84+ 'contenttypes.contenttype': {
85+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
86+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
87+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
88+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
89+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
90+ },
91+ 'reviewsapp.architecture': {
92+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
93+ 'tag': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '16'})
94+ },
95+ 'reviewsapp.consumer': {
96+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
97+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
98+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
99+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'OzuURCXRwhChWIMXbLbcgooiStFeze'", 'max_length': '255', 'blank': 'True'}),
100+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
101+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
102+ },
103+ 'reviewsapp.nonce': {
104+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Consumer']"}),
105+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
106+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
107+ 'nonce': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
108+ 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Token']"})
109+ },
110+ 'reviewsapp.repository': {
111+ 'distroseries': ('django.db.models.fields.SlugField', [], {'max_length': '25', 'db_index': 'True'}),
112+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
113+ 'origin': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'})
114+ },
115+ 'reviewsapp.review': {
116+ 'app_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
117+ 'architecture': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Architecture']"}),
118+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 25, 823060)'}),
119+ 'hide': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
120+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
121+ 'language': ('django.db.models.fields.SlugField', [], {'max_length': '10', 'db_index': 'True'}),
122+ 'rating': ('django.db.models.fields.IntegerField', [], {}),
123+ 'repository': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Repository']"}),
124+ 'review_text': ('django.db.models.fields.CharField', [], {'max_length': '5000'}),
125+ 'reviewer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
126+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"}),
127+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
128+ 'usefulness_favorable': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
129+ 'usefulness_percentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
130+ 'usefulness_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
131+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '100'})
132+ },
133+ 'reviewsapp.reviewmoderation': {
134+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 25, 770486)'}),
135+ 'date_moderated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
136+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
137+ 'moderation_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
138+ 'moderator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
139+ 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Review']"}),
140+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})
141+ },
142+ 'reviewsapp.reviewmoderationflag': {
143+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 25, 569414)'}),
144+ 'description': ('django.db.models.fields.TextField', [], {}),
145+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
146+ 'review_moderation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.ReviewModeration']"}),
147+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
148+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
149+ },
150+ 'reviewsapp.rnrsettings': {
151+ 'blacklist_words': ('django.db.models.fields.TextField', [], {'default': "''"}),
152+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
153+ 'moderation_mode': ('django.db.models.fields.CharField', [], {'default': "'passive'", 'max_length': '8'})
154+ },
155+ 'reviewsapp.softwareitem': {
156+ 'app_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
157+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 24, 339806)'}),
158+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
159+ 'package_name': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'}),
160+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
161+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'})
162+ },
163+ 'reviewsapp.softwareiteminorigin': {
164+ 'Meta': {'unique_together': "(('softwareitem', 'origin'),)"},
165+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 24, 339806)'}),
166+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
167+ 'origin': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'}),
168+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
169+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
170+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"})
171+ },
172+ 'reviewsapp.softwareiteminrepository': {
173+ 'Meta': {'unique_together': "(('softwareitem', 'repository'),)"},
174+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 11, 24, 339806)'}),
175+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
176+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
177+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
178+ 'repository': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Repository']"}),
179+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"})
180+ },
181+ 'reviewsapp.token': {
182+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Consumer']"}),
183+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
184+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
185+ 'token': ('django.db.models.fields.CharField', [], {'default': "'LXGJOoldWHCXXULnsTXhHOGmAaKCYUrXJRRgrfUjMaJfObdlEU'", 'max_length': '50', 'primary_key': 'True'}),
186+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'MnRkFdkPZXmIqCKNExfyzqdWifIWiWaoZGQKNdtoounoziJVsU'", 'max_length': '50'}),
187+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
188+ },
189+ 'reviewsapp.usefulness': {
190+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
191+ 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Review']"}),
192+ 'useful': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
193+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
194+ }
195+ }
196+
197+ complete_apps = ['reviewsapp']
198
199=== added file 'src/reviewsapp/migrations/0007_populate_review_app_name_and_remove_redundant_software_items.py'
200--- src/reviewsapp/migrations/0007_populate_review_app_name_and_remove_redundant_software_items.py 1970-01-01 00:00:00 +0000
201+++ src/reviewsapp/migrations/0007_populate_review_app_name_and_remove_redundant_software_items.py 2011-03-18 17:09:28 +0000
202@@ -0,0 +1,234 @@
203+
204+from south.db import db
205+from django.db import models
206+from reviewsapp.models import *
207+
208+class Migration:
209+
210+ def forwards(self, orm):
211+ """Copy the app name from the software item and update the link."""
212+ # For each software_item, we want to update all the related
213+ # reviews.
214+ for software_item in orm['reviewsapp.softwareitem'].objects.all():
215+ software_item.review_set.update(
216+ app_name=software_item.app_name)
217+
218+ # Next, we ensure that all the reviews for a given packagename
219+ # refer to a single software_item, and delete the redundant
220+ # softawre_items.
221+ package_names = orm['reviewsapp.softwareitem'].objects.values_list(
222+ 'package_name', flat=True)
223+ package_names = package_names.order_by('package_name').distinct()
224+ for package_name in package_names:
225+ # Grab all the reviews for a given package name and update
226+ # them to all point to the one software item.
227+ reviews = orm['reviewsapp.review'].objects.filter(
228+ softwareitem__package_name=package_name)
229+ first_review = reviews[0]
230+ remaining_software_item = reviews[0].softwareitem
231+ reviews.update(softwareitem=remaining_software_item)
232+
233+ # Now delete all the other software items with the same
234+ # package name (and software item in origins /
235+ # repositories).
236+ orm['reviewsapp.softwareitem'].objects.filter(
237+ package_name=package_name).exclude(
238+ id=remaining_software_item.id).delete()
239+
240+ # Finally, we need to ensure that each review corresponds
241+ # to a valid SoftwareItemInRepository so that stats can be
242+ # calculated.
243+ self.ensure_softwareitem_in_repos(orm, remaining_software_item)
244+
245+ def backwards(self, orm):
246+ """Create the software items with app names and update reviews."""
247+ SoftwareItemClass = orm['reviewsapp.softwareitem']
248+ for software_item in SoftwareItemClass.objects.all():
249+ app_names = software_item.review_set.values_list(
250+ 'app_name', flat=True).distinct()
251+ software_item.app_name = app_names[0]
252+ software_item.save()
253+ package_name = software_item.package_name
254+ # We need to recreate the repository/origin stats for
255+ # the existing softwareitem.
256+ software_item.softwareiteminrepository_set.all().delete()
257+ software_item.softwareiteminorigin_set.all().delete()
258+
259+ # For any other app names, we create a new software item and
260+ # point the relevant reviews to it.
261+ for app_name in app_names:
262+ new_item, created = SoftwareItemClass.objects.get_or_create(
263+ package_name=package_name,
264+ app_name=app_name)
265+
266+ orm['reviewsapp.review'].objects.filter(
267+ softwareitem=software_item,
268+ app_name=app_name).update(
269+ softwareitem=new_item)
270+
271+ # Once all review.software_items have been updated, we can
272+ # create the related items in repos etc.
273+ for item in SoftwareItemClass.objects.all():
274+ self.ensure_softwareitem_in_repos(orm, item)
275+
276+ # Finally, blank out the Review.app_name field.
277+ orm['reviewsapp.review'].objects.all().update(app_name='')
278+
279+ def ensure_softwareitem_in_repos(self, orm, software_item):
280+ # Find all reviews referencing the item and get a list of
281+ # distint repositories.
282+ reviews = orm['reviewsapp.review'].objects.filter(
283+ softwareitem=software_item)
284+ repository_ids = reviews.values_list('repository', flat=True).distinct()
285+ SIIRepo = orm['reviewsapp.softwareiteminrepository']
286+ SIIOrigin = orm['reviewsapp.softwareiteminorigin']
287+ for repo_id in repository_ids:
288+ repo = orm['reviewsapp.repository'].objects.get(id=repo_id)
289+ item_in_repo, created = SIIRepo.objects.get_or_create(
290+ softwareitem=software_item, repository=repo)
291+ item_in_origin, created = SIIOrigin.objects.get_or_create(
292+ softwareitem=software_item, origin=repo.origin)
293+
294+
295+ models = {
296+ 'auth.group': {
297+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
298+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
299+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
300+ },
301+ 'auth.permission': {
302+ 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
303+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
304+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
305+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
306+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
307+ },
308+ 'auth.user': {
309+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
310+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
311+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
312+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
313+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
314+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
315+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
316+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
317+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
318+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
319+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
320+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
321+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
322+ },
323+ 'contenttypes.contenttype': {
324+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
325+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
326+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
327+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
328+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
329+ },
330+ 'reviewsapp.architecture': {
331+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
332+ 'tag': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '16'})
333+ },
334+ 'reviewsapp.consumer': {
335+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
336+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
337+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
338+ 'secret': ('django.db.models.fields.CharField', [], {'default': "'qBxqBnulneXJvOyccXpAJdJmhzatIw'", 'max_length': '255', 'blank': 'True'}),
339+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
340+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'oauth_consumer'", 'unique': 'True', 'to': "orm['auth.User']"})
341+ },
342+ 'reviewsapp.nonce': {
343+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.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['reviewsapp.Token']"})
348+ },
349+ 'reviewsapp.repository': {
350+ 'distroseries': ('django.db.models.fields.SlugField', [], {'max_length': '25', 'db_index': 'True'}),
351+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
352+ 'origin': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'})
353+ },
354+ 'reviewsapp.review': {
355+ 'app_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
356+ 'architecture': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Architecture']"}),
357+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 11, 823749)'}),
358+ 'hide': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
359+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
360+ 'language': ('django.db.models.fields.SlugField', [], {'max_length': '10', 'db_index': 'True'}),
361+ 'rating': ('django.db.models.fields.IntegerField', [], {}),
362+ 'repository': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Repository']"}),
363+ 'review_text': ('django.db.models.fields.CharField', [], {'max_length': '5000'}),
364+ 'reviewer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
365+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"}),
366+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
367+ 'usefulness_favorable': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
368+ 'usefulness_percentage': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
369+ 'usefulness_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
370+ 'version': ('django.db.models.fields.CharField', [], {'max_length': '100'})
371+ },
372+ 'reviewsapp.reviewmoderation': {
373+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 11, 959691)'}),
374+ 'date_moderated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
375+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
376+ 'moderation_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
377+ 'moderator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
378+ 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Review']"}),
379+ 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'})
380+ },
381+ 'reviewsapp.reviewmoderationflag': {
382+ 'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 12, 530560)'}),
383+ 'description': ('django.db.models.fields.TextField', [], {}),
384+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
385+ 'review_moderation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.ReviewModeration']"}),
386+ 'summary': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
387+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
388+ },
389+ 'reviewsapp.rnrsettings': {
390+ 'blacklist_words': ('django.db.models.fields.TextField', [], {'default': "''"}),
391+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
392+ 'moderation_mode': ('django.db.models.fields.CharField', [], {'default': "'passive'", 'max_length': '8'})
393+ },
394+ 'reviewsapp.softwareitem': {
395+ 'app_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True', 'default': "''"}),
396+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 11, 529267)'}),
397+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
398+ 'package_name': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'}),
399+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
400+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'})
401+ },
402+ 'reviewsapp.softwareiteminorigin': {
403+ 'Meta': {'unique_together': "(('softwareitem', 'origin'),)"},
404+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 11, 529267)'}),
405+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
406+ 'origin': ('django.db.models.fields.SlugField', [], {'max_length': '100', 'db_index': 'True'}),
407+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
408+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
409+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"})
410+ },
411+ 'reviewsapp.softwareiteminrepository': {
412+ 'Meta': {'unique_together': "(('softwareitem', 'repository'),)"},
413+ 'date_ratings_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2011, 3, 18, 11, 13, 11, 529267)'}),
414+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
415+ 'ratings_average': ('django.db.models.fields.DecimalField', [], {'default': '0', 'max_digits': '3', 'decimal_places': '2'}),
416+ 'ratings_total': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
417+ 'repository': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Repository']"}),
418+ 'softwareitem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.SoftwareItem']"})
419+ },
420+ 'reviewsapp.token': {
421+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Consumer']"}),
422+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
423+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
424+ 'token': ('django.db.models.fields.CharField', [], {'default': "'mwoIqDbtCeQyFKvSAnleoBTvXpZzbKjDdkxAmBxsqpuAftkRvt'", 'max_length': '50', 'primary_key': 'True'}),
425+ 'token_secret': ('django.db.models.fields.CharField', [], {'default': "'wTeEOEKkKzbDsJPtEUszuDgiDToSnKlTZjnOIrDlWXYKNkDkSr'", 'max_length': '50'}),
426+ 'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'})
427+ },
428+ 'reviewsapp.usefulness': {
429+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
430+ 'review': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['reviewsapp.Review']"}),
431+ 'useful': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True', 'blank': 'True'}),
432+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
433+ }
434+ }
435+
436+ complete_apps = ['reviewsapp']
437
438=== modified file 'src/reviewsapp/models/reviews.py'
439--- src/reviewsapp/models/reviews.py 2011-03-18 07:29:55 +0000
440+++ src/reviewsapp/models/reviews.py 2011-03-18 17:09:28 +0000
441@@ -307,6 +307,11 @@
442 usefulness_total = models.IntegerField(default=0)
443 usefulness_favorable = models.IntegerField(default=0)
444 usefulness_percentage = models.IntegerField(default=0)
445+ # XXX 2011-03-18 michaeln Remove application_name eventually.
446+ # After removing the app_name attribute of software item, we add it
447+ # here on the review so that we're not losing any information being
448+ # passed by the client.
449+ app_name = models.CharField(max_length=100, default='', blank=True)
450
451 def __unicode__(self):
452 return u'lang: {0}, rating: {1}, summary: {2}\n{3}'.format(
453@@ -322,9 +327,6 @@
454 def package_name(self):
455 return self.softwareitem.package_name
456
457- def app_name(self):
458- return self.softwareitem.app_name
459-
460 def origin(self):
461 return self.repository.origin
462
463@@ -332,7 +334,7 @@
464 return self.repository.distroseries
465
466 def package_app_name(self):
467- app_name = self.app_name()
468+ app_name = self.app_name
469 if app_name:
470 return self.package_name() + "/" + app_name
471 else:
472
473=== modified file 'src/reviewsapp/tests/factory.py'
474--- src/reviewsapp/tests/factory.py 2011-03-17 09:21:32 +0000
475+++ src/reviewsapp/tests/factory.py 2011-03-18 17:09:28 +0000
476@@ -160,7 +160,7 @@
477 def makeReview(self, reviewer=None, software_item=None, repository=None,
478 version=None, rating="5", summary=None, review_text=None,
479 date_created=None, language=None, hide=False, num_flags=0,
480- architecture='i386'):
481+ architecture='i386', app_name=''):
482 if reviewer is None:
483 reviewer = self.makeUser()
484 if software_item is None:
485@@ -185,7 +185,8 @@
486 softwareitem=software_item, repository=repository,
487 version=version, rating=rating, summary=summary,
488 review_text=review_text, language=language, hide=hide,
489- date_created=date_created, architecture=architecture)
490+ date_created=date_created, architecture=architecture,
491+ app_name=app_name)
492 review.softwareitem_in_repository.update_stats()
493 for flag_count in range(num_flags):
494 review.flag(
495
496=== modified file 'src/reviewsapp/tests/test_handlers.py'
497--- src/reviewsapp/tests/test_handlers.py 2011-03-17 11:52:10 +0000
498+++ src/reviewsapp/tests/test_handlers.py 2011-03-18 17:09:28 +0000
499@@ -69,7 +69,7 @@
500 def _get_url_kwargs_dict_for_review(review):
501 # this is the quoting we expect
502 appname=quote_plus(quote_plus(
503- ";"+review.softwareitem.app_name.encode("utf-8")))
504+ ";"+review.app_name.encode("utf-8")))
505 pkgname=review.softwareitem.package_name
506 return dict(language=review.language,
507 origin=review.repository.origin,
508@@ -332,8 +332,8 @@
509
510 def test_review_for_app(self):
511 review = self.factory.makeReview(
512- software_item=self.factory.makeSoftwareItem('package_foo',
513- app_name='app_foo'))
514+ software_item=self.factory.makeSoftwareItem('package_foo'),
515+ app_name='app_foo')
516 handler = ShowReviewsHandler()
517
518 reviews = handler.read(None, 'any', 'any', 'any', 'any',
519@@ -345,15 +345,15 @@
520 def test_review_for_app_url(self):
521 # test utf8 encoded app
522 review = self.factory.makeReview(
523- software_item=self.factory.makeSoftwareItem('package_foo',
524- app_name=u'Déjà Dup'))
525+ software_item=self.factory.makeSoftwareItem('package_foo'),
526+ app_name=u'Déjà Dup')
527 url = reverse(
528 'rnr-api-reviews-for-package',
529 kwargs=_get_url_kwargs_dict_for_review(review))
530 response = self.client.get(url)
531 self.assertEqual(response.status_code, 200)
532 reviews = simplejson.loads(response.content)
533- self.assertEqual(reviews[0]["app_name"], review.app_name())
534+ self.assertEqual(reviews[0]["app_name"], review.app_name)
535 self.assertEqual(reviews[0]["package_name"], review.package_name())
536
537 # now repeat with blender that contains a ()
538@@ -366,7 +366,7 @@
539 response = self.client.get(url)
540 self.assertEqual(response.status_code, 200)
541 reviews = simplejson.loads(response.content)
542- self.assertEqual(reviews[0]["app_name"], review.app_name())
543+ self.assertEqual(reviews[0]["app_name"], review.app_name)
544 self.assertEqual(reviews[0]["package_name"], review.package_name())
545
546 # now repeat with a fake app that contains a ";"
547@@ -379,7 +379,7 @@
548 response = self.client.get(url)
549 self.assertEqual(response.status_code, 200)
550 reviews = simplejson.loads(response.content)
551- self.assertEqual(reviews[0]["app_name"], review.app_name())
552+ self.assertEqual(reviews[0]["app_name"], review.app_name)
553 self.assertEqual(reviews[0]["package_name"], review.package_name())
554
555 def test_review_for_language(self):
556
557=== modified file 'src/reviewsapp/tests/test_rnrclient.py'
558--- src/reviewsapp/tests/test_rnrclient.py 2011-03-17 11:52:10 +0000
559+++ src/reviewsapp/tests/test_rnrclient.py 2011-03-18 17:09:28 +0000
560@@ -147,7 +147,7 @@
561
562 self.assertEqual(1, len(reviews))
563 self.assertEqual(review.package_name(), reviews[0].package_name)
564- self.assertEqual(review.app_name(), reviews[0].app_name)
565+ self.assertEqual(review.app_name, reviews[0].app_name)
566 self.assertEqual(review.summary, reviews[0].summary)
567 self.assertEqual(review.version, reviews[0].version)
568 self.assertEqual(review.reviewer_username(),
569@@ -166,13 +166,13 @@
570 origin=review.repository.origin,
571 distroseries=review.repository.distroseries,
572 packagename=review.package_name(),
573- appname=review.app_name(),
574+ appname=str(review.app_name),
575 version=review.version
576 )
577
578 self.assertEqual(1, len(reviews))
579 self.assertEqual(review.package_name(), reviews[0].package_name)
580- self.assertEqual(review.app_name(), reviews[0].app_name)
581+ self.assertEqual(review.app_name, reviews[0].app_name)
582 self.assertEqual(review.summary, reviews[0].summary)
583 self.assertEqual(review.version, reviews[0].version)
584

Subscribers

People subscribed via source and target branches