Widget fill values ​​in two fields

I know that if I need a custom "selector" for a field in django-admin, I need to create my own widget. But what if the widget needs to create two values, for example, the X and Y coordinates, how can I fill them in two different fields from the model?

+6
django django-models django-admin django-widget
source share
4 answers

Jannis Leidel released the widget a long time ago. django-coordinatesfield As far as I remember, he took the coordinates from the map and gave it one field, and some javascript cut it into 2 coordinates for 2 fields.

Combined with a custom form, it should work pretty well

+3
source share

You can look at the implementation of the date and time field, which displays 2 fields in the admin panel.

Top to bottom

admin uses

class AdminSplitDateTime(forms.SplitDateTimeWidget): """ A SplitDateTime Widget that has some admin-specific styling. """ def __init__(self, attrs=None): widgets = [AdminDateWidget, AdminTimeWidget] # Note that we're calling MultiWidget, not SplitDateTimeWidget, because # we want to define widgets. forms.MultiWidget.__init__(self, widgets, attrs) def format_output(self, rendered_widgets): return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \ (_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) 

which in turn uses SplitDateTimeWidget :

 class SplitDateTimeWidget(MultiWidget): """ A Widget that splits datetime input into two <input type="text"> boxes. """ date_format = DateInput.format time_format = TimeInput.format def __init__(self, attrs=None, date_format=None, time_format=None): if date_format: self.date_format = date_format if time_format: self.time_format = time_format widgets = (DateInput(attrs=attrs, format=self.date_format), TimeInput(attrs=attrs, format=self.time_format)) super(SplitDateTimeWidget, self).__init__(widgets, attrs) def decompress(self, value): if value: return [value.date(), value.time().replace(microsecond=0)] return [None, None] 

Which, in turn, extends the MultiWidget defined in django.forms.widgets , which you must also expand. It has many useful methods that you can override.

 class MultiWidget(Widget): """ A widget that is composed of multiple widgets. Its render() method is different than other widgets', because it has to figure out how to split a single value for display in multiple widgets. The ``value`` argument can be one of two things: * A list. * A normal value (eg, a string) that has been "compressed" from a list of values. In the second case -- ie, if the value is NOT a list -- render() will first "decompress" the value into a list before rendering it. It does so by calling the decompress() method, which MultiWidget subclasses must implement. This method takes a single "compressed" value and returns a list. When render() does its HTML rendering, each value in the list is rendered with the corresponding widget -- the first value is rendered in the first widget, the second value is rendered in the second widget, etc. Subclasses may implement format_output(), which takes the list of rendered widgets and returns a string of HTML that formats them any way you'd like. You'll probably want to use this class with MultiValueField. """ def __init__(self, widgets, attrs=None): self.widgets = [isinstance(w, type) and w() or w for w in widgets] super(MultiWidget, self).__init__(attrs) def render(self, name, value, attrs=None): # value is a list of values, each corresponding to a widget # in self.widgets. if not isinstance(value, list): value = self.decompress(value) output = [] final_attrs = self.build_attrs(attrs) id_ = final_attrs.get('id', None) for i, widget in enumerate(self.widgets): try: widget_value = value[i] except IndexError: widget_value = None if id_: final_attrs = dict(final_attrs, id='%s_%s' % (id_, i)) output.append(widget.render(name + '_%s' % i, widget_value, final_attrs)) return mark_safe(self.format_output(output)) def id_for_label(self, id_): # See the comment for RadioSelect.id_for_label() if id_: id_ += '_0' return id_ id_for_label = classmethod(id_for_label) def value_from_datadict(self, data, files, name): return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] def _has_changed(self, initial, data): if initial is None: initial = [u'' for x in range(0, len(data))] else: if not isinstance(initial, list): initial = self.decompress(initial) for widget, initial, data in zip(self.widgets, initial, data): if widget._has_changed(initial, data): return True return False def format_output(self, rendered_widgets): """ Given a list of rendered widgets (as strings), returns a Unicode string representing the HTML for the whole lot. This hook allows you to format the HTML design of the widgets, if needed. """ return u''.join(rendered_widgets) def decompress(self, value): """ Returns a list of decompressed values for the given compressed value. The given value can be assumed to be valid, but not necessarily non-empty. """ raise NotImplementedError('Subclasses must implement this method.') def _get_media(self): "Media for a multiwidget is the combination of all media of the subwidgets" media = Media() for w in self.widgets: media = media + w.media return media media = property(_get_media) def __deepcopy__(self, memo): obj = super(MultiWidget, self).__deepcopy__(memo) obj.widgets = copy.deepcopy(self.widgets) return obj 
+7
source share

Here is an example of ModelForm: http://www.adamalton.co.uk/blog/displaying-django-genericforeignkey-as-single-form-field/

Add an extra form field to the form (for your single widget) and exclude two "real" fields, then override the init and save methods to execute additional logic that makes it work.

Also, the same question: How to get a separate widget to set 2 fields in Django?

+1
source share

You can force the widget to display two (hidden) html inputs, whose names refer to the fields of the model that need to be filled, and assign them the necessary values ​​using javascript!

0
source share

All Articles