DRF deserializing data from API to django model - python

I'm using serializers of django-rest-framework (DRK)
I'm fetch data from an external API, and I want to convert this datas into an internal model (here Period)
The thing is that the field's format of the external api are like this :
{"DateFrom": "2020-02-10T00:00:00"}
I want to rename into "date_from" field.
Here what I tried :
Serializer :
class PeriodSerializer(serializers.ModelSerializer):
date_from = serializers.DateTimeField(write_only=True, source='DateFrom')
class Meta:
model = Period
fields = ('date_from',)
Notice that I tried with "write_only=True, source='DateFrom'"
And then in my code :
json = {"DateFrom": "2020-02-10T00:00:00"}
serializer = PeriodSerializer(data=json)
serializer.is_valid() # This is returning False
print(serializer.errors)
And then the output is :
{'date_from': [ErrorDetail(string='This field is required.', code='required')]}
How handle that ? (in the best way (good practice))

I think you have something backwards here. Given your model and what you expose in your API, you would want to do:
class PeriodSerializer(serializers.ModelSerializer):
DateFrom = serializers.DateTimeField(write_only=True, source='date_from')
source specifies the data source on the model, while the name of the serializer field is what the field will be named when serialized.

Related

Django Rest Framework: Create / Update by id, output dictionary

I have a django-rest-framework model viewset (for tests) that is using a serialize like this:
class ProcessSerializer(serializers.Serializer):
class Meta:
model = Process.draft_model
fields = ['id', 'name']
class TestSerializer(serializers.ModelSerializer):
process = ProcessSerializer()
class Meta:
model = ConfigurationTest
fields = [
'id',
'name',
'process',
]
This works great when retrieving tests, but doesn't work for creation / updates, where I would ideally like to just provide the ID with a request like this:
{
process: 1
name: 'A new test'
}
When sending that request to the server I get an error like Invalid data. Expected a dictionary, but got int
What I tried:
Have process and process_id as included fields and just sending process_id in a POST request. In that case I get an error like process_id cannot be null, which is plain confusing.
Use a different serializer for the create action, that uses process as a plain PrimaryKeyRelatedField without a nested serializer. This works nicely for getting the request, but also obviously means the server reponse to that POST request doesn't include the nicely nested serializer.
Models for reference
class ConfigurationTest(...):
name = CharField(max_length=120)
process = ForeignKey(Process)
class Process(...):
name = CharField(max_length=120)
I would give a serializer like this. One serializer field for read_only where it uses ProcessSerializer and process_id for write_only as integer.
class TestSerializer(serializers.ModelSerializer):
process = ProcessSerializer(read_only=True)
process_id = IntegerField(write_only=True)
class Meta:
model = ConfigurationTest
fields = [
'id',
'name',
'process',
'process_id',
]
And POST this:
{
process_id: 1
name: 'A new test'
}
I am not 100% sure you don't need to override create/update but this should work fine.
N.B.:
I see that you tried something with similar logic. Was it the same code though ?

Python/Django: Populating Model Form Fields with values from Imported JSON file

I have a model form that saves all form field inputs to the backend database as one entry. I also have a JSON file that contains multiple JSON objects whose fields corresponds to the model form field. This JSON file is being uploaded via FileField in the model. Ultimately, I want to be able to upload a JSON file with the multiple JSON objects into my model form and populate the fields with the corresponding values from the uploaded JSON file. Each JSON object will be a single entry to my database and they can have null values for at least one field. Ideally, I would like to be able to choose which JSON object (from the uploaded JSON file) gets loaded to my model form fields to eventually be saved in my database. How would I go about implementing this?
To unpack a JSON string into a Django model, you can use the Python Standard Library json package to convert it into a dict and then unpack it into the object as keyword arguments using **:
>>> from user.models import User
>>> import json
>>> some_json = '{"username": "cole","password": "testing123"}'
>>> User(**json.loads(some_json))
<User: cole>
>>> User(**json.loads(some_json)).username
'cole'
>>> User(**json.loads(some_json)).password
'testing123'
By the way, there's a nice StackOverflow answer about ** here.
You could use the django rest framework. It will provide a post function and serializers.
You'll wind up with some stuff like this:
# the model
class Book(Model):
title = TextField()
author = TextField()
# the serializer
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
# the view
class BookView(generics.ListCreateApiView):
queryset = Book.objects.all()
serializer_class = BookSerializer
see the tutorial for more details:
http://www.django-rest-framework.org/tutorial/1-serialization/#tutorial-1-serialization
Then you can post your data to the database
data = [{'title': 'Moby Dick', 'author': 'Herman Melville'},
{'title': 'A Game of Thrones', 'author': 'George R. R. Martin'}]
for d in data:
r = requests.post('django_api_url/book/', d)
r.raise_for_status()
# the new record is returned if successful
print(r.json())

How to automatically fill-in model fields in Django rest_framework serializer?

