I have a ModelForm with the following init method:
def __init__(self, *args, **kwargs):
super(FragebogenForm, self).__init__(*args, **kwargs)
self.fields['birth_date'].widget.attrs.update({'type': 'date'})
This doesn't change the type attribute of the input tag, although it should according to the documentation (ctrl + f -> "Or if the field isn’t declared directly on the form"). If I change it to e.g. .widget.attrs.update({'placeholder': '12.12.1999'}) it works, the new placeholder appears on the page. Only setting the type to date does not work, but why?
I just discovered my own error. I didn't put the initializer or __init__ outside the class metafunction. None of my widgets worked well
###Ensure the init block is outside
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ['avatar','company']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['avatar'].widget.attrs.update({'class': 'form-control'})
The type of the widget is determined by the .input_type attribute, so:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['birth_date'].widget.input_type = 'date'
This is however often not a good idea, since other items of the widget are then not altered correctly. Normally you specify the widget in the Meta class:
class MyForm(forms.ModelForm):
class Meta:
model = Meta
widgets = {
'birth_date': forms.DateInput()
}
But if the widget is not a DateInput, then likely the field birth_date in your model is not a models.DateField, so it might be better to fix this instead of solving it at the widget level.
Related
I am trying to create a form with a multiple-choice field. I have a SchoolClass model and I want to select multiple classes in the form. I can select SchoolClasses in the form but it doesn't show anything as label. I don't know how to pass a name to the choice field.
Here is the form:
class ExamForm(forms.Form):
def __init__(self, class_choices, teacher_choices,teacher, *args, **kwargs):
super(ExamForm, self).__init__(*args, **kwargs)
self.fields['classes'].choices=SchoolClass.objects.filter(school_id=teacher.school_id)
I am getting SchoolClass objects for choices
classes = forms.MultipleChoiceField(choices=(), widget = forms.CheckboxSelectMultiple,
label = "Classes for this exam.")
When I run my project it shows like that :
Blank choice fields
The choices expect an iterable of 2-tuples where the first item is the key, and the second one the rendered value.
You thus can implement this with:
def __init__(self, class_choices, teacher_choices,teacher, *args, **kwargs):
super(ExamForm, self).__init__(*args, **kwargs)
self.fields['classes'].choices = [
(s.pk, str(s))
for s in SchoolClass.objects.filter(school_id=teacher.school_id)
]
It might however be better to use a ModelMultipleChoiceField [Django-doc], since then we can simply use the queryset, so something like:
class MyForm(forms.Form):
classes = forms.ModelMultipleChoiceField(
queryset=SchoolClass.objects.none(),
widget = forms.CheckboxSelectMultiple,
label = 'Classes for this exam.'
)
def __init__(self, class_choices, teacher_choices,teacher, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['classes'].queryset = SchoolClass.objects.filter(school_id=teacher.school_id)
I wonder if that is the right approach. I first call queryset=Reward.objects.all() just to change it right after and filter it. However, I couldn't come up with a better solution. Do you have any thoughts on that?
class ClaimRewardForm(forms.ModelForm):
note = forms.CharField(widget=forms.Textarea)
title = forms.ModelChoiceField(queryset=Reward.objects.all())
# note = forms.DropDown()
class Meta:
model = Reward
fields = ['title']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].queryset = Reward.objects.filter(event=self.initial['event'])
That queryset is never evaluated, because you always replace it on instantiation, so it doesn't really matter what you put there.
One alternative night be to use Reward.objects.none() to indicate that it's never used.
If you should process set of models, try change your approach to FormSets https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
Briefly, FormSets approach seem so: 1. Declare ClaimRewardForm class for single model (Reward in your case) 2. Declare ClaimRewardFormSet for ClaimRewardForm with overriding
class BaseClaimRewardFormSet(BaseModelFormSet):
"""By default, when you create a formset from a model, the formset
will use a queryset that includes all objects in the model"""
def __init__(self, *args, **kwargs):
if 'event' in kwargs.keys():
event = kwargs.pop('event')
else:
event = None
super().__init__(*args, **kwargs)
if event is not None:
self.queryset = Reward.objects.filter(event=event)
else:
self.queryset = Reward.objects.none()
ClaimRewardFormSet = forms.modelformset_factory(Reward, RewardForm,
formset=BaseClaimRewardFormSet)
Lets say I have the following model
class Application(models.Model):
occupation = models.TextField()
and form
class ApplicationForm(forms.ModelForm):
def __init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self.fields['occupation'] = forms.MultipleChoiceField(choices=OCCUPATION_CHOICES, widget=CheckboxSelectMultiple)
class Meta:
model = Application
When I use it with a instance (for example via admin) the choices are not selected.
What am I doing wrong?
Edit: Clarification: When I select some choices, I hit submit it saves the data. They look like ['undergraduate', 'postdoc'] in the database. But they are not checked in the form anymore.
I managed to get it working like this.
import ast
class ApplicationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
kwargs['initial'] = {
'occupation': ast.literal_eval(kwargs.get('instance').occupation or '[]'),
}
super().__init__(*args, **kwargs)
class Meta:
model = Application
widgets = {
'occupation': CheckboxSelectMultiple(choices=OCCUPATION_CHOICES),
}
Thanks go to RodrigoDela who made me realize what I was doing wrong and pointed me in the right direction.
Edit: A TextField cannot store multiple choices. Take a look at this:
Django Model MultipleChoice
Either you use this, or you override your forms, so that these can parse the instance.occupation content into their form.occupation field.
In my ModelForm, I have to override some settings of the fields (e.g. choices, or required state). This requires declaring the entire field again as formfield.
Is there a simple way to access the verbose_name of the model field, so this doesn't have to redefined?
You don't have to redefine the field to change these settings. You can access the field in the form __init__ like below.
class MyForm(forms.ModelForm):
class Meta(object):
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['my_field'].required = True
I'd like to programmatically enable a field that is excluded by default...
model:
class MyModel(models.Model):
name = models.CharField(max_length=100)
an_excluded_field = models.TextField()
my_bool = models.BooleanField(default=False) # this is the field to conditionally enable...
form:
class MyModelForm(ModelForm):
class Meta:
model = EmailTemplate
excludes = ('an_excluded_field', 'my_bool')
I would like to do something like this(or something to that effect...):
form = MyModelForm(enable_my_bool=True)
This is almost like this post(i want the field excluded by default):
How can I exclude a declared field in ModelForm in form's subclass?
1) You could define a second version of the form:
class MyExcludedModelForm(MyModelForm):
class Meta:
excludes = ('my_bool',) # or could use fields in similar manner
2) You could overwrite the form's constructor:
(same as described in the other SO post you reference)
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
if not kwargs.get('enable_my_bool', false):
self.fields.pop('my_bool')
super(MyModelForm, self).__init__(*args, **kwargs) # maybe move up two lines? (see SO comments)
If you overwrite the constructor then you need to pop the value from kwargs before calling the superclass constructor (like mgalgs) mentions:
def __init__(self, *args, **kwargs):
enable_my_bool = kwargs.pop('enable_my_bool', True) # True is the default
super(MyModelForm, self).__init__(*args, **kwargs)
if not enable_my_bool:
self.fields.pop('my_bool')