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 ?
Related
i have serialization class like this
class MenuSerializer(serializers.ModelSerializer):
data = Menu.objects.raw('''SELECT menu_menu.*, menu_permission.role_id FROM menu_menu
JOIN menu_permission ON menu_menu.id = menu_permission.menu_id
WHERE sub_menu_id IS NULL
ORDER BY menu_menu.id ASC''')
subItems = SubMenuSerializer(data=data,many=True)
class Meta:
model = Menu
fields = ('id',
'label',
'icon',
'link',
'isTitle',
'isMenuCollapse',
'subItems')
how to filter subItems based request header
This is not the way to go.
Serializers serialize data but don't extract it, you "choose" the data to be shown in the controller ( viewset, in DRF lang ).
Check this https://www.django-rest-framework.org/api-guide/viewsets/
Anyway, you can access the request information using the method "save" in the serializer, it is in the var named "context". You can find it in the documentation too.
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.
class Event(models.Model):
start_date = models.DateTimeField()
end_date = models.DateTimeField()
class EventSerializer(serializers.ModelSerializer):
bar = serializers.SerializerMethodField()
class Meta:
model = Event
fields = ('id', 'start_date', 'end_date')
It works all fine and dandy:
GET .../api/v1/Event/
{
"count":23,
"next":null,
"previous":null,
"results":[{
"databaseId":101488,
"start_date":"2013-11-01T09:46:25",
"end_date":"2013-11-02T09:46:25"
},...
]}
Now when I create a new event:
POST /api/v1/Event/
{
"start_date":"2013-11-03T09:46:25",
"end_date":"2013-11-04T09:46:25"
}
In the JSON response I get:
{
"databaseId":101489,
"start_date":"2013-11-03T09:46:25.250000",
"end_date":"2013-11-04T09:46:25.750000"
}
So I get back a more precise format. I'd like to get back exactly the same format, so the client developer won't have to write different parser codes.
I'm using Python 2.7, DRF 3.1.3, Django 1.4.21 (I know it's old but it's a large codebase, one day we'll migrate).
So far I couldn't figure out what causes this, but explicitly enforcing format string helps:
start_date=serializers.DateTimeField(format='%Y-%m-%d %H:%M:%S')
I've been developing a web app on top of Django and I use the Django Rest Framework for my API. There's a model class named Events and my EventsSerializer in DRF is a pretty common serializer without any special configuration. It just dumps data returned by the EventManager.
There is a field "type" in the Event model class.
My json returned now is:
{
events: [
{object1},
{object2},
.....
]
}
, as anything dumped in a DRF api and returned by django.
For some reason, I need my events objects returned categorized by the "type" field. For example, I need to get this:
{
events: [
type1: [{object1}, {object2},...],
type2: [{object3}, {object4}, ...],
.......
]
}
I have literally searched anything related to that but couldn't find a proper solution. Do you have anything to suggest about that?
Thanks in advance
You can use SerializerMethodField and provide custom serialization logic there:
class EventSerializer(serializers.Serializer):
events = serializers.SerializerMethodField(source="get_events")
def get_events(self, events):
event_list = {}
return [event_list[e.type].add({e}) if event.type in event_list else event_list[event.type] = [] for event in events]
I had a model similar to the following:
class Book(models.Model):
title = models.CharField(max_length=200)
class Author(models.Model):
name = models.CharField(max_length=200)
books = models.ManyToManyField(Book)
The JSON that was being generated for an Author looks like this:
{
"name": "Sir Arthur C. Clarke",
"books": [
{
"title": "Rendezvous with Rama",
},
{
"title": "Childhood's End",
}
]
}
In the JSON wanted the books to be sorted by title. Since the books are pulled into the queryset via a prefetch_related adding an order_by to the View's queryset had no effect (generated SQL didn't have a join to the Books table). The solution I came up with was to override the get method. In my version of the get method, I have the super class generate the Response and I modify it's data (a Python dict) before returning it as shown below.
I'm not too worried about performance for two reasons:
because of the prefetch_related the join is already being done in Python rather than in the database
In my case the number of Books per Author is relatively small
class AuthorView(RetrieveUpdateAPIView):
queryset = Author.objects.prefetch_related(
'books',
)
serializer_class = AuthorSerializer
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
def key_func(book_json):
return book_json.get('title', '')
books = response.data.get('books', [])
books = sorted(books, key=key_func)
response.data['books'] = books
return response
We are creating an API that needs to allow a user to update a record. In many cases, like a status update or name change, only one field will change. This seems an appropriate use-case scenario for a PATCH request. As I understand it this is a 'partial' update.
We've implemented Django's REST Framework and run into this issue. For a record such as a "AccountUser" I want to change a name field only so I send the following request:
PATCH /api/users/1/ HTTP/1.1
Host: localhost
X-CSRFToken: 111122223333444455556666
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
{ "fullname": "John Doe" }
The record obviously has other attributes including a couple of 'related' fields such as 'account' which is required for a new record. When submit the request, the response is a 400 error with the following body:
{ "account": [ "This field is required." ] }
The serializer for the user looks like this:
class AccountUserSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField()
class Meta:
model = AccountUser
fields = ('id', 'account', 'fullname', ... )
depth = 1
And the model looks like this:
class AccountUser(models.Model):
''' Account User'''
fullname = models.CharField(max_length=200,
null=True,blank=True)
account = models.ForeignKey(Account,
on_delete=models.PROTECT
)
objects = AccountUserManager()
def __unicode__(self):
return self.email
class Meta:
db_table = 'accounts_account_user'
Am I doing something wrong here or is it wrong to expect to be able to update a single field on a record this way. Thanks! This community rocks!
EDIT:
Requested - AccountUserManager:
class AccountUserManager(BaseUserManager):
def create_user(self, email, account_name):
username = hash_email_into_username(email)
...
account = Account.objects.get(name=account_name)
account_user = AccountUser(email=email,user=user,account=account)
account_user.save()
return account_user
It doesn't look like your manager is filtering the user. I'd encourage you to use pdb and set a breakpoint in your view code and step through to see why its attempting to create a new record. I can vouch that we use PATCH to complete partial updates all the time and only send a few fields to update without issue.
Only other thought is that you're some how sending a value for account (like null) that's triggering the validation error even though you're listed example only shows sending the fullname field.
See my answer about partial updates. Also you can see the drf docs and this one docs
In my case, all I had to do was add required=False to my serializer field's arguments.
A novice mistake to be sure, but I thought I'd mention it here in case it helps anyone.
I came here because I got into a similar problem. CREATE is fine but PATCH isn't.
Since OP doesn't post the ViewSets and I suspend if he is using a custom ViewSets, which I got a mistake when override the update function:
class MyViewSet(ModelViewSet):
def update(self, request, *args, **kwargs):
// Do some checking
return super().update(request, args, kwargs)
My mistake is in the call to super(). Missing the asterisks. This fix my problem
return super().update(request, *args, **kwargs)
you can simply add this line after the fields variable in serializers.py
class AccountUserSerializer(serializers.ModelSerializer):
account = serializers.PrimaryKeyRelatedField()
class Meta:
model = AccountUser
fields = ('id', 'account', 'fullname', ... )
depth = 1
extra_kwargs = {
'account' : {'required' : False}
}