Let's assume I have a model like this:
class Data(models.Model):
a = models.CharField()
b = models.CharField()
c = models.IntegerField()
I would like to setup a serializer in such a way that it automatically fills in field c and it is not required for a POST. I tried to overwrite the create function of the serializer, but it doesn't work:
class DataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Data
fields = ('a', 'b')
def create(self, validated_data, **kwargs):
Data.objects.c = 5
return Data.objects.create(**validated_data)
However, if I try this, I end up with an IntegrityError: NOT NULL constraint failed: model_data.c. What is the syntax that I have to use here?
EDIT: Updated formatting.
The reason you're getting the error because field c is not set to null = True - as such an error is raised at the validation stage even before the serializer hits the create method.
Bear in mind that the process goes like this:
Submit serializer data
field-level validation happens - this includes checks for null integrity, min/max length etc and also any custom field validations defined in def validate_<field_name>
object-level validation happens - this calls the def validate method
validated data is passed to the save method, depending on how you designed the serializer - it will save the instance, or route the data to either create or update
All of the info regarding this can be found in Django's and DRF's docs.
A few things to consider:
are you setting a global default for that field? If so, set the default in your models - c = models.IntegerField(default=a_number_or_a_callable_that_returns_an_integer)
do you intend to display the field? If so, include c in your fields and add one more Meta attribute - read_only_fields = ('c',)
If it's neither of the above, you might want to override the validate_c method
Apologies for the poor formatting, typing it on my phone - will update once I get to a computer
In your code Data.objects.c = 5 does nothing.
If you want to set this value yourself use validated_data['c'] = 5 or Data.objects.create(c=5, **validated_data) (just not both at the same time).
Rather than doing this in the serializer, there are hooks in the generic views that allow you to pass values to the serializer. So in your case you might have:
class DataViewSet(ModelViewSet):
# ...
def perform_create(self, serializer):
serializer.save(c=5)
See the "Save and deletion hooks" section here

Validating a Django model field based on another field's value?

I have a Django app with models accessible by both Django REST Framework and a regular form interface. The form interface has some validation checks before saving changes to the model, but not using any special Django framework, just a simple local change in the view.
I'd like to apply the same validation to forms and REST calls, so I want to move my validation into the model. I can see how to do that for simple cases using the validators field of the Field, but in one case I have a name/type/value model where the acceptable values for 'value' change depending on which type is selected. The validator doesn't get sent any information about the model that the field is in, so it doesn't have access to other fields.
How can I perform this validation, without having essentially the same code in a serializer for DRF and my POST view for the form?
I dug around codebase of drf a little bit. You can get values of all fields using following approach. Doing so, you can throw serialization error as
{'my_field':'error message} instead of {'non_field_error':'error message'}.
def validate_myfield(self, value):
data = self.get_initial() # data for all the fields
#do your validation
However, if you wish to do it for ListSerializer, i.e for serializer = serializer_class(many=True), this won't work. You will get list of empty values.
In that scenario, you could write your validations in def validate function and to avoid non_field_errors in your serialization error, you can raise ValidationError with error message as a dictionary instead of string.
def validate(self, data):
# do your validation
raise serializers.ValidationError({"your_field": "error_message"})
The validation per-field doesn't get sent any information about other fields, when it is defined like this:
def validate_myfield(self, value):
...
However, if you have a method defined like this:
def validate(self, data):
...
Then you get all the data in a dict, and you can do cross-field validation.
You can use the required package for your cross-field validation. It allows you to express your validation rules declaratively in python. You would have something like this with DRF:
class MySerializer(serializers.Serializer):
REQUIREMENTS = (
Requires("end_date", "start_date") +
Requires("end_date", R("end_date") > R("start_date")) +
Requires("end_date", R("end_date") < today.date() + one_year) +
Requires("start_date", R("start_date") < today.date() + one_year)
)
start_date = serializers.DateField(required=False, null=True, blank=True)
end_date = serializers.DateField(required=False, null=True, blank=True)
def validate(self, data):
self.REQUIREMENTS.validate(data) # handle validation error
You could put the REQUIREMENTS on your Model and have both your DRF and Django Form validate your data using it.
Here is a blog post explaining more

Returning non-predefined fields via. API with Tastypie in Django

I am using Tastypie for non-ORM data source (Amazon Dynamodb). I have gone through the official documentation for non-ORM source and found the following code:
class MessageResource(Resource):
# Just like a Django ``Form`` or ``Model``, we're defining all the
# fields we're going to handle with the API here.
uuid = fields.CharField(attribute='uuid')
user_uuid = fields.CharField(attribute='user_uuid')
message = fields.CharField(attribute='message')
created = fields.IntegerField(attribute='created')
I am new to Tastypie and what I understand is that fields uuid, message, created.. which are returned by API are defined over here. Is there any way that I return those fields that are not defined here i.e. all those fields returned by the dictionary in obj_get_list or obj_get.
You can use the dehydrade method. Simply add a new key to bundle.data.
def dehydrate(self, bundle):
for item in bundle.obj.iteritems():
bundle.data["new_key"] = "new_value"
return bundle

Categories