I've got the following django crispy form:
class ConsultForm(forms.ModelForm):
class Meta:
model = Consults # Your User model
fields = [ 'TEMPLATE','EMAIL', 'DATE']
labels = {
'EMAIL' : 'Your Email',
'DATE' : 'Todays date',
# 'captcha': "Enter captcha"
}
helper = FormHelper()
helper.form_method = 'POST'
helper.form_action = "/contact/"
helper.form_id = 'form' # SET THIS OR BOOTSTRAP JS AND VAL.JS WILL NOT WORK
helper.add_input(Submit('Submit', 'Submit', css_class='btn-primary'))
helper.layout = Layout(
Field('TEMPLATE', type="hidden"),
Field('DATE', type="hidden"))
I want to pass a value with the hidden field TEMPLATE. I've read https://django-crispy-forms.readthedocs.io/en/latest/api_helpers.html , but can't see how to do this. How can I get this done?
You can set Form field initial values like this:
class ConsultForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial['TEMPLATE'] = 'my_initial_value'
You can also change the value of the field at other places in your code like:
form = ConsultForm(instance=instance)
form.initial['TEMPLATE'] = 'new_value'
With formhelper (with crispy Universal Layout Objects like Field) you set attributes as you already did, like:
Field('TEMPLATE', id="template", value="something" template="my-template.html")
If that's what you were asking for.
Or if the above does not work easy then there is a layout object called Hidden in crispy. You can create hidden input fields with that:
Hidden('name', 'value')
You use it as Hidden('TEMPLATE', 'mysomethingvalue')
Like:
Button('name', 'value')
To make it fully clear:
helper.layout = Layout(
Hidden('TEMPLATE', 'myvalue'),
Hidden('DATE', 'anydate'))
Related
I developed a Django application in which i have a form with some fields. Depending on the input additional fields are displayed are hidden. Now everything worked quit fine in Django 3.2.14 since the update in Django 4.0.6 it didn't worked anymore.
I first build a form, where if a "field_rule_display" exists the field widget is set as "HiddenInput".
class AnalysisForm(forms.Form):
def __init__(self, analysis_form_template: AnalysisFormTemplate, disable_required: bool, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout()
self.helper.add_input(Submit("submit", _("Evaluate"), css_class="btn-primary btn-lg"))
analysis_field_queryset = analysis_form_template.analysis_fields
analysis_form_url = reverse("analysis_form", args=(analysis_form_template.id,))
for field in analysis_field_queryset.all():
htmx_dictionary = _htmx_dictionary(analysis_form_url, field)
self.fields[field.name_for_formula] = _get_field_by_type(
field, htmx_dictionary, analysis_form_template, self.data
)
self.fields[field.name_for_formula].empty_values = empty_values()
self.helper.layout.fields.append(
Div(Field(field.name_for_formula), css_class=AnalysisFieldKind(field.kind).name)
)
if field.field_rule_display is not None and disable_required is False:
self.fields[field.name_for_formula].widget = forms.HiddenInput()
self.fields[field.name_for_formula].widget.attrs["disabled"] = True
if disable_required:
self.fields[field.name_for_formula].required = False
After the user enters a specific input into the form, htmx will send a request and i rebuild the form with the new fields. And here the problem starts, even if i update my field in the "self.fields" Django does not render the update and my form field is still hidden.
if field.field_rule_display is not None:
evaluated_result_display = self._evaluated_formula(
field,
analysis_form_template,
field.field_rule_display,
field.field_rule_display.formula,
cleaned_data,
)
if evaluated_result_display:
field_type = _get_field_by_type(
field, htmx_dictionary, analysis_form_template, cleaned_data
)
self.fields[field.name_for_formula] = field_type
self.fields[field.name_for_formula].initial = cleaned_data[field.name_for_formula]
Here the second field should be displayed, but only my border is shown as a result of changing the crispy layout helper.
if (
field.field_rule_display is None or (field.field_rule_display is not None and evaluated_result_display)
) and field.field_rule_highlight is not None:
evaluated_result_highlight = self._evaluated_formula(
field.name_for_formula,
analysis_form_template,
field.field_rule_highlight,
field.field_rule_highlight.formula,
cleaned_data,
)
if evaluated_result_highlight:
field_layout = Div(
Field(field.name_for_formula),
css_class=f"{AnalysisFieldKind(field.kind).name} border border-primary mb-2 p-2",
)
self.fields[field.name_for_formula].empty_values = empty_values()
field_layout_list.append(field_layout)
self.helper.layout.fields = field_layout_list
I would appreciate some help, why this does not work anymore in Django 4 but in Django 3 it worked without a problem.
After some debugging i found a possible solution to my problem.
It seems that Django caches my form fields and their widgets in "_bound_fields_cache". Now there my fields widget is still set to "HiddenInput", even after updating the widget to a "TextInput".
So i tried updating the field in "_bound_fields_cache" and to my suprise it worked.
if field.field_rule_display is not None:
evaluated_result_display = self._evaluated_formula(
field,
analysis_form_template,
field.field_rule_display,
field.field_rule_display.formula,
cleaned_data,
)
if evaluated_result_display:
field_type = _get_field_by_type(
field, htmx_dictionary, analysis_form_template, cleaned_data
)
self.fields[field.name_for_formula] = field_type
self.fields[field.name_for_formula].initial = cleaned_data[field.name_for_formula]
if field.name_for_formula in self._bound_fields_cache:
self._bound_fields_cache[field.name_for_formula].field = field_type
But i am not quite satisfied with this kind of solution. I can't really point my finger why Django doesn't change the "_bound_fields_cache" after updating my form fields. Changing the "_bound_fields_cache" seems for me like an ugly hack... Is there perhaps a better solution?
I have a form:
class SideEffectForm(ModelForm):
class Meta:
model = SideEffect
fields = ['se_name']
def __init__(self, *args, p, **kwargs):
super().__init__(*args, **kwargs)
if p == 'antipsychotic':
self.fields['se_name'].choices = [
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("dystonia", "Dystonia"),
]
That is based on this model:
class SideEffect(TimeStampedModel):
SE_CHOICES = [
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("anticholinergic_effects", "Anticholinergic Side Effects")
]
se_name = models.CharField("",max_length=200, choices=SE_CHOICES, blank=False)
concern = models.IntegerField("",default=50)
case = models.ForeignKey(Case, on_delete=models.CASCADE)
And this is the view:
class CaseView(LoginRequiredMixin, TemplateView):
model = Case
template_name = "se_balance/se_balance.html"
def get(self, *args, **kwargs):
p = self.request.GET.get("p", None)
sideeffect_formset = SideeffectFormSet(queryset=SideEffect.objects.none(),
form_kwargs={'p': p})
return self.render_to_response(
{ "page_title": p.capitalize(),
"sideeffect_formset": sideeffect_formset,
"sideeffect_formsethelper": SideEffectFormSetSetHelper,
}
)
def post(self, *args, **kwargs):
p = self.request.GET.get("p", None)
case_instance = Case(pharm_class_name=p)
sideeffect_formset = SideeffectFormSet(data=self.request.POST, form_kwargs={'p': p})
case_instance.save()
if sideeffect_formset.is_valid():
print('seform valid')
sideeffect_name = sideeffect_formset.save(commit=False)
for sideeffect in sideeffect_name:
sideeffect.case = case_instance
sideeffect.save()
return redirect(
reverse(
"results",
kwargs = {"case_id": case_instance.case_id} )
)
As it stands the form displays the first option. I would however like to have a place holder e.g. 'Please select a side effect'. I could do this by having it as one of the options (e.g. (None, 'This is the placeholder prompt')) but would prefer not to as then would need to implement measures to stop the placeholder being saved as a valid user entry. I have tried a range of suggestions on the site but none have been suitable.
You stated:
I could do this by having it as one of the options (e.g. (None, 'This is the placeholder prompt')) but would prefer not to as then would need to implement measures to stop the placeholder being saved as a valid user entry.
No, that's not true, when we set (None, 'This is the placeholder prompt') there's no need to write the custom logic for not saving selected option in the database as valid user entry.
An Excerpt from the Django-doc about this issue.
Unless blank=False is set on the field along with a default then a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense - such as on a CharField.
Solution:
You can simply do this (None, 'please select the side effect'), but as stated while using CharField you should use ('', 'please select the side effect') this instead and also you've already set blank=False.
Try this:
class SideEffectForm(ModelForm):
class Meta:
model = SideEffect
fields = ['se_name']
def __init__(self, *args, p, **kwargs):
super().__init__(*args, **kwargs)
if p == 'antipsychotic':
self.fields['se_name'].choices = [
('','Please select a side effect'),
("insomnia_and_agitation", "Insomnida and Agitation"),
("akathisia", "Akathisia"),
("dystonia", "Dystonia"),
]
After, doing this, see source code through Ctrl+U and you'll see below code of html:
<select ...>
<option value="" selected>please select the side effect</option>
<option value="insomnia_and_agitation">Insomnida and Agitation</option>
<option value="akathisia">Akathisia</option>
<option value="dystonia">Dystonia</option>
</select>
And selected value doesn't save in database, so if you submit form with it, you'll see the error of required.
I have a form with a search box that uses jQuery to fill a multipleChoiceField. I need to use a custom multipleChoiceField so I can control the validation and only check if the choice exists, not if it was one of the original choices as a modelMultipleChoiceField with a queryset would. However, the custom multipleChoiceField renders on the page as empty until you enter something in the search box to fill it with choices via jQuery. I would like it to render with a few choices to begin with instead.
class ArticleMultipleChoiceField(forms.MultipleChoiceField):
def __init__(self, *args, **kwargs):
super(ArticleMultipleChoiceField, self).__init__(*args, **kwargs)
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
self.choices = Article.objects.filter(id__in=include_articles).order_by('-sub_date')
In this form, I get the error "Article object is not iterable". I have also tried changing that self.choices to self.data, self.queryset, and self.initial, and in all those 3 cases, I keep getting an empty multiple choice field instead.
How can I use a queryset to provide the initial set of choices here?
Here is the form it is used in:
class StorylineAddArticleForm(forms.Form):
articleSearchBox = forms.CharField(label="Search to narrow list below:")
include_articles = [article.id for article in Article.objects.order_by('-sub_date')[:5]]
articles = ArticleMultipleChoiceField()
def __init__(self, *args, **kwargs):
super(StorylineAddArticleForm, self).__init__(*args, **kwargs)
self.fields['articleSearchBox'].required = False
self.helper = FormHelper(self)
self.helper.layout = Layout(
Field('articleSearchBox'),
Field('articles'),
ButtonHolder(
Submit('submit', 'Add', css_class='button white')
)
)
Also, this is being rendered by Crispy Forms.
choices doesn't accept a QuerySet as an argument, it needs a list or tuple of two-tuples with acceptable values. See the documentation on choices here: https://docs.djangoproject.com/en/2.0/ref/models/fields/#field-choices .
In this case you need to turn your Article queryset into a list or tuple of the above format.
Due to a BD design where depending on a value, the data is stored in different cells, I have to add form fields dynamically. I was thinking on this:
class EditFlatForm(BaseModelForm):
on_sale = forms.BooleanField(required=False)
on_rent = forms.BooleanField(required=False)
class Meta:
model = Flat
fields = ('title', 'flat_category', 'description')
...
def __init__(self, *args, **kwargs):
super(EditFlatForm, self).__init__(*args,**kwargs)
flat_properties = FlatProperty.objects.all()
for p in flat_properties:
if p.type_value == 1:
# Text
setattr(self, p.title, forms.CharField(label=p.human_title, required=False))
elif p.type_value == 2:
# Number
setattr(self, p.title, forms.IntegerField(label=p.human_title, required=False))
else:
# Boolean
setattr(self, p.title, forms.BooleanField(label=p.human_title, required=False))
But the fields don't get added, what am I missing?
I recommend creating the form on the fly using type. So, you'll need to create a function that will generate a list of all the fields you want to have in your form and then use them to generate the form, something like this :
def get_form_class():
flat_properties = FlatProperty.objects.all()
form_fields = {}
for p in flat_properties:
if p.type_value == 1:
form_fields['field_{0}'.format(p.id)] = django.forms.CharField(...)
elif p.type_value == 2:
form_fields['field_{0}'.format(p.id)] = django.forms.IntegerField(...)
else:
form_fields['field_{0}'.format(p.id)] = django.forms.BooleanField(...)
# ok now form_fields has all the fields for our form
return type('DynamicForm', (django.forms.Form,), form_fields )
Now you can use get_form_class wherever you want to use your form, for instance
form_class = get_form_class()
form = form_class(request.GET)
if form.is_valid() # etc
For more info, you can check my post on creating dynamic forms with django:
http://spapas.github.io/2013/12/24/django-dynamic-forms/
Update to address OP's comment (But then how to gain advantage of all the things ModelForm provides?): You can inherit your dynamic form from ModelForm. Or, even better, you can create a class that is descendant of ModelForm and defines all the required methods and attributes (like clean, __init__, Meta etc). Then just inherit from that class by changing the type call to type('DynamicForm', (CustomForm,), form_fields ) !
Assuming that p.title is a string variable, then this should work:
if p.type_value == 1:
# Text
self.fields[p.title] = forms.CharField(label=p.human_title, required=False))
I have a field in one of my models like the following:
PAYROLL_CHOICES = (
('C1', 'Choice1'),
('C2', 'Choice2')
etc.....
)
payrollProvider = models.CharField(max_length=2, choices=PAYROLL_CHOICES)
When I create a model form for this field, Django correctly generates an HTML select box, but includes a default blank value of "---------".
I would like to know how to change this default value to some other text, such as "please choose value".
I believe I should be able to set this in my model form's init via the following, as documented in this answer and several others:
self.fields['payrollProvider'].empty_label = "please choose value"
However, this isn't working for me. When I include that line in my form's init, "--------" still shows up as the initial choice in the select box. I'm pasting the relevant forms.py below, but it seems that others have also been unable to access / modify empty_label. At this link, the questioner describes a way to delete the default empty_label value (which I was able to do successfully via his method) but what I really want to do is to modify the empty_label that is displayed.
Any ideas?
Here's the code for the form in forms.py, with the empty_label code that isn't successful at changing the default "----------":
class PayrollCredentialForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields['payrollUsername'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollPassword'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollProvider'].widget.attrs.update({'class' : 'yp-signup'})
self.fields['payrollUsername'].widget.attrs.update({'placeholder' : ' Payroll Username'})
self.fields['payrollPassword'].widget.attrs.update({'placeholder' : ' Payroll Password'})
self.fields['payrollProvider'].empty_label = "please choose value"
class Meta:
model = Company
fields = ('payrollProvider', 'payrollUsername', 'payrollPassword')
widgets = {
'payrollPassword': forms.PasswordInput(),
}
dokkaebi, that won't work properly. You'll receive the following select code:
<select name="payrollProvider" id="id_payrollProvider">
<option value="" selected="selected">---------</option>
<option value="" selected="selected">please choose value</option>
<option value="C1">Choice1</option>
<option value="C2">Choice2</option>
</select>
The only relatively convenient way that came to my mind is to do something like this in the form:
class PayrollCredentialForm(forms.ModelForm):
class Meta:
model = Company
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields["payrollProvider"].choices = [("", "please choose value"),] + list(self.fields["payrollProvider"].choices)[1:]
Actually, now (as of Django 1.8 and higher) override of an empty_label works:
class PayrollCredentialForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PayrollCredentialForm, self).__init__(*args, **kwargs)
self.fields['payrollProvider'].empty_label = 'Please, choose value'
Also, if you working with Django Admin, there is an option to set empty value for a list view:
class PayrollCredentialAdmin(admin.ModelAdmin):
list_display = ('payrollProvider_value', )
def payrollProvider_value(self, instance):
return instance.payrollProvider
payrollProvider_value.empty_value_display = 'Empty value'
What if field should be readonly?
There is a catch if field modified in such way should be readonly.
If overridden form field will be specified in readonly_fields attribute inside PayrollCredentialAdmin class, it would result in KeyError exception in PayrollCredentialForm (because readonly field won't be included in form's self.fields). To handle that, it's required to override formfield_for_dbfield instead of using readonly_fields:
def formfield_for_dbfield(self, db_field, **kwargs):
field = super(PayrollCredentialAdmin, self).formfield_for_dbfield(
db_field, **kwargs
)
db_fieldname = canonical_fieldname(db_field)
if db_fieldname == 'payrollProvider':
field.widget = forms.Select(attrs={
'readonly': True, 'disabled': 'disabled',
})
return field
Might be useful.
Update for Django 1.11:
Comments below brought assumption that such override is no longer valid for newer version of Django.
The problem is that you are trying to specify something that is not available for the type of Select field.
The empty_label option is for forms.ModelChoiceField, which happens to use a Select widget, but is not the same kind of field as your CharField that you are providing options for.
https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield
You can see this also in a previous question here: https://stackoverflow.com/a/740011/1406860
You could try and override the html of the modelform to add the first option as "please choose value". Alternatively, you could use a template filter to do the same thing. Lastly, you could and ("", "please choose value") to PAYROLL_CHOICES, and if you don't want it to be submitted without a payrollProvider just set blank=False for the field in the model.
JD
In your forms.py file,
This would definitely work.. Try this...
class Meta:
model = StaffDetails
fields =['photo','email', 'first_name','school','department', 'middle_name','last_name','gender', 'is_active']
def __init__(self, *args, **kwargs):
super(StaffDetailsForm, self).__init__(*args, **kwargs)
self.fields['Field_name'].empty_label = 'Please Select'
It worked for me.. just replace the field names with yours...
Only ModelChoiceField (generated for ForeignKey fields) supports the empty_label parameter, and in that case it's tricky to get at as those fields are usually generated by django.forms.models.ModelFormMetaclass within a call to django.forms.models.modelform_factory.
ModelFormMetaclass uses the empty_label param to add another choice to the list, with empty_label as the display and '' as its value.
The simplest way to do what you want is just to add an empty choice to your choices list:
PAYROLL_CHOICES = (
('', 'please choose value'),
('C1', 'Choice1'),
('C2', 'Choice2'),
etc.....
)
Another simple way worked for me is:
country = forms.ModelChoiceField(queryset=Country.objects.filter(), empty_label='--Select--')
However, my Django version is 2.2.7
Just add a tuple to your model.field.choices with a value of None:
payrollProvider = models.CharField(max_length=2, choices=PAYROLL_CHOICES)
PAYROLL_CHOICES = (
(None, 'please choose'),
('C1', 'Choice1'),
('C2', 'Choice2')
etc.....
)
From the docs (v4.0):
Unless blank=False is set on the field along with a default then a label containing "---------" will be rendered with the select box. To override this behavior, add a tuple to choices containing None; e.g. (None, 'Your String For Display'). Alternatively, you can use an empty string instead of None where this makes sense - such as on a CharField.
Adapted from Javed answer. Since I have tons of fields in my form I just want to replace all labels in the html by placeholders so for select tags I use their field label.
class PacienteForm(forms.ModelForm):
class Meta:
model=Paciente
fields=('__all__')
def __init__(self, *args, **kwargs):
super(PacienteForm, self).__init__(*args, **kwargs)
for f in self.fields:
if hasattr(self.fields[f], 'choices'):
choices=self.fields[f].choices
if type(choices) == list:
choices[0]=('',self.fields[f].label)
self.fields[f].choices=choices
Add Empty String with "Please Select" to choices as shown below:
class DateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Months(models.TextChoices):
EMPTY_LABEL = '', 'Please Select' # Here
JANUARY = 'JAN', 'January'
FEBRUARY = 'FEB', 'February'
MARCH = 'MAR', 'March'
self.fields['month'].choices = Months.choices