Merge lp:~rvb/maas/multifield into lp:maas/trunk
| Status: | Merged |
|---|---|
| Approved by: | Julian Edwards on 2012-06-05 |
| Approved revision: | 604 |
| Merged at revision: | 609 |
| Proposed branch: | lp:~rvb/maas/multifield |
| Merge into: | lp:maas/trunk |
| Diff against target: |
581 lines (+572/-0) 2 files modified
src/maasserver/config_forms.py (+332/-0) src/maasserver/tests/test_config_forms.py (+240/-0) |
| To merge this branch: | bzr merge lp:~rvb/maas/multifield |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Julian Edwards (community) | 2012-05-31 | Approve on 2012-06-05 | |
|
Review via email:
|
|||
Commit Message
Add DictCharField, a form field used to store/display a dictionary.
Description of the Change
This branch adds a new (form) field used to expose and store a dictionary. This new field (DictCharField) will use a list of widgets to display the data. Each key/value will be associated with a widget.
I'll use that to change src/maasserver/
= Notes =
* Improvement over power-param-api4 *
This is a much better version of what I've done in https:/
* DictCharField and DictCharWidget *
One might find that this is a little bit hacky. I confess I had to perform open heart surgery on forms.MultiValu
MyForm(forms.Form)
# A structured DictCharField which will create dicts like
# {'sub_field1': 'value1', 'sub_field2': 'value2'}
# or {'sub_field1': 'value1'} (Note that the second field is optional.
constrained
[
],
# A completely un-constrained DictCharField.
free_
* Isolation *
Again, I want to state that this is fairly well isolated:
- this first branch only creates a completely independent module.
- the second branch simply updates the power_parameter module to use the newly created DictCharField.
https:/
- the third branch creates a new form used by the API, based on the one used in the UI to edit nodes and uses that form in the API. In this branch, the change to the API code is a one-liner and the change to forms.py is pretty minimal and fairly straightforward.
https:/
| Julian Edwards (julian-edwards) wrote : | # |
Thank you LP for stripping all the formatting spaces on the code snippets >:(
| Raphaël Badin (rvb) wrote : | # |
Thanks a lot for the review Julian, I'll fix this when I'll be back on Tuesday. I had a quick glance at your comments and it seems that most of the weirdness that you pointed out to me are snippets of Django code that I had to embed in my own code because, like I said on the MP, the Django code is not sufficiently well written to allow me to override only the parts that I needed to override :(. eg:
** original Django code **
if not value or isinstance(value, (list, tuple)):
if not value or not [v for v in value if v not in validators.
** Semi-refactored Django code **
115 + if not value or isinstance(value, (list, tuple)):
116 + is_empty = (
117 + not value or
118 + not [v for v in value if v not in validators.
But anyway, since this becomes our code, I'll fix this… thanks again for the thorough review.
| Julian Edwards (julian-edwards) wrote : | # |
On Sunday 03 June 2012 10:12:18 you wrote:
> Thanks a lot for the review Julian, I'll fix this when I'll be back on
> Tuesday. I had a quick glance at your comments and it seems that most of
> the weirdness that you pointed out to me are snippets of Django code that I
> had to embed in my own code because, like I said on the MP, the Django code
> is not sufficiently well written to allow me to override only the parts
> that I needed to override :(. eg:
>
> ** original Django code **
> if not value or isinstance(value, (list, tuple)):
> if not value or not [v for v in value if v not in
> validators.
>
> ** Semi-refactored Django code **
> 115 + if not value or isinstance(value, (list, tuple)):
> 116 + is_empty = (
> 117 + not value or
> 118 + not [v for v in value if v not in validators.
>
> But anyway, since this becomes our code, I'll fix this… thanks again for the
> thorough review.
Ok, thanks. I knew there had to be a reason you'd not be writing code that
badly on purpose!
| Raphaël Badin (rvb) wrote : | # |
> Ok, thanks. I knew there had to be a reason you'd not be writing code that
> badly on purpose!
Yeah, I confess I was tempted to leave it like it was written originally to better identify what was "stolen" from Django and what was not… If I refactor heavily the Django code, what is our code and what is not will become less obvious.
- 602. By Raphaël Badin on 2012-06-05
-
First batch of fixes, as per review.
| Raphaël Badin (rvb) wrote : | # |
> Thanks Raphers. This is needs-fixing because there's a few coding style
> violations and a test antipattern.
Thanks for the review. This is a first batch of fixes. Now I'm working on improving the test file.
>
> s/correspond/
> s/to store/the storing of/
Done.
>
> 44 + For instance a DictCharField created with the following list:
> 45 + [
[...]
> 50 + {'field1': 'subvalue1', 'field2': 'subvalue2'}
>
> I think this example needs expanding to show how assignment works. It wasn't
> clear to me how you get from the first bit to the second bit, which suddenly
> has values.
Dood point, done
> 55 + self.field_dict = OrderedDict(
>
> Why ordered? Shouldn't that be an option?
No, I need self.fields and self.names to be ordered in the same way. But note that this is an implementation detail.
> 60 + if skip_check:
> 61 + self.field_
> 62 + required=False)
>
> I think SKIP_CHECK_NAME needs to be more obscure; it's quite conceivable that
> someone will have a field named skip_check. Perhaps '__skip_check' ?
You've got a point, but since SKIP_CHECK_NAME is exposed to the user, I think it should be readable. Instead I've added a check to make sure that no user-defined subfield is named 'skip_check'. I've added a test for that (test_DictCharF
> 76 + def compress(self, data):
> 77 + if data:
>
> Docstring missing.
Done.
> s/splitted/split/
Fixed.
> The code has 'clean_sub_fields' ...
Right… fixed.
> 114 + value = value if not self.skip_check else value[:-1]
>
> How about:
>
> if self.skip_check:
> value = value[:-1]
>
> which is easier to read. We tend to eschew this logic shortcut constraint,
> it's hideously ugly and error prone.
Done. I've also added a comment to explain why this is for.
> 111 + def clean_global(self, value):
> 112 + # Remove the value corresponding to the SKIP_CHECK_NAME boolean
> field
> 113 + # if required.
>
> Missing docstring.
Fixed.
> 115 + if not value or isinstance(value, (list, tuple)):
> 116 + is_empty = (
> 117 + not value or
> 118 + not [v for v in value if v not in validators.
>
> *blink*
>
> Firstly, our coding standards say to check explicitly for None or False, and
> not use "if not value".
>
> Second, this really needs splitting up into something that's more
> understandable. I don't have any good suggestions because I can't tell what
> you're trying to do here. The code appears to be doing more than the comment
> suggests.
Done, and I've added a comment.
> 127 + raise ValidationError
>
> Is this "invalid" text something that Django needs or can we write a better
> error text here?
This is precisely done so that you can override all the default error messages on a per-field basis:
e.g.:
name = forms.CharField
>
> 129 + def clean_sub_
> 130 + clean_data = []
>
> Missing docstring.
Fixed.
> 134 + fields = self.field...
- 603. By Raphaël Badin on 2012-06-05
-
Fix tests.
| Raphaël Badin (rvb) wrote : | # |
>
> 324 +class DictCharFieldTe
> 325 +
> 326 + def test_DictCharFi
> 327 + self.assertEqua
> testField.names)
> 328 + self.assertEqual(
> 329 + ['field_a', 'field_b', 'field_c'], testField.
> 330 + self.assertEqual(
> 331 + [field.widget for field in testField.
> 332 + testField.
>
> This is a poor test because it's testing values that are set elsewhere in the
> test file, and is one of our testing antipatterns. Test values should be
> initialised in the same test. (i.e. the global "testField" is bad!)
>
> I'd fix this by having a factory for it so you can supply initialiser values
> in the test.
Fixed. I've not created a factory because I thought it wouldn't give us much considering that all the initialization code is in the creation of the list of the subfields. But now each test is isolated and much more readable.
- 604. By Raphaël Badin on 2012-06-05
-
Fix value if skip_check is True.
| Julian Edwards (julian-edwards) wrote : | # |
status approve
On Tuesday 05 Jun 2012 09:06:22 Raphaël Badin wrote:
> > I think SKIP_CHECK_NAME needs to be more obscure; it's quite conceivable
> > that someone will have a field named skip_check. Perhaps '__skip_check'
> > ?
> You've got a point, but since SKIP_CHECK_NAME is exposed to the user, I
> think it should be readable. Instead I've added a check to make sure that
> no user-defined subfield is named 'skip_check'. I've added a test for that
> (test_DictCharF
This is good that you've checked its presence - however I'd make the error
message convey more information like:
"skip_check is a reserved name"
so the user knows why it's disallowed.
+ """Extract the values for each widget from a data dict (QueryDict).
+ :param data: The data dict (usually request.data or request.GET where
+ request is a django.
+ :param data: dict
+ :param files: The files dict (usually request.FILES where request is
a
+ django.
+ :param files: dict
+ :param name: The name of the widget.
+ :param name: basestring
Some of these s/param/type/
Everything else is much better! I particularly like the doctest-like nature
of some of the docstrings, thank you.
Approved with the change above!
| Raphaël Badin (rvb) wrote : | # |
> This is good that you've checked its presence - however I'd make the error
> message convey more information like:
> "skip_check is a reserved name"
> so the user knows why it's disallowed.
Done.
> Some of these s/param/type/
Done.
Thanks!


Thanks Raphers. This is needs-fixing because there's a few coding style violations and a test antipattern.
35 +class DictCharField( forms.MultiValu eField) :
36 + """A field to edit a dictionary of strings. Each entry in the
37 + dictionary correspond to a sub-field.
s/correspond/ corresponds/
41 + 'skip_check' allows to store an arbitrary dictionary in the field,
s/to store/the storing of/
44 + For instance a DictCharField created with the following list: (label= "Field 1"), (label= "Field 2"),
45 + [
46 + ('field1', forms.CharField
47 + ('field2', forms.CharField
48 + ]
49 + Will produce dictionaries of the form:
50 + {'field1': 'subvalue1', 'field2': 'subvalue2'}
I think this example needs expanding to show how assignment works. It wasn't clear to me how you get from the first bit to the second bit, which suddenly has values.
55 + self.field_dict = OrderedDict( field_items)
Why ordered? Shouldn't that be an option?
60 + if skip_check: dict[SKIP_ CHECK_NAME] = forms.BooleanField(
61 + self.field_
62 + required=False)
I think SKIP_CHECK_NAME needs to be more obscure; it's quite conceivable that someone will have a field named skip_check. Perhaps '__skip_check' ?
76 + def compress(self, data):
77 + if data:
Docstring missing.
91 + - the method is splitted into clean_global and
s/splitted/split/
92 + clean_individua l_fields;
The code has 'clean_sub_fields' ...
114 + value = value if not self.skip_check else value[:-1]
How about:
if self.skip_check:
value = value[:-1]
which is easier to read. We tend to eschew this logic shortcut constraint, it's hideously ugly and error prone.
111 + def clean_global(self, value):
112 + # Remove the value corresponding to the SKIP_CHECK_NAME boolean field
113 + # if required.
Missing docstring.
115 + if not value or isinstance(value, (list, tuple)): EMPTY_VALUES] )
116 + is_empty = (
117 + not value or
118 + not [v for v in value if v not in validators.
*blink*
Firstly, our coding standards say to check explicitly for None or False, and not use "if not value".
Second, this really needs splitting up into something that's more understandable. I don't have any good suggestions because I can't tell what you're trying to do here. The code appears to be doing more than the comment suggests.
127 + raise ValidationError (self.error_ messages[ 'invalid' ])
Is this "invalid" text something that Django needs or can we write a better error text here?
129 + def clean_sub_ fields( self, value):
130 + clean_data = []
Missing docstring.
134 + fields = self.fields if not self.skip_check else self.fields[:-1]
fields = self.fields
if self.skip_check:
fields = self.fields[:-1]
135 + for i, field in enumerate(fields):
Our coding standards say to use useful variable names. Perhaps "index" here.
163 +def get_all_ prefixed_ values( data, name):
164 + result = {}
Missing docstring.
185 + - DictCharWidget has the (optional) ability to skip all the validation
186 + are instead fetch all the values prefixed by 'main_widget_' in the
187 + input data.
s/are instead/and instead/ ?
194 + - 'value_ from_datadict' which fetches the value of the data to be
195 + processed by thi...