How to Override CreateModelMixin corrctly - django

I want to override CreateModelMixin with my own class which derived from ModelViewSet
this is the class i want to override
class CreateModelMixin:
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
in the first instinct, I want to override only the create function with a different serializer
but would it be more correct to override perform_create?
if someone can guide me how it is the most correct way to override that will be much of help
thank you

Related

How to set the Current user_id in the Django-Rest-Framework?

Alsomt all the answers are talking about how to set current user in the class with paraemter generics.ListCreateAPIView. But I'm just using APIView and I'm using function "post" to post the data.
Can someone tell me how can I set a current user_id in this file. And do I need to add something in the serializers.py too?
views.py
class CreateVideo(APIView):
permissions_classes = [IsAuthenticated]
parser_classes = [MultiPartParser, FormParser]
def post(self, request, format=None):
print(request.data)
serializer = VideoSerializer(data=request.data)
user=self.request.user.id
if serializer.is_valid():
serializer.save(user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I created a variable "user" with self.request.user.id in it and then I passed it to the serializer.save(user). And when I created new video on the frontend side. It me gave a Bad Reuest Error.
You can use:
serializer.save(user=request.user)
You need Pass request.user instead of self.request.user.
-make sure user logged in or not ??
def post(self, request, format=None):
print(request.data)
serializer = VideoSerializer(data=request.data)
user=request.user
if serializer.is_valid():
serializer.save(user=user)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Res
ponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Passing Model objects into function from viewset

I have a Django viewset, where I have a function, that sends an email, whenever the viewset, is used.
I override the create function, to send the email, where I have a function, that sends the email to the user.
I want to pass in some arguments, to the function, to display in the email (done with Django's template engine)
class ContactRequestViewSet(viewsets.ModelViewSet):
queryset = ContactRequest.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = ContactRequestSerializer
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
send_email()
return response
#function to send email
def send_email():
htmly = get_template('email.html')
d = {'company_name': 'dodo'} #i want this dictionary, to contain the attributes from the viewset
send_mail(
subject='Test email',
message='',
from_email='test#email.com',
recipient_list=['test#email.com'],
html_message= htmly.render(d)
)
right now I just have a sample placeholder as d but here I want to pass in attributes from the serializers/model, that the user provided, I tried passing in the serializer, and accessing its attributes, but I don't know how to do this the proper way
You can access response.data after this line
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs)
which will hold the serializer's data. Yet if you want an actual instance of your model this will not be sufficient and you will need to hack your way around a little bit. The CreateModelMixin that is used in ModelViewSet of django-rest-framework has the following methods:
class CreateModelMixin:
# ... more stuff here
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
so you could override the perform_create method to save the instance into an attribute of your ContactRequestViewSet like this:
def perform_create(self, serializer):
self.instance = serializer.save()
then in the create method you could do something like this:
def create(self, request, *args, **kwargs):
response = super(ContactRequestViewSet, self).create(request, *args, **kwargs) # This calls perform_create internally
send_email(self.instance)
return response

check changes before saving into database

I am using Django 1.11 and DRF 3.6.2 and just started developing an API...
I am trying to check what are the changes to be performed in the database with the data being sent.
class IndividualViewSet(viewsets.ModelViewSet):
"""Individual ViewSet."""
serializer_class = serializers.IndividualSerializer
queryset = models.Individual.objects.all()
def update(self, request, equipment_serial, pk=None):
queryset = models.Individual.objects.get(pk=pk)
serializer = serializers.IndividualSerializer(queryset, data=request.data["entities"][0])
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_200_OK)
return Response(status.HTTP_400_BAD_REQUEST)
def perform_update(self, serializer):
old_obj = self.get_object()
new_data_dict = serializer.validated_data
if old_obj.name != new_data_dict['name']:
# logic for different data
# ...
serializer.save()
However, with the code as above, the perform_update function is never being called by serializer.save() on the update function.
According to the docs, ModelViewSet is inherited from GenericAPIView and it has UpdateModelMixin, which should automatically calls the perform_update function before saving
My questions surrounds on why it is happen and how should I do in order to accomplish the desired behaviour.
This is because you are overriding the update method in your custom viewset. This is the original code for the UpdateModelMixin that the ModelViewSet mixes in:
class UpdateModelMixin(object):
"""
Update a model instance.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# refresh the instance from the database.
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
In this original version, perform_update is called whenever in the update method. If you override that method and still want to call perform_update, you need to put it there.

Django REST framework: restricting user access for objects

I'm trying to build a REST API for books:
/api/book
/api/book/{book_id}
A user should have access to his books only. The way I'm doing this now is by filtering the result using username i.e Book.objects.all().filter(owner=request.user)
views.py
class Book_List(APIView):
permission_classes=(permissions.IsAuthenticated)
def get(self, request, format=None):
**books= Book.objects.all().filter(owner=request.user)**
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BookSerializer(data=request.data)
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)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
#/book/{pk}
class Book_Detail(APIView):
permission_classes = (permissions.IsAuthenticated)
def get_object(self, pk, request):
try:
return Book.objects.get(pk=pk, owner=request.user)
except Playlist.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
book = self.get_object(pk, request)
serializer = BookSerializer(playlist, context={'request': request})
return Response(serializer.data)
def put(self, request, pk, format=None):
book= self.get_object(pk)
serializer = BookSerializer(playlist, data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
book= self.get_object(pk)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
serializers.py
class BookSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Book
fields = ('url', 'owner','title', 'created_date', 'shared_with', 'tracks')
class UserSerializer(serializers.HyperlinkedModelSerializer):
books = serializers.HyperlinkedRelatedField(many=True,view_name='playlist-detail', read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = User
fields = ('url', 'username', 'owner', 'books')
But is this the correct way?
Does Django Rest Framework provide any in-built solution for this?
Does the solution lie in permissions? If yes, then how do we set it for all objects created by a user (I understand the for getting a particular object we can put a permission check like obj.user==request.user). Am I right?
You could use the ModelViewset, which contains all the logic for the typical CRUD:
class BooksViewSet(ModelViewset):
serializer_class = BookSerializer
permission_classes=[permissions.IsAuthenticated, ]
def get_queryset(self):
return Books.objects.filter(owner=self.request.user)
def perform_create(self, serializer):
serializer.data.owner = self.request.user
super(BooksViewSet, self).perform_create(serializer)

django-rest-framework return created object but with fewer fields

When creating and object in POST method how do you return only a few fields of created object? This is taken from the documentation:
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
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)
Here serializer.data includes all of the fields defined in serializer but I only want to return just a few of them.
Well I see multiple possibilites here:
First (my favorite), is send request object to serializer, then serializer will dynamically select your wanted fields:
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data,context={'request': request})
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)
Then inside serializer:
class SnippetSerializer(serializers.IDontKnow):
class Meta:
model = YourModel
fields = ('url', 'field1','field2','field3')
write_only_fields = ()
def __init__(self, *args, **kwargs):
super(SnippetSerializer, self).__init__(*args, **kwargs)
if self.context != {}:
request = self.context['request']
if request.method == 'POST':
self.write_only_fields = {'field1':self.fields['field1'], 'field3':self.fields['field3']}
This should make field1 and field3 only writeable, so they won't be returned.
Second, maybe easier solution is to define other serializer, unique for the post method:
def post(self, request, format=None):
serializer = PostSnippetSerializer(data=request.data)
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)
you can specify which field you dont wont to be retuned:
class PostSnippetSerializer(serializers.IDontKnow):
class Meta:
model = YourModel
fields = ('url', 'field1','field2','field3')
write_only_fields = ('field1','field3')
field1 and field 3 wont be returned in response,
Third way, is to directly create your response in your view:
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
results = {'url': serializer.data['url'],'field2':serializer.data['field2']}
return Response(results, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This will return only url and field2, so field1 and field3 will be saved but not returned.
For anyone else having the same question, this is what Tom Christie has said:
Either:
Consider using write_only=True on the fields you don't want as output.
Use a different serializer for returning the response to the one you use for validation.
Just return the response data directly, without using a serializer.