I am trying to apply custom validation on a Mongoengine modify operation as seen below:
class Form(Document):
fields = ListField(EmbeddedDocumentField(Field))
def modify(self, *args, **kwargs):
for field in self.fields:
if not [field for field in self.fields if field.type == "email"]:
raise ValidationError("Form must have an email field")
super(Form, self).modify(**kwargs)
def update_form(self, modify_kwargs):
return self.modify(**modify_kwargs)
However when I call update_form, the custom validation does not take the updated data into account in modify. Is there some sort of a pre-hook for doing this type of validation?
You're verifying against the objects field attribute rather than kwargs. But make sure each field is an object that contains .type. You shouldn't be using the python reserved word type though.
class Form(Document):
fields = ListField(EmbeddedDocumentField(Field))
def modify(self, *args, **kwargs):
if not [field for field in kwargs.get('fields', []) if field.type == "email"]:
raise ValidationError("Form must have an email field")
super(Form, self).modify(**kwargs)
def update_form(self, modify_kwargs):
return self.modify(**modify_kwargs)
Related
I'm calling a custom save method in my model. The save method is designed to produce some values in the model based on the content of a TextField. This content is stored in a dictionary, and is generated correctly.
I'm trying to write a for loop to store the values of the dictionary. The keys correspond to the model field names:
def save(self, *args, **kwargs):
if self.text != "":
results_dict = analyze(self.text)
for field_name in field_names_list:
self.field_name = results_dict[field_name]
The value is not stored in the corresponding field. Do I need to use setattr here instead?
Try this:
def save(self, *args, **kwargs):
if self.text != "":
results_dict = analyze(self.text)
for field_name in field_names_list:
setattr(self, field_name, results_dict[field_name])
super().save(*args, **kwargs)
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)
Is it possible to make a Django Proxy Field that has access to another field, but doesn't save anything to the database for it's own value(s), and doesn't have a database column for itself?
The use case for this is we'd like to store values in a JsonField, but be able to use the built in validations of Django Fields. A second benefit of this would being able to add new fields (with validation capability) without affecting the database schema.
The sudo code would probably look something like this:
from django.db import models
from django.contrib.postgres.fields import JsonField
class ProxyInitMixin(object):
def __init__(self, *args, *kwargs):
# some logic that will hold values if set on the Model
# but won't create a column or save anything to the
# database for this Field.
super(ProxyInitMixin, self).__init__(*args, **kwargs)
class ProxyIntegerField(ProxyInitMixin, models.Field):
pass
class ProxyCharField(ProxyInitMixin, models.Field):
pass
class MyModel(models.Model):
proxy_int = ProxyIntegerField()
proxy_char = ProxyCharField()
data = JsonField()
def save(self, *args, **kwargs):
self.data = {
'foo': self.proxy_int,
'bar': self.proxy_char
}
return super(MyModel, self).save(*args, **kwargs)
There are proxy models in django, But I am not sure if it has something like proxy fields.
For your use case, you can do as mentioned below:
Create a list of fields with each field containing name, type, nullable, etc.
Add a function in your model to return actual django rest framework (DRF) field class instance, corresponding to each field type passed to it.
Use DRF inbuilt field class validation to validate your field data against specified field type in save().
In addition to automatic validation, you will also get automatic type conversion. Eg. If user entered number 1 as text: "1" for a integer field then it will automatically convert it back to integer 1. Same way, it will work for float, Bool, Char, etc
`
from django.db import models
from rest_framework.fields import IntegerField, FloatField, BooleanField, DateTimeField, CharField
class MyModel(models.Model):
FIELDS = [{'field_name': 'proxy_int', 'field_type': 'int', 'null_allowed': 'True'},
{'field_name': 'proxy_char', 'field_type': 'string', 'null_allowed': 'True'}]
data = JsonField()
def field_type(self, field):
if field.field_type == 'int':
return IntegerField()
elif field.field_type == 'float':
return FloatField()
elif field.field_type == 'bool':
return BooleanField()
elif field.field_type == 'date':
return DateTimeField()
elif self.value_type == 'string':
return CharField()
return CharField()
def save(self, *args, **kwargs):
data = kwargs.get('data', {})
new_data = {}
for (field in FIELDS)
field_name = field['field_name']
field_type = field['field_type']
field_value = data.get(field_name, None)
validated_value = self.field_type(field_type).run_validation(field_value)
new_data[field_name] = validated_value
kwargs['data'] = new_data
return super(MyModel, self).save(*args, **kwargs)`
You may try and figure out django's field classes (Instead of DRF) and use them for validation, if required.
You can inherit this new MyModel class to achieve similar capability in other models and to reuse code.
In order to make the field virtual, you need to:
Override the Field.get_attname_column() method, which must return two-tuple attname, None as the value for attname and column.
Set the private_only parameter to True in the Field.contribute_to_class() method.
A proxy field must also have a reference to the concrete field in order to be able to access to it. Here I will use the concrete_field parameter.
class ProxyMixin(object):
"""
A mixin class that must be mixed-in with model fields.
The descriptor interface is also implemented in this mixin
class to keep value getting/setting logic on the Model.
"""
def __init__(self, *args, concrete_field=None, **kwargs):
self._concrete_field = concrete_field
super().__init__(*args, **kwargs)
def check(self, **kwargs):
return [
*super().check(**kwargs),
*self._check_concrete_field(),
]
def _check_concrete_field(self):
try:
self.model._meta.get_field(self._concrete_field)
except FieldDoesNotExist:
return [
checks.Error(
"The %s concrete field references the "
"nonexistent field '%s'." % (self.__class__.__name__, self._concrete_field),
obj=self,
id='myapp.E001',
)
]
else:
return []
def get_attname_column(self):
attname, column = super().get_attname_column()
return attname, None
def contribute_to_class(self, cls, name, private_only=False):
super().contribute_to_class(cls, name, private_only=True)
setattr(cls, name, self)
#property
def concrete_field(self):
"""
Returns the concrete Field instance.
"""
return self.model._meta.get_field(self._concrete_field)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self._concrete_field is not None:
kwargs['concrete_field'] = self._concrete_field
return name, path, args, kwargs
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self._concrete_field)
def __set__(self, instance, value):
setattr(instance, self._concrete_field, value)
If you are sure that the concrete field represents a dict-like object, then you can change the logic for getting / setting value. Maybe something like this:
def __get__(self, instance, owner=None):
if instance is None:
return self
data = getattr(instance, self._concrete_field) or {}
return data.get(self.name, self.get_default())
def __set__(self, instance, value):
data = getattr(instance, self._concrete_field)
if data is None:
setattr(instance, self._concrete_field, {})
data = getattr(instance, self._concrete_field)
data[self.name] = value
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+.
I'm using a TabularInline in Django's admin, configured to show one extra blank form.
class MyChildInline(admin.TabularInline):
model = MyChildModel
form = MyChildInlineForm
extra = 1
The model looks like MyParentModel->MyChildModel->MyInlineForm.
I'm using a custom form so I can dynamically lookup values and populate choices in a field. e.g.
class MyChildInlineForm(ModelForm):
my_choice_field = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(MyChildInlineForm, self).__init__(*args, **kwargs)
# Lookup ID of parent model.
parent_id = None
if "parent_id" in kwargs:
parent_id = kwargs.pop("parent_id")
elif self.instance.parent_id:
parent_id = self.instance.parent_id
elif self.is_bound:
parent_id = self.data['%s-parent'% self.prefix]
if parent_id:
parent = MyParentModel.objects.get(id=parent_id)
if rev:
qs = parent.get_choices()
self.fields['my_choice_field'].choices = [(r.name,r.value) for r in qs]
This works fine for the inline records bound to an actual record, but for the extra blank form, it doesn't display any values in my choice field, since it doesn't have any record id and there can't lookup the associated MyParentModel record.
I've inspected all the values I could find (args, kwargs, self.data, self.instance, etc) but I can't find any way to access the parent object the tabular inline is bound to. Is there any way to do this?
Update: As of Django 1.9, there is a def get_form_kwargs(self, index) method in the BaseFormSet class. Hence, overriding that passes the data to the form.
This would be the Python 3 / Django 1.9+ version:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs['parent_object'] = self.instance
return kwargs
class MyForm(forms.ModelForm):
def __init__(self, *args, parent_object, **kwargs):
self.parent_object = parent_object
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
For Django 1.8 and below:
To pass a value of a formset to the individual forms, you'd have to see how they are constructed. An editor/IDE with "jump to definition" really helps here to dive into the ModelAdmin code, and learn about the inlineformset_factory and it's BaseInlineFormSet class.
From there you'll find that the form is constructed in _construct_form() and you can override that to pass extra parameters. It will likely look something like this:
class MyFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
#property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
parent_object=self.instance,
)
self.add_fields(form, None)
return form
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.parent_object = kwargs.pop('parent_object', None)
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
Yes, this involves a private _construct_form function.
update Note: This doesn't cover the empty_form, hence your form code needs to accept the parameters optionally.
I'm using Django 1.10 and it works for me:
Create a FormSet and put the parent object into kwargs:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super(MyFormSet, self).get_form_kwargs(index)
kwargs.update({'parent': self.instance})
return kwargs
Create a Form and pop an atribute before super called
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
parent = kwargs.pop('parent')
super(MyForm, self).__init__(*args, **kwargs)
# do whatever you need to with parent
Put that in the inline admin:
class MyModelInline(admin.StackedInline):
model = MyModel
fields = ('my_fields', )
form = MyFrom
formset = MyFormSet
AdminModel has some methods like get_formsets. It receives an object and returns a bunch of formsets. I think you can add some info about parent object to that formset classes and use it later in formset's __init__
Expanding on ilvar's answer a bit, If the form field of interest is constructed from a model field, you can use the following construction to apply custom behavior to it:
class MyChildInline(admin.TabularInline):
model = MyChildModel
extra = 1
def get_formset(self, request, parent=None, **kwargs):
def formfield_callback(db_field):
"""
Constructor of the formfield given the model field.
"""
formfield = self.formfield_for_dbfield(db_field, request=request)
if db_field.name == 'my_choice_field' and parent is not None:
formfield.choices = parent.get_choices()
return formfield
return super(MyChildInline, self).get_formset(
request, obj=obj, formfield_callback=formfield_callback, **kwargs)
return result