Merge lp:~blake-rouse/maas/license-key-form into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 2499
Proposed branch: lp:~blake-rouse/maas/license-key-form
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~blake-rouse/maas/node-effective-license-key
Diff against target: 602 lines (+419/-48)
5 files modified
src/maasserver/forms.py (+109/-0)
src/maasserver/models/licensekey.py (+12/-1)
src/maasserver/models/tests/test_licensekey.py (+9/-0)
src/maasserver/tests/test_forms.py (+188/-0)
src/maasserver/utils/osystems.py (+101/-47)
To merge this branch: bzr merge lp:~blake-rouse/maas/license-key-form
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+224663@code.launchpad.net

Commit message

Add LicenseKeyForm for the LicenseKey model.

Description of the change

This is the third part of a series of changes to allow a global registry of license key's in MAAS. The end result will allow a user to set a license key for each operating system and series globally. When a booting operating system and series requires a license key, first the node will be check to see if a specific key has been set in the license_key field. If not then this LicenseKey model will be used to see if one is set globally for the selected operating system and series.

This branch includes the form that will be used in the WebUI and API.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

I see what you're trying to do here and you're on the right path… but I've spotted problems in the form you're adding here, see my comments inline. Also, the code of the list_all_* utilities is really starting to look like spaghetti code; since these methods are well-encapsulated helpers I wouldn't mind so much if they were clearly documented and well tested but it doesn't look like it's the case; I'd like you to have another look at this and see if you can improve the situation and reply to my questions: see the inline comments.

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Quick question before I work on the fixes, question is in the inline comments.

Revision history for this message
Raphaël Badin (rvb) wrote :

Replies inline…

Revision history for this message
Blake Rouse (blake-rouse) wrote :

I have fixed the items you requested. I did not add any more tests for the utils/osystems.py, as I will move the tests from test_forms into utils/tests/test_osystems.py and update the tests for the new optional parameters in another branch.

Revision history for this message
Raphaël Badin (rvb) wrote :

