Save Django ModelForm with CheckboxSelectMultiple with no boxes checked - django

A Django Form using a CheckboxSelectMultiple widget posts back nothing for the field using the CheckboxSelectMultiple widget if no boxes are checked.
class AlarmForm(forms.ModelForm):
class Meta:
model = models.Alarm
fields = ['phone_contacts', 'disabled']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
user = self.instance.user
self.fields["phone_contacts"].widget = forms.widgets.CheckboxSelectMultiple()
self.fields['phone_contacts'].queryset = user.phonecontact_set
If the user viewing the form checks no checkboxes, the idea is that the model's phonecontact_set field ( a ManyToManyField) should be cleared. Instead, if no boxes are check, that field isn't saved at all. This is apparently because unchecked check boxes include no values in the form POST in the web browser.
Still, it seems there must be a way to make Django do this without completely re-implementing the save functionality, but I can't seem to find the Django way to do this..

Related

Django admin: don't send all options for a field?

One of my Django admin "edit object" pages started loading very slowly because of a ForeignKey on another object there that has a lot of instances. Is there a way I could tell Django to render the field, but not send any options, because I'm going to pull them via AJAX based on a choice in another SelectBox?
You can set the queryset of that ModelChoiceField to empty in your ModelForm.
class MyAdminForm(forms.ModelForm):
def __init__(self):
self.fields['MY_MODEL_CHOIE_FIELD'].queryset = RelatedModel.objects.empty()
class Meta:
model = MyModel
fields = [...]
I think you can try raw_id_fields
By default, Django’s admin uses a select-box interface () for fields that are ForeignKey. Sometimes you don’t want to incur the overhead of having to select all the related instances to display in the drop-down.
raw_id_fields is a list of fields you would like to change into an Input widget for either a ForeignKey or ManyToManyField
Or you need to create a custom admin form
MY_CHOICES = (
('', '---------'),
)
class MyAdminForm(forms.ModelForm):
my_field = forms.ChoiceField(choices=MY_CHOICES)
class Meta:
model = MyModel
fields = [...]
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
Neither of the other answers worked for me, so I read Django's internals and tried on my own:
class EmptySelectWidget(Select):
"""
A class that behaves like Select from django.forms.widgets, but doesn't
display any options other than the empty and selected ones. The remaining
ones can be pulled via AJAX in order to perform chaining and save
bandwidth and time on page generation.
To use it, specify the widget as described here in "Overriding the
default fields":
https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/
This class is related to the following StackOverflow problem:
> One of my Django admin "edit object" pages started loading very slowly
> because of a ForeignKey on another object there that has a lot of
> instances. Is there a way I could tell Django to render the field, but
> not send any options, because I'm going to pull them via AJAX based on
> a choice in another SelectBox?
Source: http://stackoverflow.com/q/37327422/1091116
"""
def render_options(self, *args, **kwargs):
# copy the choices so that we don't risk affecting validation by
# references (I hadn't checked if this works without this trick)
choices_copy = self.choices
self.choices = [('', '---------'), ]
ret = super(EmptySelectWidget, self).render_options(*args, **kwargs)
self.choices = choices_copy
return ret

Django ModelForm- How to make a form generated uneditable

