Check a field in model in generics.RetrieveDestroyAPIView - django

I am writing a rest API. this is my view:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
and this is its model:
class Order(models.Model):
product = models.ForeignKey(Product)
customer = models.ForeignKey(Customer, null=True)WAITING = 'WA'
PREPARATION = 'PR'
READY = 'RD'
DELIVERED = 'DV'
STATUS_CHOICES = (
(WAITING, 'waiting'),
(PREPARATION, 'preparation'),
(READY, 'ready'),
(DELIVERED, 'delivered'),
)
status = models.CharField(
max_length=2,
choices=STATUS_CHOICES,
default=WAITING,
)
and:
class Customer(models.Model):
name = models.CharField(max_length=40)
customer_email = models.EmailField()
def __str__(self):
return self.name
and this is its serializer:
class OrderDeleteSerializer(ModelSerializer):
class Meta:
model = Order
fields = '__all__'
What should I do if I want the object(order) can be deleted, only when the status field is 'waiting' ?

You can implement some checks, in the destroy and return an error message as response in case the constraints are not met:
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
queryset = Order.objects.all()
serializer_class = OrderDeleteSerializer
# permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.status != Order.WAITING:
return JsonResponse(
status=412,
data={'status':'false',
'message': 'status should be WAITING'}
)
super(OrderDeleteAPIView, self).destroy(request, *args, **kwargs)
Of course you can return any sort of answer (not per se a JSON response, nor does the status has to be 412). Usually HTTP status code 412 means that 412 Precondion failed.

The question is not about permissions, but it is a close situation.
If you want to check permissions, there is a way to do this via overridden check_permissions:
from functools import lru_cache
from rest_framework.exceptions import PermissionDenied
class OrderDeleteAPIView(generics.RetrieveDestroyAPIView):
serializer_class = OrderDeleteSerializer
permission_classes = (OwnerCanManageOrReadOnly,)
lookup_field = 'id'
#lru_cache
def get_object(self, *args, **kwargs):
return Order.objects.get(self.kwargs['order_id'])
def check_permissions(self, request):
# this method will call OwnerCanManageOrReadOnly first
super().check_permissions(request)
# and then do other checks
instance = self.get_object()
if instance.owner != request.user:
raise PermissionDenied()
As you can notice, I use #lru_cache to cache get_object. The reason to use it is because after check_permissions there will be another method (in this case retrieve or destroy) which will call this method again. To reduce database requests count I use caching.

Related

How we can use model objects in ModelSerializer?

