Merge lp:~abentley/charmworld/related-updates into lp:~juju-jitsu/charmworld/trunk

Proposed by Aaron Bentley
Status: Merged
Approved by: Curtis Hovey
Approved revision: 270
Merged at revision: 271
Proposed branch: lp:~abentley/charmworld/related-updates
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 170 lines (+80/-4)
5 files modified
charmworld/models.py (+6/-0)
charmworld/tests/test_models.py (+12/-0)
charmworld/views/api.py (+9/-1)
charmworld/views/tests/test_api.py (+47/-3)
docs/api.rst (+6/-0)
To merge this branch: bzr merge lp:~abentley/charmworld/related-updates
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+170072@code.launchpad.net

Commit message

Add needed fields to 'related' charm format.

Description of the change

This branch adds fields to the "related" charm format: has_icon, downloads_in_past_30_days, commits_in_past_30_days, is_approved.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you. The changes_since method convinces me to wrap pretty_timedelta as a Charm method so that we don't need to pass the helper to the template.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/models.py'
2--- charmworld/models.py 2013-06-13 21:23:38 +0000
3+++ charmworld/models.py 2013-06-18 13:07:27 +0000
4@@ -2,6 +2,7 @@
5 # licensed under the GNU Affero General Public License version 3 (see
6 # the file LICENSE).
7
8+from calendar import timegm
9 import errno
10 import logging
11 from mimetypes import guess_type
12@@ -590,6 +591,11 @@
13 """Is this charm featured in the interesting view?"""
14 return self._representation['is_featured']
15
16+ def changes_since(self, date):
17+ """The changes that have been introduced since a given datetime."""
18+ date = timegm(date.timetuple())
19+ return [change for change in self.changes if change['created'] >= date]
20+
21
22 class QAData(object):
23 """Build a usable object out of the QA questions and instance data."""
24
25=== modified file 'charmworld/tests/test_models.py'
26--- charmworld/tests/test_models.py 2013-06-13 21:22:17 +0000
27+++ charmworld/tests/test_models.py 2013-06-18 13:07:27 +0000
28@@ -3,6 +3,10 @@
29 # the file LICENSE).
30
31 """Unit tests for our application models."""
32+from datetime import (
33+ date,
34+ datetime,
35+)
36 import logging
37 import md5
38 import os
39@@ -514,6 +518,14 @@
40 charm = Charm({})
41 self.assertIs(False, charm.is_featured)
42
43+ def test_changes_since(self):
44+ changes = [factory.makeChange(created=date(2013, 6, num))
45+ for num in range(1, 31)]
46+ charm = Charm(factory.get_charm_json(changes=changes))
47+ self.assertEqual([], charm.changes_since(datetime(2013, 7, 1)))
48+ self.assertEqual(30, len(charm.changes_since(datetime(2013, 5, 1))))
49+ self.assertEqual(5, len(charm.changes_since(datetime(2013, 6, 26))))
50+
51
52 class TestUser(MongoTestBase):
53 """Unit tests for our User object wrapping of the mongo dict."""
54
55=== modified file 'charmworld/views/api.py'
56--- charmworld/views/api.py 2013-06-17 14:12:26 +0000
57+++ charmworld/views/api.py 2013-06-18 13:07:27 +0000
58@@ -217,13 +217,21 @@
59 return output
60
61 @classmethod
62- def _format_related(cls, charm_data, weight):
63+ def _format_related(cls, charm_data, weight, _now=None):
64+ if _now is None:
65+ now = datetime.utcnow()
66+ else:
67+ now = _now
68 charm = Charm(charm_data)
69 result = {
70 'id': cls._get_api_id(charm),
71 'name': charm.name,
72+ 'has_icon': 'icon.svg' in charm.files,
73 'categories': charm.categories,
74+ 'downloads_in_past_30_days': charm.downloads_in_past_30_days,
75+ 'commits_in_past_30_days': len(charm.changes_since(now)),
76 'weight': weight,
77+ 'is_approved': charm.promulgated,
78 }
79 return result
80
81
82=== modified file 'charmworld/views/tests/test_api.py'
83--- charmworld/views/tests/test_api.py 2013-06-17 14:12:26 +0000
84+++ charmworld/views/tests/test_api.py 2013-06-18 13:07:27 +0000
85@@ -3,7 +3,10 @@
86
87 __metaclass__ = type
88
89-from datetime import datetime
90+from datetime import (
91+ date,
92+ datetime,
93+)
94 import hashlib
95
96 from pyramid.httpexceptions import HTTPNotFound
97@@ -1172,13 +1175,54 @@
98 self.assertIn('charm', result)
99
100 def test_format_related(self):
101- charm = factory.get_charm_json()
102+ charm = factory.get_charm_json(downloads_in_past_30_days=57)
103 formatted = self.api_class._format_related(charm, 0)
104- self.assertItemsEqual(['id', 'name', 'categories', 'weight'],
105+ self.assertItemsEqual(['id', 'name', 'categories', 'weight',
106+ 'has_icon', 'downloads_in_past_30_days',
107+ 'commits_in_past_30_days', 'is_approved'],
108 formatted.keys())
109 self.assertEqual(self.api_class._get_api_id(charm), formatted['id'])
110 self.assertEqual(charm['name'], formatted['name'])
111 self.assertEqual(charm['categories'], formatted['categories'])
112+ self.assertEqual(charm['downloads_in_past_30_days'], 57)
113+
114+ def test_format_related_is_approved(self):
115+ charm = factory.get_charm_json(promulgated=True)
116+ formatted = self.api_class._format_related(charm, 0)
117+ self.assertTrue(formatted['is_approved'])
118+ charm = factory.get_charm_json(promulgated=False)
119+ formatted = self.api_class._format_related(charm, 0)
120+ self.assertFalse(formatted['is_approved'])
121+
122+ def test_format_related_downloads_in_past_30_days(self):
123+ charm = factory.get_charm_json(downloads_in_past_30_days=42)
124+ formatted = self.api_class._format_related(charm, 0)
125+ self.assertEqual(42, formatted['downloads_in_past_30_days'])
126+ charm = factory.get_charm_json(downloads_in_past_30_days=52)
127+ formatted = self.api_class._format_related(charm, 0)
128+ self.assertEqual(52, formatted['downloads_in_past_30_days'])
129+
130+ def test_format_related_commits_in_past_30_days(self):
131+ now = datetime(2013, 06, 17)
132+ charm = factory.get_charm_json(
133+ changes=[factory.makeChange(created=now.date())
134+ for x in range(27)])
135+ formatted = self.api_class._format_related(charm, 0, _now=now)
136+ self.assertEqual(27, formatted['commits_in_past_30_days'])
137+ charm = factory.get_charm_json(
138+ changes=[factory.makeChange(created=now) for x in range(32)] +
139+ [factory.makeChange(created=date(2013, 04, 17))])
140+ formatted = self.api_class._format_related(
141+ charm, 0, _now=now)
142+ self.assertEqual(32, formatted['commits_in_past_30_days'])
143+
144+ def test_format_related_has_icon(self):
145+ charm = factory.get_charm_json(files={})
146+ formatted = self.api_class._format_related(charm, 0)
147+ self.assertFalse(formatted['has_icon'])
148+ charm = factory.get_charm_json(files={'icon.svg': {}})
149+ formatted = self.api_class._format_related(charm, 0)
150+ self.assertTrue(formatted['has_icon'])
151
152 def test_format_related_no_categories(self):
153 charm = factory.get_charm_json()
154
155=== modified file 'docs/api.rst'
156--- docs/api.rst 2013-06-03 15:02:38 +0000
157+++ docs/api.rst 2013-06-18 13:07:27 +0000
158@@ -251,6 +251,12 @@
159 "has_icon": true,
160 /* The categories associated with the charm. */
161 "categories": [],
162+ /* The number of downloads of this charm in the past 30 days */
163+ "downloads_in_past_30_days": 41,
164+ /* The number of commits to this charm in the past 30 days */
165+ "commits_in_past_30_days": 5,
166+ /* See Terminology_ */
167+ "is_approved": true,
168 /* The weight of this charm, for sorting purposes.
169 * Weights are relative to other weights-- their
170 * absolute value has no meaning. */

Subscribers

People subscribed via source and target branches