Django Rest Framework problem in CreateAPIView - python

I have a project with Courses and Sections in it. I am building this project with django rest framework and i have the function that should create a section with 3 fields: course, title and creator. I want to understand how i can take a course slug from url and put it in course field, i mean don't pick course manually. How to implement that?
models.py
class CourseSections(models.Model):
creator = models.ForeignKey(User,related_name='creator_sections',on_delete=models.CASCADE,null=True)
title = models.CharField(max_length=50)
course = models.OneToOneField(Course, related_name='course_section', on_delete=models.CASCADE,null=True)
serializers.py
class CourseSectionSerializer(serializers.ModelSerializer):
class Meta:
model = CourseSections
fields = ['creator', 'title', 'course']
def create(self, validated_data):
instance = self.Meta.model(**validated_data)
request = self.context.get('request')
if request and hasattr(request, 'user'):
user = request.user
instance.save()
return instance
views.py
class SectionsCreateAPIView(CreateAPIView):
queryset = CourseSections.objects.all()
serializer_class = CourseSectionSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsAdminOrReadOnly]
lookup_field = 'slug'
lookup_url_kwarg = 'course__slug'
def perform_create(self, serializer):
serializer.save(creator=self.request.user)
urls.py
url(r'^sections/(?P<course__slug>[-\w]+)/create/$', SectionsCreateAPIView.as_view(), name='create_sections'),

Related

how to filter many-to-many fields in django models

I have 3 models: User , Tag and Recipe.
User model is so basic and it's not important.
Here is the Tag model:
class Tag(models.Model):
name = models.CharField(max_length=255)
user = models.ForeignKey(settings.AUTH_USER_MODEL , on_delete=CASCADE)
And here is the Recipe model:
class Recipe(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL , on_delete=CASCADE)
title = models.CharField(max_length=255)
tags = models.ManyToManyField('Tag',)
I made endpoint for all of these models but there is one problem!
When I try to create a Recipe object, all of Tag objects will be listed, But I want to list just the logged in user tags.
Here is my Tag serializer:
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = models.Tag
fields = ('id' , 'name')
extra_kwargs = {
'id' : {
'read_only' : True,
}
}
And here is my Tag viewset:
class TagsView(viewsets.GenericViewSet,mixins.CreateModelMixin,mixins.ListModelMixin):
queryset = models.Tag.objects.all()
serializer_class = serializers.TagSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return self.queryset.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
How can I filter tags, so that all listed tag objects belong to the logged in user?
A queryset is created only once, that is when you start the server. So the queryset attribute is evaluated when the process starts that is when your page renders which causes the class TagsView view to execute.
A get_queryset method is called upon every request, which can be particularly useful if you want to adjust the query dynamically, which I think you are trying to do here.
So the get_queryset is a method that is supposed to be used in place of queryset variable as an alternative to it, providing more flexibility.
You really don't need both of them together, which might be causing your problem.
You can just do it this way.
class TagsView(RecipeAttrsView):
serializer_class = serializers.TagSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return models.Tags.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)

How to get object_list of another user using ListView in Django?

I am new to Django and I am creating a simple blog web application. I would like to get the blog post of another user (not the user that is Authenticated) using the get_queryset Method. I tried the script below but, it shows an empty list on the template. I am able to use get_queryset to show all the blogpost, but my main concern is to show all the blogpost of a specific user (not the user that is authenticated)
View.py
class OtherUserProfileView(LoginRequiredMixin, ListView):
model = Post
template_name = "core/otheruser.html"
def get_queryset(self):
queryset = super(OtherUserProfileView, self).get_queryset()
queryset = queryset.filter(pk=self.user.id)
return queryset
Model.py
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=250)
content = models.TextField()
created = models.DateTimeField(auto_now_add=True)
publish = models.BooleanField(blank=True, default=False)
def __str__(self):
return self.title
You can pass the id of the user that you want to filter the queryset by in the url pattern
urlpatterns = [
path('profile/<int:user_id>/', views.OtherUserProfileView.as_view(), name='profile'),
]
In your view you can access the user_id from the path via self.kwargs['user_id'] and use this to filter your queryset
class OtherUserProfileView(LoginRequiredMixin, ListView):
model = Post
template_name = "core/otheruser.html"
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(user_id=self.kwargs['user_id'])
return queryset

Update ManytoMany relationship in Django Rest Framework