I am learning django form and want to know how to make a model form generated display only.
models.py
class Person(models.Model):
first_name = models.CharField(max_length=40, null=True)
last_name = models.CharField(max_length=40, null=True)
#more fields
forms.py
class PersonForm(ModelForm):
class Meta:
model = Person
To generate a form with some existing data in the database:
person=Person.objects.get(id=someid)
person_form = PersonForm(instance = person)
All the fields in the form are editable in the page. However, I just want to display the data.
After some searching in StackOverflow I found a similar solution how to show a django ModelForm field as uneditable , which teaches how to set individual field uneidtable.
But I want to make the whole form uneditable. Is there any better way to do so instead of setting all the fields as uneditable one by one?
Thank you very much for your help.
Updates: I find the flowing code helps make the form uneditable, but still not sure whether this is the correct way to do it.
for field in person_form.fields:
person_form.fields[field].widget.attrs['readonly'] = True
Thank you for giving your advice.
There is no attribute called editable or something similar on the form which can act on all the fields. So, you can't do this at form level.
Also, there is no such attribute on Field class used by django forms as well, so it wouldn't be possible to set such attribute and make the field read only. So, you will have to operate on on the fields of the form in __init__ of your form.
class PersonForm(ModelForm):
class Meta:
model = Person
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
for name, field in self.fields.iteritems():
field.widget.attrs['readonly'] = 'true'
In case, you only want to make some fields uneditable, change the __init__.
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
uneditable_fields = ['first_name', 'last_name']
for field in uneditable_fields:
self.fields[field].widget.attrs['readonly'] = 'true'
Another solution perhaps, do not have to do any processing, just display like this..
<table border='1'>
{% for field in form%}
<tr>
<td>{{field.label}}</td>
<td>{{field.value}}</td>
</tr>
{% endfor%}
</table>
I know, old question, but since I had the same question this week it might help other people.
This technique only works if you want the whole form to be readonly. It overrides any posted data (see def clean(self)) and sets the widget attributes to readonly.
Note: Setting the widget attributes to readonly does not prevent altering the model object instance.
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
if self.is_readonly():
for k,f in self.fields.iteritems():
f.widget.attrs['readonly'] = True
def clean(self):
if self.is_readonly():
return {}
return super(CompanyQuestionUpdateForm, self).clean()
def is_readonly(self, question):
if your_condition:
return True
return False
class Meta:
model = MyModel
It is possible to implement field widget to render bound ModelForm field values wrapped into div or td, sample implementation is there
https://github.com/Dmitri-Sintsov/django-jinja-knockout/blob/master/django_jinja_knockout/widgets.py
# Read-only widget for existing models.
class DisplayText(Widget):
Then a form metaclass can be implemented which will set field widget to DisplayText for all ModelForm fields automatically like that:
https://github.com/Dmitri-Sintsov/djk-sample/search?utf8=%E2%9C%93&q=DisplayModelMetaclass
class ClubDisplayForm(BootstrapModelForm, metaclass=DisplayModelMetaclass):
class Meta(ClubForm.Meta):
widgets = {
'category': DisplayText()
}
Feel free to use or to develop your own versions of widget / form metaclass.
There was discussion about read-only ModelForms at django bug ticket:
https://code.djangoproject.com/ticket/17031
closed as "Froms are for processing data, not rendering it."
But I believe that is mistake for these reasons:
ModelForms are not just processing data, they also map forms to models. Read-only mapping is the subset of mapping.
There are inline formsets and having read-only inline formsets is even more convenient, it leaves a lot of burden from rendering relations manually.
Class-based views can share common templates to display and to edit ModelForms. Thus read-only display ModelForms increase DRY (one of the key Django principles).

How to override field value display in Django admin change form

How can I override the value that is displayed for a field in the Django admin? The field contains XML and when viewing it in the admin I want to pretty-format it for easy readability. I know how to do reformatting on read and write of the field itself, but this is not what I want to do. I want the XML stored with whitespace stripped and I only want to reformat it when it is viewed in the admin change form.
How can I control the value displayed in the textarea of the admin change form for this field?
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.initial['some_field'] = some_encoding_method(self.instance.some_field)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
...
Where, some_encoding_method would be something you've set up to determine the spacing/indentation or some other 3rd-party functionality you're borrowing on. However, if you write your own method, it would be better to put it on the model, itself, and then call it through the instance:
class MyModel(models.Model):
...
def encode_some_field(self):
# do something with self.some_field
return encoded_some_field
Then:
self.instance.encode_some_field()

Building Select Options in Django

