Better ArrayField admin widget?

前端 未结 4 1911
抹茶落季
抹茶落季 2020-12-30 22:42

Is there any way to make ArrayField\'s admin widget allow adding and deleting objects? It seems that by default, it is instead displayed just a text field, and uses comma se

相关标签:
4条回答
  • 2020-12-30 22:49

    I take no credit for this (original source), but if you are using PostgreSQL as the database and are happy to use the Postgres-specific ArrayField implementation there is an even easier option: subclass ArrayField on the model and override the default admin widget. A basic implementation follows (tested in Django 1.9, 1.10, 1.11, 2.0, 2.1 & 2.2):

    models.py

    from django import forms
    from django.db import models
    from django.contrib.postgres.fields import ArrayField
    
    
    class ChoiceArrayField(ArrayField):
        """
        A field that allows us to store an array of choices.
        Uses Django's Postgres ArrayField
        and a MultipleChoiceField for its formfield.
        """
    
        def formfield(self, **kwargs):
            defaults = {
                'form_class': forms.MultipleChoiceField,
                'choices': self.base_field.choices,
            }
            defaults.update(kwargs)
            # Skip our parent's formfield implementation completely as we don't
            # care for it.
            # pylint:disable=bad-super-call
            return super(ArrayField, self).formfield(**defaults)
    
    
    FUNCTION_CHOICES = (
        ('0', 'Planning'),
        ('1', 'Operation'),
        ('2', 'Reporting'),
    )
    
    
    class FunctionModel(models.Model):
        name = models.CharField(max_length=128, unique=True)
        function = ChoiceArrayField(
            base_field=models.CharField(max_length=256, choices=FUNCTION_CHOICES),
            default=list)
    
    0 讨论(0)
  • 2020-12-30 22:50

    This is another version using the Django Admin M2M filter_horizontal widget, instead of the standard HTML select multiple.

    We use Django forms only in the Admin site, and this works for us, but the admin widget FilteredSelectMultiple probably will break if used outside the Admin. An alternative would be overriding the ModelAdmin.get_form to instantiate the proper form class and widget for the array field. The ModelAdmin.formfields_overrides is not enough because you need to instantiate the widget setting the positional arguments as shown in the code snippet.

    from django.contrib.admin.widgets import FilteredSelectMultiple
    from django.contrib.postgres.fields import ArrayField
    from django.forms import MultipleChoiceField
    
    
    class ChoiceArrayField(ArrayField):
        """
        A choices ArrayField that uses the `horizontal_filter` style of an M2M in the Admin
    
        Usage::
    
            class MyModel(models.Model):
                tags = ChoiceArrayField(
                    models.TextField(choices=TAG_CHOICES),
                    verbose_name="Tags",
                    help_text="Some tags help",
                    blank=True,
                    default=list,
                )
        """
    
        def formfield(self, **kwargs):
            widget = FilteredSelectMultiple(self.verbose_name, False)
            defaults = {
                "form_class": MultipleChoiceField,
                "widget": widget,
                "choices": self.base_field.choices,
            }
            defaults.update(kwargs)
            # Skip our parent's formfield implementation completely as we don't
            # care for it.
            return super(ArrayField, self).formfield(**defaults)
    
    0 讨论(0)
  • 2020-12-30 22:52

    django-select2 offers a way to render the ArrayField using Select2. In their documentation, the example is for ArrayField:

    http://django-select2.readthedocs.io/en/latest/django_select2.html#django_select2.forms.Select2TagWidget

    To render the already selected values:

    class ArrayFieldWidget(Select2TagWidget):
    
        def render_options(self, *args, **kwargs):
            try:
                selected_choices, = args
            except ValueError:  # Signature contained `choices` prior to Django 1.10
                choices, selected_choices = args
            output = ['<option></option>' if not self.is_required and not self.allow_multiple_selected else '']
            selected_choices = {force_text(v) for v in selected_choices.split(',')}
            choices = {(v, v) for v in selected_choices}
            for option_value, option_label in choices:
                output.append(self.render_option(selected_choices, option_value, option_label))
            return '\n'.join(output)
    
        def value_from_datadict(self, data, files, name):
            values = super().value_from_datadict(data, files, name)
            return ",".join(values)
    

    To add the widget to your form:

    class MyForm(ModelForm):
    
        class Meta:
            fields = ['my_array_field']
            widgets = {
                'my_array_field': ArrayFieldWidget
            }
    
    0 讨论(0)
  • 2020-12-30 23:09

    For OP, or anyone out there looking, between these helpful bits you should be good to go:

    • 1. Extending SelectMultiple or CheckboxSelectMultiple widget to parse arrayfield and
    • 2. Creating or extending admin form to display the arrayfield using the widget above
    0 讨论(0)
提交回复
热议问题