Merge lp:~benji/charmworld/expose-bundle-changes into lp:~juju-jitsu/charmworld/trunk

Proposed by Benji York
Status: Merged
Merged at revision: 427
Proposed branch: lp:~benji/charmworld/expose-bundle-changes
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 445 lines (+122/-69)
10 files modified
charmworld/jobs/ingest.py (+33/-23)
charmworld/jobs/tests/test_bzr.py (+51/-22)
charmworld/jobs/tests/test_ingest.py (+3/-3)
charmworld/migrations/versions/tests/test_migrations.py (+1/-0)
charmworld/models.py (+10/-5)
charmworld/templates/bundle.pt (+0/-8)
charmworld/testing/factory.py (+2/-1)
charmworld/tests/test_models.py (+15/-4)
charmworld/tests/test_search.py (+3/-2)
charmworld/views/tests/test_api.py (+4/-1)
To merge this branch: bzr merge lp:~benji/charmworld/expose-bundle-changes
Reviewer Review Type Date Requested Status
Juju-Jitsu Hackers Pending
Review via email: mp+192389@code.launchpad.net

Description of the change

Add recent bzr changes to bundles.

https://codereview.appspot.com/15100046/

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

LGTM

https://codereview.appspot.com/15100046/diff/1/charmworld/jobs/ingest.py
File charmworld/jobs/ingest.py (right):

https://codereview.appspot.com/15100046/diff/1/charmworld/jobs/ingest.py#newcode424
charmworld/jobs/ingest.py:424: if revision.timestamp < since:
So since cannot be None?

https://codereview.appspot.com/15100046/diff/1/charmworld/jobs/ingest.py#newcode424
charmworld/jobs/ingest.py:424: if revision.timestamp < since:
So we're sure since can never be None?

