How to choose the value and label from Django ModelChoiceField queryset - python

I was trying to create a django form and one of my field contain a ModelChoiceField
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
When I try the code above what it return as an html ouput is
<option value="1">Person object</option>
on my Person Model I have the fields "id, fname, lname, is_active" . Is it possible to specify that my dropdown option will use "id" as the value and "lname" as the label? The expected html
should be
<option value="1">My Last Name</option>
Thanks in advance!

You can just add a call to label_from_instance in the init of Form ie
by adding something like
class TestForm(ModelForm):
def __init__(self, *args, **kwargs):
super(TestForm, self).__init__(*args, **kwargs)
self.fields['field_name'].label_from_instance = self.label_from_instance
#staticmethod
def label_from_instance(obj):
return "My Field name %s" % obj.name

From the Django docs:
https://docs.djangoproject.com/en/dev/ref/forms/fields/#django.forms.ModelChoiceField
The __unicode__ (__str__ on Python 3) method of the model will be
called to generate string representations of the objects for use in
the field’s choices; to provide customized representations, subclass
ModelChoiceField and override label_from_instance. This method will
receive a model object, and should return a string suitable for
representing it. For example:
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return "My Object #%i" % obj.id
So, you can do that, or override __str__ on your model class to return the last name.

In your Person model add:
def __unicode__(self):
return u'{0}'.format(self.lname)
If you are using Python 3, then define __str__ instead of __unicode__.
def __str__(self):
return u'{0}'.format(self.lname)

You can overwrite label_from_instance method of the ModelChoiceField instance to your custom method. You can do it inside the __init__ method of the form
class FooForm(forms.Form):
person = forms.ModelChoiceField(queryset=Person.objects.filter(is_active=True).order_by('id'), required=False)
age = forms.IntegerField(min_value=18, max_value=99, required=False)
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['person'].label_from_instance = lambda instance: instance.name

to chose/change the value you can use "to_field_name" options ,
and to change the label of option you can overwrite the "label_from_instance" function inside ModelChoiceField class,
and here is a simple example ,
forms.py:
from django import forms
from .models import Group
from django.forms import ModelChoiceField
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return f"My Object {obj.group_name}"
class MyPureDjangoForm(forms.Form):
group = MyModelChoiceField(queryset=Group.objects.all(),
widget=forms.Select(attrs={
'class': 'form-control'
}),
to_field_name='id',
)
for more information's kindly visit the following URL :
https://docs.djangoproject.com/en/3.2/ref/forms/fields/#django.forms.ModelChoiceField
i hope this helpful .

Similar to Thomas's answer, I recommend setting the label_from_instance method reference when creating the field. However, as I almost always want the model to have the same value for select field drop downs, I just define a get_select_field_label() method on the Model itself. This is especially useful when I want different select labels and __str__() representations. Heres a minimal example:
from django.db import models
from django import forms
class Book(models.Model):
author = models.ForeignKey("Author", on_delete=models.CASCADE)
title = models.TextField()
def __str__(self):
return f"{self.title}, by {self.author.full_name}"
def get_select_field_label(self):
return f"{self.title}"
class BookForm(forms.Form):
title = forms.ModelChoiceField(queryset=Book.objects.all(), widget=forms.Select())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label_from_instance = Book.get_select_field_label

Related

Pass a argument to Django meta class

I can alternatively create different types of form, but that's tedious.
So is it possible to pass the type to the form,then show the form accordingly?
This code shows NameError: name 'review_type' is not defined
class Contest1_for_review(ModelForm, review_type):
class Meta:
model = Contest1
decision = review_type + '_decision'
comment = review_type +'comment'
fields = [
decision,
comment,
]
Is it possible to pass a argument to meta class, like this?
Form is a class and when its rendered in the HTML, its rendering an instance of the form class. So when passing a value to that instance, you can use its __init__ method. For example:
class Contest1_for_review(ModelForm):
def __init__(self, *args, **kwargs):
review_type = kwargs.pop('review_type') # <-- getting the value from keyword arguments
super().__init__(*args, **kwargs)
self.fields[f'{review_type}_decision'] = forms.CharField()
self.fields[f'{review_type}_comment'] = forms.CharField()
class Meta:
model = Contest1
fields = "__all__"
Also, you need to send the value of review_type from view to form. Like this in function based view:
form = Contest1_for_review(review_type="my_value")
Or use get_form_kwargs to send the value from a Class based view. FYI: you don't need to change anything in Meta class.
Update:
From discussion in comments, OP should use forms.Form instead of ModelForm as using model form requires fields /exclude value in Meta class.

