DRF update model with many-to-many field - django

I have two models with many-to-many relationship and I also have nested routers between them.
When I'm trying to create a tag at the endpoint api/page/4/tags/, tag is created in the database for tags, but nothing happend in my table for many to many relationship. How can I fix it?
I want to update my M2M everytime a new tag is created. Thank you
models.py
class Tag(models.Model):
title = models.CharField(max_length=30, unique=True)
class Page(models.Model):
...
tags = models.ManyToManyField(Tag, related_name='pages')
My serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class PageSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Page
fields = ['id', 'title', 'uuid', 'description', 'owner', 'is_private', 'tags']
views.py
class PageViewSet(viewsets.ModelViewSet):
queryset = Page.objects.all()
serializer_class = PageSerializer
def perform_update(self, serializer):
serializer.save(owner=self.request.user)
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
class NestedTagViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
def get_page(self, request, page_pk=None):
page = get_object_or_404(Page.objects.all(), pk=page_pk)
self.check_object_permissions(self.request, page)
return page
def create(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().create(request, *args, **kwargs)
def get_queryset(self):
return Tag.objects.filter(pages=self.kwargs['page_pk'])
def list(self, request, *args, **kwargs):
self.get_page(request, page_pk=kwargs['page_pk'])
return super().list(request, *args, **kwargs)
my urls.py
from django.urls import path, include
from rest_framework_nested import routers
from .views import PageViewSet, TagViewSet, NestedTagViewSet
router = routers.SimpleRouter()
router.register(r'pages', PageViewSet)
pages_router = routers.NestedSimpleRouter(router, r'pages', lookup='page')
pages_router.register(r'tags', NestedTagViewSet, basename='page-tags')
app_name = 'page'
urlpatterns = [
path(r'', include(router.urls)),
path(r'', include(pages_router.urls)),
]
I'm trying to create new tags at the api/ ^pages/(?P<page_pk>[^/.]+)/tags/$ [name='page-tags-list'] host

I will assume after you create a tag you want to add it to the current page in the route.
With this in mind, you have a tag viewset, so you need to override the create method when you are creating a tag.
class TagViewSet(viewsets.ModelViewSet):
serializer_class = TagSerializer
queryset = Tag.objects.all()
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
new_tag = Tag.objects.get(id=response["id"])
page = Page.objects.get(id=kwards["page_pk"])
page.tags.add(new_tag)
return response
The idea is to create a tag and after you create it, append it to the page M2M relationship. Hope this helps.

Related

partial update in django rest viewset(not modelviewset)

i am beginner in django and i was trying to learn work with djangorest viewset(not modelviewset)
and i cant find any resource to undrestand it.
i want to learn how to write a partial_update with viewset.
here is what i have tried:
models.py :
class UserIdentDocs(models.Model):
owner = models.ForeignKey(User,on_delete=models.CASCADE, related_name = "user_ident_Docs")
code = models.PositiveBigIntegerField(null=True)
img = models.ImageField(null=True)
video = models.FileField(null=True)
is_complete = models.BooleanField(default=False)
serializers.py:
class AdminUserIdentSerializer(serializers.ModelSerializer):
class Meta:
model = UserIdentDocs
fields = [
"owner",
"code",
"img",
"video",
"is_complete",
]
views.py:
from rest_framework.parsers import MultiPartParser, FormParser
class UserDocAdminViewSet(viewsets.ViewSet):
"""User docs admin view set"""
permission_classes = [AllowAny]
parser_classes = (MultiPartParser, FormParser)
serializer_class = AdminUserIdentSerializer
queryset = UserIdentDocs.objects.filter(is_complete=False)
def list(self, request):
serializer = self.serializer_class(self.queryset, many=True)
return Response(serializer.data)
def retrive(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object)
return Response(serializer.data)
def create(self, request ):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data,status=status.HTTP_201_CREATED)
def partial_update(self, request, pk=None):
doc_object = get_object_or_404(self.queryset, pk=pk)
serializer = self.serializer_class(doc_object,data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({"detail":"item updated succesfuly"}, status=status.HTTP_205_RESET_CONTENT)
urls.py:
urlpatterns = [
...
# admin viewset for docs
# list of accounts with is_complete=False
path("admin/userdocs/", UserDocAdminViewSet.as_view({"get":"list", "post":"create"}), name="user-docs-list"),
path("admin/userdocs/<int:pk>", UserDocAdminViewSet.as_view({"get":"retrive","patch":"partial_update"}), name="user-docs-detail"),
]
i can create user in browsable api but when i want to use partial update i can't even see the fields in browsable api and i only see this :
media type: multipart/form-data
content:this is what i see

Adding a url field linking to an action in serializer in DRF

I am using django rest framework. I want to include the url of an action defined in a view in its serializer.
My serializers.py:
from rest_framework import serializers
class CommentSerializer(serializers.ModelSerializer):
"""Serializer for comments."""
class Meta:
model = Comment
fields = ["id", "item", "author", "content", "date_commented", "parent"]
class ItemDetailSerializer(serializers.ModelSerializer):
"""Serializer for items (for retrieving/detail purpose)."""
category = CategorySerializer(many=True, read_only=True)
media = MediaSerializer(many=True, read_only=True)
brand = BrandSerializer(many=False, read_only=True)
specifications = serializers.SerializerMethodField(source="get_specifications")
comments = ??????????????????????????????????????????????????
class Meta:
model = Item
fields = [
"id",
"slug",
"name",
"description",
"brand",
"show_price",
"location",
"specifications",
"is_visible",
"is_blocked",
"created_at",
"updated_at",
"seller",
"category",
"media",
"comments",
"users_wishlist",
"reported_by",
]
read_only = True
editable = False
lookup_field = "slug"
def get_specifications(self, obj):
return ItemSpecificationSerializer(obj.item_specification.all(), many=True).data
My views.py:
from rest_framework import viewsets, mixins, status
from ramrobazar.inventory.models import Item, Comment
from ramrobazar.drf.serializers ItemSerializer, ItemDetailSerializer, CommentSerializer
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.filters import SearchFilter
from django_filters.rest_framework import DjangoFilterBackend
class ItemList(viewsets.GenericViewSet, mixins.ListModelMixin):
"""View for listing and retrieving all items for sale."""
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializer_action_classes = {
"retrieve": ItemDetailSerializer,
}
permission_classes = [IsAuthenticatedOrReadOnly]
lookup_field = "slug"
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = [
"category__slug",
"brand__name",
]
search_fields = ["name", "description", "category__name", "brand__name", "location"]
def get_serializer_class(self):
try:
return self.serializer_action_classes[self.action]
except:
return self.serializer_class
def retrieve(self, request, slug=None):
item = self.get_object()
serializer = self.get_serializer(item)
return Response(serializer.data)
#action(detail=True, methods=['GET'])
def comments(self, request, slug=None):
item = Item.objects.get(slug=slug)
queryset = Comment.objects.filter(item=item)
serializer = CommentSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I have an action named comments in the ItemList view which gives all the comments of a specific item. I can get the details of the item from the url /api/items/<slug>. I can get all the comments of the item from the url api/items/<slug>/comments. I want to include a comments field in the ItemDetailSerializer serializer which is a link to api/items/<slug>/commments. How can I accomplish this?
You can do this via SerializerMethodField and reverse:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.SerializerMethodField(source="get_comments")
...
def get_comments(self, obj):
return reverse(YOUR_URL_NAME, kwargs={'slug': obj.slug})
try this:
class ItemDetailSerializer(serializers.ModelSerializer):
...
comments = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
validated_data['comments'] = self.context['request'].build_absolute_uri() + 'comments'
return super(ContentGalleryListSerializer, self).create(validated_data)
You can use the HyperlinkedIdentityField for this:
comments = serializers.HyperlinkedIdentityField(
view_name='item-comments',
lookup_field='slug'
)
This will then render a comments field that contains a URL to the item-comments view, with the slug as lookup parameter.
You will need to register the view with the name item-comments, for example with:
urlpatterns = [
path('items/<slug>/comments/', ItemList.as_view({'get': 'comments'}), name='item-comments')
]
Note that normally nested routes are discouraged, and you'd put the comments in a separate view. But the above should work.

AttributeError: 'Request' object has no attribute 'query_param'

Views.py -
#api_view(['GET'])
def view_items(request):
if request.query_params:
items = Item.objects.filter(**request.query_param.dict()) #error line
else:
items=Item.objects.all()
serializer=ItemSerializer(items,many=True)
if items:
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
The **request.query_param should be changed into **request.query_params.
Let's say you want to filter by name.
#api_view(['GET'])
def view_items(request):
name = request.query_params.get("name", None)
if name:
items = Item.objects.filter(name=name)
I would suggest you to use django-filters, that's a better way to structure your code.
Create Item app
urls.py
app_name = "app-name"
router = SimpleRouter()
router.register("items", ItemViewSet, basename="item")
views.py
class ItemViewSet(views.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filterset_class = ItemFilterSet
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ("id", "name", "label")
filters.py
from django_filters import rest_framework as filters
class ItemFilterSet(filters.FilterSet):
class Meta:
model = Item
fields = ("name", "label")

Django Rest Framework ViewSets is returning selected records rather than all records in list view

I have a django application. I have one part of my app that is not working as I was expecting. As of now I am using the django rest framework view sets. What I want is two basically have 2 different list views in the same viewset. 1 that returns all of the accounts in the accountviewset and another list view that returns all of the accounts associated with a user based on the user foreignkey in the account model.
The code below returns all the acccounts associated with a specific person, but I am not able to have a list view that returns all of the accounts in the database.
class AccountViewSet(viewsets.ModelViewSet):
serializer_class = AccountSerializer
queryset = Account.objects.all()
lookup_field = 'user__username'
def list(self, request):
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get_object(self):
return self.queryset.get(user__username=self.kwargs.get('username'))
**UPDATE**
class AccountViewSet(viewsets.ModelViewSet):
queryset = Account.objects.all();
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('user', 'name', '_class')
lookup_field = 'user__username'
# def get_queryset(self):
# return self.queryset.filter(user__username=self.kwargs.get('username'))
it works when there is one account associated with one user.
does not work with more than 2 accounts with error:
MultipleObjectsReturned at /api/users/accounts/omarjandali/
get() returned more than one Account -- it returned 2!
other attempts doesnt return any accounts with a specific user or no user in the endpoint.
The biggest problem is that it returns an id of the user not user username.
current code:
serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'is_staff')
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('user', 'gender', 'phone', 'ip_address', 'dob_month', 'dob_day',
'dob_year', 'address_street', 'address_city', 'address_state',
'address_postal', 'address_country', 'profile_pic', 'role')
class FriendSerializer(serializers.ModelSerializer):
class Meta:
model = Friend
fields = ('requested', 'friended', 'status', 'blocked')
class AccountSerializer(serializers.ModelSerializer):
user = UserSerializer(
read_only = True,
)
class Meta:
model = Account
fields = ('user', '_id', 'name', 'balance', 'currency', 'bank_name',
'routing', '_class', 'type', 'active', 'main', 'synapse')
Views.py:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'username'
class ProfileViewSet(viewsets.ModelViewSet):
serializer_class = ProfileSerializer
queryset = Profile.objects.all()
lookup_field = 'user__username'
class FriendViewSet(viewsets.ModelViewSet):
serializer_class = FriendSerializer
querset = Friend.objects.all()
lookup_field = 'user__username'
class AccountViewSet(viewsets.ModelViewSet):
queryset = Account.objects.all();
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('user',)
urls:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user')
router.register(r'profiles', ProfileViewSet, basename='profile')
router.register(r'friends', FriendViewSet, basename='friend')
router.register(r'accounts', AccountViewSet, basename='account')
urlpatterns = router.urls
First, you have a lot of issues with your code. If you need to return a list, then you should filter in the get_object method. it is meant to only return one single instance, so do that in the get_queryset method. Secondly, you have the get_object() inside your list method which is wrong. Thirdly, assigning the serializer class in the list method doesn't make sense. It is assigned before that method is even called.
Now back to your question, 'list' is a viewset action and you can't implement it twice. Instead you can add an extra action on the list endpoint.
To do this, just add a method in the action method in the viewset.
In your particular case I would do this: I will introduce an instance variable, qs_type which is used to control the type of result returned by the get_queryset() method.
Something like this:
def __init__(self, *args, **kwargs):
self.qs_type = 'user_only'
super().__init__(*args, **kwargs)
def get_queryset(self):
if self.qs_type == 'user_only':
qs = Account.objects.filter(username=self.kwargs.get('username'))
else:
qs = Account.objects.all()
return qs)
#action(detail=False, methods=['get'])
def all(self, request, *args, **kwargs):
self.qs_type = 'all'
return self.list(request, *args, **kwargs)
This assumes that by default, username filtered version is returned while the all accounts will be returned in the new action. You can make qs_type a boolean if you are sure you won't need more similar actions but I made it a string just in case. I don't know how your url is configured so I don't really know how the username is retrieved from the url kwargs, but this code should help you solve your problem

