Merge lp:~michael.nelson/ubuntu-webcatalog/788207-include-ratings-stats into lp:ubuntu-webcatalog
- 788207-include-ratings-stats
- Merge into trunk
Proposed by
Michael Nelson
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Michael Foord | ||||||||
Approved revision: | 52 | ||||||||
Merged at revision: | 43 | ||||||||
Proposed branch: | lp:~michael.nelson/ubuntu-webcatalog/788207-include-ratings-stats | ||||||||
Merge into: | lp:ubuntu-webcatalog | ||||||||
Diff against target: |
517 lines (+319/-16) 12 files modified
.bzrignore (+1/-0) django_project/config/main.cfg (+2/-0) fabtasks/bootstrap.py (+6/-0) new_file_template.py (+0/-9) requirements.txt (+1/-0) src/webcatalog/management/commands/import_app_install_data.py (+1/-1) src/webcatalog/management/commands/import_ratings_stats.py (+98/-0) src/webcatalog/migrations/0006_add_review_stats.py (+92/-0) src/webcatalog/models/__init__.py (+3/-1) src/webcatalog/models/applications.py (+15/-0) src/webcatalog/schema.py (+4/-0) src/webcatalog/tests/test_commands.py (+96/-5) |
||||||||
To merge this branch: | bzr merge lp:~michael.nelson/ubuntu-webcatalog/788207-include-ratings-stats | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Foord (community) | Approve | ||
Review via email: mp+68381@code.launchpad.net |
Commit message
Add import_
Description of the change
Overview
========
This is the first branch for bug 813346 - displaying ratings stats for each app. It defines a management command to pull in the review stats for a given distroseries and update the application objects (with new attributes defined in the migration).
The next branch will add the stats to the UI.
To test:
follow the readme to bootstrap and then `fab test`
To post a comment you must log in.
- 52. By Michael Nelson
-
Updated new_file_template removing unnecessary cruft (according to mfoord).
Revision history for this message
Michael Hall (mhall119) wrote : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2011-07-06 13:53:41 +0000 |
3 | +++ .bzrignore 2011-07-20 15:32:34 +0000 |
4 | @@ -10,3 +10,4 @@ |
5 | branches/ |
6 | sourcecode/framework/ |
7 | sourcecode/django/ |
8 | +django_project/rnrclient.py |
9 | |
10 | === modified file 'django_project/config/main.cfg' |
11 | --- django_project/config/main.cfg 2011-07-05 16:09:31 +0000 |
12 | +++ django_project/config/main.cfg 2011-07-20 15:32:34 +0000 |
13 | @@ -98,3 +98,5 @@ |
14 | [google] |
15 | google_analytics_id = UA-1018242-24 |
16 | |
17 | +[rnr] |
18 | +rnr_service_root = http://reviews.ubuntu.com/reviews/api/1.0 |
19 | |
20 | === modified file 'fabtasks/bootstrap.py' |
21 | --- fabtasks/bootstrap.py 2011-07-05 16:09:31 +0000 |
22 | +++ fabtasks/bootstrap.py 2011-07-20 15:32:34 +0000 |
23 | @@ -112,6 +112,12 @@ |
24 | _symlink( |
25 | "branches/django-openid-auth/django_openid_auth", |
26 | "django_project/django_openid_auth") |
27 | + _get_or_pull_bzr_branch( |
28 | + "lp:/~rnr-developers/rnr-server/rnrclient", |
29 | + "rnrclient") |
30 | + _symlink( |
31 | + "branches/rnrclient/rnrclient.py", |
32 | + "django_project/rnrclient.py") |
33 | |
34 | def bootstrap(): |
35 | virtualenv_create() |
36 | |
37 | === modified file 'new_file_template.py' |
38 | --- new_file_template.py 2011-04-07 08:57:13 +0000 |
39 | +++ new_file_template.py 2011-07-20 15:32:34 +0000 |
40 | @@ -16,12 +16,3 @@ |
41 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
42 | |
43 | """XXX: Module docstring goes here.""" |
44 | - |
45 | -from __future__ import ( |
46 | - absolute_import, |
47 | - with_statement, |
48 | - ) |
49 | - |
50 | -__metaclass__ = type |
51 | -__all__ = [ |
52 | - ] |
53 | |
54 | === modified file 'requirements.txt' |
55 | --- requirements.txt 2011-06-28 10:29:23 +0000 |
56 | +++ requirements.txt 2011-07-20 15:32:34 +0000 |
57 | @@ -8,6 +8,7 @@ |
58 | django-preflight |
59 | mock |
60 | PIL |
61 | +piston-mini-client |
62 | python-debian |
63 | python-openid |
64 | setuptools |
65 | |
66 | === modified file 'src/webcatalog/management/commands/import_app_install_data.py' |
67 | --- src/webcatalog/management/commands/import_app_install_data.py 2011-07-07 14:15:04 +0000 |
68 | +++ src/webcatalog/management/commands/import_app_install_data.py 2011-07-20 15:32:34 +0000 |
69 | @@ -241,7 +241,7 @@ |
70 | |
71 | prefetched_apps = dict((app.package_name, app) for app in |
72 | Application.objects.filter(distroseries=distroseries)) |
73 | - |
74 | + |
75 | # For each package in the apt-cache, we update (or possibly |
76 | # create) a matching db entry. |
77 | for package in cache: |
78 | |
79 | === added file 'src/webcatalog/management/commands/import_ratings_stats.py' |
80 | --- src/webcatalog/management/commands/import_ratings_stats.py 1970-01-01 00:00:00 +0000 |
81 | +++ src/webcatalog/management/commands/import_ratings_stats.py 2011-07-20 15:32:34 +0000 |
82 | @@ -0,0 +1,98 @@ |
83 | +# -*- coding: utf-8 -*- |
84 | +# This file is part of the Ubuntu Web Catalog |
85 | +# Copyright (C) 2011 Canonical Ltd. |
86 | +# |
87 | +# This program is free software: you can redistribute it and/or modify |
88 | +# it under the terms of the GNU Affero General Public License as |
89 | +# published by the Free Software Foundation, either version 3 of the |
90 | +# License, or (at your option) any later version. |
91 | +# |
92 | +# This program is distributed in the hope that it will be useful, |
93 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
94 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
95 | +# GNU Affero General Public License for more details. |
96 | +# |
97 | +# You should have received a copy of the GNU Affero General Public License |
98 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
99 | + |
100 | +"""Management command to import review statistics for a distroseries.""" |
101 | + |
102 | +import os |
103 | +import shutil |
104 | +import tempfile |
105 | +import urllib |
106 | +import operator |
107 | +from datetime import ( |
108 | + datetime, |
109 | + timedelta, |
110 | + ) |
111 | + |
112 | +from django.conf import settings |
113 | +from django.core.management.base import LabelCommand |
114 | + |
115 | +from rnrclient import RatingsAndReviewsAPI |
116 | + |
117 | +from webcatalog.models import ( |
118 | + Application, |
119 | + DistroSeries, |
120 | + ReviewStatsImport, |
121 | + ) |
122 | + |
123 | + |
124 | +class Command(LabelCommand): |
125 | + |
126 | + help = "Update application review statistics." |
127 | + verbosity = 1 |
128 | + |
129 | + def handle_label(self, distroseries_name, **options): |
130 | + self.verbosity = int(options['verbosity']) |
131 | + # Check when we most recently imported review stats for this |
132 | + # distroseries. |
133 | + distroseries, created = DistroSeries.objects.get_or_create( |
134 | + code_name=distroseries_name) |
135 | + |
136 | + stats = self.download_review_stats(distroseries) |
137 | + |
138 | + self.update_apps_with_stats(distroseries, stats) |
139 | + |
140 | + self.update_last_import_timestamp(distroseries) |
141 | + |
142 | + def update_apps_with_stats(self, distroseries, stats): |
143 | + stats_dict = dict((stat.package_name, stat) for stat in stats) |
144 | + apps = Application.objects.filter(distroseries=distroseries, |
145 | + package_name__in=stats_dict.keys()).order_by('package_name') |
146 | + |
147 | + for app in apps: |
148 | + stat = stats_dict[app.package_name] |
149 | + app.ratings_average = stat.ratings_average |
150 | + app.ratings_total = stat.ratings_total |
151 | + app.ratings_histogram = stat.histogram |
152 | + app.save() |
153 | + |
154 | + def update_last_import_timestamp(self, distroseries): |
155 | + review_stats_import, created = ReviewStatsImport.objects.get_or_create( |
156 | + distroseries=distroseries) |
157 | + review_stats_import.last_import = datetime.utcnow() |
158 | + review_stats_import.save() |
159 | + |
160 | + def download_review_stats(self, distroseries): |
161 | + # Download the appropriate stats based on above. |
162 | + other_imports = ReviewStatsImport.objects.filter( |
163 | + distroseries=distroseries).order_by('-last_import') |
164 | + num_days = None |
165 | + if other_imports: |
166 | + stats_import = other_imports[0] |
167 | + time_since_last_import = ( |
168 | + datetime.utcnow() - stats_import.last_import) |
169 | + if time_since_last_import < timedelta(days=1): |
170 | + num_days = 1 |
171 | + elif time_since_last_import < timedelta(days=3): |
172 | + num_days = 3 |
173 | + elif time_since_last_import < timedelta(days=7): |
174 | + num_days = 7 |
175 | + |
176 | + rnr_api = RatingsAndReviewsAPI(service_root=settings.RNR_SERVICE_ROOT) |
177 | + kwargs = dict(origin='ubuntu', distroseries=distroseries.code_name) |
178 | + if num_days: |
179 | + kwargs['days'] = num_days |
180 | + return rnr_api.review_stats(**kwargs) |
181 | |
182 | === added file 'src/webcatalog/migrations/0006_add_review_stats.py' |
183 | --- src/webcatalog/migrations/0006_add_review_stats.py 1970-01-01 00:00:00 +0000 |
184 | +++ src/webcatalog/migrations/0006_add_review_stats.py 2011-07-20 15:32:34 +0000 |
185 | @@ -0,0 +1,92 @@ |
186 | +# encoding: utf-8 |
187 | +import datetime |
188 | +from south.db import db |
189 | +from south.v2 import SchemaMigration |
190 | +from django.db import models |
191 | + |
192 | +class Migration(SchemaMigration): |
193 | + |
194 | + def forwards(self, orm): |
195 | + |
196 | + # Adding model 'ReviewStatsImport' |
197 | + db.create_table('webcatalog_reviewstatsimport', ( |
198 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
199 | + ('distroseries', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['webcatalog.DistroSeries'])), |
200 | + ('last_import', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.utcnow)), |
201 | + )) |
202 | + db.send_create_signal('webcatalog', ['ReviewStatsImport']) |
203 | + |
204 | + # Adding field 'Application.ratings_total' |
205 | + db.add_column('webcatalog_application', 'ratings_total', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) |
206 | + |
207 | + # Adding field 'Application.ratings_average' |
208 | + db.add_column('webcatalog_application', 'ratings_average', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=3, decimal_places=2, blank=True), keep_default=False) |
209 | + |
210 | + # Adding field 'Application.ratings_histogram' |
211 | + db.add_column('webcatalog_application', 'ratings_histogram', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) |
212 | + |
213 | + |
214 | + def backwards(self, orm): |
215 | + |
216 | + # Deleting model 'ReviewStatsImport' |
217 | + db.delete_table('webcatalog_reviewstatsimport') |
218 | + |
219 | + # Deleting field 'Application.ratings_total' |
220 | + db.delete_column('webcatalog_application', 'ratings_total') |
221 | + |
222 | + # Deleting field 'Application.ratings_average' |
223 | + db.delete_column('webcatalog_application', 'ratings_average') |
224 | + |
225 | + # Deleting field 'Application.ratings_histogram' |
226 | + db.delete_column('webcatalog_application', 'ratings_histogram') |
227 | + |
228 | + |
229 | + models = { |
230 | + 'webcatalog.application': { |
231 | + 'Meta': {'unique_together': "(('distroseries', 'archive_id'),)", 'object_name': 'Application'}, |
232 | + 'app_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), |
233 | + 'architectures': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
234 | + 'archive_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'null': 'True', 'blank': 'True'}), |
235 | + 'categories': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
236 | + 'channel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
237 | + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
238 | + 'departments': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['webcatalog.Department']", 'symmetrical': 'False', 'blank': 'True'}), |
239 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
240 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
241 | + 'for_purchase': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
242 | + 'icon': ('django.db.models.fields.files.ImageField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
243 | + 'icon_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
244 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
245 | + 'keywords': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
246 | + 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'blank': 'True'}), |
247 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
248 | + 'package_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
249 | + 'popcon': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
250 | + 'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '7', 'decimal_places': '2'}), |
251 | + 'ratings_average': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '3', 'decimal_places': '2', 'blank': 'True'}), |
252 | + 'ratings_histogram': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), |
253 | + 'ratings_total': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
254 | + 'screenshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
255 | + 'section': ('django.db.models.fields.CharField', [], {'max_length': '32'}) |
256 | + }, |
257 | + 'webcatalog.department': { |
258 | + 'Meta': {'object_name': 'Department'}, |
259 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
260 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}), |
261 | + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.Department']", 'null': 'True', 'blank': 'True'}) |
262 | + }, |
263 | + 'webcatalog.distroseries': { |
264 | + 'Meta': {'object_name': 'DistroSeries'}, |
265 | + 'code_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}), |
266 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
267 | + 'version': ('django.db.models.fields.CharField', [], {'max_length': '10', 'blank': 'True'}) |
268 | + }, |
269 | + 'webcatalog.reviewstatsimport': { |
270 | + 'Meta': {'object_name': 'ReviewStatsImport'}, |
271 | + 'distroseries': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['webcatalog.DistroSeries']"}), |
272 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
273 | + 'last_import': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) |
274 | + } |
275 | + } |
276 | + |
277 | + complete_apps = ['webcatalog'] |
278 | |
279 | === modified file 'src/webcatalog/models/__init__.py' |
280 | --- src/webcatalog/models/__init__.py 2011-06-27 16:31:36 +0000 |
281 | +++ src/webcatalog/models/__init__.py 2011-07-20 15:32:34 +0000 |
282 | @@ -26,11 +26,13 @@ |
283 | 'DistroSeries', |
284 | 'Application', |
285 | 'Department', |
286 | + 'ReviewStatsImport', |
287 | ] |
288 | |
289 | from .oauthtoken import Token, Consumer, Nonce, DataStore |
290 | from .applications import ( |
291 | - DistroSeries, |
292 | Application, |
293 | Department, |
294 | + DistroSeries, |
295 | + ReviewStatsImport, |
296 | ) |
297 | |
298 | === modified file 'src/webcatalog/models/applications.py' |
299 | --- src/webcatalog/models/applications.py 2011-07-07 20:54:45 +0000 |
300 | +++ src/webcatalog/models/applications.py 2011-07-20 15:32:34 +0000 |
301 | @@ -24,6 +24,7 @@ |
302 | |
303 | import logging |
304 | import re |
305 | +from datetime import datetime |
306 | |
307 | from django.core.urlresolvers import reverse |
308 | from django.db import models |
309 | @@ -34,6 +35,7 @@ |
310 | __all__ = [ |
311 | 'Application', |
312 | 'DistroSeries', |
313 | + 'ReviewStatsImport', |
314 | ] |
315 | |
316 | |
317 | @@ -82,6 +84,11 @@ |
318 | price = models.DecimalField(max_digits=7, decimal_places=2, null=True, |
319 | help_text="For-purchase applications (in US Dollars).") |
320 | |
321 | + ratings_total = models.IntegerField(null=True, blank=True) |
322 | + ratings_average = models.DecimalField(max_digits=3, decimal_places=2, |
323 | + null=True, blank=True) |
324 | + ratings_histogram = models.CharField(max_length=128, blank=True) |
325 | + |
326 | # Other desktop fields used by s-c |
327 | # x-gnome-fullname |
328 | # X-AppInstall-Ignore |
329 | @@ -171,3 +178,11 @@ |
330 | |
331 | class Meta: |
332 | app_label = 'webcatalog' |
333 | + |
334 | + |
335 | +class ReviewStatsImport(models.Model): |
336 | + distroseries = models.ForeignKey(DistroSeries, unique=True) |
337 | + last_import = models.DateTimeField(default=datetime.utcnow) |
338 | + |
339 | + class Meta: |
340 | + app_label = 'webcatalog' |
341 | |
342 | === modified file 'src/webcatalog/schema.py' |
343 | --- src/webcatalog/schema.py 2011-07-05 16:09:31 +0000 |
344 | +++ src/webcatalog/schema.py 2011-07-20 15:32:34 +0000 |
345 | @@ -60,3 +60,7 @@ |
346 | preflight = ConfigSection() |
347 | preflight.preflight_base_template = StringConfigOption( |
348 | default="webcatalog/base.html") |
349 | + |
350 | + rnr = ConfigSection() |
351 | + rnr.rnr_service_root = StringConfigOption( |
352 | + default="http://reviews.ubuntu.com/reviews/api/1.0") |
353 | |
354 | === modified file 'src/webcatalog/tests/test_commands.py' |
355 | --- src/webcatalog/tests/test_commands.py 2011-07-06 14:12:57 +0000 |
356 | +++ src/webcatalog/tests/test_commands.py 2011-07-20 15:32:34 +0000 |
357 | @@ -25,6 +25,10 @@ |
358 | import os |
359 | import shutil |
360 | import tempfile |
361 | +from datetime import ( |
362 | + datetime, |
363 | + timedelta, |
364 | + ) |
365 | from decimal import Decimal |
366 | |
367 | from django.conf import settings |
368 | @@ -34,14 +38,19 @@ |
369 | MagicMock, |
370 | Mock, |
371 | ) |
372 | +from rnrclient import ReviewsStats |
373 | |
374 | from webcatalog.models import ( |
375 | Application, |
376 | DistroSeries, |
377 | + ReviewStatsImport, |
378 | ) |
379 | from webcatalog.management.commands.import_app_install_data import ( |
380 | Cache as CacheContextManager, |
381 | - Command, |
382 | + Command as ImportAppInstallCommand, |
383 | + ) |
384 | +from webcatalog.management.commands.import_ratings_stats import ( |
385 | + Command as ImportRatingsStatsCommand, |
386 | ) |
387 | from webcatalog.tests.factory import TestCaseWithFactory |
388 | |
389 | @@ -49,6 +58,7 @@ |
390 | __all__ = [ |
391 | 'ImportAppInstallTestCase', |
392 | 'ImportForPurchaseAppsTestCase', |
393 | + 'ImportRatingsTestCase', |
394 | ] |
395 | |
396 | |
397 | @@ -159,7 +169,7 @@ |
398 | with patch(get_uri_fn) as mock_get_uri: |
399 | mock_get_uri.return_value = 'http://example.com/my.deb' |
400 | with patch('urllib.urlretrieve') as mock_urlretrieve: |
401 | - Command().get_latest_app_data_for_series('natty', tmp_dir) |
402 | + ImportAppInstallCommand().get_latest_app_data_for_series('natty', tmp_dir) |
403 | shutil.rmtree(tmp_dir) |
404 | |
405 | mock_urlretrieve.assert_called_with( |
406 | @@ -252,7 +262,7 @@ |
407 | self.assertTrue(scribus.icon.size > 1) |
408 | |
409 | def test_get_app_data_uri_for_series(self): |
410 | - uri = Command().get_app_data_uri_for_series('natty') |
411 | + uri = ImportAppInstallCommand().get_app_data_uri_for_series('natty') |
412 | |
413 | self.assertEqual('http://example.com/app-install-1.01.deb', uri) |
414 | |
415 | @@ -342,7 +352,7 @@ |
416 | code_name='natty').count()) |
417 | |
418 | with CacheContextManager('natty') as cache: |
419 | - Command().update_from_cache(cache, 'natty') |
420 | + ImportAppInstallCommand().update_from_cache(cache, 'natty') |
421 | |
422 | self.assertEqual(1, DistroSeries.objects.filter( |
423 | code_name='natty').count()) |
424 | @@ -351,7 +361,7 @@ |
425 | self.assertEqual(0, Application.objects.count()) |
426 | |
427 | with CacheContextManager('natty') as cache: |
428 | - Command().update_from_cache(cache, 'natty') |
429 | + ImportAppInstallCommand().update_from_cache(cache, 'natty') |
430 | |
431 | # There are 4 packages in our mock_cache which is patched in the |
432 | # setUp. |
433 | @@ -437,3 +447,84 @@ |
434 | app = Application.objects.get(name='MyApp', |
435 | distroseries=self.distroseries) |
436 | self.assertEqual(6461, app.icon.size) |
437 | + |
438 | + |
439 | +class ImportRatingsTestCase(TestCaseWithFactory): |
440 | + |
441 | + def setUp(self): |
442 | + super(ImportRatingsTestCase, self).setUp() |
443 | + review_stats_fn = 'rnrclient.RatingsAndReviewsAPI.review_stats' |
444 | + self.ratings_stats_patcher = patch(review_stats_fn) |
445 | + self.mock_review_stats = self.ratings_stats_patcher.start() |
446 | + self.mock_review_stats.return_value = [] |
447 | + |
448 | + def tearDown(self): |
449 | + self.ratings_stats_patcher.stop() |
450 | + super(ImportRatingsTestCase, self).tearDown() |
451 | + |
452 | + def test_creates_last_import_record(self): |
453 | + onion = self.factory.make_distroseries(code_name='onion') |
454 | + self.assertEqual(0, |
455 | + ReviewStatsImport.objects.filter(distroseries=onion).count()) |
456 | + |
457 | + call_command('import_ratings_stats', 'onion') |
458 | + |
459 | + self.assertEqual(1, |
460 | + ReviewStatsImport.objects.filter(distroseries=onion).count()) |
461 | + |
462 | + def test_updates_last_import_record(self): |
463 | + onion = self.factory.make_distroseries(code_name='onion') |
464 | + orig_timestamp = datetime(2011, 07, 18, 14, 43) |
465 | + import_record = ReviewStatsImport.objects.create( |
466 | + distroseries=onion, last_import=orig_timestamp) |
467 | + |
468 | + call_command('import_ratings_stats', 'onion') |
469 | + |
470 | + import_records = ReviewStatsImport.objects.filter(distroseries=onion) |
471 | + self.assertEqual(1, import_records.count()) |
472 | + self.assertTrue(orig_timestamp < import_records[0].last_import) |
473 | + |
474 | + def test_download_review_stats_no_previous(self): |
475 | + # If there have been no previous imports, the complete stats |
476 | + # will be retrieved. |
477 | + onion = self.factory.make_distroseries(code_name='onion') |
478 | + |
479 | + call_command('import_ratings_stats', 'onion') |
480 | + |
481 | + self.mock_review_stats.assert_called_with( |
482 | + origin='ubuntu', distroseries='onion') |
483 | + |
484 | + def test_download_review_stats_with_recent(self): |
485 | + # If there has been a recent import, we'll only grab the |
486 | + # relevant stats. |
487 | + onion = self.factory.make_distroseries(code_name='onion') |
488 | + orig_timestamp = datetime.utcnow() - timedelta(2) |
489 | + import_record = ReviewStatsImport.objects.create( |
490 | + distroseries=onion, last_import=orig_timestamp) |
491 | + |
492 | + call_command('import_ratings_stats', 'onion') |
493 | + |
494 | + self.mock_review_stats.assert_called_with( |
495 | + origin='ubuntu', distroseries='onion', days=3) |
496 | + |
497 | + def test_update_app_stats(self): |
498 | + # Any stats provided in the api call will be used to update our |
499 | + # apps in the db. |
500 | + natty = self.factory.make_distroseries(code_name='natty') |
501 | + scribus = self.factory.make_application(package_name='scribus', |
502 | + distroseries=natty) |
503 | + otherapp = self.factory.make_application(package_name='otherapp', |
504 | + distroseries=natty) |
505 | + |
506 | + scribus_stats = ReviewsStats.from_dict(dict(package_name='scribus', |
507 | + ratings_average='4.00', ratings_total=4, |
508 | + histogram='[0, 1, 0, 1, 2]')) |
509 | + self.mock_review_stats.return_value = [scribus_stats] |
510 | + call_command('import_ratings_stats', 'natty') |
511 | + |
512 | + scribus = Application.objects.get(id=scribus.id) |
513 | + self.assertEqual(Decimal('4.00'), scribus.ratings_average) |
514 | + self.assertEqual(4, scribus.ratings_total) |
515 | + self.assertEqual('[0, 1, 0, 1, 2]', scribus.ratings_histogram) |
516 | + otherapp = Application.objects.get(id=otherapp.id) |
517 | + self.assertEqual(None, otherapp.ratings_total) |
There isn't actually a README file