I am wondering if anyone could help me with the appropriate way to create a unitest in DRF when using context inside a serializer.
as you can see in the serializer below, I am adding a field called distance to my serializer.
class CompanySerializer(serializers.ModelSerializer):
"""Serializer for companies."""
distance = serializers.SerializerMethodField()
class Meta:
model = Company
fields = ['id', 'company_name', 'lat', 'long', 'logo', 'distance']
def get_distance(self, company):
"""Return context"""
print(self.context)
return self.context['distances'][company.pk]
Then when calling the unitest I get an error: KeyError: 'distances'
COMPANY_URL = reverse('company:company-list')
def test_company_list_limited_to_user(self):
"""Test list of companies is limited to authenticated user."""
other_user = create_user(
email='other#example.com', password='password123'
)
create_company(user=other_user)
create_company(user=self.user)
res = self.client.get(COMPANY_URL)
companies = Company.objects.filter(user=self.user)
serializer = CompanySerializer(companies, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
Is there a better way to write this test, so it can pass? I did check and the context distance is create and passed to the serializer with no issue, but the test is not passing :/
Tks a lot!
Kind regards
Victor Almeida
Related
I getting null in return when in fact i have data in the database, I don't where the problem lies, pretty frustrated by this, if I am not calling is_valid then it throws an assertion error but anyways data is still empty
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
View
#api_view(['GET'])
def get_data(request):
product_data = Product.objects.all()
print(product_data)
serialized_data = ProductSerializer(data = product_data, many =True)
data = {}
if serialized_data.is_valid():
data['status']='valid'
else:
data['status']='not valid'
return Response(data)
You were using the DRF serializer in the wrong way, try to use the instance argument, instead of data while serializing the data.
#api_view(['GET'])
def get_data(request):
product_qs = Product.objects.all()
serializer = ProductSerializer(instance=product_qs, many=True)
return Response(serializer.data)
I am trying to allow user to update the name of Lists they have created. However, when I attempt to POST the data I return the following error:
user: ["This field is required."]
I have racked my brain trying to solve this, hardcoded the username etc. but I keep turning up empty. I'd be grateful for some more expertise to assist me.
Here is my view:
class UpdateUserListViewSet(viewsets.ModelViewSet):
serializer_class = UserListSerializer
queryset = UserList.objects.all()
def update(self, instance, validated_data):
serializer_class = UserListSerializer
if self.request.method == "POST":
list_id = request.data.get('id')
user = UserList(user=self.request.user.id)
list_name = request.data.get('list_name')
data = {'id':int(list_id), 'list_name': list_name}
serializer = serializer_class(user, data=data)
if serializer.is_valid():
serializer.update()
return Response({'status' : 'ok'}, status=200)
else:
return Response({'error' : serializer.errors}, status=400)
And here is my serializer:
class UserListSerializer(serializers.ModelSerializer):
class Meta:
model = UserList
fields = ['id', 'user', 'list_name']
So actually you are trying to update with POST request, you have to check wheather your code had been reached inside this update function and if it is reaching there then you have to pass partial=True in update otherwise serailizer will try to validate all the required fields so for that you can change as :-
serializer = serializer_class(user, data=data, partial=True)
I want to do the following:
models.py
class MyModel(TimeStampedModel, models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
serializers.py
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (
'name',
)
And I would like to add as owner the current user in request.user.
Currently I am adding this in my view directly by uptading request.data with user and then pass the updated data to my serializer.
data = request.data
# Add owner to data
data["owner"] = request.user.pk
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
I would like to do this in my serializer directly but can't find a way to properly do it because it looks like data validation to me. Is this a good idea ? Should I keep this logic in my views or move it to my serializer ?
You can get a user from serializer context:
self.context['request'].user
It is passed from a method get_serializer_context which originally created in a GenericAPIView:
class GenericAPIView(APIView):
....
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
Inside a serializer you need to override a create method:
class MyModelSerializerCreate(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', )
def create(self, validated_data):
validated_data['owner'] = self.context['request'].user
return super(MyModelSerializerCreate, self).create(validated_data)
You could also override an update and delete methods if you need some special interactions with the User model.
Unfortunatly I dont have the reputation points to comment on #ivan-Semochkin post above, but should the last line not be:
return super(MyModelSerializerCreate, self).create(validated_data)
The solution from Ivan Semochkin did not work for me, as it never entered into the create() method of the serializer. As request.data field is immutable, you need to copy it and then extend it.
from django.http import HttpRequest
from rest_framework.request import Request
class MyModelViewSet(ModelViewSet):
def _extend_request(self, request):
data = request.POST.copy()
data['owner'] = request.user
request_extended = Request(HttpRequest())
request_extended._full_data = data
def create(self, request, *args, **kwargs):
request_extended = self._extend_request(request)
return super().create(request_extended, *args, **kwargs)
I am trying to make a simple api for marine vessels and ships and I am using django-rest-framework as the library to generate the api. With models that has simple fields like char fields and integers in the models level everything is working properly (create, delete, update, list, get).
The problem is with image fields whenever I try to post a record that has images it does not detect it. And it always causes an error "sequence item 1: expected string, NoneType found"
Below is my model, serializer, and view files.
serializer
class VesselSerializer(serializers.ModelSerializer):
image = serializers.ImageField(source='image')
class Meta:
model = Vessel
fields = ('image', 'id', 'vesselType', 'name')
class VesselTypeSerilizer(serializers.ModelSerializer):
class Meta:
model = VesselType
models
def vessel_file_name(instance, filename):
return '/'.join(['vessels', instance.id, filename]) #the error is in this line
class VesselType(models.Model):
name = models.CharField(max_length=50)
class Vessel(models.Model):
name = models.CharField(max_length=50)
vesselType = models.ForeignKey(VesselType)
image = models.ImageField(upload_to=vessel_file_name, null=True)
def __unicode__(self):
return u'%s' % self.name
views
class VesselList(generics.ListCreateAPIView):
queryset = Vessel.objects.all()
serializer_class = VesselSerializer
fields = ('url', 'id', 'image', 'vesselType')
def post(self, request, format=None):
print 'entered here'
print '%' * 10
print request.DATA
print request.FILES
print '%' * 10
serializer = VesselSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class VesselDetails(generics.RetrieveUpdateDestroyAPIView):
queryset = Vessel.objects.all()
serializer_class = VesselSerializer
It should be noted as well that the request.FILES and request.DATA when printed are displayed correctly yet that error appears which indicates that there is no file name although the name appears in request.FILES.
I am stuck in this problem for several hours and I can't seem to find what the problem is or what I am doing wrong. Any help would be appreciated.
The problem is that when vessel_file_name is called, the instance object is not saved to the db and instance.id is None.
I am using DRF to expose some API endpoints.
# models.py
class Project(models.Model):
...
assigned_to = models.ManyToManyField(
User, default=None, blank=True, null=True
)
# serializers.py
class ProjectSerializer(serializers.ModelSerializer):
assigned_to = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, many=True)
class Meta:
model = Project
fields = ('id', 'title', 'created_by', 'assigned_to')
# view.py
class ProjectList(generics.ListCreateAPIView):
mode = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
# get a list of user.id of assigned_to users
assigned_to = [x.get('id') for x in request.DATA.get('assigned_to')]
# create a new project serilaizer
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
"assigned_to": assigned_to,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
This all works fine, and I can POST a list of ids for the assigned to field. However, to make this function I had to use PrimaryKeyRelatedField instead of RelatedField. This means that when I do a GET then I only receive the primary keys of the user in the assigned_to field. Is there some way to maintain the current behavior for POST but return the serialized User details for the assigned_to field?
I recently solved this with a subclassed PrimaryKeyRelatedField() which uses the id for input to set the value, but returns a nested value using serializers. Now this may not be 100% what was requested here. The POST, PUT, and PATCH responses will also include the nested representation whereas the question does specify that POST behave exactly as it does with a PrimaryKeyRelatedField.
https://gist.github.com/jmichalicek/f841110a9aa6dbb6f781
class PrimaryKeyInObjectOutRelatedField(PrimaryKeyRelatedField):
"""
Django Rest Framework RelatedField which takes the primary key as input to allow setting relations,
but takes an optional `output_serializer_class` parameter, which if specified, will be used to
serialize the data in responses.
Usage:
class MyModelSerializer(serializers.ModelSerializer):
related_model = PrimaryKeyInObjectOutRelatedField(
queryset=MyOtherModel.objects.all(), output_serializer_class=MyOtherModelSerializer)
class Meta:
model = MyModel
fields = ('related_model', 'id', 'foo', 'bar')
"""
def __init__(self, **kwargs):
self._output_serializer_class = kwargs.pop('output_serializer_class', None)
super(PrimaryKeyInObjectOutRelatedField, self).__init__(**kwargs)
def use_pk_only_optimization(self):
return not bool(self._output_serializer_class)
def to_representation(self, obj):
if self._output_serializer_class:
data = self._output_serializer_class(obj).data
else:
data = super(PrimaryKeyInObjectOutRelatedField, self).to_representation(obj)
return data
You'll need to use a different serializer for POST and GET in that case.
Take a look into overriding the get_serializer_class() method on the view, and switching the serializer that's returned depending on self.request.method.