I have to create an endpoint to make a customer subscribes to a chef.
The subscription model has two FK fields chef and customer.
model/subscriptionSerializer:
class Subscription(models.Model):
chef = models.ForeignKey(Chef, on_delete=models.CASCADE, db_column="chef")
customer = models.ForeignKey(User, on_delete=models.CASCADE, db_column="customer")
I created this serializer:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = serializers.SerializerMethodField("get_chef")
customer = serializers.SerializerMethodField("get_customer")
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
def get_chef(self, *args):
return Chef.objects.get(pk=self.context.get("chef"))
def get_customer(self, *args):
return User.objects.get(pk=self.context.get("customer"))
I want to have one endpoint like this: customer/chef/<int:chef_id>/subscribe
So in view.py I used ModelViewSet:
class CustomerSubscribeChefView(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = SubscriptionSerializer
def get_serializer_context(self):
context = super(CustomerSubscribeChefView, self).get_serializer_context()
context.update({"chef": self.kwargs['chef_id']})
context.update({"customer": self.request.user.id})
return context
def get_queryset(self):
return self.request.user.subscription_set
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(
{"msg": "Chef is subscribed!"}, status=status.HTTP_201_CREATED
)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response({"msg": "Chef is unsubscribed!"}, status=status.HTTP_200_OK)
when I try to send a post request I got this error:
django.db.utils.IntegrityError: NOT NULL constraint failed: core subscription. chef _id
I've tried to use my custom serializer so I've updated SubscriptionSerializer to be like this:
class SubscriptionSerializer(serializers.ModelSerializer):
chef = ChefListSerializer
customer = CustomerSerializer
class Meta:
model = Subscription
fields = [
"id",
"chef",
"customer",
]
But I don't know how to serialize chef and customer objects.
What is the problem with my approach, and how I can fix this?
Update
I changed the subscription serializer to be like this
class SubscriptionSerializer(serializers.ModelSerializer):
chef = Chef()
customer = User()
def __init__(self, *args, **kwargs):
context = kwargs.pop('context')
self.chef = Chef.objects.get(pk=context.get("chef"))
self.customer = User.objects.get(pk=context.get("customer"))
super().__init__(*args, **kwargs)
class Meta:
model = Subscription
fields = ["id", "chef", "customer"]
But I still get the same error.

Django Rest Framework: validate HiddenField CurrentUserDefault property

I am using DRF to create an API in a single page application.
I have a customer user class to which I have only added a is_manager flag and a managerEntity model where users that have the is_manager flag as True can create managerEntities becoming owners of them.
The problem is that I can't seem to figure out how to validate the data from the serializer before create method to check whether the is_manager is set or not. If set, the managerEntity should be created, if not, raise an exception.
class DeepmetricsUser(AbstractUser):
is_manager = models.BooleanField(default=False)
class managerEntity(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
team = models.ManyToManyField(get_user_model(), blank=True)
views.py
class managersEntityViewSet(viewsets.ModelViewSet):
queryset = managerEntity.objects.all()
serializer_class = managerEntityModelSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return self.queryset.filter(Q(owner = self.request.user) | Q(team=self.request.user.id))
def create(self, request, *args, **kwargs):
serializer = managerEntitySerializer(data=request.data, context={"request": self.request})
serializer.is_valid(raise_exception=True)
res = serializer.save()
data = managerEntityModelSerializer(res).data
return Response(data, status=status.HTTP_201_CREATED)
serializer.py
class managerEntitySerializer(serializers.Serializer):
name = serializers.CharField(max_length=255)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
def create(self, data):
res = managerEntity.objects.create(**data)
return res
You need to override the validate method in Serializer
def validate(self, attrs):
if not self.context["request"].user.is_manager:
raise serializers.ValidationError("Validation error")
return attrs
I found a solution that fits better my needs by using permissions. The answer provided by Shakeel is correct as I asked for validation and that should be done as he suggested but, what I really wanted to do was checking for enough clearance for the user to manipulate a resource, then, permissions is what's best fits:
class createManagerEntity(BasePermission):
message = "Not enough privilegies"
def has_permission(self, request, view):
return request.user.is_manager

'Response' object has no attribute 'user'

I am getting error AttributeError: 'Response' object has no attribute 'user' for the below code I have written
I am trying to get the user info from the context and create a notification model. I am getting the above error while returning the statement. I don't understand why I am getting this error
Model
class CourseNotification(models.Model):
uid = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False,
unique=True)
course = models.ForeignKey('Course.Course', on_delete=models.SET_NULL, null=True)
user = models.ManyToManyField('Profile.myUser',null=True)
def get_user(self):
return [i for i in self.user.all()]
def __str__(self):
return self.course.course_title
View
class CourseNotificationView(ModelViewSet):
queryset = CourseNotification.objects.all()
serializer_class = CourseNotificationSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_queryset(self):
if self.request.user.email is not None:
profile = myUser.objects.get(email=self.request.user.email)
if profile is not None:
notification = CourseNotification.objects.filter(user=profile)
return notification
else:
return Response(data={"User": "Unauthorized User"}, status=HTTP_401_UNAUTHORIZED)
def retrieve(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(data=serializer.data)
Serializer
class CourseNotificationSerializer(serializers.ModelSerializer):
class Meta:
model = CourseNotification
fields = '__all__'
def create(self, validated_data):
users = self.context['request'].user
subject = validated_data['course']
if users is None and subject is None or subject == "":
raise serializers.ValidationError({"Invalid": "Subject could not be Invalid"})
checkNotification = self.checkNotification(users, subject)
if checkNotification is not None and checkNotification.status_code == 200:
return checkNotification
validate_subject = self.validateSubject(users, subject)
if validate_subject.status_code == 200:
return validate_subject
get_data = CourseNotification.objects.create(course=subject)
get_data.user.add(users)
get_data.save()
return Response(data=get_data, status=HTTP_201_CREATED, content_type="application/json")
#staticmethod
def checkNotification(users, subject):
get_data = CourseNotification.objects.filter(user=users, course=subject)
if get_data:
for data in get_data:
data.user.remove(users)
data.save()
return Response(data=get_data, status=HTTP_200_OK, content_type="application/json")
#staticmethod
def validateSubject(users, subject):
get_data = CourseNotification.objects.filter(course=subject).exclude(user=users)
if get_data:
subject = CourseNotification.objects.get(course=subject)
subject.user.add(users)
subject.save()
return Response(data=get_data, status=HTTP_200_OK, content_type="application/json")
I am trying to add data to the model through API. I am facing the problem

view count increase in DRF

I need to increase the view count with 1 on each refresh. I'm not sure about the method since I'm new to DRF. Thanks in advance.
models.py
class Module(models.Model):
name = models.CharField(max_length=250, default="")
view_count = models.IntegerField(default=0)
serializers.py
class ModuleSerializer(serializers.ModelSerializer):
class Meta:
model = Module
fields = "__all__"
views.py
class ModuleView(generics.ListAPIView):
queryset = Module.objects.all()
serializer_class = ModuleSerializer
def get(self, request):
obj = self.get_object
print(obj)
obj.view_count = obj.view_count + 1
obj.save(view_count="view_count")
return super().get(request)
i implemented view count with F expression because a view count is basicly a race condidition and the docs clearly state that
Avoiding race conditions using F()
Documentation
class ElonDetail(generics.RetrieveAPIView):
queryset = Elon.objects.all()
serializer_class = ElonDetailSerializer
lookup_field = 'slug'
#retrieve
def retrieve(self, request, *args, **kwargs):
obj = self.get_object()
print(obj)
obj.view = obj.view + 1
obj.save(update_fields=['view',])
serializer = self.get_serializer(obj)
return Response(serializer.data, status=200)
here i used RetrieveAPIView

Getting and saving value from POST with nested serializer

class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = EventInfo
fields = ('email', 'pin')
class EventSerializer(DataSerializer, GeoModelAPIView):
# other fields
event_info = InfoSerializer(read_only=True)
def create(self, validated_data):
event_info = validated_data.pop('event_info', {})
event = super().create(validated_data)
EventInfo.objects.create(event=event, **event_info)
return event
Model
class EventInfo(models.Model):
pin = models.CharField(max_length=60, null=False, blank=False)
email = models.EmailField()
event = models.ForeignKey(Event)
POST
{
# other data
"event_info": {
"email": "example#example.com",
"pin": "1234567890"
}
}
So I have a model that is not visible on the browsable API, but I want to be able to save data from POST request to that model. Using this code I can create the objects and it correctly links the info to a correct Event model. However the email and pin fields won't get saved. What I have figured out is that the 'event_info' data from the POST is not visible on the validated_data.
The validation goes to the DataSerializer's validation method but I guess that I should somehow bypass the validation for just the 'event_info' data?
Edit:
class EventViewSet(BulkModelViewSet, JSONAPIViewSet):
queryset = Event.objects.filter(deleted=False)
queryset = queryset.select_related('location')
queryset = queryset.prefetch_related(list of related fields)
serializer_class = EventSerializer
filter_backends = (EventOrderingFilter, filters.DjangoFilterBackend)
filter_class = EventFilter
ordering_fields = (fields to order by)
ordering = ('-last_modified_time',)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
def get_serializer_context(self):
context = super(EventViewSet, self).get_serializer_context()
context.setdefault('skip_fields', set()).update(set([
'headline',
'secondary_headline']))
return context
#atomic
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
if isinstance(serializer.validated_data, list):
event_data_list = serializer.validated_data
else:
event_data_list = [serializer.validated_data]
super().perform_create(serializer)