I'm overriding get_queryset in a ModelViewSet to support "me" as filter and multiple pk search:
class UserViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
def get_queryset(self):
qs = UserProfile.objects.all()
if 'pk' in self.kwargs:
pk_user = self.kwargs['pk']
if ',' in pk_user: # Multiquery
pk_users = pk_user.split(',')
qs = qs.filter(pk__in=pk_users)
elif pk_user == "me":
qs = qs.filter(pk=self.request.user)
else:
qs = qs.filter(pk=pk_user)
# By default return all the items
return qs
I'm using the following serializer:
class UserProfileSerializer(serializers.ModelSerializer):
avatar_thumbnail_small = serializers.ImageField(read_only=True)
avatar_thumbnail_medium = serializers.ImageField(read_only=True)
id = serializers.CharField(source='user.id')
username = serializers.CharField(source='user.username')
firstname = serializers.CharField(source='user.first_name')
lastname = serializers.CharField(source='user.last_name')
class Meta:
model = UserProfile
fields = ('id', 'username', 'firstname', 'lastname', 'karma', 'avatar_thumbnail_small', 'avatar_thumbnail_medium', 'contacts', 'suggested_contacts')
and I've registered the url in urls.py
router = routers.DefaultRouter()
router.register(r'users', app.views_rest.UserViewSet, base_name="users")
but when I tried to GET the url /api/users/2,3/ or /api/users/me/ it gives a json message saying that detail is not found.
/api/users/2/, /api/users/3/ and /api/users/ works fine.
Thanks for the help.
This will not work because the retrieve action also calls the get_object after and you didn't overrode it. See https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/generics.py#L76
Related
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")
I need to get this endpoint /comments/int:post_id/
I can GET and POST comments and posts but I need to show all comments to specific post. I donĀ“t know how to connect it. My code look like
comment urls
urlpatterns = [
path('', views.CommentsView.as_view()),
path('<int:post_id>/', views.CreateCommentsView.as_view()),
]
comment view.py
# I get all comments/
class CommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
# Comments to specific post
class CreateCommentsView(ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
lookup_url_kwarg = 'post_id'
def perform_create(self,serializer):
post = self.kwargs.get('post_id')
post =set.get_queryset().filter(id = 'post_id')
post.comments.add(comment)
post = Post.objects.filter(id=self.kwargs.get('post_id'))
serializer.save(user=self.request.user, post=post)
comment serializer
from rest_framework import serializers
from .models import Comment
from django.contrib.auth import get_user_model
User = get_user_model()
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ['id', 'user', 'post', 'content', 'created']
class UserSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username']
class CommentSimpleSerializer(serializers.ModelSerializer):
user = UserSimpleSerializer()
class Meta:
model = Comment
fields = ['user', 'content', 'created']
post.view.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class LikePost(generics.UpdateAPIView):
permission_classes = [IsNotPostUser]
queryset = Post.objects.all()
serializer_class = PostSerializer
you can pass the post_id in url parameters for /comments/ endpoint and call HTTP GET method. Then in CommentsView you need to override the get_queryset method. Your implementation will be like
class CommentsView(ListCreateAPIView):
serializer_class = CommentSerializer
def get_queryset(self):
query = self.request.query_params.get('post_id', None)
if query is not None:
queryset = Comment.objects.filter(post_id=post_id)
else:
queryset = Comment.objects.all()
return queryset
This /comments/ endpoint will return all the comments and this one /comments/?post_id=1 will return comments only related to specified post.
I'm teaching my self how to use the Django REST api but I'm running into a JSONDecodeError. I'm trying to display posts depending on the user that is stored in the session. I'm storing the user username, password, and id in the session for later use.
The JSONDecodeError is targeting the line with instruction posts = posts_response.json() when I look at the HTML error page
Here's my views.py:
posts_path = "http://127.0.0.1:8000/posts/"
def index(request):
if request.method=='GET':
post_form = PostForm()
posts_response = requests.get(posts_path)
posts = posts_response.json()
context = {
'post_form': post_form,
'post_list': posts,
}
return render(request, 'UserLanding/index.html', context)
else:
return render(request, "UserLanding/index.html")
This is my viewsets:
class PostView(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_queryset(self):
query_set = self.queryset.filter(user=self.request.session['id'])
return query_set
class SubscriberView(viewsets.ModelViewSet):
queryset = Subscriber.objects.all()
serializer_class = SubscriberSerializer
My registered urls:
router = routers.DefaultRouter()
router.register('posts', viewsets.PostView)
router.register('users', viewsets.SubscriberView)
And serializers:
class SubscriberSerializer(serializers.ModelSerializer):
class Meta:
model = Subscriber
fields = ('id', 'username', 'password')
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('id', 'post_text', 'pub_date', 'user')
Please help me spot the problem. Thanks in advance
EDIT:
I was able to fix the problem by adding a function to my PostView code like this:
#action(detail=False, methods=['GET'], url_path=r'(?P<user_id>[0-9]+)')
def find_by_user_id(self, request, user_id):
user = Subscriber.objects.get(id=user_id)
posts = Post.objects.filter(user=user)
serializer = self.serializer_class(posts, many=True)
return Response(serializer.data)
and my request.get() now points to this:
posts_response = requests.get(posts_path + str(request.session['id']))
I've got a time obj serialized to DRF.
model:
class Time(TimeStampedModel):
user = models.ForeignKey(
'users.User', on_delete=models.CASCADE, related_name='time_logs')
amount = models.DurationField()
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
serializer:
class TimeSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Time
fields = ('__all__')
extra_kwargs = {'date_created': {'read_only': True}, 'date_updated': {'read_only': True}}
viewset:
class UserTimeViewSet(viewsets.ModelViewSet):
serializer_class = TimeSerializer
queryset = Time.objects.all()
def get_queryset(self):
return Time.objects.filter(user=self.request.user)
urls
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users/time', UserTimeViewSet)
this gives me an endpoint: /users/time/{id} where {id} is the Time obj id. when I tried to modify the endpoint to the same url route but instead pass it a user id as the arg (to get only that user's time) by overwriting lookup_field on the serializer and ViewSet, I'm either getting response errs or {"detail":"Not found."}
class UserTimeViewSet(viewsets.ModelViewSet):
serializer_class = TimeSerializer
queryset = Time.objects.all().select_related('user')
lookup_field = 'user'
also tried:
class UserTimeViewSet(generics.ListAPIView):
serializer_class = TimeSerializer
def get_queryset(self):
user = self.kwargs['user']
return Time.objects.filter(user=user)
how can I GET Time with a relationship to a user by user's id?
If I'm understood correctly, this view class will do the magic
class UserTimeViewSet(viewsets.ModelViewSet):
serializer_class = TimeSerializer
queryset = Time.objects.all()
lookup_field = 'user' # add "lookup_field" attribute
# remove "get_queryset()" method
def get_queryset(self):
return Time.objects.filter(user=self.request.user)
JPG's other answer is a valid solution if the user only has one Time object. however, multiple objs will return a MultipleObjectsReturned get() returned more than one Time err
a solution for multiple objs was to convert the viewset into a ListAPIView:
views:
class UserTimeViewSet(generics.ListAPIView):
serializer_class = TimeSerializer
queryset = Time.objects.all()
def get_queryset(self):
return Time.objects.filter(user=self.kwargs['user'])
urls:
url(r'users/time/(?P<user>\d+)/$', UserTimeViewSet.as_view(), name='user_time'),
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