Вопрос

I'm writing a custom Form widget that overrides the choice method in django.forms.models.ModelChoiceIterator:

class CustomIterator(ModelChoiceIterator):                                                                                                                                                                                                                                       
    def choice(self, obj):                                                       
        return (self.field.prepare_value(obj),                                   
            self.field.label_from_instance(obj), obj) 

As well as the _get_choices method on django.forms.models.ModelChoiceField:

class CustomField(ModelChoiceField):                                                
    def _get_choices(self):                                                      
        if hasattr(self, '_choices'):                                            
            return self._choices                                                 
        return ElfIterator(self)                                                 
    choices = property(_get_choices, ChoiceField._set_choices)

(I followed the example on this blog post)

I need to create a totally custom widget that selects objects based on, say, the value of the data-selected attribute on an HTML element. I've been able to get the custom HTML/styling to be displayed on the form using the instance attributes added by the above:

from django.template.loader import render_to_string

class CustomWidget(Widget):                                                                                                        

    def render(self, name, value, attrs=None):                                                                                                                                                                                                                 
        obj_list = [item[2] for item in self.choices]                                                                                 
        obj_dict = [model_to_dict(obj) for obj in obj_list]                                                                         
        output = render_to_string('myapp/widgets/custom_widget.html',                                                              
            { 'obj_dict': obj_dict })                                                                                                  
        return mark_safe(output) 

Now I'm attempting to override the value_from_datadict method on this same class, however it's not clear to me, even from reading the source code, how I'd be able to return the selected value based on an arbitrary HTML attribute without a Select widget.

Это было полезно?

Решение

I figured it out! I passed the widget name to the template:

class CustomWidget(Widget):

    class Media:
        js = ('http://code.jquery.com/jquery-1.10.2.js', 'custom_widget.js')

    def render(self, name, value, attrs=None):
        instance_list = [item[2] for item in self.choices]
        obj_dict_list = [model_to_dict(obj) for obj in obj_list]
        output = render_to_string('myapp/widgets/custom_widget.html', {
            'widget_name': name,
            'obj_list': obj_dict_list,
        })
        return mark_safe(output)

Created a hidden input field in the template, specifying the widget name for the name attr:

<!-- myapp/widgets/custom_widget.html -->
<input type="hidden" name="{{ widget_name }}" value="some-initial-value" />

{% for obj in obj_list %}
  {{ do_something_with_obj }}
{% endfor %}

<script>
  // make {{ widget_name }} accessible to the included js on the widget
  var widgetName = "{{ widget_name }}";
</script>

Using JavaScript/jQuery I changed the value attr of the hidden input:

// custom_widget.js

(function() {
  var hiddenInput = $("input[name='" + widgetName + "']");
  if (someCondition) {
    hiddenInput.val(newVal);
  }
)();

Finally, in value_from_data, get the value of the hidden input:

class CustomWidget(Widget):

    ...

    def value_from_datadict(self, data, files, name):
        return data.get(name, None)

Другие советы

At the top of the template, place the hidden div which has {{widget.name}}, ex.:

<div id='widget-name-indicator' style="display: none">{{ widget.name }</div>

Now you can select it in your appended js:

var widgetName = $('#widget-name-indicator').text();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top