I'd like to create a form that when viewed, the user's favorite fruits are queried from the database and displayed as follows:
<select size="4">
<option selected>Apples</option>
<option>Bananas</option>
<option>Oranges</option>
<option>Watermelon</option>
</select>
The view that uses the form will:
Get the user object.
Query the database for the user's favorite fruits. (Each is a separate object of the Fruit model.)
Load the form with the fruit choices collected in (2).
I was considering using the ChoiceField, but it looks like you cannot load the list of choices into the form dynamically, at least in a straightforward manner. Am I better off skipping the form and generating the code directly at the template? Or is there a way to load the form's ChoiceField with the user items at the view?
Also, are there any general rules of thumb that dictate where it's easier to build a form using the django form fields vs generating the form code at the template?
I found the answer in this stack overflow topic. The trick is to override the form __init__() so that it accepts a new keyword argument, which in this case is the user.
views.py snippet
context = RequestContext(request)
user = User.objects.get(username=context['user'])
form = forms.FruitForm(user=user)
forms.py snippet
from django import forms
class FruitForm(forms.Form):
fruits = forms.ModelChoiceField(queryset=Fruit.objects.all())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(FruitForm, self).__init__(*args, **kwargs)
if user:
self.fields['fruits'].queryset = Fruit.objects.filter(user=user)
It's not that difficult. You can accomplish this easily using a modelform.
See: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
One of the strengths of the Django framework is it's form handling and validation methods. So if possible, it always better for you to use Django forms or model forms.
Create a Form or a ModelForm that will be used in you view. The differnce between the two classes is the the ModelForm is built to closely resemble a database model defined in your models.py file where a Form can have custom attributes.
from django.forms import ModelForm
class FruitForm(ModelForm):
class Meta:
model = User
fields = ('favorite-fruits', )

Python/Django BooleanField model with RadioSelect form default to empty

I'm using a Django ModelForm where my model contains a BooleanField and the form widget associated with that BooleanField is a RadioSelect widget. I'd like the the RadioSelect widget that renders to have no options selected so the user has to explicitly make a choice, but the form validation to fail if they make no selection. Is there a way to do this?
models.py
myboolean = models.BooleanField(choices=YES_NO)
forms.py
class myModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(myModelForm, self).__init__(*args, **kwargs)
self.fields['myboolean'].widget = forms.RadioSelect(choices=YES_NO)
Your code actually does what you need. It renders the radio buttons with no options selected and generate the error message if nothing is selected.
A small note about your form code. I would do it like this:
class myModelForm(forms.ModelForm):
myboolean = forms.BooleanField(widget=forms.RadioSelect(choices=YES_NO))
class Meta:
model = MyModel
Unfortunately, this is less of a Django issue than an HTML question. The HTML specification (RFC1866) says:
At all times, exactly one of the radio buttons in a set is checked. If none of the <INPUT> elements of a set of radio buttons specifies `CHECKED', then the user agent must check the first radio button of the set initially.
However, browsers have historically ignored this and implemented radio buttons in different ways.
HTML also makes this difficult because the "checked" attribute of the <INPUT> tag doesn't take a parameter, so you can't use a customized Django widget that sets this attribute to False or No.
A possible workaround is to put in a little Javascript that runs as part of the document's onLoad event that finds all the radio buttons on the page and sets the 'checked' attribute to false (using JQuery, for example).
see this:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#a-full-example
I creates custom field with default widget.
Cut of my models.py:
class Order(models.Model):
...
# transport = models.CharField(choices=transport.choices,max_length=25,null=True)
transport = SelectField(choices=transport.choices,max_length=25,null=True)
...
Field definition:
from django.db import models
from django.forms import widgets
class SelectField(models.CharField):
def formfield(self, **kwargs):
if self._choices:
defaults = {'widget': widgets.RadioSelect}
defaults.update(kwargs)
return super(SelectField, self).formfield(**defaults)