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?
Related
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.
My site supports multiple languages. I have an object (Category) that should have 1 or many translation objects (CategoryTranslation) respective to supported languages (they are not so simple, so, please, don't advice to use translation apps). Besides, dependent objects have link to user that creates and last updates them.
I want following in my Category Admin panel:
Category fields.
Inlined CategoryTranslation object initialized by existing languages. For example, in my site supports Russian and English, 2 (and only 2) inlines should be shown, first should have non-editable field language equals to 'ru', second - 'en'.
After Category save, new CategoryTranslation should get created_by field value equals to current user, edited CategoryTranslation should get edited_by field value equals to current user.
Here is what I have done for now:
class CategoryTranslationFormSet(BaseInlineFormSet):
def total_form_count(self):
return len(settings.LANGUAGES)
def _construct_form(self, i, **kwargs):
form = super(CategoryTranslationFormSet, self)._construct_form(i, **kwargs)
form.initial['language_code'] = settings.LANGUAGES[i][0]
return form
def save(self, commit=True):
translations = super(CategoryTranslationFormSet, self).save(commit=False)
for translation in translations:
if not translation.id:
translation.created_by = self.user
translation.edited_by = self.user
translation.save()
class CategoryTranslationAdmin(admin.TabularInline):
model = CategoryTranslation
formset = CategoryTranslationFormSet
max_num = 0
fields = ('title', 'description', 'language_code', 'created_by', 'created', 'edited_by', 'edited')
readonly_fields = ('created_by', 'created', 'edited_by', 'edited')
class CategoryAdmin(tree_editor.TreeEditor):
fieldsets = (
(None, {'fields': ('parent', 'is_list', 'is_active')}),
(_('Audit'), {'fields': ('created_by', 'created', 'edited_by', 'edited'),
'classes': ('collapse',)
}),
)
readonly_fields = ('created_by', 'created', 'edited_by', 'edited')
list_display = ('is_list', 'is_active')
inlines = [CategoryTranslationAdmin]
def save_model(self, request, obj, form, change):
add_user_for_audit(request, obj, change)
super(CategoryAdmin, self).save_model(request, obj, form, change)
def save_formset(self, request, form, formset, change):
formset.user = request.user
super(CategoryAdmin, self).save_formset(request, form, formset, change)
As you can see, max_num field of CategoryTranslationAdmin is 0 to prevent adding of additional translations, total_form_count method of form set returns count of supported langauges and forms initialized with available languages on form creation.
Update
I also set current user to formset in save_formset(self, request, form, formset, change) method and use him to populate fields of changed objects in save() method of CategoryTranslationFormSet class.
Is it correct?
So now I need:
Make language_code field read_only (if I simply add it to readonly_fields tuple, I will not be able to initialize it in form).
Understand, is my solution with appending user before save is correct.
Check this one:
from django.forms.widgets import HiddenInput
def _construct_form(self, i, **kwargs):
form = super(CategoryTranslationFormSet, self)._construct_form(i, **kwargs)
form.empty_permitted = False # Force the translation
if 'language_code' not in form.initial.keys():
form.initial['language_code'] = settings.LANGUAGES[i][0]
for k in form.fields.keys():
lang = LANG_CHOICES[zip(*settings.LANGUAGES)[0].index(form.initial['language_code'])][1]
if k == 'language':
form.fields[k].widget = HiddenInput()
form.fields[k].label = u'%s %s' % (form.fields[k].label, lang)
else:
form.fields[k].label = u'%s (%s)' % (form.fields[k].label, lang)
return form
How do I go about adding a new class attribute for each for my fields in my formset? I was thinking it would be along these lines of code but the attribute fields does not exist in a formset object.
55 # create the formset
56 ItemUserDetailsFormset = modelformset_factory(CartItemUserDetails,
57 exclude=['cart', 'cart_item'], formset=BaseItemUserDetailsFormset)
37 class BaseItemUserDetailsFormset(BaseModelFormSet):
38 def __init__(self, *args, **kwargs):
39 super(BaseItemUserDetailsFormset, self).__init__(*args, **kwargs)
40 self.fields['first_name'].widgets = forms.TextInput(attrs={"class":"required"})
You're trying to set the fields attribute on the formset. You need to set it on the form class, not the formset class.
So, you want something like...
class ItemUserDetailsForm(forms.ModelForm):
class Meta:
model = CartItemUserDetails
def __init__(self, *args, **kwargs):
super(ItemUserDetailsForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget = forms.TextInput(attrs={ 'class': 'required' })
# [...]
ItemUserDetailsFormset = modelformset_factory(CartItemUserDetails,
form=ItemUserDetailsForm,
exclude=['cart', 'cart_item'],
)
Sadly, I scanned the docs and didn't see documentation of the form keyword argument in the Django docs, but it's clearly presented if you look at the modelform_factory function itself (see line 371).
Also, if the only thing that you're changing is the widget property, and if you're using Django >= 1.2 (which you should be), there's a slightly simpler syntax on the form itself:
class ItemUserDetailsForm(forms.ModelForm):
class Meta:
model = CartItemUserDetails
widgets = {
'first_name': forms.TextInput(attrs={ 'class': 'required' }),
# [...]
}
My admin looks like this (with no exclude variable):
class MovieAdmin(models.ModelAdmin)
fields = ('name', 'slug', 'imdb_link', 'start', 'finish', 'added_by')
list_display = ('name', 'finish', 'added_by')
list_filter = ('finish',)
ordering = ('-finish',)
prepopulated_fields = {'slug': ('name',)}
form = MovieAdminForm
def get_form(self, request, obj=None, **kwargs):
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = request.user
return form
admin.site.register(Movie, MovieAdmin)
The form:
class MovieAdminForm(forms.ModelForm):
class Meta:
model = Movie
def save(self, commit=False):
instance = super(MovieAdminForm, self).save(commit=commit)
if not instance.pk and not self.current_user.is_superuser:
if not self.current_user.profile.is_manager:
instance.added_by = self.current_user.profile
instance.save()
return instance
I'm trying to remove the added_by field for users since I'd prefer to populate that from the session. I've tried methods from the following:
Django admin - remove field if editing an object
Remove fields from ModelForm
http://www.mdgart.com/2010/04/08/django-admin-how-to-hide-fields-in-a-form-for-certain-users-that-are-not-superusers/
However with each one I get: KeyError while rendering: Key 'added_by' not found in Form. It seems I need to remove the field earlier in the form rendering process but I'm stuck on where to do this.
So how can I exclude the added_by field for normal users?
You're probably getting that error when list_display is evaluated. You can't show a field that's excluded. The version with added_by removed also needs a corresponding list_display.
def get_form(self, request, obj=None, **kwargs):
current_user = request.user
if not current_user.profile.is_manager:
self.exclude = ('added_by',)
self.list_display = ('name', 'finish')
form = super(MovieAdmin, self).get_form(request, obj, **kwargs)
form.current_user = current_user
return form
I have a read-only field in a django form that I sometimes want to edit.
I only want the right user with the right permissions to edit the field. In most cases the field is locked, but an admin could edit this.
Using the init function, I am able to make the field read-only or not, but not optionally read-only. I also tried passing an optional argument to StudentForm.init but that turned much more difficult that I expected.
Is there a proper way to do accomplish this?
models.py
class Student():
# is already assigned, but needs to be unique
# only privelidged user should change.
student_id = models.CharField(max_length=20, primary_key=True)
last_name = models.CharField(max_length=30)
first_name = models.CharField(max_length=30)
# ... other fields ...
forms.py
class StudentForm(forms.ModelForm):
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name',
# ... other fields ...
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance:
self.fields['student_id'].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
form = StudentForm()
# Test for user privelige, and disable
form.fields['student_id'].widget.attrs['readonly'] = False
c = {'form':form}
return render_to_response('app/edit_student.html', c, context_instance=RequestContext(request))
Is that what you are looking for? By modifying your code a little bit:
forms.py
class StudentForm(forms.ModelForm):
READONLY_FIELDS = ('student_id', 'last_name')
class Meta:
model = Student
fields = ('student_id', 'last_name', 'first_name')
def __init__(self, readonly_form=False, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
if readonly_form:
for field in self.READONLY_FIELDS:
self.fields[field].widget.attrs['readonly'] = True
views.py
def new_student_view(request):
if request.user.is_staff:
form = StudentForm()
else:
form = StudentForm(readonly_form=True)
extra_context = {'form': form}
return render_to_response('forms_cases/edit_student.html', extra_context, context_instance=RequestContext(request))
So the thing is to check permissions on the views level, and then to pass argument to your form when it is initialized. Now if staff/admin is logged in, fields will be writeable. If not, only fields from class constant will be changed to read only.
It would be pretty easy to use the admin for any field editing and just render the student id in the page template.
I'm not sure if this answers your questions though.