Merge lp:~gmb/maas/json-schema-part-1 into lp:~maas-committers/maas/trunk

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 1921
Proposed branch: lp:~gmb/maas/json-schema-part-1
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 531 lines (+379/-98)
4 files modified
required-packages/base (+1/-0)
src/maasserver/power_parameters.py (+227/-89)
src/maasserver/tests/test_power_parameters.py (+146/-4)
src/provisioningserver/enum.py (+5/-5)
To merge this branch: bzr merge lp:~gmb/maas/json-schema-part-1
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Julian Edwards (community) Approve
Review via email: mp+205305@code.launchpad.net

Commit message

Convert the definition of power type parameters from straight Python to something that's JSON-schema-validatable. This is the first step in making it possible for hardware enablement drivers to specify what power parameters they accept.

Description of the change

After a lot of dancing around, this is the first part of the work to making power type parameters specifiable in JSON.

In order to do this I've:

 - Defined a JSON schema to describe how power type parameters should look
   (this is split up so that we can validate separate parts of it individually)
 - Created a JSON-validatable version of the existing POWER_TYPE_PARAMETERS dict
 - Created a function, get_power_type_parameters_from_json() to convert the
   JSON power type parameters to something similar to the existing
   POWER_TYPE_PARAMETERS dict.
 - Use get_power_type_parameters_from_json() to create POWER_TYPE_PARAMETERS.
   This is done once at the bottom of maasserver.power_parameters so that we're
   not parsing and validating JSON all the time.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 07 Feb 2014 09:20:37 you wrote:
> Commit message:
> Convert the definition of power type parameters from straight Python to
> something that's JSON-schema-validatable.

Can you add something about why this is happening please.

> After a lot of dancing around, this is the first part of the work to making
> power type parameters specifiable in JSON.

\o/

> In order to do this I've:
>
> - Defined a JSON schema to describe how power type parameters should look
> (this is split up so that we can validate separate parts of it
> individually) - Created a JSON-validatable version of the existing
> POWER_TYPE_PARAMETERS dict - Created a function,
> get_power_type_parameters_from_json() to convert the JSON power type
> parameters to something similar to the existing POWER_TYPE_PARAMETERS dict.
> - Use get_power_type_parameters_from_json() to create
> POWER_TYPE_PARAMETERS. This is done once at the bottom of
> maasserver.power_parameters so that we're not parsing and validating JSON
> all the time.

This is mostly looking *great*. I'd like to get Gavin to take a peek to make
sure it matches his grand vision, but it looks like it's on the right track to
me.

Some nits:

[1] You're missing :param:, :type: and :return: info on the docstrings for the
new functions.

(FWIW My vim plugin reads the :type: and offers better auto-completions :)

[2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to form
part of the API with the drivers. I think putting these in their own module
would be better (like errno), so we can do something like
powerfield.mac_address, for example. This also then forms a firm contract
with the driver, which consumes the API. (It also makes it easier to install
for packaging and is something we should have done for enums so that maas api
clients can use them)

[3] jsonschema. MY EYES!
OK this ones not a nit :)

[4] Missing tests for make_json_field.

cheers.

Revision history for this message
Julian Edwards (julian-edwards) :
review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

Looks grand :)

[1]

Couple of minor things:

+def make_json_field(name, label, field_type=None, choices=None, default=None,
+                    required=False):

Dosnae matter much, but try using the following form for indentation:

def make_json_field(
        name, label, field_type=None, choices=None, default=None,
        required=False):
    ... code here ...

+    """Helper function for build a JSON power type parameters field."""

s/for/to/

review: Approve
Revision history for this message
Graham Binns (gmb) wrote :

On 10 February 2014 02:24, Julian Edwards <email address hidden> wrote:
> On Friday 07 Feb 2014 09:20:37 you wrote:
>> Commit message:
>> Convert the definition of power type parameters from straight Python to
>> something that's JSON-schema-validatable.
>
> Can you add something about why this is happening please.

Yeah, sorry. I've gotten into a laziness habit with commit messages...

>> After a lot of dancing around, this is the first part of the work to making
>> power type parameters specifiable in JSON.
>
> \o/
>
> This is mostly looking *great*. I'd like to get Gavin to take a peek to make
> sure it matches his grand vision, but it looks like it's on the right track to
> me.

+1. Will bug Gavin.

