I am using a modelformset_factory in Django to have a user fill out an unknown number of fields.
I have made the fields required but in the HTML rendering Django does not add required to the HTML element, looking around online this seems to be a common issue but I have not seen any valid answers that apply for what I want and I feel like this should be simple.
How do I make Django add the required tag to appropriate HTML elements for a Formset?
class ItemForm(forms.ModelForm):
class Media:
js = (formset_js_path,)
class Meta:
model = PurchaseOrderItems
fields = ['name', 'vendor', 'quantity', 'price', 'description']
labels = {
'name': 'Item',
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# self.fields['description'] .widget.attrs['required'] = 'required'
self.empty_permitted = False
self.fields['description'] = forms.CharField(
required=False,
label='Description',
)
def clean(self):
"""
:return:
"""
cleaned_data = super().clean()
# print(cleaned_data)
ItemFormSet = modelformset_factory(
PurchaseOrderItems,
form=ItemForm,
extra=0,
min_num=1,
validate_min=True,
can_delete=True,
)
Here is the HTML rendered for the name field, no required yet in my model it certainly is, so if this form is submitted I get DB errors because of empty values:
<input type="text" name="form-0-name" maxlength="150" class="form-control" id="id_form-0-name">
According to the release notes:
Required form fields now have the required HTML attribute. Set the new
Form.use_required_attribute attribute to False to disable it. The
required attribute isn’t included on forms of formsets because the
browser validation may not be correct when adding and deleting
formsets.
So if you want to disable, from your view you must submit the form in this way. This will affect all your fields.
form = ItemForm(use_required_attribute=False)
However, if you only want to affect some you must do the previous step and also in your form add this
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'required': ''})
self.fields['vendor'].widget.attrs.update({'required': ''})
self.fields['quantity'].widget.attrs.update({'required': ''})
self.fields['price'].widget.attrs.update({'required': ''})
self.fields['description'].widget.attrs.update({'required': 'False'})
On the other hand I see that you are not using widgets in your form you should also use them to make it work.
widgets = {
'name': forms.TextInput(),
'vendor': forms.TextInput(),
'quantity': forms.TextInput(),
'price': forms.TextInput(),
'description': forms.TextInput(),
}
I put all the fields as TextInput, but look for what is indicated according to the type of data here Field types.
Related
I would have thought this would be pretty simple but I have plugged away at this for some time and I can't figure it out.
I have a multiple choice checkbox in a model form which is in a formset. All I want to do is remove the labels from the checkboxes. I can easily remove the label from the field but I can't figure out how to remove the labels from the checkboxes.
I am trying to convert the display of the form to a grid.
From this:
To this:
Here's my form code:
class ResourceEditAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.portfolio = kwargs.pop('portfolio')
super(ResourceEditAddForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_show_labels = False
self.fields["portfolio"].initial = self.portfolio
self.fields["portfolio"].required = False
self.fields["skills"].queryset = self.portfolio.getSkills()
class Meta:
model = Resource
fields = ['portfolio',
'name',
'skills',
'pct_availability',
'cost_per_day',
'email',
'timezone',
'update_request_time',
'calendar',
'start_date',
'end_date']
widgets = {
'portfolio': forms.HiddenInput(),
'start_date': DateInput(),
'end_date': DateInput(),
'update_request_time': TimeInput(),
'skills': forms.CheckboxSelectMultiple(),
}
ResourceEditAddFormSet = modelformset_factory(
Resource,
form=ResourceEditAddForm,
extra=0
)
I could build a manual form to achieve this but I want to keep using model forms as there's a few fields other than skills which are managed fine by the form.
If anyone can tell me how to hide the labels "animation", "art", etc. next to the checkboxes in forms, css or whatever that would be much appreciated.
You can simply hide your label from models.py. Labels on the form come from verbose_name attributes:
class Resource(models.Model):
skills = models...(...,verbose_name="")
or
try this under ResourceEditAddForm's __init__ function:
def __init__(self, *args, **kwargs):
...
self.fields['skills '].label = ''
...
I appear to have resolved this by slightly customising the widget:
skills_widget = forms.CheckboxSelectMultiple
skills_widget.option_template_name = "django/forms/widgets/input.html"
model = Resource
fields = ['portfolio',
'name',
'skills',
'end_date']
widgets = {
'portfolio': forms.HiddenInput(),
'skills': skills_widget
},
So this replaces the full template for the checkbox (which wraps the input in a label) with just the input part.
I'm dealing with an RTL CharField so I impelemted the below in my admin.py:
class CustomPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'lead', 'body', 'status', 'is_featured']
widgets = {'title': forms.TextInput(attrs={'dir': 'rtl'})}
#admin.register(Post)
class PostAdmin(admin.ModelAdmin):
form = CustomPostForm
This works fine except that when I look at the form in the Admin site the width of the RTL field is less than the next LTR field:
And if I remove the whole custom form, then both fields look the same:
So I inspect the code and realize that the slug field has a css class called vTextField and if I change the custom form's attrs both fields look the same:
widgets = {'title': forms.TextInput(attrs={'dir': 'rtl', 'class': 'vTextField'})}
So my question is, is it normal or intentional for Django to remove CSS class if attrs is used and my approach in putting the class back is correct, or I'm missing something here?
You are replacing attrs {'class': 'vTextField'} with {'dir': 'rtl'}. So yes, original attrs is replaced with yours. It's normal and expected.
Add the attribute inside init method:
class CustomPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'slug', 'lead', 'body', 'status', 'is_featured']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs['dir'] = 'rtl'
I want to change the name of the widget but then i have two names inside the input
class DocumentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# to delete colon after field's name
kwargs.setdefault('label_suffix', '')
super(DocumentForm, self).__init__(*args, **kwargs)
class Meta:
model = Document
name = "document"
fields = ('id',
'comment',
'private',
'position',
'marked')
# fields = '__all__'
marked = forms.IntegerField(
required=False,
widget=forms.NumberInput(
attrs={
'name': "asdasdjasdasjdldsa",
'id': "device_documents___name___marked",
'class': 'check preview_image',
'onchange': 'cbChange(this)',
'mazen': "document_set-__name__-position"
},
)
)
but if i print this i have two name name="marked" and name="asdasdjasdasjdldsa" how to delete the first one?
print(f["marked"])
<input type="number" name="marked" name="asdasdjasdasjdldsa" id="device_documents___name___marked" class="check preview_image" onchange="cbChange(this)" mazen="document_set-__name__-position">
Django needs the name of the field to match the class definition. Otherwise it wouldn't be possible to process the submitted form data. Your only way of changing the widget's name attribute is to change the form field definition.
You didn't say why you want the name attribute to change. If you really wanted to hack it. You could create a custom widget with a template that uses the desired name.
I have a form widget (for the bio field) which is not being required by the form (no validation, no HTML attribute set to required) despite me setting it explicitly in the form constructor (as suggested in this SO question). When I say it is not required, I mean I can submit the form with the field empty (which I am blocked from doing for other fields):
users/forms.py:
from wagtail.admin.rich_text import get_rich_text_editor_widget
class UpdateProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UpdateProfileForm, self).__init__(*args, **kwargs)
self.fields['bio'].required = True
class Meta():
model = _user_profiles.__model__
fields = (
'email', 'first_name', 'last_name', 'organisation',
'bio', 'city', 'country', 'photo_file'
)
widgets = {
'bio': get_rich_text_editor_widget('submission')
}
If I set a breakpoint in users/views.py I can see that all the way until the end of the stack, the form field is correctly set to 'required'=True, just like the email field which the form correctly requires, and unlike city which it does not require:
users/views.py:
> /root/.pyenv/versions/3.7.2/lib/python3.7/site-packages/django/views/generic/base.py(151)get()
150 context = self.get_context_data(**kwargs)
--> 151 return self.render_to_response(context)
152
ipdb> context['form'].fields['bio'].required
True
ipdb> context['form'].fields['email'].required
True
ipdb> context['form'].fields['city'].required
False
Any ideas why the field is not required?
I am using django autocomplete_light to autocomplete the value inserted on a ForeignKeyField. I want to apply extra filtering on the suggestions based upon the arguements passed to__init__ method.
This is my form
class CamenuForm(autocomplete_light.ModelForm):
class Meta:
model = Ca_dispensaries_item
autocomplete_fields = ('item',)
def __init__(self, *args, **kwargs):
self.category = kwargs.pop('category', None)
super(CamenuForm, self).__init__(*args, **kwargs)
self.fields['item'].queryset=Items.objects.filter(product_type__name=self.category)
for field_name in self.fields:
field = self.fields.get(field_name)
if field:
field.widget.attrs.update({
'placeholder': field.label,
'class': 'form-control'
})
Here is the registry
autocomplete_light.register(Items, search_fields=('item_name',))
Here is this line in the __init__ method which doesnt seems to work with autocomplete. Though I used filter over here, yet the suggestions are not filtered.
self.fields['item'].queryset=Items.objects.filter(product_type__name=self.category)