Merge lp:~adeuring/charmworld/1199790-last-change-error into lp:~juju-jitsu/charmworld/trunk

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: 329
Merged at revision: 329
Proposed branch: lp:~adeuring/charmworld/1199790-last-change-error
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 228 lines (+82/-22)
7 files modified
charmworld/models.py (+17/-2)
charmworld/testing/factory.py (+17/-13)
charmworld/tests/test_models.py (+18/-4)
charmworld/views/charms.py (+0/-1)
charmworld/views/helpers.py (+7/-2)
charmworld/views/tests/test_api.py (+12/-0)
charmworld/views/tests/test_helpers.py (+11/-0)
To merge this branch: bzr merge lp:~adeuring/charmworld/1199790-last-change-error
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Aaron Bentley (community) Approve
Review via email: mp+177865@code.launchpad.net

Commit message

Return dummy data for Charm.last_change and Charm.first_change, if a branch does not have any commits.

Description of the change

This branch fixes bug 1199780: search requests errors when a charm has no
last_change

While the root cause of the error described in the bug report (a branch is
registered in LP but no real branch exists) may be fixed by recent changes
to the ingest job, a similar situation may still exist: A branch that
exists but that has no commits.

This problem affects not only the API views but also views which
use the function views.helpers.format_change().

I changed class Charm in models.py so that it does not return None in
this case but some dummy data.

The function format_change() still needs a change: Calling
datetime.strftime() obviously does not work if for non-existent
timestamp -- and using a default timestamp does not make sense either.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

This is fine as a first step, but I hope there will be a follow-on that changes all the views to check for None when accessing Charm.last_change and Charm.first_change, since that will look better.

review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

No commit message specified.

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

Attempt to merge into lp:charmworld failed due to conflicts:

text conflict in charmworld/testing/factory.py

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Abel Deuring (adeuring) wrote :

I think there is no need for further changes. format_change() is used in templates/charm.pt, but not for first_change or last_change:

              <tr tal:repeat="change charm.changes">
                <td tal:content="structure: format_change(change, charm)"></td>
              </tr>

The second callsite of format_change is in views.feeds._recently_changed(), which in turn is used in recently_changed()
 serving the URL http://staging.jujucharms.com/recently-changed and in recently_changed_feed(), serving the URL http://staging.jujucharms.com/feed/charm-changes. IMHO, an empty string dor the last change does not matter on these pages.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmworld/models.py'
