Permission depends on field django rest framework - django-rest-framework

I'm trying to write little project which is using django rest framework and I have the Book model and rest api for that:
#models.py
class Book(models.Model):
name = models.CharField(max_length=100)
language = models.CharField(max_length=3, choices=LANGUAGES)
publication_date = models.CharField(max_length=4)
class Meta:
verbose_name = 'Книга'
verbose_name_plural = 'Книги'
def __str__(self):
return self.name
The next serialiser:
#serializers.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=120)
language = serializers.CharField()
publication_date = serializers.CharField()
def create(self, validated_data):
return Book.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
instance.language = validated_data.get('language', instance.language)
instance.publication_date = validated_data.get(
'publication_date',
instance.publication_date
)
instance.save()
return instance
And the next view:
#views.py
class BookView(APIView):
http_method_names = ['get', 'post', 'put', 'delete']
def get(self, request):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response({"books": serializer.data})
def post(self, request):
book = request.data.get('book')
# Create an article from the above data
serializer = BookSerializer(data=book)
if serializer.is_valid(raise_exception=True):
book_saved = serializer.save()
return Response({"success": "Book '{}' created successfully".format(book_saved.name)})
def put(self, request, pk, *args, **kwargs):
saved_book = get_object_or_404(Book.objects.all(), pk=pk)
data = request.data.get('book')
serializer = BookSerializer(instance=saved_book, data=data, partial=True)
if serializer.is_valid(raise_exception=True):
book_saved = serializer.save()
return Response({
"success": "Book '{}' updated successfully".format(book_saved.name)
})
def delete(self, request, pk, *args, **kwargs):
book = get_object_or_404(Book.objects.all(), pk=pk)
book.delete()
return Response(
{"message": "Book with id `{}` has been deleted.".format(pk)},
status=204
)
How I can to implement permission which depends on publication_date field, literally if publication_date more than current date, then get request is available only for admins.

Instead of using Book.objects.all() everywhere, define a function:
def get_queryset(self, request):
if request.user.is_superuser:
return Book.objects.all()
else:
return Book.objects.filter(publication_date__lte=datetime.datetime.now())
Then call it like self.get_queryset(request).
This will only make books published earlier than right now to be visible to non-admins.
Also, you have a lot of boilerplate code. You should probably be using ModelViewSet

Related

Django REST - PUT and PATCH create new object instead of updating