> Some nits:
>
> [1] You're missing :param:, :type: and :return: info on the docstrings for the
> new functions.
>
> (FWIW My vim plugin reads the :type: and offers better auto-completions :)

Which vim plugin loveliness is it of which you speak? Fixed.

>
> [2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to form
> part of the API with the drivers. I think putting these in their own module
> would be better (like errno), so we can do something like
> powerfield.mac_address, for example. This also then forms a firm contract
> with the driver, which consumes the API. (It also makes it easier to install
> for packaging and is something we should have done for enums so that maas api
> clients can use them)

Okay, I'm a bit lost as to what you mean here... I was with you up
until "their own module" but then you started talking about packaging
and I got confused. Are you talking about just adding a module under
src/maasserver (which is what I think you mean)?

Anyway, in the interests of getting this landed I'll do that in a
followup branch once I've got clarity.

> [3] jsonschema. MY EYES!
> OK this ones not a nit :)

DEAR GOD YES.

> [4] Missing tests for make_json_field.
>

Fixed. Cheers for the review.

Revision history for this message
Graham Binns (gmb) wrote :

On 10 February 2014 10:25, Gavin Panella <email address hidden> wrote:
> Couple of minor things:
>
> +def make_json_field(name, label, field_type=None, choices=None, default=None,
> + required=False):
>
> Dosnae matter much, but try using the following form for indentation:

Done. Is that the standard form for MAAS? I was doing it the LP way
but this looks better.

> + """Helper function for build a JSON power type parameters field."""

Actually should have been s/build/building/. Fixed.

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

This introduces a new dependency: python-jsonschema. This means the packaging branch needs to be updated.

Next time, let's try to do this before landing the branch on lp:maas please. Otherwise it breaks the package, causes the integration tests to fail, and whoever ends up looking into it is wasting his time.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 10 Feb 2014 12:06:29 you wrote:
> > (FWIW My vim plugin reads the :type: and offers better auto-completions :)
>
> Which vim plugin loveliness is it of which you speak? Fixed.

https://github.com/Valloric/YouCompleteMe

It is seriously *awesome*.

> > [2] I realise it's early days, but the FIELD_TYPE_MAPPINGS are going to
> > form part of the API with the drivers. I think putting these in their
> > own module would be better (like errno), so we can do something like
> > powerfield.mac_address, for example. This also then forms a firm contract
> > with the driver, which consumes the API. (It also makes it easier to
> > install for packaging and is something we should have done for enums so
> > that maas api clients can use them)
>
> Okay, I'm a bit lost as to what you mean here... I was with you up
> until "their own module" but then you started talking about packaging
> and I got confused. Are you talking about just adding a module under
> src/maasserver (which is what I think you mean)?

Ok so these are a bit like the enums in that third party code (drivers) are
going to need access. Putting it in its own module will help.

For packaging, I was thinking more about maas-cli because we could install an
enums.py (for example) along with the cli itself. This doesn't translate too
well for the drivers since they are going to be part and parcel of maas
itself; it's just that we want to make life as easy as possible for driver
writers so I'd prefer if we had all things that drivers will need to import in
a separate and convenience place.

Hope that makes sense :)

> Anyway, in the interests of getting this landed I'll do that in a
> followup branch once I've got clarity.

Naw worries.

It could probably even wait until Gavin starts writing the API layer.

> Fixed. Cheers for the review.

Pleasure.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 10 Feb 2014 13:17:31 you wrote:
> This introduces a new dependency: python-jsonschema. This means the
> packaging branch needs to be updated.
>
> Next time, let's try to do this before landing the branch on lp:maas please.
> Otherwise it breaks the package, causes the integration tests to fail, and
> whoever ends up looking into it is wasting his time.