customize select in django admin

I have a model, one field of it is a ForeignKey, so i see select in django admin, is it possiable to customize labels of this select?
class Model(models.Model):
name = models.CharField()
def __unicode__(self):
return self.name
class Part(models.Model):
name = models.CharField()
parent = model.ForeignKey(Model)
def __unicode__(self):
return self.name
def name_with_model(self):
return self.name + ' ' + parent.name
class SmallPart(models.Model):
name = models.CharField()
parent = model.ForeignKey(Part)
when I add new SmallPart I see select tag with names of parts, I need to see name_with_model
If you mean the field label:
using code from:
Django Admin - Overriding the widget of a custom form field
# forms.py
from django import forms
from django.contrib import admin
class ProductAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields['tags'].label = 'Custom Label'
Then, in your ModelAdmin object, you specify the form:
from django.contrib import admin
from models import Product
from forms import ProductAdminForm
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
If you mean the labels in the select drop down:
Override the widget like in the answer above.
edit:
The default form field for a fk field is a model choice field. From the docs
The unicode method of the model
will be called to generate string
representations of the objects for use
in the field's choices; to provide
customized representations, subclass
ModelChoiceField and override
label_from_instance. This method will
receive a model object, and should
return a string suitable for
representing it. For example:
class MyModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.name_with_model()
and then:
class SmallPartAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SmallPartAdminForm, self).__init__(*args, **kwargs)
self.fields['parent'] = MyModelChoiceField(queryset=Part.objects.all())

How to set True as default value for BooleanField on Django?

I am using BooleanField in Django.
By default, the checkbox generated by it is unchecked state. I want the state to be checked by default. How do I do that?
If you're just using a vanilla form (not a ModelForm), you can set a Field initial value ( https://docs.djangoproject.com/en/2.2/ref/forms/fields/#django.forms.Field.initial ) like
class MyForm(forms.Form):
my_field = forms.BooleanField(initial=True)
If you're using a ModelForm, you can set a default value on the model field ( https://docs.djangoproject.com/en/2.2/ref/models/fields/#default ), which will apply to the resulting ModelForm, like
class MyModel(models.Model):
my_field = models.BooleanField(default=True)
Finally, if you want to dynamically choose at runtime whether or not your field will be selected by default, you can use the initial parameter to the form when you initialize it:
form = MyForm(initial={'my_field':True})
from django.db import models
class Foo(models.Model):
any_field = models.BooleanField(default=True)
I am using django==1.11. The answer get the most vote is actually wrong. Checking the document from django, it says:
initial -- A value to use in this Field's initial display. This value
is not used as a fallback if data isn't given.
And if you dig into the code of form validation process, you will find that, for each fields, form will call it's widget's value_from_datadict to get actual value, so this is the place where we can inject default value.
To do this for BooleanField, we can inherit from CheckboxInput, override default value_from_datadict and init function.
class CheckboxInput(forms.CheckboxInput):
def __init__(self, default=False, *args, **kwargs):
super(CheckboxInput, self).__init__(*args, **kwargs)
self.default = default
def value_from_datadict(self, data, files, name):
if name not in data:
return self.default
return super(CheckboxInput, self).value_from_datadict(data, files, name)
Then use this widget when creating BooleanField.
class ExampleForm(forms.Form):
bool_field = forms.BooleanField(widget=CheckboxInput(default=True), required=False)
In Django 3.0 the default value of a BooleanField in model.py is set like this:
class model_name(models.Model):
example_name = models.BooleanField(default=False)
I found the cleanest way of doing it is this.
Tested on Django 3.1.5
class MyForm(forms.Form):
my_boolean = forms.BooleanField(required=False, initial=True)
I found the answer here
Another way to check the default state in BooleanField is:
active = forms.BooleanField(
widget=forms.CheckboxInput(
attrs={
'checked': True
}
)
)
Both initial and default properties were not working for me, if that's your case try this:
class MyForm(forms.ModelForm):
validated = forms.BooleanField()
class Meta:
model = MyModel
fields = '__all__'
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['validated'].widget.attrs['checked'] = True
I tried to change inital of BooleanField:
def __init__(self, *args, **kwargs):
super(UserConfirmForm,self).__init__(*args, **kwargs)
self.fields['bool_field'].initial = True
but it didn't work.
My solve:
def __init__(self, *args, **kwargs):
kwargs['initial'] = {'bool_field': True}
super(UserConfirmForm,self).__init__(*args, **kwargs)
It works like:
UserConfirmForm(initial={'bool_field':True})
but we can't call form in Generic editing views.
I think this is a great alternative to a regular call form object.

