Accessing ModelChoiceField queryset from custom widget - django

I have a form that has a ModelChoiceField. I have created a custom widget for dealing with ModelChoiceFields, the widget extends forms.TextInput, so:
class SelectWidget(forms.TextInput):
def __init__(self, attrs):
super(SelectWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
value = "" if value is None else value
# html stuff here
return html_stuff
and on the form:
class Form(forms.ModelForm)
address = forms.ModelChoiceField(queryset=models.Address.objects.all(),
widget=SelectWidget(attrs={}))
I understand that when I submit the form, it will validate what ever was entered in the SelectWidget text input against the queryset provided to the ModelChoiceField which is what I want.
My question is: in the SelectWidget where I am overriding the render method, how can I access whatever queryset was passed to the ModelChoiceField in order to check it against the "value" attribute (if any) of the widget?

You can access self.choices in your custom select widget which is a ModelChoiceIterator object

Related

How can I get a dynamically assigned widget to display HTML in Django?

I'm trying to get my custom widget to display without the HTML being escaped by Django. Here's my widget:
class MyInput(Widget):
def __init__(self, obj, attrs=None):
super(MyInput, self).__init__(attrs)
def render(self, name, value, attrs=None):
return mark_safe(u'<img src="{url}">').format(url=self.url)
It gets instantiated via a form factory:
def MyFormFactory():
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
desired_fields = ['field1', 'field2',]
for f in desired_fields:
self.fields[f].widget = MyInput(self.instance)
return MyForm
This gets called in my Django Admin class:
class MyAdminClass(admin.ModelAdmin):
form = MyFormFactory()
Everything 'works' except for the fact that my widget has its HTML escaped. This does not happen if I use the widget via direct form instantiation (using a regular form class and form field widget assignment), but I need to be able to set it up via the factory like this. How can I force Django to allow the HTML? allow_tags doesn't seem to apply in this case, and I've already used mark_safe. What am I still missing?
Try to change
return mark_safe(u'<img src="{url}">').format(url=self.url)
to
return mark_safe(u'<img src="{url}">'.format(url=self.url))
The first line returns a string, the latter returns a SafeBytes instance, and Django treats them differently.

Sending url parameter to generic view which sends it to an element in a ModelForm

I'm sending an optional value through a url to a generic view which uses a Modelform like so:
class myClass(generic.CreateView):
template_name = 'template.html'
form_class = myForm
...
The modelform is something like:
class myForm(ModelForm):
institution = CustomModelChoiceField(...)
class Meta:
model = myModel
...
I need to set the default selected value in the dropdown field to the value passed in the url.
The value being passed is the 'id' of the model.
How would I pass the value to myForm?
How would myForm set it as the 'selected' value?
I'm open to other ways to doing this.
The bigger picture:
I have a form1(model 1) where I popup another form2(model2)(foreign key to model 1). On success of form2, form1 dropdown sets to the new foreigh key.
I've thought about doing it through AJAX, but for future features, it would be a good idea to pass the value in the url.
Again, open to other ways.
Thanks.
If I understand correctly, you can override a get_form_kwargs method to pass id into the form constructor (see this)
# views.py
def get_form_kwargs(self):
kwargs = super(CreateView, self).get_form_kwargs()
kwargs.update({'id': self.kwargs.get('id')})
return kwargs
# forms.py
def __init__(self, *args, **kwargs):
id = kwargs.pop('id', None)
super(Form, self).__init__(*args, **kwargs)
self.fields['id'].initial = id

Read-only form field formatting

I want to display a field as read only in a ModelAdmin form, so I added it to the readonly_fields attribute.
However, since the field contains a currency, stored as an integer, I want to apply some nice formatting it. I've created a custom ModelForm for my ModelAdmin, trying to apply the formatting in the overridden __init__ method.
The problem is, I cannot find the value. The field is not present in the self.fields attribute.
Does anyone know where the values for the readonly_fields are kept, or is there a better/different approach?
Just do something like:
class MyAdmin(admin.ModelAdmin):
readonly_fields = ('foo',)
def foo(self, obj):
return '${0}'.format(obj.amount)
An alternate approach, which works for all types of forms is to create a widget to represent a read only field. Here is one that I wrote for my own use. You can change the <span %s>%s</span> to suit your own requirements.
from django import forms
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode
class ReadOnlyWidget(forms.TextInput):
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<span%s />%s</span>' % (flatatt(final_attrs),value))
Once you have that added, simply do this:
class MyAdmin(admin.ModelAdmin):
foo = models.TextField(widget=ReadOnlyWidget(attrs={'class':'read-only'}
initial="$50")
Then in your CSS, do some styling for a read-only class, or you can adjust the attributes accordingly.
Another, more appropriate solution, works in Django 2.1.2:
ModelAdmin renders read-only fields via special wrapper AdminReadonlyField (django/contrib/admin/helpers.py) if we look at contents method, we can see
the code
if getattr(widget, 'read_only', False):
return widget.render(field, value)
It means that if a widget has read_only attribute with True value
then the read-only field will invoke widget's render method.
Hence, you can use render method to format your value.
For example:
class CustomDateInput(widgets.DateInput):
read_only = True
def _render(self, template_name, context, renderer=None):
return 'you value'
class CustomForm(forms.ModelForm):
some_field = forms.DateTimeField(widget=CustomDateInput())
#admin.register(SomeModel)
class SomeModelAdmin(admin.ModelAdmin):
form = CustomForm
readonly_fields = ['some_field']

Django: Custom widget that can pre-fill from POST/GET data

Updated with my final solution, below.
I wrote a custom Django form widget to create a range query. It renders two input fields to define the min and max values for a query.
With well-crafted forms and widgets, fields can be filled with the values from the last query like so:
form = my_form(request.GET)
However, I cannot figure out a way to fill the values of those fields in my custom widget. Here is the widget code:
class MinMax(Widget):
input_type = None # Subclasses must define this.
def _format_value(self, value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<input type="text" name="min-%s" /> to
<input type="text" name="max-%s" />' % (name, name) )
Probably because of the custom input field names, the values are not accessible. Is there a way to route them, or a way to rewrite the widget to include this useful functionality? One non-widget solution I can think of is some simple jquery logic, but that's less than optimal.
Here's the code I ended up using:
class MinMax(MultiWidget):
def __init__(self, attrs=None):
""" pass all these parameters to their respective widget constructors..."""
widgets = (forms.TextInput(attrs=attrs), forms.TextInput(attrs=attrs) )
super(MinMax, self).__init__(widgets, attrs)
def decompress(self, value):
return value or ''
def value_from_datadict(self, data, files, name):
value = ''
for key, value in data.items():
value += value
def format_output(self, rendered_widgets):
"""
Given a list of rendered widgets (as strings), it inserts stuff
between them.
Returns a Unicode string representing the HTML for the whole lot.
"""
rendered_widgets.insert(-1, ' to ')
return u''.join(rendered_widgets)
Note that these fields are returned as fieldname_0, fieldname_1 (and so on if you add additional widgets).

Dynamic choices for Django SelectMultiple Widget

I'm building a form (not modelForm) where i'd like to use the SelectMultiple Widget to display choices based on a query done during the init of the form.
I can think of a few way to do this but I am not exactly clear on the right way to do it. I see different options.
I get the "choices" I should pass to the widget in the form init but I am not sure how I should pass them.
class NavigatorExportForm(forms.Form):
def __init__(self,user, app_id, *args,**kwargs):
super (NavigatorExportForm,self ).__init__(*args,**kwargs) # populates the form
language_choices = Navigator.admin_objects.get(id=app_id).languages.all().values_list('language', flat=True)
languages = forms.CharField(max_length=2, widget=forms.SelectMultiple(choices=???language_choices))
Why not use a ModelMultipleChoiceField instead?
You could do simply this :
class NavigatorExportForm(forms.Form):
languages = forms.ModelMultipleChoiceField(queryset=Language.objects.all())
def __init__(self, app_id, *args, **kwargs):
super(NavigatorExportForm, self).__init__(*args, **kwargs)
# Dynamically refine the queryset for the field
self.fields['languages'].queryset = Navigator.admin_objects.get(id=app_id).languages.all()
This way you don't only restrict the choices available on the widget, but also on the field (that gives you data validation).
With this method, the displayed string in the widget would be the result of the __unicode__ method on a Language object. If it's not what you want, you could write the following custom field, as documented in ModelChoiceField reference :
class LanguageMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.language_code # for example, depending on your model
and use this class instead of ModelMultipleChoiceField in your form.
def __init__(self,user, app_id, *args,**kwargs):
super (NavigatorExportForm,self ).__init__(*args,**kwargs)
self.fields['languages'].widget.choices = Navigator.admin_objects.get(id=app_id).languages.all().values_list('language', flat=True)
that seems to do the trick, but even by not specifying a max_length, the widget only display the first letter of the choices...