In my django application I have a ManytoMany relationship between Orders and Packages. An order can have multiple packages. I want to know about the update and create methods
Models.py
class Package(models.Model):
prod_name = models.CharField(max_length=255, default=0)
quantity = models.IntegerField(default=0)
unit_price = models.IntegerField(default=0)
class Orders(models.Model):
order_id = models.CharField(max_length=255, default=0)
package = models.ManyToManyField(Package)
is_cod = models.BooleanField(default=False)
Serializers.py
class PackageSerializer(serializers.ModelSerializer):
class Meta:
model = Package
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
class Meta:
model = Orders
fields = "__all__"
Views.py
class OrdersCreateAPIView(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = OrderSerializer
def post(self, request):
serializer = OrderSerializer(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)
Is that sufficient to handle the related data? I am trying to understand ManytoMany relationship both in Django as well as DRF so please explain if I need to change the Models or views in anyway
Update:
I have updated my serializer as well as view in order to create manytomany related objectslike this:
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
class Meta:
model = Orders
fields = "__all__"
def create(self, validated_data):
package_data = validated_data.pop('package')
pkgs = []
order = Orders.objects.create(**validated_data)
for i in package_data:
try:
p = Package.objects.create(**i)
pkgs.append(p)
except:
pass
order.package.set(pkgs)
return order
Views.py
class OrdersCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = OrderSerializer
def perform_create(self,serializer):
serializer.save(owner=self.request.user)
However I am still unclear about overriding the update method of RetrieveUpdateDestroyAPIView. Also, Is the above method is the right method to store M2M related objects ?
Please help with the update part of the serializer, I understand that I have to pass the query in the serializer
Working codebase
#serializers.py
class PackageSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = Package
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
package = PackageSerializer(many=True)
def get_or_create_packages(self, packages):
package_ids = []
for package in packages:
package_instance, created = Package.objects.get_or_create(pk=package.get('id'), defaults=package)
package_ids.append(package_instance.pk)
return package_ids
def create_or_update_packages(self, packages):
package_ids = []
for package in packages:
package_instance, created = Package.objects.update_or_create(pk=package.get('id'), defaults=package)
package_ids.append(package_instance.pk)
return package_ids
def create(self, validated_data):
package = validated_data.pop('package', [])
order = Orders.objects.create(**validated_data)
order.package.set(self.get_or_create_packages(package))
return order
def update(self, instance, validated_data):
package = validated_data.pop('package', [])
instance.package.set(self.create_or_update_packages(package))
fields = ['order_id', 'is_cod']
for field in fields:
try:
setattr(instance, field, validated_data[field])
except KeyError: # validated_data may not contain all fields during HTTP PATCH
pass
instance.save()
return instance
class Meta:
model = Orders
fields = "__all__"
#views.py
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
queryset = Orders.objects.all()
Register this view with the help of DefaultRouter as,
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'order', OrderViewSet, basename='order')
urlpatterns = [
] + router.urls
Thus you will get basic CRUD end-points as described in that table (see the DefaultRouter ref).
Let your order list end-point be /foo-bar/order/
HTTP POST to /foo-bar/order/ to create a new instance
HTTP PUT or HTTP PATCH to /foo-bar/order/<ORDER_PK>/ to update the content
Note
In this case, you should pass the id value of package if you wish to map an existing package relation with the Order
References
DRF ModelVieSet
Django get_or_create(...)
Django create_or_update(...)
Django M2M set(...)
DRF DefaultRouter
UPDATE-1
You can wire-up the view like this
urlpatterns = [
path('foo/order/', OrderViewSet.as_view({'post': 'create'})), # create new Order instance
path('foo/order/<int:pk>/', OrderViewSet.as_view({'patch': 'partial_update'})), # update Order instance
]
Note: This supports only HTTP POST and HTTP PATCH

How to associate user to a post request in Django drf

I have the following :
I am working with DRF, based JWT token.
I want to associate an experiment with a USER, i.e when a post request is arriving I want to be able to save that post request with the Foreginkey it needed for the author by the user whom sent the request.
The POST request is always authenticated and never anonymous, i.e request.user is always exist ( I can see it when debugging)
I tried to add the following
def create(self, request, **kwargs):
request.data["author"] = request.user
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save()
return....
But is_valid return always False ( the only time ts was true, was when I took out the author from the ExperimentsSerializers fields....
will be happy for any leads....
my code attached below
Model.py:
class User(AbstractUser):
pass
def __str__(self):
return self.username
class Experiments(models.Model):
name = models.CharField(max_length=40)
time = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
View.py:
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
serializer_class = ExperimentsSerializers
queryset = Experiments.objects.all()
filterset_fields = '__all__'
permission_classes = (permissions.IsAuthenticated,)
serializers.py
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
You can just pass additional data with save arguments:
def create(self, request, **kwargs):
serializer = ExperimentsSerializers(data=request.data)
if serializer.is_valid():
serializer.save(author=request.user)
Note that you may need to specify author field as read_only so it would not be required in request body:
class ExperimentsSerializers(serializers.ModelSerializer):
class Meta:
model = models.Experiments
fields = '__all__'
read_only_fields = ['author']
One more approach can be to use
HiddenField with default value set to CurrentUserDefault
This way that field will not be exposed at the same time current user will be accessible and other operations can be done on that user context.
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
Something like this:
class ExperimentsSerializers(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = models.Experiments
fields = '__all__'
Reference :
HiddenField - https://www.django-rest-framework.org/api-guide/fields/#hiddenfield
CurrentUserDefault - https://www.django-rest-framework.org/api-guide/validators/#currentuserdefault

Django REST and ModelViewSet filtering

I was previously using APIViews such as the following:
views.py
class AllProgramsApi(APIView):
def get(self, request):
user = self.request.user
userprograms = Program.objects.filter(user=user)
serializer = ProgramSerializer(userprograms, many=True)
return Response(serializer.data)
here's my model:
class Program(models.Model):
program_name = models.CharField(max_length=50)
program_description = models.CharField(max_length=250)
cycles = models.ManyToManyField(Cycle)
is_favourite = models.BooleanField(default="False")
user = models.ForeignKey(User, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse('programs:program', kwargs={'pk': self.pk})
def __str__(self):
return self.program_name
Now I've discovered ModelViewSet, which looks very convenient, but I can't seem to be able to filter for the user as I was previously doing in the APIView.
my attempt at views.py with ModelViewSet is the following and it works but I get all the content and not just the content related to a single user.
class AllProgramsApi(ModelViewSet):
serializer_class = ProgramSerializer
queryset = Program.objects.all()
How can I tweak the ModelViewSet so that it displays only the content related to the user who sends the request? What is the best method?
Thanks.
You can use get queryset method,if you know more refer the doc Filtering against the current user
class AllProgramsApi(ModelViewSet):
serializer_class = ProgramSerializer
queryset = Program.objects.all()
def get_queryset(self):
queryset = self.queryset
query_set = queryset.filter(user=self.request.user)
return query_set
there are permission_classes in django you can add permissions as per your requirements or you can create custom permissions
you would get better idea from django permission
or you can create your queryset by defining get_queryset method.

Categories

Resources