--- charmworld/models.py 2013-07-30 19:14:07 +0000
+++ charmworld/models.py 2013-07-31 17:04:21 +0000
@@ -470,15 +470,30 @@
470 """The list of revision info dicts from the charm's branch."""470 """The list of revision info dicts from the charm's branch."""
471 return self._representation['changes']471 return self._representation['changes']
472472
473 def _get_change(self, name):
474 # Some view functions assume that last_change and first_change
475 # always exist, but this is not true for empty branches. Return
476 # some default data in this case.
477 change = self._representation[name]
478 if change is None:
479 return {
480 'authors': [],
481 'revno': 0,
482 'committer': '',
483 'created': None,
484 'message': '',
485 }
486 return change
487
473 @property488 @property
474 def first_change(self):489 def first_change(self):
475 """The revision dict of the first change to the charm's branch."""490 """The revision dict of the first change to the charm's branch."""
476 return self._representation['first_change']491 return self._get_change('first_change')
477492
478 @property493 @property
479 def last_change(self):494 def last_change(self):
480 """The revision dict of the last change to the charm's branch."""495 """The revision dict of the last change to the charm's branch."""
481 return self._representation['last_change']496 return self._get_change('last_change')
482497
483 @property498 @property
484 def revision(self):499 def revision(self):
485500
=== modified file 'charmworld/testing/factory.py'
--- charmworld/testing/factory.py 2013-07-31 14:41:29 +0000
+++ charmworld/testing/factory.py 2013-07-31 17:04:21 +0000
@@ -172,7 +172,7 @@
172 date_created=None, downloads=0,172 date_created=None, downloads=0,
173 downloads_in_past_30_days=0, is_featured=False,173 downloads_in_past_30_days=0, is_featured=False,
174 promulgated=False, categories=None, branch_deleted=False,174 promulgated=False, categories=None, branch_deleted=False,
175 proof=None, payload=None):175 proof=None, payload=None, empty_branch=False):
176 """Return the json of a charm."""176 """Return the json of a charm."""
177 if not description:177 if not description:
178 description = """byobu-class provides remote terminal access through178 description = """byobu-class provides remote terminal access through
@@ -254,12 +254,6 @@
254 "changes": changes,254 "changes": changes,
255 "description": description,255 "description": description,
256 "doctype": "charm",256 "doctype": "charm",
257 "first_change": {
258 "committer": "Jane Doe <jane@sample.com>",
259 "created": 1308093138.892,
260 "message": "initial checkin\n",
261 "revno": 1
262 },
263 "hooks": [257 "hooks": [
264 "install",258 "install",
265 "start",259 "start",
@@ -267,12 +261,6 @@
267 "website-relation-joined"261 "website-relation-joined"
268 ],262 ],
269 'is_featured': is_featured,263 'is_featured': is_featured,
270 "last_change": {
271 "committer": "John Doe <jdoe@example.com>",
272 "created": 1343082725.06,
273 "message": commit_message,
274 "revno": revno,
275 },
276 "maintainer": maintainer,264 "maintainer": maintainer,
277 "proof": proof,265 "proof": proof,
278 "provides": provides,266 "provides": provides,
@@ -287,6 +275,22 @@
287 'downloads_in_past_30_days': downloads_in_past_30_days,275 'downloads_in_past_30_days': downloads_in_past_30_days,
288 'date_created': date_created.isoformat(),276 'date_created': date_created.isoformat(),
289 })277 })
278 if empty_branch:
279 charm["first_change"] = None
280 charm["last_change"] = None
281 else:
282 charm["first_change"] = {
283 "committer": "Jane Doe <jane@sample.com>",
284 "created": 1308093138.892,
285 "message": "initial checkin\n",
286 "revno": 1
287 }
288 charm["last_change"] = {
289 "committer": "John Doe <jdoe@example.com>",
290 "created": 1343082725.06,
291 "message": commit_message,
292 "revno": revno,
293 }
290 if tests is not None:294 if tests is not None:
291 charm['tests'] = tests295 charm['tests'] = tests
292 charm['test_results'] = {}296 charm['test_results'] = {}
293297
=== modified file 'charmworld/tests/test_models.py'
--- charmworld/tests/test_models.py 2013-07-30 19:14:07 +0000
+++ charmworld/tests/test_models.py 2013-07-31 17:04:21 +0000
@@ -346,9 +346,16 @@
346 charm_data['first_change'] = change346 charm_data['first_change'] = change
347 charm = Charm(charm_data)347 charm = Charm(charm_data)
348 self.assertIs(change, charm.first_change)348 self.assertIs(change, charm.first_change)
349 # The default is None.349 # The default is some dummy data.
350 charm = Charm({})350 charm = Charm({})
351 self.assertIs(None, charm.first_change)351 expected = {
352 'authors': [],
353 'committer': '',
354 'created': None,
355 'message': '',
356 'revno': 0,
357 }
358 self.assertEqual(expected, charm.first_change)
352359
353 def test_last_change(self):360 def test_last_change(self):
354 # The last_change property is a revision info dict.361 # The last_change property is a revision info dict.
@@ -357,9 +364,16 @@
357 charm_data['last_change'] = change364 charm_data['last_change'] = change
358 charm = Charm(charm_data)365 charm = Charm(charm_data)
359 self.assertIs(change, charm.last_change)366 self.assertIs(change, charm.last_change)
360 # The default is None.367 # The default is some dummy data.
361 charm = Charm({})368 charm = Charm({})
362 self.assertIs(None, charm.last_change)369 expected = {
370 'authors': [],
371 'committer': '',
372 'created': None,
373 'message': '',
374 'revno': 0,
375 }
376 self.assertEqual(expected, charm.last_change)
363377
364 def test_revision(self):378 def test_revision(self):
365 # The revision property is an int.379 # The revision property is an int.
366380
=== modified file 'charmworld/views/charms.py'
--- charmworld/views/charms.py 2013-07-24 15:05:38 +0000
+++ charmworld/views/charms.py 2013-07-31 17:04:21 +0000
@@ -124,7 +124,6 @@
124 endpoint='charm'124 endpoint='charm'
125 )125 )
126 icon_path = icon_path + '/' + remainder126 icon_path = icon_path + '/' + remainder
127
128 return {127 return {
129 "charm": charm,128 "charm": charm,
130 'format_change': format_change,129 'format_change': format_change,
131130
=== modified file 'charmworld/views/helpers.py'
--- charmworld/views/helpers.py 2013-07-24 20:01:52 +0000
+++ charmworld/views/helpers.py 2013-07-31 17:04:21 +0000
@@ -13,8 +13,13 @@
13def format_change(change, charm=None):13def format_change(change, charm=None):
14 """Format a bzr commit."""14 """Format a bzr commit."""
15 parts = []15 parts = []
16 date = datetime.utcfromtimestamp(change["created"])16 date = change["created"]
17 parts.append(unicode(date.strftime("%Y/%m/%d")))17 # bug 1199790: We may have a branch without any commits.
18 # In this case, class Charm returns dummy data for its attributes
19 # first_change and last_change.
20 if date is not None:
21 date = datetime.utcfromtimestamp(date)
22 parts.append(unicode(date.strftime("%Y/%m/%d")))
18 parts.append(name_filter(change['committer']))23 parts.append(name_filter(change['committer']))
19 parts.append(change["message"][:100])24 parts.append(change["message"][:100])
20 if charm is not None:25 if charm is not None:
2126
=== modified file 'charmworld/views/tests/test_api.py'
--- charmworld/views/tests/test_api.py 2013-07-26 14:33:11 +0000
+++ charmworld/views/tests/test_api.py 2013-07-31 17:04:21 +0000
@@ -8,6 +8,7 @@
8 datetime,8 datetime,
9)9)
10import hashlib10import hashlib
11import json
1112
12from pyramid.httpexceptions import HTTPNotFound13from pyramid.httpexceptions import HTTPNotFound
13from webob.etag import ETagMatcher14from webob.etag import ETagMatcher
@@ -607,6 +608,17 @@
607 self.assertNotIn('related', data['result']['popular'][0]['metadata'])608 self.assertNotIn('related', data['result']['popular'][0]['metadata'])
608 self.assertNotIn('related', data['result']['featured'][0]['metadata'])609 self.assertNotIn('related', data['result']['featured'][0]['metadata'])
609610
611 def test_charm_with_empty_branch(self):
612 # Empty branches do not have real data for the attributes
613 # first_change and last_change. The view is nevertheless properly
614 # rendered.
615 self.makeCharm(empty_branch=True)
616 result = json.loads(self.get_response('charms').body)['result'][0]
617 # But the returned data does contain only dummy revision related
618 # data.
619 self.assertEqual('0', result['charm']['code_source']['revision'])
620 self.assertEqual('', result['charm']['code_source']['last_log'])
621
610622
611class TestAPI2Charms(TestAPICharms, API2Mixin):623class TestAPI2Charms(TestAPICharms, API2Mixin):
612 """Test API 2's "charms" endpoint."""624 """Test API 2's "charms" endpoint."""
613625
=== modified file 'charmworld/views/tests/test_helpers.py'
--- charmworld/views/tests/test_helpers.py 2013-06-26 12:20:17 +0000
+++ charmworld/views/tests/test_helpers.py 2013-07-31 17:04:21 +0000
@@ -3,6 +3,7 @@
33
4from charmworld.models import Charm4from charmworld.models import Charm
5from charmworld.views.helpers import (5from charmworld.views.helpers import (
6 format_change,
6 interfaces,7 interfaces,
7 result_sorter,8 result_sorter,
8)9)
@@ -129,3 +130,13 @@
129 charm_b = factory.get_charm_json(series='raring')130 charm_b = factory.get_charm_json(series='raring')
130 with self.assertNoException():131 with self.assertNoException():
131 result_sorter(charm_a, charm_b)132 result_sorter(charm_a, charm_b)
133
134 def test_format_change(self):
135 charm = Charm(factory.get_charm_json())
136 result = format_change(charm.last_change)
137 self.assertEqual('2012/07/23 John Doe maintainer', result)
138
139 def test_format_change_empty_branch(self):
140 charm = Charm(factory.get_charm_json(empty_branch=True))
141 result = format_change(charm.last_change)
142 self.assertEqual(' ', result)

Subscribers

People subscribed via source and target branches