Argh, I meant to say something about that when I read the branch and totally
forgot.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'required-packages/base'
--- required-packages/base 2014-01-20 09:39:00 +0000
+++ required-packages/base 2014-02-10 12:07:12 +0000
@@ -24,6 +24,7 @@
24python-docutils24python-docutils
25python-formencode25python-formencode
26python-httplib226python-httplib2
27python-jsonschema
27python-lockfile28python-lockfile
28python-netaddr29python-netaddr
29python-netifaces30python-netifaces
3031
=== modified file 'src/maasserver/power_parameters.py'
--- src/maasserver/power_parameters.py 2014-01-21 06:08:12 +0000
+++ src/maasserver/power_parameters.py 2014-02-10 12:07:12 +0000
@@ -33,6 +33,7 @@
3333
3434
35from django import forms35from django import forms
36from jsonschema import validate
36from maasserver.config_forms import DictCharField37from maasserver.config_forms import DictCharField
37from maasserver.fields import MACAddressFormField38from maasserver.fields import MACAddressFormField
38from provisioningserver.enum import (39from provisioningserver.enum import (
@@ -40,92 +41,229 @@
40 IPMI_DRIVER_CHOICES,41 IPMI_DRIVER_CHOICES,
41 )42 )
4243
43# FIXME: These should come from a hardware driver.44
44POWER_TYPE_PARAMETERS = {45# Represent the Django choices format as JSON; an array of 2-item
45 # Empty type, for the case where nothing is entered in the form yet.46# arrays.
46 '': DictCharField(47CHOICE_FIELD_SCHEMA = {
47 [], required=False, skip_check=True),48 'type': 'array',
48 'ether_wake': DictCharField(49 'items': {
49 [50 'title': "Power type paramter field choice",
50 ('mac_address',51 'type': 'array',
51 MACAddressFormField(label="MAC Address", required=False)),52 'minItems': 2,
52 ],53 'maxItems': 2,
53 required=False,54 'uniqueItems': True,
54 skip_check=True),55 'items': {
55 'virsh': DictCharField(56 'type': 'string',
56 [57 }
57 ('power_address',58 },
58 forms.CharField(label="Address", required=False)),59}
59 ('power_id',60
60 forms.CharField(label="Power ID", required=False)),61
61 ],62POWER_TYPE_PARAMETER_FIELD_SCHEMA = {
62 required=False,63 'title': "Power type parameter field",
63 skip_check=True),64 'type': 'object',
64 'fence_cdu': DictCharField(65 'properties': {
65 [66 'name': {
66 ('power_id',67 'type': 'string',
67 forms.CharField(label="Power ID", required=False)),68 },
68 ('power_address',69 'field_type': {
69 forms.CharField(label="IP Address or Hostname", required=False)),70 'type': 'string',
70 ('power_user',71 },
71 forms.CharField(label="Username", required=False)),72 'label': {
72 ('power_pass',73 'type': 'string',
73 forms.CharField(label="Password", required=False)),74 },
74 ],75 'required': {
75 required=False,76 'type': 'boolean',
76 skip_check=True),77 },
77 'ipmi': DictCharField(78 'choices': CHOICE_FIELD_SCHEMA,
78 [79 'default': {
79 ('power_driver',80 'type': 'string',
80 forms.ChoiceField(81 },
81 label="Driver", required=False,82 },
82 choices=IPMI_DRIVER_CHOICES,83 'required': ['field_type', 'label', 'required'],
83 initial=IPMI_DRIVER.DEFAULT)),84}
84 ('power_address',85
85 forms.CharField(label="IP Address or Hostname", required=False)),86
86 ('power_user',87# A basic JSON schema for what power type parameters should look like.
87 forms.CharField(label="Username", required=False)),88JSON_POWER_TYPE_PARAMETERS_SCHEMA = {
88 ('power_pass',89 'title': "Power parameters set",
89 forms.CharField(label="Password", required=False)),90 'type': 'array',
90 ],91 'items': {
91 required=False,92 'title': "Power type parameters",
92 skip_check=True),93 'type': 'object',
93 'moonshot': DictCharField(94 'properties': {
94 [95 'fields': {
95 ('power_address',96 'type': 'array',
96 forms.CharField(97 'items': POWER_TYPE_PARAMETER_FIELD_SCHEMA,
97 label="IP Address or Hostname", required=False)),98 },
98 ('power_user',99 },
99 forms.CharField(label="Username", required=False)),100 'required': ['fields'],
100 ('power_pass',101 },
101 forms.CharField(label="Password", required=False)),102}
102 ('power_hwaddress',103
103 forms.CharField(label="Hardware Address", required=False)),104
104 ],105def make_json_field(
105 required=False,106 name, label, field_type=None, choices=None, default=None,
106 skip_check=True),107 required=False):
107 'sm15k': DictCharField(108 """Helper function for building a JSON power type parameters field.
108 [109
109 ('power_address',110 :param name: The name of the field.
110 forms.CharField(111 :type name: string
111 label="IP Address or Hostname", required=False)),112 :param label: The label to be presented to the user for this field.
112 ('power_user',113 :type label: string
113 forms.CharField(label="Username", required=False)),114 :param field_type: The type of field to create. Can be one of
114 ('power_pass',115 (string, choice, mac_address). Defaults to string.
115 forms.CharField(label="Password", required=False)),116 :type field_type: string.
116 ('system_id',117 :param choices: The collection of choices to present to the user.
117 forms.CharField(label="System ID", required=False)),118 Needs to be structured as a list of lists, otherwise
118 ],119 make_json_field() will raise a ValidationError.
119 required=False,120 :type list:
120 skip_check=True),121 :param default: The default value for the field.
121 'amt': DictCharField(122 :type default: string
122 [123 :param required: Whether or not a value for the field is required.
123 ('power_address',124 :type required: boolean
124 forms.CharField(125 """
125 label="IP Address or Hostname", required=False)),126 if field_type not in ('string', 'mac_address', 'choice'):
126 ('power_pass',127 field_type = 'string'
127 forms.CharField(label="Password", required=False)),128 if choices is None:
128 ],129 choices = []
129 required=False,130 validate(choices, CHOICE_FIELD_SCHEMA)
130 skip_check=True),131 if default is None:
131}132 default = ""
133 field = {
134 'name': name,
135 'label': label,
136 'required': required,
137 'field_type': field_type,
138 'choices': choices,
139 'default': default,
140 }
141 return field
142
143
144# FIXME this should all be produced by hardware drivers, not defined statically
145# like this.
146JSON_POWER_TYPE_PARAMETERS = [
147 {
148 'name': 'ether_wake',
149 'fields': [
150 make_json_field(
151 'mac_address', "MAC Address", field_type='mac_address'),
152 ],
153 },
154 {
155 'name': 'virsh',
156 'fields': [
157 make_json_field('power_address', "Power address"),
158 make_json_field('power_id', "Power ID"),
159 ],
160 },
161 {
162 'name': 'fence_cdu',
163 'fields': [
164 make_json_field('power_address', "Power address"),
165 make_json_field('power_id', "Power ID"),
166 make_json_field('power_user', "Power user"),
167 make_json_field('power_pass', "Power password"),
168 ],
169 },
170 {
171 'name': 'ipmi',
172 'fields': [
173 make_json_field(
174 'power_driver', "Power driver", field_type='choice',
175 choices=IPMI_DRIVER_CHOICES, default=IPMI_DRIVER.DEFAULT),
176 make_json_field('power_address', "Power address"),
177 make_json_field('power_user', "Power user"),
178 make_json_field('power_pass', "Power password"),
179 ],
180 },
181 {
182 'name': 'moonshot',
183 'fields': [
184 make_json_field('power_address', "Power address"),
185 make_json_field('power_user', "Power user"),
186 make_json_field('power_pass', "Power password"),
187 make_json_field('power_hwaddress', "Power hardware address"),
188 ],
189 },
190 {
191 'name': 'sm15k',
192 'fields': [
193 make_json_field('system_id', "System ID"),
194 make_json_field('power_address', "Power address"),
195 make_json_field('power_user', "Power user"),
196 make_json_field('power_pass', "Power password"),
197 ],
198 },
199 {
200 'name': 'amt',
201 'fields': [
202 make_json_field('power_address', "Power address"),
203 make_json_field('power_pass', "Power password"),
204 ],
205 },
206]
207
208
209FIELD_TYPE_MAPPINGS = {
210 'string': forms.CharField,
211 'mac_address': MACAddressFormField,
212 'choice': forms.ChoiceField,
213}
214
215
216def make_form_field(json_field):
217 """Build a Django form field based on the JSON spec.
218
219 :param json_field: The JSON-specified field to convert into a valid
220 Djangoism.
221 :type json_field: dict
222 :return: The correct Django form field for the field type, as
223 specified in FIELD_TYPE_MAPPINGS.
224 """
225 field_class = FIELD_TYPE_MAPPINGS.get(
226 json_field['field_type'], forms.CharField)
227 if json_field['field_type'] == 'choice':
228 extra_parameters = {
229 'choices': json_field['choices'],
230 'initial': json_field['default'],
231 }
232 else:
233 extra_parameters = {}
234 form_field = field_class(
235 label=json_field['label'], required=json_field['required'],
236 **extra_parameters)
237 return form_field
238
239
240def get_power_type_parameters_from_json(json_power_type_parameters):
241 """Return power type parameters.
242
243 :param json_power_type_parameters: Power type parameters expressed
244 as a JSON string or as set of JSONSchema-verifiable objects.
245 Will be validated using jsonschema.validate().
246 :type json_power_type_parameters: JSON string or iterable.
247 :return: A dict of power parameters for all power types, indexed by
248 power type name.
249 """
250 validate(json_power_type_parameters, JSON_POWER_TYPE_PARAMETERS_SCHEMA)
251 power_parameters = {
252 # Empty type, for the case where nothing is entered in the form yet.
253 '': DictCharField(
254 [], required=False, skip_check=True),
255 }
256 for power_type in json_power_type_parameters:
257 fields = []
258 for json_field in power_type['fields']:
259 fields.append((
260 json_field['name'], make_form_field(json_field)))
261 params = DictCharField(fields, required=False, skip_check=True)
262 power_parameters[power_type['name']] = params
263 return power_parameters
264
265
266# We do this once because there's no point re-parsing the JSON every
267# time we need to look up the power type parameters code.
268POWER_TYPE_PARAMETERS = get_power_type_parameters_from_json(
269 JSON_POWER_TYPE_PARAMETERS)
132270
=== modified file 'src/maasserver/tests/test_power_parameters.py'
--- src/maasserver/tests/test_power_parameters.py 2014-01-21 06:08:12 +0000
+++ src/maasserver/tests/test_power_parameters.py 2014-02-10 12:07:12 +0000
@@ -14,13 +14,20 @@
14__metaclass__ = type14__metaclass__ = type
15__all__ = []15__all__ = []
1616
17from django import forms
18import jsonschema
17from maasserver.config_forms import DictCharField19from maasserver.config_forms import DictCharField
18from maasserver.power_parameters import POWER_TYPE_PARAMETERS20from maasserver.fields import MACAddressFormField
21from maasserver.power_parameters import (
22 get_power_type_parameters_from_json,
23 make_form_field,
24 make_json_field,
25 POWER_TYPE_PARAMETERS,
26 POWER_TYPE_PARAMETER_FIELD_SCHEMA,
27 )
19from maasserver.testing.factory import factory28from maasserver.testing.factory import factory
20from maasserver.testing.testcase import MAASServerTestCase29from maasserver.testing.testcase import MAASServerTestCase
21from provisioningserver.enum import (30from provisioningserver.enum import get_power_types
22 get_power_types,
23 )
24from provisioningserver.power.poweraction import PowerAction31from provisioningserver.power.poweraction import PowerAction
25from testtools.matchers import (32from testtools.matchers import (
26 AllMatch,33 AllMatch,
@@ -76,3 +83,138 @@
76 script = action.render_template(action.get_template(), **params)83 script = action.render_template(action.get_template(), **params)
77 # The real check is that the rendering went fine.84 # The real check is that the rendering went fine.
78 self.assertIsInstance(script, bytes)85 self.assertIsInstance(script, bytes)
86
87
88class TestGetPowerTypeParametersFromJSON(MAASServerTestCase):
89 """Test that get_power_type_parametrs_from_json."""
90
91 def test_validates_json_power_type_parameters(self):
92 invalid_parameters = [{
93 'name': 'invalid_power_type',
94 'fields': 'nothing to see here',
95 }]
96 self.assertRaises(
97 jsonschema.ValidationError, get_power_type_parameters_from_json,
98 invalid_parameters)
99
100 def test_includes_empty_power_type(self):
101 json_parameters = [{
102 'name': 'something',
103 'fields': [{
104 'name': 'some_field',
105 'label': 'Some Field',
106 'field_type': 'string',
107 'required': False,
108 }],
109 }]
110 power_type_parameters = get_power_type_parameters_from_json(
111 json_parameters)
112 self.assertEqual(['', 'something'], power_type_parameters.keys())
113
114 def test_creates_dict_char_fields(self):
115 json_parameters = [{
116 'name': 'something',
117 'fields': [{
118 'name': 'some_field',
119 'label': 'Some Field',
120 'field_type': 'string',
121 'required': False,
122 }],
123 }]
124 power_type_parameters = get_power_type_parameters_from_json(
125 json_parameters)
126 for name, field in power_type_parameters.items():
127 self.assertIsInstance(field, DictCharField)
128
129
130class TestMakeFormField(MAASServerTestCase):
131 """Test that make_form_field() converts JSON fields to Django."""
132
133 def test_creates_char_field_for_strings(self):
134 json_field = {
135 'name': 'some_field',
136 'label': 'Some Field',
137 'field_type': 'string',
138 'required': False,
139 }
140 django_field = make_form_field(json_field)
141 self.assertIsInstance(django_field, forms.CharField)
142
143 def test_creates_choice_field_for_choices(self):
144 json_field = {
145 'name': 'some_field',
146 'label': 'Some Field',
147 'field_type': 'choice',
148 'choices': [
149 ['choice-one', 'Choice One'],
150 ['choice-two', 'Choice Two'],
151 ],
152 'default': 'choice-one',
153 'required': False,
154 }
155 django_field = make_form_field(json_field)
156 self.assertIsInstance(django_field, forms.ChoiceField)
157 self.assertEqual(json_field['choices'], django_field.choices)
158 self.assertEqual(json_field['default'], django_field.initial)
159
160 def test_creates_mac_address_field_for_mac_addresses(self):
161 json_field = {
162 'name': 'some_field',
163 'label': 'Some Field',
164 'field_type': 'mac_address',
165 'required': False,
166 }
167 django_field = make_form_field(json_field)
168 self.assertIsInstance(django_field, MACAddressFormField)
169
170 def test_sets_properties_on_form_field(self):
171 json_field = {
172 'name': 'some_field',
173 'label': 'Some Field',
174 'field_type': 'string',
175 'required': False,
176 }
177 django_field = make_form_field(json_field)
178 self.assertEqual(
179 (json_field['label'], json_field['required']),
180 (django_field.label, django_field.required))
181
182
183class TestMakeJSONField(MAASServerTestCase):
184 """Test that make_json_field() creates JSON-verifiable fields."""
185
186 def test_returns_json_verifiable_dict(self):
187 json_field = make_json_field('some_field', 'Some Label')
188 jsonschema.validate(json_field, POWER_TYPE_PARAMETER_FIELD_SCHEMA)
189
190 def test_provides_sane_default_values(self):
191 json_field = make_json_field('some_field', 'Some Label')
192 expected_field = {
193 'name': 'some_field',
194 'label': 'Some Label',
195 'required': False,
196 'field_type': 'string',
197 'choices': [],
198 'default': '',
199 }
200 self.assertEqual(expected_field, json_field)
201
202 def test_sets_field_values(self):
203 expected_field = {
204 'name': 'yet_another_field',
205 'label': 'Can I stop writing tests now?',
206 'required': True,
207 'field_type': 'string',
208 'choices': [
209 ['spam', 'Spam'],
210 ['eggs', 'Eggs'],
211 ],
212 'default': 'spam',
213 }
214 json_field = make_json_field(**expected_field)
215 self.assertEqual(expected_field, json_field)
216
217 def test_validates_choices(self):
218 self.assertRaises(
219 jsonschema.ValidationError, make_json_field,
220 'some_field', 'Some Label', choices="Nonsense")
79221
=== modified file 'src/provisioningserver/enum.py'
--- src/provisioningserver/enum.py 2014-01-31 10:46:57 +0000
+++ src/provisioningserver/enum.py 2014-02-10 12:07:12 +0000
@@ -92,11 +92,11 @@
92 LAN_2_0 = 'LAN_2_0'92 LAN_2_0 = 'LAN_2_0'
9393
9494
95IPMI_DRIVER_CHOICES = (95IPMI_DRIVER_CHOICES = [
96 (IPMI_DRIVER.DEFAULT, "Auto-detect"),96 [IPMI_DRIVER.DEFAULT, "Auto-detect"],
97 (IPMI_DRIVER.LAN, "LAN (IPMI 1.5)"),97 [IPMI_DRIVER.LAN, "LAN [IPMI 1.5]"],
98 (IPMI_DRIVER.LAN_2_0, "LAN_2_0 (IPMI 2.0)"),98 [IPMI_DRIVER.LAN_2_0, "LAN_2_0 [IPMI 2.0]"],
99 )99 ]
100100
101101
102class ARP_HTYPE:102class ARP_HTYPE: