Merge lp:~jpds/launchpad/fix_517839 into lp:launchpad

Proposed by Jonathan Davies on 2010-02-20
Status: Merged
Approved by: Tim Penhey on 2010-02-22
Approved revision: not available
Merged at revision: 10377
Proposed branch: lp:~jpds/launchpad/fix_517839
Merge into: lp:launchpad
Diff against target: 413 lines (+206/-20)
11 files modified
lib/canonical/launchpad/browser/launchpad.py (+2/-0)
lib/canonical/launchpad/security.py (+6/-0)
lib/lp/registry/interfaces/distributionmirror.py (+7/-4)
lib/lp/registry/stories/webservice/xx-distribution-mirror.txt (+23/-1)
lib/lp/registry/stories/webservice/xx-distribution.txt (+1/-0)
lib/lp/services/worlddata/browser/configure.zcml (+28/-0)
lib/lp/services/worlddata/browser/country.py (+10/-0)
lib/lp/services/worlddata/configure.zcml (+2/-0)
lib/lp/services/worlddata/interfaces/country.py (+43/-15)
lib/lp/services/worlddata/model/country.py (+12/-0)
lib/lp/services/worlddata/stories/webservice/xx-country.txt (+72/-0)
To merge this branch: bzr merge lp:~jpds/launchpad/fix_517839
Reviewer Review Type Date Requested Status
Tim Penhey (community) Approve on 2010-02-22
Canonical Launchpad Engineering code 2010-02-20 Pending
Review via email: mp+19774@code.launchpad.net

Commit Message

Exported countries and DistributionMirror.country's via the API.

To post a comment you must log in.
Jonathan Davies (jpds) wrote :

= Summary =

As per bug #517839, we should export the country attribute of DistributionMirror's. This is to compliment bug #361650 later on.

= Pre-implementation details =

I spoke to Curtis, who said that the simplest fix would be to register a canonical URL for country. I have done this by exporting countries as +countries/$CC.

= Tests =

Newly exported Country's can be tested with:

bin/test -vvct webservice/xx-country.txt

And DistributionMirror changes:

bin/test -vvct webservice/xx-distribution-mirror.txt

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/canonical/launchpad/browser/launchpad.py
  lib/lp/registry/interfaces/distributionmirror.py
  lib/lp/registry/stories/webservice/xx-distribution-mirror.txt
  lib/lp/services/worlddata/browser/
  lib/lp/services/worlddata/configure.zcml
  lib/lp/services/worlddata/stories/
  lib/lp/services/worlddata/browser/__init__.py
  lib/lp/services/worlddata/browser/configure.zcml
  lib/lp/services/worlddata/browser/country.py
  lib/lp/services/worlddata/interfaces/country.py
  lib/lp/services/worlddata/model/country.py
  lib/lp/services/worlddata/stories/webservice/
  lib/lp/services/worlddata/stories/webservice/xx-country.txt

Tim Penhey (thumper) wrote :
Download full text (3.7 KiB)

Mostly small style issues.

> === added file 'lib/lp/services/worlddata/browser/country.py'
> --- lib/lp/services/worlddata/browser/country.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/services/worlddata/browser/country.py 2010-02-20 15:12:23 +0000
> @@ -0,0 +1,10 @@
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).

It is 2010 now :-)

> +
> +from lp.services.worlddata.interfaces.country import ICountrySet
> +
> +from canonical.launchpad.webapp import (
> + GetitemNavigation)

This can fit on one line.

2 blank lines between module level bits.

> +
> +class CountrySetNavigation(GetitemNavigation):
> + usedfor = ICountrySet
>
> === modified file 'lib/lp/services/worlddata/interfaces/country.py'
> --- lib/lp/services/worlddata/interfaces/country.py 2009-06-25 04:06:00 +0000
> +++ lib/lp/services/worlddata/interfaces/country.py 2010-02-20 15:12:23 +0000
> @@ -20,26 +20,32 @@
> from canonical.launchpad.validators.name import valid_name
> from canonical.launchpad import _
>
> +from lazr.restful.declarations import (
> + export_as_webservice_collection, collection_default_content,
> + export_read_operation, export_as_webservice_entry, exported,
> + operation_parameters, operation_returns_entry)
> +
> class ICountry(Interface):
> """The country description."""
> + export_as_webservice_entry(plural_name='countries')
>
> id = Int(
> title=_('Country ID'), required=True, readonly=True,
> )
> - iso3166code2 = TextLine( title=_('iso3166code2'), required=True,
> - readonly=True)
> - iso3166code3 = TextLine( title=_('iso3166code3'), required=True,
> - readonly=True)
> - name = TextLine(
> + iso3166code2 = exported(TextLine( title=_('iso3166code2'), required=True,
> + readonly=True))
> + iso3166code3 = exported(TextLine( title=_('iso3166code3'), required=True,
> + readonly=True))

These could be formatted a little nicer :)