How to show hidden autofield in django formset

A Django autofield when displayed using a formset is hidden by default. What would be the best way to show it?
At the moment, the model is declared as,
class MyModel:
locid = models.AutoField(primary_key=True)
...
When this is rendered using Django formsets,
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields = ('locid', 'name')
it shows up on the page as,
<input id="id_form-0-locid" type="hidden" value="707" name="form-0-locid"/>
Thanks.
Edit
I create the formset like this -
LocFormSet = modelformset_factory(MyModel)
pformset = LocFormSet(request.POST, request.FILES, queryset=MyModel.objects.order_by('name'))
Second Edit
Looks like I'm not using the custom form class I defined there, so the question needs slight modification..
How would I create a formset from a custom form (which will show a hidden field), as well as use a custom queryset?
At the moment, I can either inherit from a BaseModelFormSet class and use a custom query set, or I can use the ModelForm class to add a custom field to a form. Is there a way to do both with a formset?
Third Edit
I'm now using,
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
locid = forms.IntegerField(min_value = 1, required=True)
self.fields['locid'].widget.attrs["type"] = 'visible'
self.queryset = MyModel.objects.order_by('name')
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet()
But this still doesn't
Show locid
Use the custom query that was specified.
Try changing the default field type:
from django import forms
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
class Meta:
model = MyModel
fields = ('locid', 'name')
EDIT: Tested and works...
As you say, you are not using the custom form you have defined. This is because you aren't passing it in anywhere, so Django can't know about it.
The solution is simple - just pass the custom form class into modelformset_factory:
LocFormSet = modelformset_factory(MyModel, form=MyModelForm)
Edit in response to update 3:
Firstly, you have the redefinition for locid in the wrong place - it needs to be at the class level, not inside the __init__.
Secondly, putting the queryset inside the form won't do anything at all - forms don't know about querysets. You should go back to what you were doing before, passing it in as a parameter when you instantiate the formset. (Alternatively, you could define a custom formset, but that seems like overkill.)
class MyModelForm(ModelForm):
locid = forms.IntegerField(min_value=1, required=True)
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields['locid'].widget.attrs["type"] = 'visible'
class Meta:
model = MyModel
fields = ('locid', 'name')
LocFormSet = modelformset_factory(MyModel, form = MyModelForm)
pformset = LocFormSet(request.POST, request.FILES,
queryset=MyModel.objects.order_by('name')))
Okay, none of the approaches above worked for me. I solved this issue from the template side, finally.
There is a ticket filed (http://code.djangoproject.com/ticket/10427), which adds a "value" option to a template variable for a form. For instance, it allows,
{{form.locid.value}}
to be shown. This is available as a patch, which can be installed in the SVN version of django using "patch -p0 file.patch"
Remember, the {{form.locid.value}} variable will be used in conjunction with the invisible form - otherwise, the submit and save operations for the formset will crash.
This is Not the same as {{form.locid.data}} - as is explained in the ticket referred to above.
The reason that the autofield is hidden, is that both BaseModelFormSet and BaseInlineFormSet override that field in add_field. The way to fix it is to create your own formset and override add_field without calling super. Also you don't have to explicitly define the primary key.
you have to pass the formset to modelformset_factory:
LocFormSet = modelformset_factory(MyModel,
formset=VisiblePrimaryKeyFormSet)
This is in the formset class:
from django.forms.models import BaseInlineFormSet, BaseModelFormSet, IntegerField
from django.forms.formsets import BaseFormSet
class VisiblePrimaryKeyFormset(BaseModelFormSet):
def add_fields(self, form, index):
self._pk_field = pk = self.model._meta.pk
if form.is_bound:
pk_value = form.instance.pk
else:
try:
pk_value = self.get_queryset()[index].pk
except IndexError:
pk_value = None
form.fields[self._pk_field.name] = IntegerField( initial=pk_value,
required=True) #or any other field you would like to display the pk in
BaseFormSet.add_fields(self, form, index) # call baseformset which does not modify your primary key field

Dynamically update ModelForm's Meta class

I am hoping to dynamically update a ModelForm's inline Meta class from my view. Although this code seems to update the exclude list in the Meta class, the output from as_p(), as_ul(), etc does not reflect the updated Meta exclude.
I assume then that the html is generated when the ModelForm is created not when the as_*() is called. Is there a way to force the update of the HTML?
Is this even the best way to do it? I just assumed this should work.
Thoughts?
from django.forms import ModelForm
from testprogram.online_bookings.models import Passenger
class PassengerInfoForm(ModelForm):
def set_form_excludes(self, exclude_list):
self.Meta.exclude = excludes_list
class Meta:
model = Passenger
exclude = []
The Meta class is used to dynamically construct the form definition - so by the time you've created the ModelForm instance, the fields not in the exclude have already been added as the new object's attributes.
The normal way to do it would be to just have multiple class definitions for each possible exclude list. But if you want the form itself to be dynamic, you'll have to create a class definition on the fly. Something like:
def get_form(exclude_list):
class MyForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
return MyForm
form_class = get_form(('field1', 'field2'))
form = form_class()
UPDATE: I just revisited this post and thought I'd post a little more idiomatic way to handle a dynamic class:
def PassengerForm(exclude_list, *args, **kwargs):
class MyPassengerForm(ModelForm):
class Meta:
model = Passenger
exclude = exclude_list
def __init__(self):
super(MyPassengerForm, self).__init__(*args, **kwargs)
return MyPassengerForm()
form = PassengerForm(('field1', 'field2'))
Another way:
class PassengerInfoForm(ModelForm):
def __init__(self, *args, **kwargs):
exclude_list=kwargs.pop('exclude_list', '')
super(PassengerInfoForm, self).__init__(*args, **kwargs)
for field in exclude_list:
del self.fields[field]
class Meta:
model = Passenger
form = PassengerInfoForm(exclude_list=['field1', 'field2'])
Similar approach, somewhat different goal (generic ModelForm for arbitrary models):
from django.contrib.admin.widgets import AdminDateWidget
from django.forms import ModelForm
from django.db import models
def ModelFormFactory(some_model, *args, **kwargs):
"""
Create a ModelForm for some_model
"""
widdict = {}
# set some widgets for special fields
for field in some_model._meta.local_fields:
if type(field) is models.DateField:
widdict[field.name] = AdminDateWidget()
class MyModelForm(ModelForm): # I use my personal BaseModelForm as parent
class Meta:
model = some_model
widgets = widdict
return MyModelForm(*args, **kwargs)
Use modelform_factory (doc):
from django.forms.models import modelform_factory
from testprogram.online_bookings.models import Passenger
exclude = ('field1', 'field2')
CustomForm = modelform_factory(model=Passenger, exclude=exclude) # generates ModelForm dynamically
custom_form = CustomForm(data=request.POST, ...) # form instance

Categories

Resources