My class looks like this:
class Foo(models.Model):
known_by = JSONField()
My data looks like this
{ "known_by" : [
{'by':'name1', 'value':['def']},
{'by':'name2', 'value':['bar']}
]
}
Is there any way for me to enforce that the Jsonfield needs to follow the format of by,value[] dict. I know how to do this using serialiazers
Any other cleaner way to enforce this(in the model definition itself)? Thanks
You can add a validator to the model field, like this:
class Foo(models.Model):
known_by = ArrayField(JSONField(max_length=100), size=4, validators=[a_json_array_validator])
And the validator is:
def a_json_array_validator(value):
if any([not is_json_valid(entry) for entry in value]):
raise ValidationError(
_('%(value) is not a valid json'),
params={'value': value},
)
(The actual json validation is up to you) ;-)
Note that validators receive python objects so its actually a dict.
You could implement it this way:
from django.db import models
class Bar(models.Model):
by = models.CharField()
value = models.ArrayField()
class Foo(models.Model):
known_by = models.ForeignKey(Bar, on_delete=models.CASCADE)
Why not just override the save method to do the enforcement?
class Foo(models.Model):
known_by = JSONField()
def save(self, *args, **kwargs):
# begin validation code
# end validation code
if valid:
super(Model, self).save(*args, **kwargs)
else:
# something else, maybe http error code?
Related
My Content model has a many-to-many relationship to the Tag model. When I save a Content object, I want to add the relationships dynamically. I'm doing this the following way.
def tag_content(obj):
for tag in Tag.objects.all():
print tag
obj.tags.add(tag)
obj.is_tagged = True
obj.save()
class Tag(models.Model):
name = models.CharField(max_length=255)
class Content(models.Model):
title = models.CharField(max_length=255)
is_tagged = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag, blank=True)
def save(self, *args, **kwargs):
super(Content, self).save(*args, **kwargs)
#receiver(post_save, sender = Content)
def update_m2m_relationships_on_save(sender, **kwargs):
if not kwargs['instance'].is_tagged:
tag_content(kwargs['instance'])
The tag_content function runs, however, the m2m relationships are not established. Im using Django 1.9.8 btw. This makes no sense. What am I missing? Moreover, if I do something like tag_content(content_instance) in shell, then the tags are set, so the function is ok. I guess the problem is in the receiver. Any help?
Edit
My question has nothing to do with m2m_changed, as I have said, creating a Content object in shell works perfectly. Therefore, the problem lies in the admin panel's setup.
Ok so I solved the problem. Basically, this has something to do with how Django handles its form in the admin panel. When trying to add the Contents from admin, I kept the tags field empty, thinking the tag_content function would handle it. However, that is exactly where the problem was, as creating a Content from shell tagged it just fine. In other words, changing the admin panel to something like this solved my problem :
from django.contrib import admin
from myapp.models import *
from django import forms
class ContentCreationForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title',)
class ContentChangeForm(forms.ModelForm):
class Meta:
model = Content
fields = ('title', 'is_tagged', 'tags')
class ContentAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if obj is None:
return ContentCreationForm
else:
return ContentChangeForm
admin.site.register(Tag)
admin.site.register(Content, ContentAdmin)
When trying to create a new Content, only the 'title' field is presented. This solves the problem.
My Models look like this:
class Car(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=200)
class Owner(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
car = models.ForeignKey(Car)
and a Form that looks like this:
class CarForm (forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CarForm, self).__init__(*args, **kwargs)
self.fields['car_name']=forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}),label='', required=False)
self.fields['person_name']=forms.CharField(widget=forms.TextInput(attrs={'autocomplete':'off'}),label='', required=False)
So when the user goes to my index file he find two forms, one for his name and one for the car's name which when submitted will be created in the database then.
So now I am in the shell and want to test that and I'm not sure what the correct syntax is, I've tried this:
response = client.post('/', {'car_name':'something','person_name':'something'})
but it always returns:
IndexError: list index out of range
What does that mean? Or what's the correct syntax to run the tests?
I've also tried this:
response = client.post('/', {'id_car_name':'something','id_first_name':'something'})
Since these are the ids that Django creates in the homepage, but it didn't work
Your test case syntax looks correct. The field names from your first example are correct and match those declared in self.fields. Here is a more detailed example (with assertions as desired):
from django.test import Client, TestCase
class CarFormTest(TestCase):
def test_form(self):
client = Client()
response = client.post('/', {'car_name':'something','person_name':'something'})
self.assertEqual(response.status_code, 200)
I don't see anything in your test line that would cause an IndexError.
Given that you did not map the Car model to the CarForm like this:
class CarForm(forms.modelForm):
class Meta:
model = Car
It appears that you don't want a direct mapping and instead you need a custom save() method on your CarForm to create Car and Owner objects when you save the form. The IndexError must be coming from a line of code that you have not shown.
Please update your question to provide additional code.
The scenario is quite straight-forward:
I have a model with some fields that are required. Let's say one of them is a TextField which can't be blank.
I also have a ModelSerializer (Django Rest Framework) that represents that model.
When an empty string is used to set that field through the serializer the error returned comes from the model itself (This field can't be blank).
I would like to override the error messages only in the serializer level, without the need to explicitly re-specifying every field in the serializer (which I believe is against the DRY principle), having to write a validate_ method for each field and raise my own ValidationError or having to change the error messages in the Model level (because sometimes the context of the error message matters to my use-case and the error message should be given accordingly).
In other words, is there a way to override error messages in the serializer level as easy as it is for a ModelForm:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
error_messages = {"field1": {"required": _("For some reason this is a custom error message overriding the model's default")}}
EDIT: I see that this question still receives some views, so it is important to note that there's another approach, much cleaner than the original answer I posted here.
You can just use the extra_kwargs attribute of the serializer's Meta class, like so:
class UserSerializer(ModelSerializer):
class Meta:
model = User
extra_kwargs = {"username": {"error_messages": {"required": "Give yourself a username"}}}
Original answer:
Using #mariodev 's answer I created a new class in my project that does that:
from rest_framework.serializers import ModelSerializer, ModelSerializerOptions
class CustomErrorMessagesModelSerializerOptions(ModelSerializerOptions):
"""
Meta class options for CustomErrorMessagesModelSerializerOptions
"""
def __init__(self, meta):
super(CustomErrorMessagesModelSerializerOptions, self).__init__(meta)
self.error_messages = getattr(meta, 'error_messages', {})
class CustomErrorMessagesModelSerializer(ModelSerializer):
_options_class = CustomErrorMessagesModelSerializerOptions
def __init__(self, *args, **kwargs):
super(CustomErrorMessagesModelSerializer, self).__init__(*args, **kwargs)
# Run through all error messages provided in the Meta class and update
for field_name, err_dict in self.opts.error_messages.iteritems():
self.fields[field_name].error_messages.update(err_dict)
The first one gives the possibility to add a new Meta class attribute to the serializer as with the ModelForm.
The second one inherits from ModelSerializer and uses #mariodev's technique to update the error messages.
All is left to do, is just inherit it, and do something like that:
class UserSerializer(CustomErrorMessagesModelSerializer):
class Meta:
model = User
error_messages = {"username": {"required": "Give yourself a username"}}
I tried to create a simple Serializer rather than a ModelSerializer. Probably because of that the accepted answer with extra_kwargs by Gabriel Amram didn't work for me. Another top answer by #mariodev did work but I was looking for a more elegant solution and found one. Turns out that the Field class accepts error_messages as a parameter, which is a dictionary that overrides the default error messages. Here is the reference to the docs. It's the same format as described in the accepted answers. Here is an example:
from rest_framework import serializers
class MySerializer(serializers.Serializer):
client_id = serializers.IntegerField(required=True, error_messages={'required': 'Custom error message'})
In your serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
self.fields['username'].error_messages['required'] = u'My custom required msg'
Please notice that some error messages consist of %s placeholders like:
'invalid': _("'%s' value must be either True or False."),
for BooleanField.
So you need to go over default_error_messages part in each field type in the DRF's fields.py, to use it properly.
unique seemed to be ignored from error_messages, so I had to take a different approach.
email = serializers.EmailField(validators=[
UniqueValidator(
queryset=models.Client.objects.all(),
message="My custom error",
)]
)
It's simpler (yet less flexible, less reusable) than #gabriel-amram's, but far less hacky than #mariodev's.
Another approach for UniqueValidator (for using with ModelSerializer):
def __init__(self, *args, **kwargs):
super(UserSerializer, self).__init__(*args, **kwargs)
# Find UniqueValidator and set custom message
for validator in self.fields['email'].validators:
if isinstance(validator, validators.UniqueValidator):
validator.message = _('This email already exist on this site')
I just spent an hour ripping my hair out over this, so figured I'd post an update here in case anyone else finds it useful.
I'm using djangorestframework version 3.10.3, and for whatever reason, it seems that drf no longer uses the 'required' key in the error_messages dict to allow customization of an error message for a missing value. Instead it uses 'blank'.
class SampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SampleModel
fields = (
'description',
)
extra_kwargs = {
'description': {'error_messages': {'blank': "Please provide a description"}},
}
DRF3.0 expects us to explicitly define the validators for fields if we wish to override the default model validators. This can be done by passing extra_kwargs and explicitly defining the validators for whichever field
you seem necessary. Also you can even specify your own custom validator which can be reused again for different fields or even other serializers
http://www.django-rest-framework.org/api-guide/serializers/#validation
http://www.django-rest-framework.org/api-guide/validators/#validation-in-rest-framework
# my_app/validators.py
def validate_required(value):
# whatever validation logic you need
if value == '' or value is None:
raise serializers.ValidationError('This field is required.')
# my_app/serializers.py
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
extra_kwargs = {"field1": {"validators": [validators.validate_required,]}}
Just a note since I played with this for awhile, if you're using something like a URLField that just adds a URLValidator, it doesn't seem to use the error_messages, so I did something similar to #Hugo's answer:
class Meta:
extra_kwargs = {"url_field": {"validators": [validators.URLValidator(message="My error message")]}}
You can create separate function in serializers.py and call it from serializer class
def checkFields(fields):
for field in fields:
fields[field].error_messages['blank']=fields[field].error_messages['required'] = 'Please enter %s'%field
This is the code that inherits the model's error message.
There is also a module, so download it if you want.
If there's a problem, leave it in the comments.
https://pypi.org/project/django-rest-inherits-error-messages/#files
from rest_framework import serializers
from rest_framework.relations import HyperlinkedRelatedField
from rest_framework.utils.field_mapping import get_nested_relation_kwargs
class InheritsModelSerializer(serializers.ModelSerializer):
def build_field(self, field_name, info, model_class, nested_depth):
'''
inherits the error_messages of the model
'''
result: tuple = super().build_field(field_name, info, model_class, nested_depth)
field = model_class._meta.get_field(field_name)
error_messages = field.error_messages
if error_messages:
result[1]['error_messages'] = field.error_messages
return result
I want to serialize a model, but want to include an additional field that requires doing some database lookups on the model instance to be serialized:
class FooSerializer(serializers.ModelSerializer):
my_field = ... # result of some database queries on the input Foo object
class Meta:
model = Foo
fields = ('id', 'name', 'myfield')
What is the right way to do this? I see that you can pass in extra "context" to the serializer, is the right answer to pass in the additional field in a context dictionary?
With that approach, the logic of getting the field I need would not be self-contained with the serializer definition, which is ideal since every serialized instance will need my_field. Elsewhere in the DRF serializers documentation it says "extra fields can correspond to any property or callable on the model". Are "extra fields" what I'm talking about?
Should I define a function in Foo's model definition that returns my_field value, and in the serializer I hook up my_field to that callable? What does that look like?
Happy to clarify the question if necessary.
I think SerializerMethodField is what you're looking for:
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can change your model method to property and use it in serializer with this approach.
class Foo(models.Model):
. . .
#property
def my_field(self):
return stuff
. . .
class FooSerializer(ModelSerializer):
my_field = serializers.ReadOnlyField(source='my_field')
class Meta:
model = Foo
fields = ('my_field',)
Edit: With recent versions of rest framework (I tried 3.3.3), you don't need to change to property. Model method will just work fine.
With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add. No need for #property and source='field' raise an error.
class Foo(models.Model):
. . .
def foo(self):
return 'stuff'
. . .
class FooSerializer(ModelSerializer):
foo = serializers.ReadOnlyField()
class Meta:
model = Foo
fields = ('foo',)
if you want read and write on your extra field, you can use a new custom serializer, that extends serializers.Serializer, and use it like this
class ExtraFieldSerializer(serializers.Serializer):
def to_representation(self, instance):
# this would have the same as body as in a SerializerMethodField
return 'my logic here'
def to_internal_value(self, data):
# This must return a dictionary that will be used to
# update the caller's validation data, i.e. if the result
# produced should just be set back into the field that this
# serializer is set to, return the following:
return {
self.field_name: 'Any python object made with data: %s' % data
}
class MyModelSerializer(serializers.ModelSerializer):
my_extra_field = ExtraFieldSerializer(source='*')
class Meta:
model = MyModel
fields = ['id', 'my_extra_field']
i use this in related nested fields with some custom logic
My response to a similar question (here) might be useful.
If you have a Model Method defined in the following way:
class MyModel(models.Model):
...
def model_method(self):
return "some_calculated_result"
You can add the result of calling said method to your serializer like so:
class MyModelSerializer(serializers.ModelSerializer):
model_method_field = serializers.CharField(source='model_method')
p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:
class Meta:
model = MyModel
read_only_fields = (
'model_method_field',
)
If you want to add field dynamically for each object u can use to_represention.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'name',)
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.name!='': #condition
representation['email']=instance.name+"#xyz.com"#adding key and value
representation['currency']=instance.task.profile.currency #adding key and value some other relation field
return representation
return representation
In this way you can add key and value for each obj dynamically
hope u like it
This worked for me.
If we want to just add an additional field in ModelSerializer, we can
do it like below, and also the field can be assigned some val after
some calculations of lookup. Or in some cases, if we want to send the
parameters in API response.
In model.py
class Foo(models.Model):
"""Model Foo"""
name = models.CharField(max_length=30, help_text="Customer Name")
In serializer.py
class FooSerializer(serializers.ModelSerializer):
retrieved_time = serializers.SerializerMethodField()
#classmethod
def get_retrieved_time(self, object):
"""getter method to add field retrieved_time"""
return None
class Meta:
model = Foo
fields = ('id', 'name', 'retrieved_time ')
Hope this could help someone.
class Demo(models.Model):
...
#property
def property_name(self):
...
If you want to use the same property name:
class DemoSerializer(serializers.ModelSerializer):
property_name = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__' # or you can choose your own fields
If you want to use different property name, just change this:
new_property_name = serializers.ReadOnlyField(source='property_name')
As Chemical Programer said in this comment, in latest DRF you can just do it like this:
class FooSerializer(serializers.ModelSerializer):
extra_field = serializers.SerializerMethodField()
def get_extra_field(self, foo_instance):
return foo_instance.a + foo_instance.b
class Meta:
model = Foo
fields = ('extra_field', ...)
DRF docs source
Even though, this is not what author has wanted, it still can be considered useful for people here:
If you are using .save() ModelSerializer's method, you can pass **kwargs into it. By this, you can save multiple dynamic values.
i.e. .save(**{'foo':'bar', 'lorem':'ipsum'})
Add the following in serializer class:
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['package_id'] = "custom value"
return representation
I have two models related by a foreign key:
# models.py
class TestSource(models.Model):
name = models.CharField(max_length=100)
class TestModel(models.Model):
name = models.CharField(max_length=100)
attribution = models.ForeignKey(TestSource, null=True)
By default, a django ModelForm will present this as a <select> with <option>s; however I would prefer that this function as a free form input, <input type="text"/>, and behind the scenes get or create the necessary TestSource object and then relate it to the TestModel object.
I have tried to define a custom ModelForm and Field to accomplish this:
# forms.py
class TestField(forms.TextInput):
def to_python(self, value):
return TestSource.objects.get_or_create(name=value)
class TestForm(ModelForm):
class Meta:
model=TestModel
widgets = {
'attribution' : TestField(attrs={'maxlength':'100'}),
}
Unfortunately, I am getting: invalid literal for int() with base 10: 'test3' when attempting to check is_valid on the submitted form. Where am I going wrong? Is their and easier way to accomplish this?
Something like this should work:
class TestForm(ModelForm):
attribution = forms.CharField(max_length=100)
def save(self, commit=True):
attribution_name = self.cleaned_data['attribution']
attribution = TestSource.objects.get_or_create(name=attribution_name)[0] # returns (instance, <created?-boolean>)
self.instance.attribution = attribution
return super(TestForm, self).save(commit)
class Meta:
model=TestModel
exclude = ('attribution')
There are a few problems here.
Firstly, you have defined a field, not a widget, so you can't use it in the widgets dictionary. You'll need to override the field declaration at the top level of the form.
Secondly get_or_create returns two values: the object retrieved or created, and a boolean to show whether or not it was created. You really just want to return the first of those values from your to_python method.
I'm not sure if either of those caused your actual error though. You need to post the actual traceback for us to be sure.
TestForm.attribution expects int value - key to TestSource model.
Maybe this version of the model will be more convenient for you:
class TestSource(models.Model):
name = models.CharField(max_length=100, primary_key=True)
Taken from:
How to make a modelform editable foreign key field in a django template?
class CompanyForm(forms.ModelForm):
s_address = forms.CharField(label='Address', max_length=500, required=False)
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
try:
self.fields['s_address'].initial = self.instance.address.address1
except ObjectDoesNotExist:
self.fields['s_address'].initial = 'looks like no instance was passed in'
def save(self, commit=True):
model = super(CompanyForm, self).save(commit=False)
saddr = self.cleaned_data['s_address']
if saddr:
if model.address:
model.address.address1 = saddr
model.address.save()
else:
model.address = Address.objects.create(address1=saddr)
# or you can try to look for appropriate address in Address table first
# try:
# model.address = Address.objects.get(address1=saddr)
# except Address.DoesNotExist:
# model.address = Address.objects.create(address1=saddr)
if commit:
model.save()
return model
class Meta:
exclude = ('address',) # exclude form own address field
This version sets the initial data of the s_address field as the FK from self, during init , that way, if you pass an instance to the form it will load the FK in your char-field - I added a try and except to avoid an ObjectDoesNotExist error so that it worked with or without data being passed to the form.
Although, I would love to know if there is a simpler built in Django override.