django rest api - adding users to keyword model

i want to implement a feature in my rest api that users can add specific keywords for a news feed.
so if the users make a post request with a keyword within, the user object will be added on the predefined keyword (predefined in the database).
I have tried it with this code, but always if i try to simulate the post request with postman and i have this problem:
the keyword will be added but not the provided json data, its just a empty string and the post request returns also an empty keyword...
I hope you are able to help me and maybe you could give me an advice how to just allow the static keywords which are already defined and allow user only have a keyword once (no double keywords with same value)
Made with this headers:
[{"key":"Content-Type","value":"application/json","description":""}]
[{"key":"Authorization","value":"Token xxxxxxx","description":""}]
Body:
{
"name": "keyword1"
}
Authorization works, so the user added to the empty keyword
I am very new to django and i am doing this project to improve my skills, so please be lenient to me :) So it could be that its completly wrong, please give me some advices to solve my problem
These are the snippets for the implementation:
models.py
class Keywords(models.Model):
name = models.CharField(max_length=200)
user = models.ManyToManyField(User)
def __str__(self):
return self.name
serializers.py
class KeywordSerializer(serializers.Serializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'user')
def create(self, validated_data):
keyword = Keywords(**validated_data)
keyword.save()
keyword.user.add(self.context['request'].user)
return keyword
views.py
class KeywordAPI(generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
keyword = serializer.save()
return Response({
"name": KeywordSerializer(keyword, context=self.get_serializer_context()).data,
})
Try this snippet:
class KeywordSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keywords
fields = '__all__'
def create(self, validated_data):
user = validated_data.pop('user')
kw = Keywords.objects.create(**validated_data)
kw.user.add(user)
kw.save()
return kw
and views:
from rest_framework import viewsets, permissions
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keywords.objects.all()
The input payload be as
{
"name":"kw1"
}
NOTE
Here I used ModelSerializer class, because it's very handy for CURD applications and HiddenField is something like write_only=True parameter for fields.
References:
DRF - Modelviewset
HiddenField
CurrentUserDefault
There are few things you are doing wrong
First
Your model's name is Keywords it shouldn't be plural use Keyword and user field is ManyToMany so you should pluralise it
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
Second
You are using Serializer instead of ModelSerializer
class KeywordSerializer(serializers.ModelSerializer):
class Meta:
model = Keywords
fields = ('id', 'name', 'users')
read_only_fields = ('users',)
def create(self, validated_data):
keyword = super().create(validated_data)
keyword.users.add(self.context['request'].user)
return keyword
Third
You don't have to write creation logic yourself use existing mixins
from rest_framework import mixins
class KeywordAPI(mixins.CreateModelMixin, generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
For those who maybe have the same problem, here is the whole solution:
Thanks to Sardorbek Imomaliev and Jerin Peter George for helping me
serializer:
class KeywordSerializer(serializers.ModelSerializer):
users = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Keyword
fields = '__all__'
def create(self, validated_data):
users = validated_data.pop('users')
if Keyword.objects.filter(**validated_data).exists():
kw = Keyword.objects.filter(**validated_data).get()
kw.users.add(self.context['request'].user)
return kw
else:
raise serializers.ValidationError("This Keyword has not been set yet.")
model:
class Keyword(models.Model):
name = models.CharField(max_length=200)
users = models.ManyToManyField(User)
def __str__(self):
return self.name
view:
class KeywordAPI(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
serializer_class = KeywordSerializer
queryset = Keyword.objects.all()