I have a form that I need to show my project outside the area of administration, some fields can not be edited but can see them.
To do this would be great "AdminReadonlyField" found in "django.contrib.admin.helpers" The problem is that you can not do.
I have tried to create some widget that can replace this complex class, but I can not get it to work properly with DateTiemField fields.
class UserUpdateForm(forms.ModelForm):
"""
We need field "date_joined" can not be edited but can see them
"""
class Meta:
model = User
fields = ('first_name', 'last_name',
'email', 'date_joined', 'slug')
def __init__(self, user, *args, **kwargs):
kwargs['instance'] = user
super(UserUpdateForm, self).__init__(*args, **kwargs)
self.fields['date_joined'].widget = widgets.CMDateTimeText()
def clean_date_joined(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.date_joined
else:
return self.cleaned_data['date_joined']
My code, something is not right.
class CMDateTimeText(DateTimeBaseInput):
"""
A SplitDateTime Widget that has some admin-specific styling.
Hereda Field and Widget.
"""
format_key = 'DATETIME_FORMAT'
def __init__(self, attrs=None, format=None):
# Use slightly better defaults than HTML's 20x2 box
default_attrs = {'class': 'date_id'}
if attrs:
default_attrs.update(attrs)
super(CMDateTimeText, self).__init__(attrs=default_attrs, format=format)
def render(self, name, value, attrs=None):
if value is None:
value = ''
value = self._format_value(value)
final_attrs = self.build_attrs(attrs, name=name)
return format_html('<p{}>{}</p>', flatatt(final_attrs), conditional_escape(value))
Result image:
any idea how to do "AdminReadonlyField"" any view or form?
So after hours of looking for various solutions, I found out how to do it the Django way.
Simply add the attribute disabled to the field in the form (not the widget!):
# in __init__() with crispy-forms for instance
self.fields['field'].disabled = True
# as form field
field = forms.CharField(disabled=True)
And it works... Django is taking care of not saving the field, if some hacker tampered with it although it's disabled. Only works with Django 1.9+.
Related
I would like to write a drf validator that will mark a field as required based on the value of an other field.
For example:
class MySerializer(serializers.Serializer):
has_children = fields.BooleanField()
nb_childs = fields.IntegerField(min_value=1, validators=[RequiredIf(field='has_children', value=True)], required=False)
At first i believed the class based validator was the way to do it, by retrieving the value of 'has_children' with a method like this:
def set_context(self, serializer_field):
print serializer_field.parent.initial_data
but the 'initial_data' is not set. Any clue?
Have a look here in the DRF documentation
Basically, to do object-level validation, you need to override the Serializer's validate(self, data) method, do your validation using the data parameter's value (this is the serializer's state provided as a dict to validate) then raise a ValidationError if anything is wrong.
If you need to raise an error for a specific field, then you can pass a dictionary as the parameter to the ValidationError constructor:
raise ValidationError({'yourfield': ['Your message']})
I am using several mixins for that purpose, which are changing field.required attribute and as result error validation messages are generated automatically by DRF
PerFieldMixin
class ConditionalRequiredPerFieldMixin:
"""Allows to use serializer methods to allow change field is required or not"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name, field in self.fields.items():
method_name = f'is_{field_name}_required'
if hasattr(self, method_name):
field.required = getattr(self, method_name)()
How to use PerFieldMixin
class MySerializer(ConditionalRequiredPerFieldMixin, serializers.ModelSerializer):
subject_id = serializers.CharField(max_length=128, min_length=3, required=False)
def is_subject_id_required(self):
study = self.context['study']
return not study.is_community_study
PerActionMixin
class ActionRequiredFieldsMixin:
"""Required fields per DRF action
Example:
PER_ACTION_REQUIRED_FIELDS = {
'update': ['notes']
}
"""
PER_ACTION_REQUIRED_FIELDS = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context.get('view'):
action = self.context['view'].action
required_fields = (self.PER_ACTION_REQUIRED_FIELDS or {}).get(action)
if required_fields:
for field_name in required_fields:
self.fields[field_name].required = True
How to use PerActionMixin
see docstrings, for action == update (ie PUT request) - field "notes" will be required)
I'm trying to populate a Select list of a ModelForm, with the Django groups the current users belongs to.
No errors arise, but I get only an empty Select list.
This is my code:
class ArchiveForm(forms.ModelForm):
class Meta:
model = Archive
fields = ['tags', 'version', 'sharegp']
localized_fields = None
labels = {'tags': 'Related Keywords'}
sharegp = forms.ChoiceField(label='Share with groups')
def __init__(self, user, *args, **kwargs):
#import pudb;pudb.set_trace()
self.user = user
super(ArchiveForm, self).__init__(*args, **kwargs)
self.fields['sharegp'].queryset = Group.objects.filter(user=self.user)
self.fields['sharegp'].widget.choices = self.fields['sharegp'].choices
Note that if I enable the debugger in the first line of the __init__ method, and step forward all along the function, the line:
self.fields['sharegp'].queryset
Gives the correct list containing the groups for that user, but that is not passed to the actual form.
What could I be missing? Thank you!
This is how I ended up solving this:
I was wrongly choosing the type of the field: The correct one is ModelChoiceField:
class ArchiveForm(forms.ModelForm):
class Meta:
model = Archive
fields = ['tags', 'version', 'sharegp']
localized_fields = None
labels = {'tags': 'Related Keywords'}
user = None
usergroups = None
sharegp = forms.ModelChoiceField(label='Share with groups', queryset=usergroups)
def __init__(self, user, *args, **kwargs):
self.user = user
self.usergroups = Group.objects.filter(user=self.user)
super(ArchiveForm, self).__init__(*args, **kwargs)
self.fields['sharegp'].queryset = self.usergroups
That last line is overwriting the queryset assigned in previous one. Remove it.
I have a foreign key field in my form, initialized with:
form=myform(intial= {'val':abc.objects.get(pk=id)})
I have tried the following set of codes individually to make the field "val" as a read only value.
form.fields['val'].widget = forms.HiddenInput()
form.fields['val'].widget.attrs['readonly'] = True
form.fields['val'].widget.attrs['disabled'] = True
form.fields['val'].widget.attrs['disabled'] = 'disabled'
form.fields['val'].widget.attrs['disabled'] = False
form.fields['val'].widget.attrs['display_only'] = True
form.fields['val'].widget.attrs['editable'] = False
Only HiddenInput worked but it didn't show the field in the form, while disabled showed an error. readonly worked on all fields except the foreign key field.
I did this by overriding the Select widgets render method, and changing its output.
class ReadOnlySelect(Select):
"""
This should replace the Select widget with a disabled text widget displaying the value,
and hidden field with the actual id
"""
def render(self, name, value, attrs=None, choices=()):
final_attrs = self.build_attrs(attrs, name=name)
display = "None"
for option_value, option_label in chain(self.choices, choices):
if str(option_value) == (value) :
display = option_label
output = format_html('<input type=text value="%s" disabled="disabled" ><input type="hidden" value="%s" %s> ' % (display, value, flatatt(final_attrs)))
return mark_safe(output)
fuller example on Django snippets.
https://djangosnippets.org/snippets/10436/
I have similar problem and I decided to use different approach. Firstly I define my own class for form, for example this way:
class MyForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = YourModel
exclude = ('foreign_key_field')
And then in models myModel did sth like this:
foreign_key_field = models.ForeignKey(KeyField)
def set_values(self, *args, **kwargs):
foreign_key_field = kwargs.pop('foreign_key_field')
I define function to set fields values that are read only. I call this method
after creating form object in views.py. (after submiting form)
And at last views.py:
if form.is_valid():
new_instance = form.save(commit=False)
new_instance.set_values(
foreign_key_field='YOUR VALUE',
)
new_instance.save()
I exclude this fields from form but after creating instance I am setting values. After setting values I save to db.
From this question I want to convert my form from regular Form to ModelForm so I can take advantage of instance parameter in ModelForm.
Here is my current form code:
class OrderDetailForm(forms.Form):
def __init__(
self,
user,
can_edit_work_type=None,
can_edit_vendor=None,
can_edit_note=None,
*args,
**kwargs
):
super(OrderDetailForm, self).__init__(*args, **kwargs)
if can_edit_work_type:
self.fields['work_type'] = forms.ChoiceField(choices=Order.WORK_TYPE_CHOICES)
if can_edit_vendor:
self.fields['vendor'] = forms.ModelChoiceField(
queryset=Vendor.objects.all(),
empty_label="Choose a vendor",
)
if can_edit_note:
self.fields['note'] = forms.CharField(widget=forms.Textarea)
def clean(self):
super(OrderDetailForm, self).clean()
if 'note' in self.cleaned_data:
if len(self.cleaned_data['note']) < 50:
self._errors['note'] = self.error_class([u"Please enter a longer note."])
del self.cleaned_data['note']
return self.cleaned_data
As you can see, I have some if statements that determine whether the fields even show you in the forms (logically it means certain users can only edit certain parts of the fields).
How would I do that in ModelForm? I understand fields to be a tuple, so it can't be appended like I did in Form. So I want to do something like
class OrderDetailForm(forms.ModelForm):
class Meta:
model = Order
# fields = ('work_type', 'vendor', 'note') I can't do that since I need to be able to control it. See below.
# Can I control widgets even if that field doesn't exist?
widgets = {
'note': forms.Textarea(),
}
def __init__(
self,
user,
can_edit_work_type=None,
can_edit_vendor=None,
can_edit_note=None,
*args,
**kwargs
):
super(OrderDetailForm, self).__init__(*args, **kwargs)
fields = []
if can_edit_work_type:
fields.append('work_type')
if can_edit_vendor:
fields.append('vendor')
if can_edit_note:
fields.append('note')
self.Meta.fields = tuple(fields) # Does this work?
def clean(self):
super(OrderDetailForm, self).clean()
if 'note' in self.cleaned_data:
if len(self.cleaned_data['note']) < 50:
self._errors['note'] = self.error_class([u"Please enter a longer note."])
del self.cleaned_data['note']
return self.cleaned_data
Is that possible? How do you control the fields in ModelForm?
Another possible way is generate a inline form class in the view to exclude fields based on the request, for example, define a normal model form for Order model, called OrderDetailForm:
class OrderDetailForm(forms.ModelForm):
class Meta:
model = Order
fields = ('work_type', 'vendor', 'note')
widgets = {
'note': forms.Textarea(),
}
In the view, for example, edit order, create a customized form based on the OrderDetailForm:
def edit(request, order_id):
order = Order.objects.get(pk=order_id)
can_edit_work_type = bool(request.REQUEST.get('can_edit_work_type', False))
can_edit_vender = bool(request.REQUEST.get('can_edit_vender', False))
can_edit_note = bool(request.REQUEST.get('can_edit_note', False))
exclude_fields = []
if not can_edit_work_type:
exclude_fields.append('work_type')
if not can_edit_vender:
exclude_fields.append('vender')
if not can_edit_note:
exclude_fields.append('note')
class CustomizedOrderForm(OrderDetailForm):
class Meta:
model = Order
exclude = tuple(exclude_fields)
if request.method == 'POST':
form = CustomizedOrderForm(instance=order, data=request.POST)
if form.is_valid():
form.save()
else:
form = CustomizedOrderForm(instance=order)
return render(request, 'order_form.html', {'form': form})
The ModelForm api is very similar to that of the regular Form. The advantage is that you now get model validation in addition to conveniences like default widgets, the instance kwarg, and the save method.
fields attr is still dict-like. You can see fields getting built by the metaclass here. Then, going through the inheritance and calling super() in the BaseModelForm.__init__, we arrive at a deepcopy of the declared fields, originally a SortedDict. This is common to Form and ModelForm, both subclasses of BaseForm.
Put the fields in the exclude and add them the way you are doing in the original __init__.
Clean them the same way.
Then, you can override the save method: you can call super() to get the object back and deal with the data in cleaned_data however you want.
class OrderDetailForm(forms.ModelForm):
# regular fields, not based on bools
# ...
class Meta:
model = Order
exclude = ('work_type', 'vendor', 'note')
# or fields = (...other fields )
def __init__(
self,
user,
can_edit_work_type=None,
can_edit_vendor=None,
can_edit_note=None,
*args,
**kwargs,
):
super(OrderDetailForm, self).__init__(*args, **kwargs)
if can_edit_work_type:
self.fields['work_type'] = forms.ChoiceField(
choices=Order.WORK_TYPE_CHOICES)
if can_edit_vendor:
self.fields['vendor'] = forms.ModelChoiceField(
queryset=Vendor.objects.all(),
empty_label="Choose a vendor",
)
if can_edit_note:
self.fields['note'] = forms.CharField(widget=forms.Textarea)
def clean(self):
# I never call super() in clean .. do I? .. hmmm
# maybe I should or is sth magic going on?
# alternately,
# data = self.cleaned_data
# let's call super though
data = super(OrderDetailForm, self).clean()
if 'note' in data:
if len(data['note']) < 50:
# I raise a validation error so .is_valid() comes back False
# form.errors happens magically ...
raise forms.ValidationError("Not long enough ...")
return data
def save(self, *args, **kwargs):
data = self.cleaned_data
# maybe do some stuff here
# ...
# commit=True or commit=False could be important
order = super(OrderDetailForm, self).save(*args, **kwargs)
if 'note' in data:
order.note = data['note']
# ... do other stuff
# probably ...
order.save()
# respect how model forms work.
return order
I want to re-use a template I have with my WTForms form:
<th>${form.name.label}</th>
<td>${form.name()}</td>
...
However, on my edit page, I want the input fields to display as normal (TextField, SelectField, etc.), while on my view page, I want to just display the value of the property, not the input field with the value.
Edit page:
<th>Name:</th>
<td><input type="text" value="Current Name" name="name" id="name"/></td>
View page:
<th>Name:</th>
<td>Current Name</td>
I know I can access a field's value via form.name.data, but is there any way I can keep the same template with form.name() being called and somehow toggle whether that outputs <input type="text"... or Current Name?
I created a custom widget:
from wtforms.fields import Field
class PlainTextWidget(object):
def __call__(self, field, **kwargs):
return field.data if field.data else ''
Then, for my view page, I added the following:
form = MyForm(obj=myDataRow)
fields = [val for val in form._fields]
for fieldName in fields:
fieldProp = getattr(form, fieldName)
setattr(fieldProp, 'widget', PlainTextWidget())
Sarah's answer above led me to the solution to a related problem: What if you want some of your fields to be read only? In that case, instead of doing run-time surgery on the form object, you could define a new ROTextField variant (for example), that always renders to the pure value. For example:
from wtforms.widgets import Input
from wtforms.fields import StringField
class TextOutput(Input):
def __call__(self, field, **kwargs):
return kwargs.get('value', field._value())
class ROTextField(StringField):
widget = TextOutput()
Now define your field with ReadOnly attributes:
class UserPrefs(Form):
name = ROTextField('name', default='Jon')
# ...
Thinking about this problem helped me better understand how WTForms work. Leaving this here in case this might help someone else work through related issues.
Based on Sarah's answer and code found in WTForms-Components I use the following to quickly turn all a form's fields into read-only and disabled fields.
Suppose we have a ProfileForm defined as follows:
class ProfileEditForm(Form):
title = StringField("Title", validators=[validators.required("Please enter your title.")])
first_name = StringField("First Name", validators=[validators.required("Please enter your first name.")])
middle_name = StringField("Middle Name")
last_name = StringField("Last Name", validators=[validators.required("Please enter your last name.")])
organisation = StringField("Company Name", validators=[validators.required("Please enter your company name.")])
organisation_website = StringField("Company Website")
# more fields ...
Define the following class (based on ReadOnlyWidgetProxy from WTForms-Components):
class ReadOnlyAndDisabledWidgetProxy(object):
def __init__(self, widget):
self.widget = widget
def __getattr__(self, name):
return getattr(self.widget, name)
def __call__(self, field, **kwargs):
kwargs.setdefault('readonly', True)
kwargs.setdefault('disabled', True)
return self.widget(field, **kwargs)
Now inherit from ProfileForm as follows:
class ReadOnlyProfileForm(ProfileForm):
def __init__(self, *args, **kwargs):
super(ReadOnlyProfileForm, self).__init__(*args, **kwargs)
for field_name in self._fields:
field_property = getattr(self, field_name)
field_property.widget = ReadOnlyAndDisabledWidgetProxy(field_property.widget)