A couple more remarks but nothing major. Thanks for all the work on this branch… I'm looking forward to the follow-up branch with the cleanups ;).

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Updated! Comment inline about the clean fields check.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/forms.py'
2--- src/maasserver/forms.py 2014-06-27 20:02:49 +0000
3+++ src/maasserver/forms.py 2014-07-02 14:55:33 +0000
4@@ -98,6 +98,7 @@
5 BootSourceSelection,
6 Config,
7 DownloadProgress,
8+ LicenseKey,
9 MACAddress,
10 Network,
11 Node,
12@@ -119,6 +120,7 @@
13 from maasserver.utils.osystems import (
14 get_distro_series_inital,
15 get_release_requires_key,
16+ list_all_releases_requiring_keys,
17 list_all_usable_osystems,
18 list_all_usable_releases,
19 list_osystem_choices,
20@@ -1826,3 +1828,110 @@
21 if kwargs.get('commit', True):
22 boot_source_selection.save(*args, **kwargs)
23 return boot_source_selection
24+
25+
26+class LicenseKeyForm(ModelForm):
27+ """Form for global license keys."""
28+
29+ class Meta:
30+ model = LicenseKey
31+ fields = (
32+ 'osystem',
33+ 'distro_series',
34+ 'license_key',
35+ )
36+
37+ def __init__(self, *args, **kwargs):
38+ super(LicenseKeyForm, self).__init__(*args, **kwargs)
39+ self.set_up_osystem_and_distro_series_fields(kwargs.get('instance'))
40+
41+ def set_up_osystem_and_distro_series_fields(self, instance):
42+ """Create the `osystem` and `distro_series` fields.
43+
44+ This needs to be done on the fly so that we can pass a dynamic list of
45+ usable operating systems and distro_series.
46+ """
47+ osystems = list_all_usable_osystems(have_images=False)
48+ releases = list_all_releases_requiring_keys(osystems)
49+
50+ # Remove the operating systems that do not have any releases that
51+ # require license keys. Don't want them to show up in the UI or be
52+ # used in the API.
53+ osystems = [
54+ osystem
55+ for osystem in osystems
56+ if osystem.name in releases
57+ ]
58+
59+ os_choices = list_osystem_choices(osystems, include_default=False)
60+ distro_choices = list_release_choices(
61+ releases, include_default=False, include_latest=False,
62+ with_key_required=False)
63+ invalid_osystem_message = compose_invalid_choice_text(
64+ 'osystem', os_choices)
65+ invalid_distro_series_message = compose_invalid_choice_text(
66+ 'distro_series', distro_choices)
67+ self.fields['osystem'] = forms.ChoiceField(
68+ label="OS", choices=os_choices, required=True,
69+ error_messages={'invalid_choice': invalid_osystem_message})
70+ self.fields['distro_series'] = forms.ChoiceField(
71+ label="Release", choices=distro_choices, required=True,
72+ error_messages={'invalid_choice': invalid_distro_series_message})
73+ if instance is not None:
74+ initial_value = get_distro_series_inital(
75+ instance, with_key_required=False)
76+ if instance is not None:
77+ self.initial['distro_series'] = initial_value
78+
79+ def full_clean(self):
80+ # When this form is used from the API, the distro_series field will
81+ # not be formatted correctly. This is to make it easy on the user, and
82+ # not have to call the api with distro_series=os/series. This occurs
83+ # in full_clean, so the value is correct before validation occurs on
84+ # the distro_series field.
85+ if 'distro_series' in self.data and 'osystem' in self.data:
86+ if '/' not in self.data['distro_series']:
87+ self.data['distro_series'] = '%s/%s' % (
88+ self.data['osystem'],
89+ self.data['distro_series'],
90+ )
91+ super(LicenseKeyForm, self).full_clean()
92+
93+ def clean(self):
94+ """Validate distro_series and osystem match, and license_key is valid
95+ for selected operating system and series."""
96+ # Get the clean_data, check that all of the fields we need are
97+ # present. If not then the form will error, so no reason to continue.
98+ cleaned_data = super(LicenseKeyForm, self).clean()
99+ required_fields = ['license_key', 'osystem', 'distro_series']
100+ for field in required_fields:
101+ if field not in cleaned_data:
102+ return cleaned_data
103+ cleaned_data['distro_series'] = self.clean_osystem_distro_series_field(
104+ cleaned_data)
105+ self.validate_license_key(cleaned_data)
106+ return cleaned_data
107+
108+ def clean_osystem_distro_series_field(self, cleaned_data):
109+ """Validate that os/distro_series matches osystem, and update the
110+ distro_series field, to remove the leading os/."""
111+ cleaned_osystem = cleaned_data['osystem']
112+ cleaned_series = cleaned_data['distro_series']
113+ series_os, release = cleaned_series.split('/', 1)
114+ if series_os != cleaned_osystem:
115+ raise ValidationError(
116+ "%s in distro_series does not match with "
117+ "operating system %s" % (release, cleaned_osystem))
118+ return release
119+
120+ def validate_license_key(self, cleaned_data):
121+ """Validates that the license key is valid."""
122+ cleaned_key = cleaned_data['license_key']
123+ cleaned_osystem = cleaned_data['osystem']
124+ cleaned_series = cleaned_data['distro_series']
125+ os_obj = OperatingSystemRegistry.get_item(cleaned_osystem)
126+ if os_obj is None:
127+ raise ValidationError(
128+ "Failed to retrieve %s from os registry." % cleaned_osystem)
129+ elif not os_obj.validate_license_key(cleaned_series, cleaned_key):
130+ raise ValidationError("Invalid license key.")
131
132=== modified file 'src/maasserver/models/licensekey.py'
133--- src/maasserver/models/licensekey.py 2014-06-26 14:09:40 +0000
134+++ src/maasserver/models/licensekey.py 2014-07-02 14:55:33 +0000
135@@ -74,10 +74,21 @@
136 distro_series = CharField(max_length=255, blank=False)
137
138 # License key for the osystem/distro_series combo.
139- license_key = CharField(max_length=255, blank=False)
140+ license_key = CharField(
141+ max_length=255, blank=False, verbose_name="License Key", help_text=(
142+ "License key for operating system"))
143
144 def __repr__(self):
145 return "<LicenseKey %s/%s>" % (
146 self.osystem,
147 self.distro_series,
148 )
149+
150+ def unique_error_message(self, model_class, unique_check):
151+ if unique_check == ('osystem', 'distro_series'):
152+ return '%s %s' % (
153+ "License key with this operating system and distro series",
154+ "already exists.",
155+ )
156+ return super(
157+ LicenseKey, self).unique_error_message(model_class, unique_check)
158
159=== modified file 'src/maasserver/models/tests/test_licensekey.py'
160--- src/maasserver/models/tests/test_licensekey.py 2014-06-26 13:17:00 +0000
161+++ src/maasserver/models/tests/test_licensekey.py 2014-07-02 14:55:33 +0000
162@@ -14,6 +14,7 @@
163 __metaclass__ = type
164 __all__ = []
165
166+from django.db import IntegrityError
167 from maasserver.models import LicenseKey
168 from maasserver.testing.factory import factory
169 from maasserver.testing.testcase import MAASServerTestCase
170@@ -46,3 +47,11 @@
171 self.assertFalse(
172 LicenseKey.objects.has_license_key(
173 osystem, series))
174+
175+ def test_errors_on_not_unique(self):
176+ key = factory.make_license_key()
177+ new_key = factory.make_name('key')
178+ self.assertRaises(
179+ IntegrityError,
180+ LicenseKey.objects.create, osystem=key.osystem,
181+ distro_series=key.distro_series, license_key=new_key)
182
183=== modified file 'src/maasserver/tests/test_forms.py'
184--- src/maasserver/tests/test_forms.py 2014-07-02 07:05:24 +0000
185+++ src/maasserver/tests/test_forms.py 2014-07-02 14:55:33 +0000
186@@ -56,6 +56,7 @@
187 initialize_node_group,
188 InstanceListField,
189 INTERFACES_VALIDATION_ERROR_MESSAGE,
190+ LicenseKeyForm,
191 list_all_usable_architectures,
192 list_all_usable_osystems,
193 list_all_usable_releases,
194@@ -83,6 +84,7 @@
195 )
196 from maasserver.models import (
197 Config,
198+ LicenseKey,
199 MACAddress,
200 Network,
201 Node,
202@@ -120,6 +122,7 @@
203 IPRange,
204 )
205 from provisioningserver import tasks
206+from provisioningserver.drivers.osystem import OperatingSystemRegistry
207 from provisioningserver.drivers.osystem.ubuntu import UbuntuOS
208 from testtools import TestCase
209 from testtools.matchers import (
210@@ -2253,3 +2256,188 @@
211 }
212 form = DeployForm(data=params)
213 self.assertTrue(form.is_valid())
214+
215+
216+class TestLicenseKeyForm(MAASServerTestCase):
217+ """Tests for `LicenseKeyForm`."""
218+
219+ def make_os_with_license_key(self, osystem=None, has_key=True,
220+ key_is_valid=True, patch_registry=True):
221+ """Makes a fake operating system that requires a license key, and
222+ validates to key_is_valid."""
223+ if osystem is None:
224+ osystem = make_usable_osystem(self)
225+ self.patch(osystem, 'requires_license_key').return_value = has_key
226+ self.patch(osystem, 'validate_license_key').return_value = key_is_valid
227+ if patch_registry:
228+ self.patch(
229+ OperatingSystemRegistry, 'get_item').return_value = osystem
230+ return osystem
231+
232+ def make_all_osystems_require_key(self, key_is_valid=True):
233+ """Patches all operating systems in the registry, to require a key."""
234+ for _, osystem in OperatingSystemRegistry:
235+ self.patch(
236+ osystem, 'requires_license_key').return_value = True
237+ self.patch(
238+ osystem, 'validate_license_key').return_value = key_is_valid
239+
240+ def make_only_one_osystem_require_key(self, key_is_valid=True):
241+ """Patches a random operating system from the registry, to require a
242+ key. Patches the remaining operating systems to not require a key."""
243+ osystem = factory.getRandomOS()
244+ self.patch(
245+ osystem, 'requires_license_key').return_value = True
246+ self.patch(
247+ osystem, 'validate_license_key').return_value = key_is_valid
248+ for _, other_osystem in OperatingSystemRegistry:
249+ if osystem == other_osystem:
250+ continue
251+ self.patch(
252+ other_osystem, 'requires_license_key').return_value = False
253+ return osystem
254+
255+ def test_creates_license_key(self):
256+ osystem = self.make_os_with_license_key()
257+ series = factory.getRandomRelease(osystem)
258+ key = factory.make_name('key')
259+ definition = {
260+ 'osystem': osystem.name,
261+ 'distro_series': series,
262+ 'license_key': key,
263+ }
264+ data = definition.copy()
265+ data['distro_series'] = '%s/%s' % (osystem.name, series)
266+ form = LicenseKeyForm(data=data)
267+ form.save()
268+ license_key_obj = LicenseKey.objects.get(
269+ osystem=osystem.name, distro_series=series)
270+ self.assertAttributes(license_key_obj, definition)
271+
272+ def test_updates_license_key(self):
273+ osystem = self.make_os_with_license_key()
274+ series = factory.getRandomRelease(osystem)
275+ license_key = factory.make_license_key(
276+ osystem=osystem.name, distro_series=series,
277+ license_key=factory.make_name('key'))
278+ new_key = factory.make_name('key')
279+ form = LicenseKeyForm(
280+ data={'license_key': new_key}, instance=license_key)
281+ form.save()
282+ license_key = reload_object(license_key)
283+ self.assertEqual(new_key, license_key.license_key)
284+
285+ def test_validates_license_key(self):
286+ osystem = self.make_os_with_license_key(key_is_valid=False)
287+ series = factory.getRandomRelease(osystem)
288+ license_key = factory.make_license_key(
289+ osystem=osystem.name, distro_series=series,
290+ license_key=factory.make_name('key'))
291+ new_key = factory.make_name('key')
292+ form = LicenseKeyForm(
293+ data={'license_key': new_key}, instance=license_key)
294+ self.assertFalse(form.is_valid(), form.errors)
295+ self.assertEqual(
296+ {'__all__': ['Invalid license key.']},
297+ form.errors)
298+
299+ def test_handles_missing_osystem_in_distro_series(self):
300+ osystem = self.make_os_with_license_key()
301+ series = factory.getRandomRelease(osystem)
302+ key = factory.make_name('key')
303+ definition = {
304+ 'osystem': osystem.name,
305+ 'distro_series': series,
306+ 'license_key': key,
307+ }
308+ form = LicenseKeyForm(data=definition.copy())
309+ form.save()
310+ license_key_obj = LicenseKey.objects.get(
311+ osystem=osystem.name, distro_series=series)
312+ self.assertAttributes(license_key_obj, definition)
313+
314+ def test_requires_all_fields(self):
315+ form = LicenseKeyForm(data={})
316+ self.assertFalse(form.is_valid(), form.errors)
317+ self.assertItemsEqual(
318+ ['osystem', 'distro_series', 'license_key'],
319+ form.errors.keys())
320+
321+ def test_errors_on_not_unique(self):
322+ osystem = self.make_os_with_license_key()
323+ series = factory.getRandomRelease(osystem)
324+ key = factory.make_name('key')
325+ factory.make_license_key(
326+ osystem=osystem.name, distro_series=series, license_key=key)
327+ definition = {
328+ 'osystem': osystem.name,
329+ 'distro_series': series,
330+ 'license_key': key,
331+ }
332+ form = LicenseKeyForm(data=definition)
333+ self.assertFalse(form.is_valid(), form.errors)
334+ self.assertEqual({
335+ '__all__': ['%s %s' % (
336+ "License key with this operating system and distro series",
337+ "already exists.")]},
338+ form.errors)
339+
340+ def test_doesnt_include_default_osystem(self):
341+ form = LicenseKeyForm()
342+ self.assertNotIn(('', 'Default OS'), form.fields['osystem'].choices)
343+
344+ def test_includes_all_osystems(self):
345+ self.make_all_osystems_require_key()
346+ osystems = [osystem for _, osystem in OperatingSystemRegistry]
347+ expected = [
348+ (osystem.name, osystem.title)
349+ for osystem in osystems
350+ ]
351+ form = LicenseKeyForm()
352+ self.assertItemsEqual(expected, form.fields['osystem'].choices)
353+
354+ def test_includes_all_osystems_sorted(self):
355+ self.make_all_osystems_require_key()
356+ osystems = [osystem for _, osystem in OperatingSystemRegistry]
357+ osystems = sorted(osystems, key=lambda osystem: osystem.title)
358+ expected = [
359+ (osystem.name, osystem.title)
360+ for osystem in osystems
361+ ]
362+ form = LicenseKeyForm()
363+ self.assertEqual(expected, form.fields['osystem'].choices)
364+
365+ def test_includes_only_osystems_that_require_license_keys(self):
366+ osystem = self.make_only_one_osystem_require_key()
367+ expected = [(osystem.name, osystem.title)]
368+ form = LicenseKeyForm()
369+ self.assertEquals(expected, form.fields['osystem'].choices)
370+
371+ def test_doesnt_include_default_distro_series(self):
372+ form = LicenseKeyForm()
373+ self.assertNotIn(
374+ ('', 'Default OS Release'), form.fields['distro_series'].choices)
375+
376+ def test_includes_all_distro_series(self):
377+ self.make_all_osystems_require_key()
378+ osystems = [osystem for _, osystem in OperatingSystemRegistry]
379+ expected = []
380+ for osystem in osystems:
381+ releases = osystem.get_supported_releases()
382+ for name, title in osystem.format_release_choices(releases):
383+ expected.append((
384+ '%s/%s' % (osystem.name, name),
385+ title
386+ ))
387+ form = LicenseKeyForm()
388+ self.assertItemsEqual(expected, form.fields['distro_series'].choices)
389+
390+ def test_includes_only_distro_series_that_require_license_keys(self):
391+ osystem = self.make_only_one_osystem_require_key()
392+ releases = osystem.get_supported_releases()
393+ expected = [
394+ ('%s/%s' % (osystem.name, name), title)
395+ for name, title in osystem.format_release_choices(releases)
396+ ]
397+ form = LicenseKeyForm()
398+ self.assertEquals(expected, form.fields['distro_series'].choices)
399
400=== modified file 'src/maasserver/utils/osystems.py'
401--- src/maasserver/utils/osystems.py 2014-06-16 14:25:56 +0000
402+++ src/maasserver/utils/osystems.py 2014-07-02 14:55:33 +0000
403@@ -14,6 +14,7 @@
404 __all__ = [
405 'get_distro_series_inital',
406 'get_release_requires_key',
407+ 'list_all_releases_requiring_keys',
408 'list_all_usable_osystems',
409 'list_all_usable_releases',
410 'list_osystem_choices',
411@@ -28,31 +29,42 @@
412 from provisioningserver.drivers.osystem import OperatingSystemRegistry
413
414
415-def list_all_usable_osystems():
416+def list_all_usable_osystems(have_images=True):
417 """Return all operating systems that can be used for nodes.
418
419- These are the operating systems for which any nodegroup has the boot images
420- required to boot the node.
421+ :param have_images: If set to true then its only the operating systems for
422+ which any nodegroup has the boot images available for that operating
423+ system.
424 """
425- # The Node edit form offers all usable operating systems as options for the
426- # osystem field. Not all of these may be available in the node's
427- # nodegroup, but to represent that accurately in the UI would depend on
428- # the currently selected nodegroup. Narrowing the options down further
429- # would have to happen browser-side.
430- osystems = set()
431- for nodegroup in NodeGroup.objects.all():
432- osystems = osystems.union(
433- BootImage.objects.get_usable_osystems(nodegroup))
434- osystems = [
435- OperatingSystemRegistry[osystem] for osystem in osystems
436- if osystem in OperatingSystemRegistry
437- ]
438+ if not have_images:
439+ osystems = set([osystem for _, osystem in OperatingSystemRegistry])
440+ else:
441+ # The Node edit form offers all usable operating systems as options for
442+ # the osystem field. Not all of these may be available in the node's
443+ # nodegroup, but to represent that accurately in the UI would depend on
444+ # the currently selected nodegroup. Narrowing the options down further
445+ # would have to happen browser-side.
446+ osystems = set()
447+ for nodegroup in NodeGroup.objects.all():
448+ osystems = osystems.union(
449+ BootImage.objects.get_usable_osystems(nodegroup))
450+ osystems = [
451+ OperatingSystemRegistry[osystem] for osystem in osystems
452+ if osystem in OperatingSystemRegistry
453+ ]
454 return sorted(osystems, key=lambda osystem: osystem.title)
455
456
457-def list_osystem_choices(osystems):
458- """Return Django "choices" list for `osystem`."""
459- choices = [('', 'Default OS')]
460+def list_osystem_choices(osystems, include_default=True):
461+ """Return Django "choices" list for `osystem`.
462+
463+ :param include_default: When true includes the 'Default OS' in choice
464+ selection.
465+ """
466+ if include_default:
467+ choices = [('', 'Default OS')]
468+ else:
469+ choices = []
470 choices += [
471 (osystem.name, osystem.title)
472 for osystem in osystems
473@@ -60,19 +72,42 @@
474 return choices
475
476
477-def list_all_usable_releases(osystems):
478- """Return dictionary of usable `releases` for each opertaing system."""
479+def list_all_usable_releases(osystems, have_images=True):
480+ """Return dictionary of usable `releases` for each operating system.
481+
482+ :param have_images: If set to true then its only the releases for
483+ which any nodegroup has the boot images available for that release.
484+ """
485 distro_series = {}
486 nodegroups = list(NodeGroup.objects.all())
487 for osystem in osystems:
488 releases = set()
489- for nodegroup in nodegroups:
490- releases = releases.union(
491- BootImage.objects.get_usable_releases(nodegroup, osystem.name))
492+ if not have_images:
493+ releases = releases.union(osystem.get_supported_releases())
494+ else:
495+ for nodegroup in nodegroups:
496+ releases = releases.union(
497+ BootImage.objects.get_usable_releases(
498+ nodegroup, osystem.name))
499 distro_series[osystem.name] = sorted(releases)
500 return distro_series
501
502
503+def list_all_releases_requiring_keys(osystems):
504+ """Return dictionary of OS name mapping to `releases` that require
505+ license keys."""
506+ distro_series = {}
507+ for osystem in osystems:
508+ releases = [
509+ release
510+ for release in osystem.get_supported_releases()
511+ if osystem.requires_license_key(release)
512+ ]
513+ if len(releases) > 0:
514+ distro_series[osystem.name] = sorted(releases)
515+ return distro_series
516+
517+
518 def get_release_requires_key(osystem, release):
519 """Return astrisk for any release that requires
520 a license key.
521@@ -84,39 +119,58 @@
522 return ''
523
524
525-def list_release_choices(releases):
526- """Return Django "choices" list for `releases`."""
527- choices = [('', 'Default OS Release')]
528+def list_release_choices(releases, include_default=True, include_latest=True,
529+ with_key_required=True):
530+ """Return Django "choices" list for `releases`.
531+
532+ :param include_default: When true includes the 'Default OS Release' in
533+ choice selection.
534+ :param include_latest: When true includes the 'Latest OS Release' in
535+ choice selection.
536+ :param with_key_required: When true includes the release_requires_key in
537+ the choice.
538+ """
539+ if include_default:
540+ choices = [('', 'Default OS Release')]
541+ else:
542+ choices = []
543 for key, value in releases.items():
544 osystem = OperatingSystemRegistry[key]
545 options = osystem.format_release_choices(value)
546- requires_key = get_release_requires_key(osystem, '')
547- choices += [(
548- '%s/%s' % (osystem.name, requires_key),
549- 'Latest %s Release' % osystem.title
550- )]
551- choices += [(
552- '%s/%s%s' % (
553- osystem.name,
554- name,
555- get_release_requires_key(osystem, name)
556- ),
557- title
558- )
559- for name, title in options
560- ]
561+ if with_key_required:
562+ requires_key = get_release_requires_key(osystem, '')
563+ else:
564+ requires_key = ''
565+ if include_latest:
566+ choices.append((
567+ '%s/%s' % (osystem.name, requires_key),
568+ 'Latest %s Release' % osystem.title
569+ ))
570+ for name, title in options:
571+ if with_key_required:
572+ requires_key = get_release_requires_key(osystem, name)
573+ else:
574+ requires_key = ''
575+ choices.append((
576+ '%s/%s%s' % (osystem.name, name, requires_key),
577+ title
578+ ))
579 return choices
580
581
582-def get_distro_series_inital(instance):
583- """Returns the distro_series initial value for the instance."""
584+def get_distro_series_inital(instance, with_key_required=True):
585+ """Returns the distro_series initial value for the instance.
586+
587+ :param with_key_required: When true includes the release_requires_key in
588+ the choice.
589+ """
590 osystem = instance.osystem
591 series = instance.distro_series
592 os_obj = OperatingSystemRegistry.get_item(osystem)
593- if os_obj is not None:
594+ if not with_key_required:
595+ key_required = ''
596+ elif os_obj is not None:
597 key_required = get_release_requires_key(os_obj, series)
598- else:
599- key_required = ''
600 if osystem is not None and osystem != '':
601 if series is None:
602 series = ''