https://codereview.appspot.com/15100046/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmworld/jobs/ingest.py'
--- charmworld/jobs/ingest.py 2013-10-17 21:32:57 +0000
+++ charmworld/jobs/ingest.py 2013-10-23 18:29:57 +0000
@@ -261,7 +261,7 @@
261 update_download_count(store, charm_data)261 update_download_count(store, charm_data)
262 update_proof_data(charm_data, log)262 update_proof_data(charm_data, log)
263 update_jenkins_data(db, charm_data, log)263 update_jenkins_data(db, charm_data, log)
264 update_from_revisions(charm_data, newest_revision=revision)264 update_revision_details(charm_data, newest_revision=revision)
265 update_date_created(charm_data, log)265 update_date_created(charm_data, log)
266 scan_charm(charm_data, fs, log)266 scan_charm(charm_data, fs, log)
267 update_hash(charm_data)267 update_hash(charm_data)
@@ -309,8 +309,11 @@
309 self.working_dir = working_dir309 self.working_dir = working_dir
310 super(UpdateBundleJob, self).__init__()310 super(UpdateBundleJob, self).__init__()
311311
312 def store_bundles(self, deployer_config, owner, basket_id):312 def store_bundles(self, deployer_config, owner, basket_id, first_change,
313 store_bundles(self.db.bundles, deployer_config, owner, basket_id)313 last_change, changes):
314 store_bundles(
315 self.db.bundles, deployer_config, owner, basket_id, first_change,
316 last_change, changes)
314317
315 @staticmethod318 @staticmethod
316 def set_basket_info(data, revno):319 def set_basket_info(data, revno):
@@ -319,6 +322,9 @@
319 def decorate_basket(self, basket_data, fs):322 def decorate_basket(self, basket_data, fs):
320 branch_dir = fetch_branch(self.working_dir, basket_data, self.log,323 branch_dir = fetch_branch(self.working_dir, basket_data, self.log,
321 revisionId=basket_data['commit'])324 revisionId=basket_data['commit'])
325 basket_data['branch_dir'] = branch_dir
326 update_revision_details(
327 basket_data, newest_revision=basket_data['commit'])
322 branch = Branch.open(branch_dir)328 branch = Branch.open(branch_dir)
323 revno = branch.revision_id_to_revno(basket_data['commit'])329 revno = branch.revision_id_to_revno(basket_data['commit'])
324 self.set_basket_info(basket_data, revno)330 self.set_basket_info(basket_data, revno)
@@ -340,7 +346,8 @@
340 self.db.baskets.save(basket_data)346 self.db.baskets.save(basket_data)
341 deployer_config = self.get_deployer_config(fs, basket_data)347 deployer_config = self.get_deployer_config(fs, basket_data)
342 self.store_bundles(348 self.store_bundles(
343 deployer_config, basket_data['owner'], basket_data['name_revno'])349 deployer_config, basket_data['owner'], basket_data['name_revno'],
350 None, None, None)
344351
345352
346def _rev_info(r, branch):353def _rev_info(r, branch):
@@ -354,27 +361,34 @@
354 return d361 return d
355362
356363
357def update_from_revisions(charm_data, limit=10, since=None,364def find_revision_cutoff(now=None, settings=settings):
358 newest_revision=None):365 days_of_revisions = settings.get('days_of_revisions')
359 if charm_data['branch_deleted']:366 if days_of_revisions is None:
367 days_of_revisions = 30
368 if now is None:
369 now = datetime.utcnow()
370 cutoff = now - timedelta(int(days_of_revisions))
371 return calendar.timegm(cutoff.timetuple())
372
373
374def update_revision_details(data, limit=10, newest_revision=None):
375 # If the branch has been deleted, there can be no new revisions to pull in.
376 if data['branch_deleted']:
360 return377 return
361 if since is None:378 since = find_revision_cutoff()
362 days_of_revisions = settings.get('days_of_revisions')379 changes, first_change, last_change = get_changes(
363 if days_of_revisions is not None:380 data['branch_dir'], limit, since, newest_revision)
364 cutoff = datetime.utcnow() - timedelta(int(days_of_revisions))381 data['changes'] = changes
365 since = calendar.timegm(cutoff.timetuple())382 data['first_change'] = first_change
366 branch_dir = charm_data['branch_dir']383 data['last_change'] = last_change
367 changes = get_changes(branch_dir, limit, since, newest_revision)
368 charm_data.update(changes)
369384
370385
371def get_changes(branch_dir, limit, since, newest_revision=None):386def get_changes(branch_dir, limit, since, newest_revision=None):
372 charm_data = {}
373 branch = Branch.open(branch_dir)387 branch = Branch.open(branch_dir)
374 branch.lock_read()388 branch.lock_read()
375 try:389 try:
376 revisions = get_revisions(branch, limit, since)390 revisions = get_revisions(branch, limit, since)
377 charm_data['changes'] = changes = []391 changes = []
378 # If there is no limit on how new a revision we can include, then392 # If there is no limit on how new a revision we can include, then
379 # consider the starting revision to be already found.393 # consider the starting revision to be already found.
380 found_start = newest_revision is None394 found_start = newest_revision is None
@@ -391,11 +405,7 @@
391 last_change = changes[0]405 last_change = changes[0]
392 first = branch.repository.get_revision(branch.get_rev_id(1))406 first = branch.repository.get_revision(branch.get_rev_id(1))
393 first_change = _rev_info(first, branch)407 first_change = _rev_info(first, branch)
394 charm_data.update({408 return changes, first_change, last_change
395 'last_change': last_change,
396 'first_change': first_change,
397 })
398 return charm_data
399 finally:409 finally:
400 branch.unlock()410 branch.unlock()
401411
@@ -411,7 +421,7 @@
411 break421 break
412 revision = branch.repository.get_revision(revision_id)422 revision = branch.repository.get_revision(revision_id)
413 if num >= limit:423 if num >= limit:
414 if since is None or revision.timestamp < since:424 if revision.timestamp < since:
415 break425 break
416 revs.append(revision)426 revs.append(revision)
417 return revs427 return revs
418428
=== modified file 'charmworld/jobs/tests/test_bzr.py'
--- charmworld/jobs/tests/test_bzr.py 2013-10-17 20:19:55 +0000
+++ charmworld/jobs/tests/test_bzr.py 2013-10-23 18:29:57 +0000
@@ -3,6 +3,7 @@
33
44
5from contextlib import contextmanager5from contextlib import contextmanager
6import datetime
6from logging import getLogger7from logging import getLogger
7import os8import os
8import time9import time
@@ -13,13 +14,14 @@
1314
14from charmworld.jobs import ingest15from charmworld.jobs import ingest
15from charmworld.jobs.ingest import (16from charmworld.jobs.ingest import (
17 _rev_info,
16 add_files,18 add_files,
17 checkout_branch,19 checkout_branch,
18 do_bzr_update,20 do_bzr_update,
21 find_revision_cutoff,
19 get_changes,22 get_changes,
20 get_revisions,23 get_revisions,
21 _rev_info,24 update_revision_details,
22 update_from_revisions,
23)25)
24from charmworld.models import getfs26from charmworld.models import getfs
25from charmworld.testing import (27from charmworld.testing import (
@@ -268,36 +270,63 @@
268 self.assertEqual([YRH, ZRH], rev_info['authors'])270 self.assertEqual([YRH, ZRH], rev_info['authors'])
269 self.assertEqual(JRH, rev_info['committer'])271 self.assertEqual(JRH, rev_info['committer'])
270272
271 def test_get_changes(self):273 def test_get_changes_with_one_revision(self):
274 tree = self.make_locked_tree()
275 self.num_commit(tree, 1)
276 changes, first_change, last_change = get_changes(
277 'tree', 10, time.time())
278 self.assertEqual(first_change, last_change)
279 self.assertEqual([first_change], changes)
280
281 def test_get_changes_with_multiple_revisions(self):
272 tree = self.make_locked_tree()282 tree = self.make_locked_tree()
273 first_id = self.num_commit(tree, 1)283 first_id = self.num_commit(tree, 1)
274 charm_data = get_changes('tree', 10, time.time())
275 self.assertItemsEqual(['changes', 'first_change', 'last_change'],
276 charm_data.keys())
277 self.assertEqual(charm_data['first_change'], charm_data['last_change'])
278 self.assertEqual([charm_data['first_change']], charm_data['changes'])
279 for num in range(1, 11):284 for num in range(1, 11):
280 self.num_commit(tree, 1)285 self.num_commit(tree, 1)
281 charm_data = get_changes('tree', 10, time.time())286 changes, first_change, last_change = get_changes(
282 self.assertNotEqual(charm_data['first_change'],287 'tree', 10, time.time())
283 charm_data['last_change'])288 self.assertNotEqual(first_change, last_change)
284 self.assertEqual(charm_data['last_change'], charm_data['changes'][0])289 self.assertEqual(last_change, changes[0])
285 self.assertNotEqual(charm_data['first_change'],290 self.assertNotEqual(first_change, changes[-1])
286 charm_data['changes'][-1])291 self.assertEqual(
287 self.assertEqual(self.get_rev_info(tree.branch, first_id),292 self.get_rev_info(tree.branch, first_id),
288 charm_data['first_change'])293 first_change)
289294
290 def test_get_changes_no_revisions(self):295 def test_get_changes_no_revisions(self):
291 self.make_locked_tree()296 self.make_locked_tree()
292 charm_data = get_changes('tree', 10, time.time())297 changes, first_change, last_change = get_changes(
293 self.assertEqual([], charm_data['changes'])298 'tree', 10, time.time())
294 self.assertIs(None, charm_data['first_change'])299 self.assertEqual([], changes)
295 self.assertIs(None, charm_data['last_change'])300 self.assertIs(None, first_change)
301 self.assertIs(None, last_change)
296302
297 def test_branch_deleted(self):303 def test_branch_deleted(self):
298 # update_from_revisions does not do anything if the Launchpad branch304 # update_revision_details does not do anything if the Launchpad branch
299 # of a charm is deleted.305 # of a charm is deleted.
300 charm = factory.get_charm_json(branch_deleted=True)306 charm = factory.get_charm_json(branch_deleted=True)
301 with patch.object(ingest, 'get_changes') as mock:307 with patch.object(ingest, 'get_changes') as mock:
302 update_from_revisions(charm)308 update_revision_details(charm)
303 self.assertFalse(mock.called)309 self.assertFalse(mock.called)
310
311
312class TestFindRevisionCutoff(TestCase):
313 """Tests for charmworld.jobs.ingest.find_revision_cutoff."""
314
315 def test_return_type(self):
316 # find_revision_cutoff returns seconds since the epoch (an int).
317 self.assertEqual(type(find_revision_cutoff()), int)
318
319 def test_days_of_revisions_is_respected(self):
320 # If the cutoff date is further in the past, the resulting seconds
321 # since the epoch is further in the past too.
322 old = find_revision_cutoff(settings=dict(days_of_revisions=10))
323 older = find_revision_cutoff(settings=dict(days_of_revisions=20))
324 self.assertGreater(old, older)
325
326 def test_days_are_the_right_size(self):
327 # If we start at time 0 and ask for one day in the past, we get the
328 # obvious value.
329 time = find_revision_cutoff(
330 now=datetime.datetime.utcfromtimestamp(0),
331 settings=dict(days_of_revisions=1))
332 self.assertEqual(time, -86400)
304333
=== modified file 'charmworld/jobs/tests/test_ingest.py'
--- charmworld/jobs/tests/test_ingest.py 2013-10-17 21:32:57 +0000
+++ charmworld/jobs/tests/test_ingest.py 2013-10-23 18:29:57 +0000
@@ -533,8 +533,8 @@
533 """)533 """)
534 fs.put(deployer_config, _id=DEPLOYER_CONFIG_HASH)534 fs.put(deployer_config, _id=DEPLOYER_CONFIG_HASH)
535 job.store_bundles(535 job.store_bundles(
536 yaml.safe_load(deployer_config),536 yaml.safe_load(deployer_config), basket_data['owner'],
537 basket_data['owner'], basket_data['name_revno'])537 basket_data['name_revno'], None, None, None)
538 self.assertIsNotNone(self.db.bundles.find_one(bundle_id))538 self.assertIsNotNone(self.db.bundles.find_one(bundle_id))
539539
540 def test_job_run_stores_bundles_in_the_db(self):540 def test_job_run_stores_bundles_in_the_db(self):
@@ -559,7 +559,7 @@
559 with patch.object(job, 'store_bundles') as store_bundles:559 with patch.object(job, 'store_bundles') as store_bundles:
560 job.run(basket_data)560 job.run(basket_data)
561 store_bundles.assert_called_with(561 store_bundles.assert_called_with(
562 {'foo': {}}, 'charmers', 'dummy')562 {'foo': {}}, 'charmers', 'dummy', None, None, None)
563563
564564
565class TestUpdateCharm(MongoTestBase):565class TestUpdateCharm(MongoTestBase):
566566
=== modified file 'charmworld/migrations/versions/tests/test_migrations.py'
--- charmworld/migrations/versions/tests/test_migrations.py 2013-09-23 15:58:47 +0000
+++ charmworld/migrations/versions/tests/test_migrations.py 2013-10-23 18:29:57 +0000
@@ -60,6 +60,7 @@
60 owner, basket_name, bundle_name)60 owner, basket_name, bundle_name)
61 store_bundles(61 store_bundles(
62 self.db.bundles, parsed, owner, basket_id,62 self.db.bundles, parsed, owner, basket_id,
63 None, None, None,
63 index_client=self.index_client)64 index_client=self.index_client)
6465
65 def test_bundles_are_removed_from_elastic_search(self):66 def test_bundles_are_removed_from_elastic_search(self):
6667
=== modified file 'charmworld/models.py'
--- charmworld/models.py 2013-10-22 22:57:51 +0000
+++ charmworld/models.py 2013-10-23 18:29:57 +0000
@@ -1743,7 +1743,8 @@
1743 return options1743 return options
17441744
17451745
1746def make_bundle_doc(data, owner, basket_id, bundle_name):1746def make_bundle_doc(data, owner, basket_id, bundle_name, first_change,
1747 last_change, changes):
1747 basket_name, basket_revision = basket_id.split('/')1748 basket_name, basket_revision = basket_id.split('/')
1748 _id = Bundle.construct_id(owner, basket_name, bundle_name, basket_revision)1749 _id = Bundle.construct_id(owner, basket_name, bundle_name, basket_revision)
1749 return {1750 return {
@@ -1753,11 +1754,14 @@
1753 'basket_name': basket_name,1754 'basket_name': basket_name,
1754 'basket_revision': int(basket_revision),1755 'basket_revision': int(basket_revision),
1755 'data': data,1756 'data': data,
1757 'first_change': first_change,
1758 'last_change': last_change,
1759 'changes': changes,
1756 }1760 }
17571761
17581762
1759def store_bundles(collection, deployer_config, owner, basket_id,1763def store_bundles(collection, deployer_config, owner, basket_id, first_change,
1760 index_client=None):1764 last_change, changes, index_client=None):
1761 """Store a basket of bundles into MongoDB and/or ElasticSearch.1765 """Store a basket of bundles into MongoDB and/or ElasticSearch.
17621766
1763 :param: collection: A db bundles collection. If None the bundles are not1767 :param: collection: A db bundles collection. If None the bundles are not
@@ -1779,8 +1783,9 @@
1779 for bundle_name in deployer_config:1783 for bundle_name in deployer_config:
1780 # Set up indexing.1784 # Set up indexing.
1781 data = get_flattened_deployment(deployer_config, bundle_name)1785 data = get_flattened_deployment(deployer_config, bundle_name)
1782 index_data[bundle_name] = make_bundle_doc(data, owner, basket_id,1786 index_data[bundle_name] = make_bundle_doc(
1783 bundle_name)1787 data, owner, basket_id, bundle_name, first_change, last_change,
1788 changes)
1784 if collection is not None:1789 if collection is not None:
1785 collection.save(index_data[bundle_name])1790 collection.save(index_data[bundle_name])
17861791
17871792
=== modified file 'charmworld/templates/bundle.pt'
--- charmworld/templates/bundle.pt 2013-10-11 13:51:05 +0000
+++ charmworld/templates/bundle.pt 2013-10-23 18:29:57 +0000
@@ -103,14 +103,6 @@
103 </li>103 </li>
104 </ul>104 </ul>
105 </div>105 </div>
106 <tal:comment condition="nothing">
107 <div tal:condition="is_owner">
108 You are the owner. In the future we'll show you extra stuff here.
109 </div>
110 <div tal:condition="not: is_owner">
111 If you were the author/owner you'd see extra stuff here.
112 </div>
113 </tal:comment>
114 </metal:block>106 </metal:block>
115 </body>107 </body>
116</html>108</html>
117109
=== modified file 'charmworld/testing/factory.py'
--- charmworld/testing/factory.py 2013-10-04 19:45:06 +0000
+++ charmworld/testing/factory.py 2013-10-23 18:29:57 +0000
@@ -331,7 +331,8 @@
331 data = dict(series=series,331 data = dict(series=series,
332 relations=relations,332 relations=relations,
333 services=services)333 services=services)
334 bundle_doc = make_bundle_doc(data, owner, basket_with_rev, name)334 bundle_doc = make_bundle_doc(
335 data, owner, basket_with_rev, name, None, None, None)
335 bundle_doc.update(dict(branch_deleted=branch_deleted,336 bundle_doc.update(dict(branch_deleted=branch_deleted,
336 data=data,337 data=data,
337 description=description,338 description=description,
338339
=== modified file 'charmworld/tests/test_models.py'
--- charmworld/tests/test_models.py 2013-10-22 22:57:51 +0000
+++ charmworld/tests/test_models.py 2013-10-23 18:29:57 +0000
@@ -1595,7 +1595,7 @@
1595 basket_id = "%s/%d" % (basket_name, basket_rev)1595 basket_id = "%s/%d" % (basket_name, basket_rev)
1596 _id = Bundle.construct_id(owner, basket_name, bundle_name, basket_rev)1596 _id = Bundle.construct_id(owner, basket_name, bundle_name, basket_rev)
1597 store_bundles(1597 store_bundles(
1598 self.db.bundles, parsed, 'bac', basket_id)1598 self.db.bundles, parsed, 'bac', basket_id, None, None, None)
1599 self.assertEqual(1599 self.assertEqual(
1600 {1600 {
1601 '_id': _id,1601 '_id': _id,
@@ -1604,6 +1604,9 @@
1604 'name': bundle_name,1604 'name': bundle_name,
1605 'owner': owner,1605 'owner': owner,
1606 'data': parsed['wordpress-stage'],1606 'data': parsed['wordpress-stage'],
1607 'first_change': None,
1608 'last_change': None,
1609 'changes': None,
1607 },1610 },
1608 self.db.bundles.find_one(_id))1611 self.db.bundles.find_one(_id))
16091612
@@ -1638,7 +1641,9 @@
16381641
1639 with patch('charmworld.models.get_flattened_deployment',1642 with patch('charmworld.models.get_flattened_deployment',
1640 get_flattened_deployment):1643 get_flattened_deployment):
1641 store_bundles(self.db.bundles, parsed, 'bac', 'wordpress-basket/5')1644 store_bundles(
1645 self.db.bundles, parsed, 'bac', 'wordpress-basket/5', None,
1646 None, None)
1642 self.assertItemsEqual(['wordpress-stage', 'wordpress-prod'], keys)1647 self.assertItemsEqual(['wordpress-stage', 'wordpress-prod'], keys)
16431648
1644 def test_storing_a_bundle_includes_indexing_it(self):1649 def test_storing_a_bundle_includes_indexing_it(self):
@@ -1656,7 +1661,9 @@
1656 with patch(1661 with patch(
1657 'charmworld.models.ElasticSearchClient',1662 'charmworld.models.ElasticSearchClient',
1658 FauxElasticSearchClient):1663 FauxElasticSearchClient):
1659 store_bundles(self.db.bundles, {}, 'owner', 'wordpress-basket/5')1664 store_bundles(
1665 self.db.bundles, {}, 'owner', 'wordpress-basket/5', None,
1666 None, None)
16601667
1661 self.assertTrue(FauxElasticSearchClient.index_bundles_called)1668 self.assertTrue(FauxElasticSearchClient.index_bundles_called)
16621669
@@ -2002,7 +2009,8 @@
2002class TestMakeBundleDoc(TestCase):2009class TestMakeBundleDoc(TestCase):
20032010
2004 def test_bundle_doc(self):2011 def test_bundle_doc(self):
2005 doc = make_bundle_doc({'a': 'b'}, 'foo', 'bar/9', 'baz')2012 doc = make_bundle_doc(
2013 {'a': 'b'}, 'foo', 'bar/9', 'baz', None, None, None)
2006 self.assertEqual({2014 self.assertEqual({
2007 'owner': 'foo',2015 'owner': 'foo',
2008 'basket_name': 'bar',2016 'basket_name': 'bar',
@@ -2010,6 +2018,9 @@
2010 'name': 'baz',2018 'name': 'baz',
2011 'data': {'a': 'b'},2019 'data': {'a': 'b'},
2012 '_id': '~foo/bar/9/baz',2020 '_id': '~foo/bar/9/baz',
2021 'first_change': None,
2022 'last_change': None,
2023 'changes': None,
2013 }, doc)2024 }, doc)
20142025
20152026
20162027
=== modified file 'charmworld/tests/test_search.py'
--- charmworld/tests/test_search.py 2013-09-16 21:14:06 +0000
+++ charmworld/tests/test_search.py 2013-10-23 18:29:57 +0000
@@ -166,8 +166,9 @@
166 charm: cs:precise/mysql166 charm: cs:precise/mysql
167 """)167 """)
168 parsed = yaml.safe_load(deployer_config)168 parsed = yaml.safe_load(deployer_config)
169 store_bundles(None, parsed, 'abentley', 'wordpress-basket/5',169 store_bundles(
170 index_client=self.index_client)170 None, parsed, 'abentley', 'wordpress-basket/5', None, None, None,
171 index_client=self.index_client)
171 return _id172 return _id
172173
173 def test_store_bundles_bundle_name_indexed(self):174 def test_store_bundles_bundle_name_indexed(self):
174175
=== modified file 'charmworld/views/tests/test_api.py'
--- charmworld/views/tests/test_api.py 2013-10-22 21:02:10 +0000
+++ charmworld/views/tests/test_api.py 2013-10-23 18:29:57 +0000
@@ -732,6 +732,9 @@
732 description='',732 description='',
733 promulgated=False,733 promulgated=False,
734 branch_deleted=False,734 branch_deleted=False,
735 first_change=None,
736 last_change=None,
737 changes=None,
735 )738 )
736 self.assertEqual(expected, bundle._representation)739 self.assertEqual(expected, bundle._representation)
737740
@@ -858,7 +861,7 @@
858 self.assertEqual(u'text/plain', response.content_type)861 self.assertEqual(u'text/plain', response.content_type)
859862
860 def test_bundle_icon(self):863 def test_bundle_icon(self):
861 # The current design is that all bundles, regardless of their id, will864 # The current design is that all bundles, regardless of their ID, will
862 # get the default bundle icon.865 # get the default bundle icon.
863 bundle = self.makeBundle()866 bundle = self.makeBundle()
864 response = self.get_response(867 response = self.get_response(

Subscribers

People subscribed via source and target branches