I'm creating a basic Django REST API that will work with a catalogue of products. I have to check every time a non-registered user GETS a product and add to the current value. And the GET and DELETE work perfectly fine. The problem is that whenever I want to update a product, a new Product is created instead.
I have 2 models:
class Brand(models.Model):
brand_name = models.CharField(max_length=500)
def __str__(self):
return self.brand_name
class Product(models.Model):
sku = models.PositiveIntegerField(primary_key=True, validators=[MaxValueValidator(999999999999999)])
product_name = models.CharField(max_length=500)
product_price = models.DecimalField( max_digits=50, decimal_places=2)
product_brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
times_searched_anonymous = models.PositiveIntegerField(default=0)
def __str__ (self):
return self.product_name + ' (' + self.product_brand.brand_name + ')' + ' - $' + str(self.product_price)
These are my serializers:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = [
"sku",
"product_name",
"product_price",
"product_brand",
"times_searched_anonymous"
]
class BrandSerializer(serializers.ModelSerializer):
class Meta:
model = Brand
fields = [
"brand_name",
"pk",
]
And this is the view I have:
class ProductDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Retrieve, update or delete a product instance.
"""
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_object(self, pk):
try:
return Product.objects.get(pk=pk)
except Product.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
product = self.get_object(pk)
serializer = ProductSerializer(product)
if not request.user.is_authenticated:
data2 = {'times_searched_anonymous': product.times_searched_anonymous + 1}
serializer2 = ProductSerializer(product, data=data2, partial=True)
if serializer2.is_valid():
serializer2.save()
return Response(serializer2.data)
return Response(serializer.data)
def put(self, request, pk, format=None):
product = self.get_object(pk)
serializer = ProductSerializer(product, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def patch(self, request, pk):
product = self.get_object(pk)
serializer = ProductSerializer(product, data=request.data,
partial=True) # set partial=True to update a data partially
if serializer.is_valid():
serializer.save()
return JsonResponse(code=201, data=serializer.data)
return JsonResponse(code=400, data="wrong parameters")
def delete(self, request, pk, format=None):
product = self.get_object(pk)
product.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Thougths?
in put method change line
serializer = ProductSerializer(product, data=request.data, partial=True)
to be serializer = ProductSerializer(instance=product, data=request.data, partial=False)
also in patch method change line
serializer = ProductSerializer(product, data=request.data, partial=True)
to be
serializer = ProductSerializer(instance=product, data=request.data, partial=False)

Update method of serializer only creates and doesn't update (Django rest framework)

I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.

AttributeError: 'collections.OrderedDict' object has no attribute 'model_id' and 'model_id' is missing from visible fields

Something strange happened: I was defining an endpoint and initially two fields were visible in the API form: model_id and payload, as given in the model definition:
### models.py:
class CarModel(models.Model):
model_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.model_id
class Calculator(models.Model):
model = models.ForeignKey(CarModel, on_delete=models.CASCADE)
payload = models.TextField()
def model_id(self):
return self.model.model_id
def __str__(self):
return f"Calculations for {self.model.name}"
### serializers.py:
class CalculatorSerializer(serializers.ModelSerializer):
model_id = serializers.SerializerMethodField()
class Meta:
model = Calculator
fields = ['model_id', 'payload']
def get_model_id(self, obj):
return obj.model_id()
### views.py:
class CalculatorViewSet(viewsets.ModelViewSet):
serializer_class = CalculatorSerializer
queryset = Calculator.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(f"{serializer.data.upper()}", status=status.HTTP_200_OK)
So, both fields were visible, but POST requests ended in the AttributeError: 'collections.OrderedDict' object has no attribute 'model_id'. Trying to fix that, I eventually and accidentally removed model_id from view - it doesn't display in DRF's forms. And the AttributeError still persists.
What is wrong with this piece of code?
OK, it turns out that defining fields in this manner:
fields = '__all__'
makes also the model_id visible. Still, no idea why explicit insert doesn't work.
In case of the other issue, the AttributeError, I had to pull the value out of an OrderedDict. Modified method looks like this:
def get_model_id(self, obj):
return obj["model"].model_id
Beside that, I found one more error inside views.py's create method: serializer.data won't implement upper() method; some key, in my case serializer.data['payload'], has to be referenced, so for example:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
res = {
"payload": f"{serializer.data['payload'].upper()}"
}
return Response(res, status=status.HTTP_200_OK)

django rest framework - override viewsets.ViewSet#retrieve in order to use 2 parameters for lookup resource

I'd like to use two model's attributes to lookup a Route's record
models.py
class Route(DateTimeModel):
start_poi = models.ForeignKey(Poi, related_name="start_pois", on_delete=models.CASCADE)
end_poi = models.ForeignKey(Poi, related_name="end_pois", on_delete=models.CASCADE)
...
def __str__(self):
return 'From %s to %s' % (self.start_poi.slug, self.end_poi.slug)
views.py
class RouteViewSet(viewsets.ViewSet):
http_method_names = ['get']
#staticmethod
def list(request):
queryset = ...
serializer = RouteSerializer(queryset, many=True)
return Response(serializer.data)
#staticmethod
def retrieve(request, from_poi_slug, to_poi_slug):
queryset = ...
route = get_object_or_404(queryset, from_poi_slug=from_poi_slug, to_poi_slug=to_poi_slug)
serializer = RouteSerializer(route)
return Response(serializer.data)
urls.py
urlpatterns.extend([
path(rf'{BASE_API_PATH}/routes/(?P<from_poi_slug>[-\w]+)/(?P<to_poi_slug>[-\w]+)', RouteViewSet),
])
I get
Not Found: /api/v1/routes/xyz/abc
[29/Apr/2019 10:07:01] "GET /api/v1/routes/molo-santa-maria/kennedy-ne HTTP/1.1" 404 13191
What am I missing?
How can I properly override #retrieve and correctly configure urls?
Since you are only using read operations, you could use ReadOnlyModelViewSet.
#views.py
class RouteViewSet(viewsets.ReadOnlyModelViewSet):
http_method_names = ['get']
def list(self, request, *args, **kwargs):
queryset = ...
serializer = RouteSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
from_poi_slug = kwargs['from_poi_slug']
to_poi_slug = kwargs['to_poi_slug']
queryset = ...
route = get_object_or_404(queryset, from_poi_slug=from_poi_slug, to_poi_slug=to_poi_slug)
serializer = RouteSerializer(route)
return Response(serializer.data)
and in your urls.py
urlpatterns.extend([
path(rf'{BASE_API_PATH}/routes/(<from_poi_slug>)/(<to_poi_slug>)/', RouteViewSet.as_view({"get": "retrieve"})),
])

multiple object creation on a single API call on django

i have to create multiple object creation on a single API call.My
serializer class is like this
def create(self, validated_data):
dt = datetime.now()
strg = '{:%d%m%Y%H%M%S}'.format(dt, dt.microsecond // 1000)
hat= "REQ" + strg
creater = dot_order_models.DotOrder(reqnum=hat,**validated_data)
creater.save()
return creater
class Meta:
model = dot_order_models.DotOrder
fields = ('merchant', 'biker','batch','customer_name','contact','city','locality','order_amount')
i just given many=true but did not work. then i give
def __init__(self, *args, **kwargs):
many = kwargs.pop('many', True)
super(BikerSerializer, self).__init__(many=many, *args, **kwargs)
this too didn't work.how can i solve it using serializers and view class.
thanks in advance
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
results = dot_order_models.DotOrder.objects.all()
output_serializer = dot_order_serializers.DotOrderSerializer(results, many=True)
return super(DotOrderViewSet, self).create(request)

Resources