Django admin custom ArrayField widget

前端 未结 2 1224
春和景丽
春和景丽 2021-02-05 18:17

The current admin widget for ArrayField is one field, with comma as delimiter, like this (text list):

This isn\'t ideal because I would have longer texts (even

2条回答
  •  鱼传尺愫
    2021-02-05 19:06

    Unfortunately Django does not ship with a convenient widget for ArrayFields yet. I'd suggest you to create your own. Here is an example for Django>=1.11:

    class DynamicArrayWidget(forms.TextInput):
    
        template_name = 'myapp/forms/widgets/dynamic_array.html'
    
        def get_context(self, name, value, attrs):
            value = value or ['']
            context = super().get_context(name, value, attrs)
            final_attrs = context['widget']['attrs']
            id_ = context['widget']['attrs'].get('id')
    
            subwidgets = []
            for index, item in enumerate(context['widget']['value']):
                widget_attrs = final_attrs.copy()
                if id_:
                    widget_attrs['id'] = '%s_%s' % (id_, index)
                widget = forms.TextInput()
                widget.is_required = self.is_required
                subwidgets.append(widget.get_context(name, item, widget_attrs)['widget'])
    
            context['widget']['subwidgets'] = subwidgets
            return context
    
        def value_from_datadict(self, data, files, name):
            try:
                getter = data.getlist
            except AttributeError:
                getter = data.get
            return getter(name)
    
        def format_value(self, value):
            return value or []
    

    Here is the widget template:

    {% spaceless %}
    
      {% for widget in widget.subwidgets %}
    • {% include widget.template_name %}
    • {% endfor %}
    {% endspaceless %}

    A few javascript (using jQuery for convenience):

    $('.dynamic-array-widget').each(function() {
        $(this).find('.add-array-item').click((function($last) {
            return function() {
                var $new = $last.clone()
                var id_parts = $new.find('input').attr('id').split('_');
                var id = id_parts.slice(0, -1).join('_') + '_' + String(parseInt(id_parts.slice(-1)[0]) + 1)
                $new.find('input').attr('id', id);
                $new.find('input').prop('value', '');
                $new.insertAfter($last);
            };
        })($(this).find('.array-item').last()));
    });
    

    And you would also have to create your own form field:

    from itertools import chain
    
    from django import forms
    from django.contrib.postgres.utils import prefix_validation_error
    
    class DynamicArrayField(forms.Field):
    
        default_error_messages = {
            'item_invalid': 'Item %(nth)s in the array did not validate: ',
        }
    
        def __init__(self, base_field, **kwargs):
            self.base_field = base_field
            self.max_length = kwargs.pop('max_length', None)
            kwargs.setdefault('widget', DynamicArrayWidget)
            super().__init__(**kwargs)
    
        def clean(self, value):
            cleaned_data = []
            errors = []
            value = filter(None, value)
            for index, item in enumerate(value):
                try:
                    cleaned_data.append(self.base_field.clean(item))
                except forms.ValidationError as error:
                    errors.append(prefix_validation_error(
                        error, self.error_messages['item_invalid'],
                        code='item_invalid', params={'nth': index},
                    ))
            if errors:
                raise forms.ValidationError(list(chain.from_iterable(errors)))
            if cleaned_data and self.required:
                raise forms.ValidationError(self.error_messages['required'])
            return cleaned_data
    

    Finally, set it explicitly on your forms:

    class MyModelForm(forms.ModelForm):
    
        class Meta:
            model = MyModel
            fields = ['foo', 'bar', 'the_array_field']
            field_classes = {
                'the_array_field': DynamicArrayField,
            }
    

提交回复
热议问题