How to allow only custom urls in Django rest api? - django

I'm trying to create a simple api to learn how Django works. I'm using rest_framework.
First, I have created a model:
class User(models.Model):
username = models.CharField(max_length=20, primary_key=True)
password = models.CharField(max_length=50)
token = models.UUIDField(default=uuid.uuid4, editable=False)
creation_dt = models.DateTimeField(auto_now_add=True)
Then I have created a serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'password', 'token', 'creation_dt')
read_only_fields = ('creation_dt', 'token',)
And then, in api.py, this code:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
permission_classes = [permissions.AllowAny]
serializer_class = UserSerializer
http_method_names = ['get', 'post']
#action(methods=['POST'], detail=False, permission_classes=[permissions.AllowAny], url_path='get_all_users')
def get_all_users(self, request, pk=None):
...
return Response(UserSerializer(self.queryset[:user_number],
As you can see, I added a custom url_path "get_all_users".
So, everything works until here. My problem is that I can still access "/users/", "/users/user_name", POST users etc, the normal CRUD app.
The question is, how can I allow only the url I have especifially created and block all the rest created automatically?
Thanks.

Apparently just changing ModelViewSet to GenericViewSet works.
class UserViewSet(viewsets.GenericViewSet):
queryset = User.objects.all()
permission_classes = [permissions.AllowAny]
serializer_class = UserSerializer
http_method_names = ['get', 'post']
#action(methods=['POST'], detail=False, permission_classes=[permissions.AllowAny], url_path='get_all_users')
def get_all_users(self, request, pk=None):
...
return Response(UserSerializer(self.queryset[:user_number],
Only our custom URLs will be exposed and not the default ones (CRUD).

Related

Create URL for detailview using DRF Modelviewset

i'm using DRF modelviewset to create an api for users model. Can anyone explain or help how can I pass a url in react-native for the detailview of user. Right now i'm getting the detailview in this manner 127.0.0.1:8000/users/users/2/. But everytime i don't want to pass the id in the url.
models.py
class User(AbstractUser):
"""This Class is used to extend the in-build user model """
ROLE_CHOICES = (('CREATOR','CREATOR'),('MODERATOR','MODERATOR'),('USERS','USERS'))
GENDER_CHOICES = (('MALE','MALE'),('FEMALE',"FEMALE"),('OTHER','OTHER'))
date_of_birth = models.DateField(verbose_name='Date of Birth', null=True)
profile_image = models.ImageField(upload_to='media/profile_images', verbose_name='Profile Image', default='media/profile_images/default.webp', blank=True)
bio = models.TextField(verbose_name='Bio')
role = models.CharField(max_length=10, verbose_name='Role', choices=ROLE_CHOICES, default='USERS')
gender = models.CharField(max_length=6, verbose_name='Gender', choices=GENDER_CHOICES)
serializers.py
class UserSerializer(serializers.ModelSerializer):
following = serializers.SerializerMethodField(read_only=True)
followers = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ('first_name','last_name','username','password','email','date_of_birth',
'profile_image','bio','role','gender', 'following','followers')
extra_kwargs = {'is_active':{'write_only':True},
'password':{'write_only':True}}
def create(self, validated_data):
logger.info('Information Stored!')
return User.objects.create_user(**validated_data)
def update(self, *args, **kwargs):
user = super().update( *args, **kwargs)
p = user.password
user.set_password(p)
user.save()
return user
views.py
class UserAPI(viewsets.ModelViewSet):
serializer_class = UserSerializer
# permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
def get_queryset(self):
users = User.objects.all()
return users
urls.py
router = DefaultRouter()
router.register('users', views.UserAPI, basename='users'),
router.register('following', views.FollowingAPI, basename='following'),
urlpatterns = router.urls
How can i solve this. Need your help please. Thank you
You can make use of #action decorator.
Give this a try:
class UserAPI(viewsets.ModelViewSet):
...
#action(detail=False)
def profile(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
Now go to 127.0.0.1:8000/users/profile/, you should see the current authenticated user's data.
this may be helpful.
You can use user = request.user.id , by in this way you can get current login user.

DRF: Need Guidance to perform a task

I need to create an API to book a call with an advisor
Endpoint: /user/<user_id>/advisor/<advisor_id>/
for this used path('user/<int:user_id>/advisor/<int:advisor_id>') in urls.py
but the problem is I have to request a Booking time(a DateTime sting) when I make a request to that link which I can perform via PostMan
In serilaizer.py I used:
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = models.Booking
fields = '__all__'
def create(self, validated_data):
user = models.Booking.objects.create(
user=validated_data['user'], advisor=validated_data['advisor'], time=validated_data['time'])
user.save()
return user
views.py
class BookAdvisorAPIView(generics.CreateAPIView):
serializer_class = serializer.BookingSerializer
permission_classes = [IsAuthenticated]
queryset = models.Booking.objects.all()
In this i have pass everything via the body in postman but I want to use the user_id and advisor_id from the url and just want to provide DateTime string via body in PostMan
I hope this should work,
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = models.Booking
fields = '__all__'
read_only_fields = ("user", "advisor")
def create(self, validated_data):
view_kwargs = self.context["view"].kwargs
booking = models.Booking.objects.create(
user_id=view_kwargs["user_id"],
advisor_id=view_kwargs["advisor_id"],
time=validated_data['time']
)
return booking

How to retrieve all items created by a user using the django rest framework

I am trying to get only courses belonging to a particular user below I have the model, serializer and view I am using to try and achieve this. If I delete the entire get_queryset function from the view the api returns the appropriate user and every course created by every user. If get_queryset remains, the api always returns user not found and still gives every course that exists. Can anyone point me to how I can achieve my desired result.
view:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
# queryset = User.objects.all()
serializer_class = UserSerializer
def get_queryset(self):
user = self.request.user
queryset = User.objects.all()
if user is not None:
queryset = queryset.filter(courses__owner_id=user.id)
return queryset
serializer
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, queryset=Course.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'courses']
Model
class Course (models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
pub_date = models.DateField(default=date.today)
owner = models.ForeignKey('auth.User', related_name='courses', on_delete=models.CASCADE)
def __str__(self):
return self.title
You need to filter objects by user
class CreatePostsView(viewsets.ModelViewSet):
model = Post
serializer_class = PostsSerializer
def get_queryset(self):
user = self.request.user
return Post.objects.filter(owner=user)
class CoursesByOwnerView(RetrieveModelMixin, GenericViewSet):
serializer_class = YourModelSerializer
authentication_classes =[TokenAuthentication,]
permission_classes = [IsAuthenticated,]
def list(self, request, *args, **kwargs):
course_taker = self.request.user
courses = YourModel.objects.filter(owner=course_taker).values('your_model_fields')
return Response(courses)
Given your answer in the comments:
Either you use self.request.user given by the authentication middleware. In this case, it will only work for authenticated users, and you can't see courses for another User.
Either you use the endpoint users/<int:pk>/ you mentioned. In this case, you can fetch the user with:
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [IsProfileOwnerOrReadOnly]
serializer_class = UserSerializer
def get_queryset(self):
return UserDetail.objects.filter(pk=self.kwargs["pk"])
See this thread if you need another example: Django 2.0 url parameters in get_queryset
EDIT: In both cases, change your UserSerializer with:
class UserSerializer(serializers.ModelSerializer):
courses = serializers.PrimaryKeyRelatedField(
many=True, read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'courses']

django REST filter query by foreign key lookup_field

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'),

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