Better form fields for django.contrib.postgres.fields

157 views
Skip to first unread message

Paul Martin

unread,
May 23, 2016, 8:56:43 AM5/23/16
to Django developers (Contributions to Django itself)
Hi,

This is my first time contributing to Django. It's a lot of different features in one to improve how form fields are produced for ArrayField, HStoreField and JSON FIeld.

I opened a ticket here with more details and a screenshot of the admin for a sample model.


I guess the next step is to push my code as a new branch?

Asif Saifuddin

unread,
May 23, 2016, 11:00:09 AM5/23/16
to Django developers (Contributions to Django itself)
Hi,

The first step is the ticket need to be accepted and then push the change as pull request on master branch.

Thanks

Tim Graham

unread,
May 23, 2016, 11:42:32 AM5/23/16
to Django developers (Contributions to Django itself)
A couple suggestions:

It'd be better to propose each enhancement separately (and on this mailing list, rather than in Trac) rather than combining them into one ticket.

It's not obvious to me that all the proposed features which mainly involve forms should be declared at the model level. I guess seeing the code might help clarify exactly what's going on.

Paul Martin

unread,
May 24, 2016, 7:32:15 AM5/24/16
to Django developers (Contributions to Django itself)
OK thanks for the replies. The first enhancement would be a change in how choices are handled for the ArrayField. With this change,
choices would be based on the base field. If choices keyword argument is given, a multiple choice field is used and the array value will be composed
of these choices. I think this would make the choices keyword argument more useful for ArrayField as currently you would have to choose between a list of arrays. 

The changes would need to be made to the validate and formfield methods:

def validate(self, value, model_instance):
"""
Validates value and throws ValidationError. Subclasses should override
this to provide validation logic.
Choices are handled differently than other fields. Choices must be based on the BaseField
and thus multiple choices can be given.
"""
if not self.editable:
# Skip validation for non-editable fields.
return

if self.choices and value not in self.empty_values:
if isinstance(value, (list, tuple)):
option_keys = [x[0] for x in self.choices]
if all(x in option_keys for x in value):
return
else:
for option_key, option_value in self.choices:
if isinstance(option_value, (list, tuple)):
# This is an optgroup, so look inside the group for
# options.
for optgroup_key, optgroup_value in option_value:
if value == optgroup_key:
return
elif value == option_key:
return
raise exceptions.ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)

if value is None and not self.null:
raise exceptions.ValidationError(self.error_messages['null'], code='null')

if not self.blank and value in self.empty_values:
raise exceptions.ValidationError(self.error_messages['blank'], code='blank')

for index, part in enumerate(value):
try:
self.base_field.validate(part, model_instance)
except exceptions.ValidationError as error:
raise prefix_validation_error(
error,
prefix=self.error_messages['item_invalid'],
code='item_invalid',
params={'nth': index},
)
if isinstance(self.base_field, ArrayField):
if len({len(i) for i in value}) > 1:
raise exceptions.ValidationError(
self.error_messages['nested_array_mismatch'],
code='nested_array_mismatch',
)

def formfield(self, **kwargs):
defaults = {
'form_class': SimpleArrayField,
'base_field': self.base_field.formfield(),
            'choices_form_class': TypedMultipleChoiceField,
            
'max_length': self.size,
}
    if self.choices:
defaults['coerce'] = self.base_field.to_python
    defaults.update(kwargs)
return super(ArrayField, self).formfield(**defaults)

Sample Usage:

class User(model.Model):
Roles = ArrayField(base_field=IntegerField(), choices=((0, 'Can Read'), (1,' Can Edit', ), (2, 'Can Create'), (3, 'Can_Delete')))
Reply all
Reply to author
Forward
0 new messages