> + name = exported(TextLine(
> title=_('Country name'), required=True,
> constraint=valid_name,
> - )
> - title = Title(
> + ))
> + title = exported(Title(
> title=_('Country title'), required=True,
> - )
> - description = Description(
> + ))
> + description = exported(Description(
> title=_('Description'), required=True,
> - )
> + ))
>
> continent = Attribute("The Continent where this country is located.")
> languages = Attribute("An iterator over languages that are spoken in "
> @@ -48,6 +54,7 @@
>
> class ICountrySet(Interface):
> """A container for countries."""
> + export_as_webservice_collection(ICountry)
>
> def __getitem__(key):
> """Get a country."""
> @@ -55,6 +62,24 @@
> def __iter__():
> """Iterate through the countries in this set."""
>
> + @operation_parameters(
> + name=TextLine(title=_("Name"), required=True))
> + @operation_...

Read more...

review: Needs Fixing
Tim Penhey (thumper) wrote :

Thanks for the update. Good to go now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
2--- lib/canonical/launchpad/browser/launchpad.py 2010-02-18 17:00:54 +0000
3+++ lib/canonical/launchpad/browser/launchpad.py 2010-02-23 19:47:16 +0000
4@@ -92,6 +92,7 @@
5 ISourcePackageNameSet)
6 from lp.blueprints.interfaces.specification import ISpecificationSet
7 from lp.blueprints.interfaces.sprint import ISprintSet
8+from lp.services.worlddata.interfaces.country import ICountrySet
9 from lp.translations.interfaces.translationgroup import (
10 ITranslationGroupSet)
11 from lp.translations.interfaces.translationimportqueue import (
12@@ -548,6 +549,7 @@
13 '+code': IBazaarApplication,
14 '+code-imports': ICodeImportSet,
15 'codeofconduct': ICodeOfConductSet,
16+ '+countries': ICountrySet,
17 'distros': IDistributionSet,
18 '+hwdb': IHWDBApplication,
19 'karmaaction': IKarmaActionSet,
20
21=== modified file 'lib/canonical/launchpad/security.py'
22--- lib/canonical/launchpad/security.py 2010-02-22 17:11:52 +0000
23+++ lib/canonical/launchpad/security.py 2010-02-23 19:47:16 +0000
24@@ -119,6 +119,7 @@
25 from lp.answers.interfaces.faqtarget import IFAQTarget
26 from lp.answers.interfaces.question import IQuestion
27 from lp.answers.interfaces.questiontarget import IQuestionTarget
28+from lp.services.worlddata.interfaces.country import ICountry
29
30
31 class AuthorizationBase:
32@@ -840,6 +841,11 @@
33 usedfor = IDistroSeries
34
35
36+class ViewCountry(AnonymousAuthorization):
37+ """Anyone can view a Country."""
38+ usedfor = ICountry
39+
40+
41 class SeriesDrivers(AuthorizationBase):
42 """Drivers can approve or decline features and target bugs.
43
44
45=== modified file 'lib/lp/registry/interfaces/distributionmirror.py'
46--- lib/lp/registry/interfaces/distributionmirror.py 2010-02-08 20:26:19 +0000
47+++ lib/lp/registry/interfaces/distributionmirror.py 2010-02-23 19:47:16 +0000
48@@ -32,7 +32,7 @@
49 from lazr.enum import DBEnumeratedType, DBItem
50 from lazr.restful.declarations import (
51 export_as_webservice_entry, export_read_operation, exported)
52-from lazr.restful.fields import Reference
53+from lazr.restful.fields import Reference, ReferenceChoice
54
55 from canonical.launchpad import _
56 from canonical.launchpad.fields import (
57@@ -40,6 +40,7 @@
58 from canonical.launchpad.validators.name import name_validator
59 from canonical.launchpad.validators import LaunchpadValidationError
60 from canonical.launchpad.webapp.menu import structured
61+from lp.services.worlddata.interfaces.country import ICountry
62
63
64 # The number of hours before we bother probing a mirror again
65@@ -353,9 +354,11 @@
66 speed = exported(Choice(
67 title=_('Link Speed'), required=True, readonly=False,
68 vocabulary=MirrorSpeed))
69- country = Choice(
70- title=_('Location'), required=True, readonly=False,
71- vocabulary='CountryName')
72+ country = exported(ReferenceChoice(
73+ title=_('Location'), description=_(
74+ "The country in which this mirror is based."),
75+ required=True, readonly=False,
76+ vocabulary='CountryName', schema=ICountry))
77 content = exported(Choice(
78 title=_('Content'), required=True, readonly=False,
79 description=_(
80
81=== modified file 'lib/lp/registry/stories/webservice/xx-distribution-mirror.txt'
82--- lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-22 17:47:59 +0000
83+++ lib/lp/registry/stories/webservice/xx-distribution-mirror.txt 2010-02-23 19:47:16 +0000
84@@ -12,6 +12,7 @@
85 >>> canonical_archive_json = webservice.get(canonical_archive['self_link']).jsonBody()
86 >>> pprint_entry(canonical_archive_json)
87 content: u'Archive'
88+ country_link: u'http://.../+countries/GB'
89 date_created: u'2006-10-16T18:31:43.434567+00:00'
90 date_reviewed: None
91 description: None
92@@ -38,6 +39,7 @@
93 >>> canonical_releases_json = webservice.get(canonical_releases['self_link']).jsonBody()
94 >>> pprint_entry(canonical_releases_json)
95 content: u'CD Image'
96+ country_link: u'http://.../+countries/GB'
97 date_created: u'2006-10-16T18:31:43.434567+00:00'
98 date_reviewed: None
99 description: None
100@@ -126,6 +128,7 @@
101 ... archive_404_mirror['self_link']).jsonBody()
102 >>> pprint_entry(response)
103 content: u'Archive'
104+ country_link: u'http://.../+countries/FR'
105 date_created: u'2006-10-16T18:31:43.438573+00:00'
106 date_reviewed: None
107 description: None
108@@ -190,7 +193,10 @@
109
110 While others can be set with the appropriate authorization:
111
112+ >>> greenland = webservice.named_get("/+countries",
113+ ... "getByCode", code="GL").jsonBody()
114 >>> patch = {
115+ ... u'country_link': greenland['self_link'],
116 ... u'status' : 'Unofficial',
117 ... u'whiteboard' : u'This mirror is too shiny to be true'
118 ... }
119@@ -203,7 +209,23 @@
120 ... canonical_releases['self_link'], 'application/json', dumps(patch)).jsonBody()
121 >>> pprint_entry(response)
122 content: u'CD Image'
123- ...
124+ country_link: u'http://.../+countries/GL'
125+ date_created: u'2006-10-16T18:31:43.434567+00:00'
126+ date_reviewed: None
127+ description: None
128+ displayname: None
129+ distribution_link: u'http://.../ubuntu'
130+ enabled: True
131+ ftp_base_url: None
132+ http_base_url: u'http://releases.ubuntu.com/'
133+ name: u'canonical-releases'
134+ official_candidate: True
135+ owner_link: u'http://.../~mark'
136+ resource_type_link: u'http://.../#distribution_mirror'
137+ reviewer_link: None
138+ rsync_base_url: None
139+ self_link: u'http://.../ubuntu/+mirror/canonical-releases'
140+ speed: u'100 Mbps'
141 status: u'Unofficial'
142 whiteboard: u'This mirror is too shiny to be true'
143
144
145=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
146--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-05 18:53:28 +0000
147+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-02-23 19:47:16 +0000
148@@ -123,6 +123,7 @@
149 ... name='canonical-releases').jsonBody()
150 >>> pprint_entry(canonical_releases)
151 content: u'CD Image'
152+ country_link: u'http://.../+countries/GB'
153 date_created: u'2006-10-16T18:31:43.434567+00:00'
154 date_reviewed: None
155 description: None
156
157=== added directory 'lib/lp/services/worlddata/browser'
158=== added file 'lib/lp/services/worlddata/browser/__init__.py'
159=== added file 'lib/lp/services/worlddata/browser/configure.zcml'
160--- lib/lp/services/worlddata/browser/configure.zcml 1970-01-01 00:00:00 +0000
161+++ lib/lp/services/worlddata/browser/configure.zcml 2010-02-23 19:47:16 +0000
162@@ -0,0 +1,28 @@
163+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
164+ GNU Affero General Public License version 3 (see the file LICENSE).
165+-->
166+
167+<configure
168+ xmlns="http://namespaces.zope.org/zope"
169+ xmlns:browser="http://namespaces.zope.org/browser"
170+ xmlns:i18n="http://namespaces.zope.org/i18n"
171+ xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
172+ i18n_domain="launchpad">
173+
174+ <browser:url
175+ for="lp.services.worlddata.interfaces.country.ICountry"
176+ path_expression="iso3166code2"
177+ parent_utility="lp.services.worlddata.interfaces.country.ICountrySet"
178+ rootsite="mainsite" />
179+
180+ <browser:url
181+ for="lp.services.worlddata.interfaces.country.ICountrySet"
182+ path_expression="string:+countries"
183+ parent_utility="canonical.launchpad.interfaces.ILaunchpadRoot"
184+ rootsite="mainsite" />
185+
186+ <browser:navigation
187+ module="lp.services.worlddata.browser.country"
188+ classes="CountrySetNavigation" />
189+
190+</configure>
191
192=== added file 'lib/lp/services/worlddata/browser/country.py'
193--- lib/lp/services/worlddata/browser/country.py 1970-01-01 00:00:00 +0000
194+++ lib/lp/services/worlddata/browser/country.py 2010-02-23 19:47:16 +0000
195@@ -0,0 +1,10 @@
196+# Copyright 2010 Canonical Ltd. This software is licensed under the
197+# GNU Affero General Public License version 3 (see the file LICENSE).
198+
199+from lp.services.worlddata.interfaces.country import ICountrySet
200+
201+from canonical.launchpad.webapp import GetitemNavigation
202+
203+
204+class CountrySetNavigation(GetitemNavigation):
205+ usedfor = ICountrySet
206
207=== modified file 'lib/lp/services/worlddata/configure.zcml'
208--- lib/lp/services/worlddata/configure.zcml 2009-09-17 14:45:59 +0000
209+++ lib/lp/services/worlddata/configure.zcml 2010-02-23 19:47:16 +0000
210@@ -55,4 +55,6 @@
211 factory="lp.translations.browser.language.LanguageSetBreadcrumb"
212 permission="zope.Public"/>
213
214+ <include package=".browser"/>
215+
216 </configure>
217
218=== modified file 'lib/lp/services/worlddata/interfaces/country.py'
219--- lib/lp/services/worlddata/interfaces/country.py 2009-06-25 04:06:00 +0000
220+++ lib/lp/services/worlddata/interfaces/country.py 2010-02-23 19:47:16 +0000
221@@ -20,26 +20,35 @@
222 from canonical.launchpad.validators.name import valid_name
223 from canonical.launchpad import _
224
225+from lazr.restful.declarations import (
226+ export_as_webservice_collection, collection_default_content,
227+ export_read_operation, export_as_webservice_entry, exported,
228+ operation_parameters, operation_returns_entry)
229+
230+
231 class ICountry(Interface):
232 """The country description."""
233+ export_as_webservice_entry(plural_name='countries')
234
235 id = Int(
236- title=_('Country ID'), required=True, readonly=True,
237- )
238- iso3166code2 = TextLine( title=_('iso3166code2'), required=True,
239- readonly=True)
240- iso3166code3 = TextLine( title=_('iso3166code3'), required=True,
241- readonly=True)
242- name = TextLine(
243+ title=_('Country ID'), required=True, readonly=True,
244+ )
245+ iso3166code2 = exported(
246+ TextLine(title=_('iso3166code2'), required=True,
247+ readonly=True))
248+ iso3166code3 = exported(
249+ TextLine(title=_('iso3166code3'), required=True,
250+ readonly=True))
251+ name = exported(
252+ TextLine(
253 title=_('Country name'), required=True,
254- constraint=valid_name,
255- )
256- title = Title(
257- title=_('Country title'), required=True,
258- )
259- description = Description(
260- title=_('Description'), required=True,
261- )
262+ constraint=valid_name))
263+ title = exported(
264+ Title(
265+ title=_('Country title'), required=True))
266+ description = exported(
267+ Description(
268+ title=_('Description'), required=True))
269
270 continent = Attribute("The Continent where this country is located.")
271 languages = Attribute("An iterator over languages that are spoken in "
272@@ -48,6 +57,7 @@
273
274 class ICountrySet(Interface):
275 """A container for countries."""
276+ export_as_webservice_collection(ICountry)
277
278 def __getitem__(key):
279 """Get a country."""
280@@ -55,6 +65,24 @@
281 def __iter__():
282 """Iterate through the countries in this set."""
283
284+ @operation_parameters(
285+ name=TextLine(title=_("Name"), required=True))
286+ @operation_returns_entry(ICountry)
287+ @export_read_operation()
288+ def getByName(name):
289+ """Return a country by its name."""
290+
291+ @operation_parameters(
292+ code=TextLine(title=_("Code"), required=True))
293+ @operation_returns_entry(ICountry)
294+ @export_read_operation()
295+ def getByCode(code):
296+ """Return a country by its code."""
297+
298+ @collection_default_content()
299+ def getCountries():
300+ """Return a collection of countries."""
301+
302
303 class IContinent(Interface):
304 """See IContinent."""
305
306=== modified file 'lib/lp/services/worlddata/model/country.py'
307--- lib/lp/services/worlddata/model/country.py 2009-06-25 04:06:00 +0000
308+++ lib/lp/services/worlddata/model/country.py 2010-02-23 19:47:16 +0000
309@@ -13,6 +13,7 @@
310 from canonical.database.constants import DEFAULT
311 from canonical.database.sqlbase import SQLBase
312 from canonical.launchpad.interfaces import NotFoundError
313+from canonical.launchpad.interfaces.lpstorm import IStore
314 from lp.services.worlddata.interfaces.country import (
315 ICountry, ICountrySet, IContinent)
316
317@@ -57,6 +58,17 @@
318 for row in Country.select():
319 yield row
320
321+ def getByName(self, name):
322+ """See `ICountrySet`."""
323+ return IStore(Country).find(Country, name=name).one()
324+
325+ def getByCode(self, code):
326+ """See `ICountrySet`."""
327+ return IStore(Country).find(Country, iso3166code2=code).one()
328+
329+ def getCountries(self):
330+ """See `ICountrySet`."""
331+ return IStore(Country).find(Country).order_by(Country.iso3166code2)
332
333 class Continent(SQLBase):
334 """See IContinent."""
335
336=== added directory 'lib/lp/services/worlddata/stories'
337=== added directory 'lib/lp/services/worlddata/stories/webservice'
338=== added file 'lib/lp/services/worlddata/stories/webservice/xx-country.txt'
339--- lib/lp/services/worlddata/stories/webservice/xx-country.txt 1970-01-01 00:00:00 +0000
340+++ lib/lp/services/worlddata/stories/webservice/xx-country.txt 2010-02-23 19:47:16 +0000
341@@ -0,0 +1,72 @@
342+= Countries =
343+
344+At the top level we provide the collection of all countries.
345+
346+ >>> countries = webservice.get("/+countries").jsonBody()
347+ >>> for entry in countries['entries']:
348+ ... print entry['self_link']
349+ http://.../+countries/AD
350+ http://.../+countries/AE
351+ http://.../+countries/AF
352+ http://.../+countries/AG
353+ http://.../+countries/AI
354+
355+And for every country we publish most of its attributes.
356+
357+ >>> from lazr.restful.testing.webservice import pprint_entry
358+ >>> country = countries['entries'][0]
359+ >>> andorra = webservice.get(country['self_link']).jsonBody()
360+ >>> pprint_entry(andorra)
361+ description: None
362+ iso3166code2: u'AD'
363+ iso3166code3: u'AND'
364+ name: u'Andorra'
365+ resource_type_link: u'http://.../#country'
366+ self_link: u'http://.../+countries/AD'
367+ title: u'Principality of Andorra'
368+
369+Make sure that invalid countries return 404s and not OOPSes.
370+
371+ >>> bogus_country = "http://api.launchpad.dev/beta/+countries/bogus"
372+ >>> print webservice.get(bogus_country)
373+ HTTP/1.1 404 Not Found
374+ ...
375+ Traceback (most recent call last):
376+ ...
377+ NotFound: Object: ..., name: u'bogus'
378+ <BLANKLINE>
379+
380+== Country Custom Operations ==
381+
382+"getByName" returns a country for the given name.
383+
384+ >>> uk = webservice.named_get(
385+ ... '/+countries', 'getByName',
386+ ... name='United Kingdom').jsonBody()
387+ >>> print uk['self_link']
388+ http://.../+countries/GB
389+
390+Ensure that unknown/non-existent countries return a None and not an OOPS:
391+
392+ >>> bogus_country_by_name = webservice.named_get(
393+ ... '/+countries', 'getByName',
394+ ... name='Klingon Land').jsonBody()
395+ >>> print bogus_country_by_name
396+ None
397+
398+
399+"getByCode" returns a country for the given code.
400+
401+ >>> au = webservice.named_get(
402+ ... '/+countries', 'getByCode',
403+ ... code='AU').jsonBody()
404+ >>> print au['self_link']
405+ http://.../+countries/AU
406+
407+Ensure that unknown/non-existent country codes return a None and not an OOPS:
408+
409+ >>> bogus_country_by_code = webservice.named_get(
410+ ... '/+countries', 'getByCode',
411+ ... code='TEST').jsonBody()
412+ >>> print bogus_country_by_code
413+ None