How do I parameterize the django url using router.register? - django

I am newbie in Django. So this might be a trivial question.
I have been been building urlpatterns as following
router = DefaultRouter()
router.register('posts', views.PostViewSet)
urlpatterns = [
path('', include(router.urls))
]
This creates URLs like api/posts and so on.
Now, I am trying to add a voting model to this.. For which I want to create URL like
api/posts/<id>/vote
But I don't want to create a path like
urlpatterns = [
path('', include(router.urls)),
path('posts/<int:pk>/vote', views.SomeView)
]
Is there a way to do this via router.register way?

First You will define default router same way as you defined.
router = DefaultRouter()
router.register('posts', views.PostViewSet, basename='posts')
urlpatterns = [
path('', include(router.urls))
]
Then define Serializer class as you needed.
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
Now define the custom method of vote inside PostViewSet with Allowed HTTP methods.
from rest_framework.response import Response
from rest_framework import viewsets, status
from rest_framework.decorators import action
from .models import Post
from .serializers import PostSerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
#action(detail=True, methods=['Get'])
def vote(self, request, pk=None):
queryset = Post.objects.filter(pk=pk)
serializer = PostSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Now you will be able to access the vote with your desired URL as api/posts/<pk>/vote

Related

Django create user Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead

I am using the Django default User model to do user registration. When I was trying to create a user I got this error message. Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead.
# views.py
from django.contrib.auth.models import User
from rest_framework import viewsets
from app.serializers import UserSerializer
# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
# serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
extra_kwargs = {'password': {'write_only': True, 'required': False}}
def create(self, validated_data):
print(validated_data)
user = User.objects.create_user(**validated_data)
return user
# urls.py
from django.urls import path, include
from rest_framework import routers
from app import views
from rest_framework.authtoken.views import obtain_auth_token
router = routers.SimpleRouter()
router.register(r'users', views.UserViewSet)
urlpatterns = [
path('auth/', obtain_auth_token),
]
urlpatterns += router.urls
I only insert the user record, there is no foreign table or many-to-many relationship. Not sure why the system throws this error.
Attached with the postman screenshot.

REST Framework Retrieved Wrong Data

I was just getting started on using Django's rest framework. The problem I faced is that Rest Framework didn't fetch from the right URL: I want it to get the list of Todos but it returned the URL where the API was located.
(Might be easy for many of you, but I am completely fresh to drf)
serializers
from .models import Todo
from rest_framework import serializers
class TodoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Todo
fields = ['title', 'desc', 'level', 'created']
urls
from django.urls import path, include
from rest_framework import routers
from . import views
router = routers.DefaultRouter()
router.register(r'todos', views.TodoViewSet)
urlpatterns = [
path('', views.IndexView.as_view(), name='todo_all'),
path('api/', include(router.urls)),
]
views
from django.views.generic.base import TemplateView
from rest_framework import viewsets
from .models import Todo
from .serializers import TodoSerializer
class IndexView(TemplateView):
template_name = "todo/index.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['todos'] = Todo.objects.all()
return context
class TodoViewSet(viewsets.ModelViewSet):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
What I want Rest to get:
What Rest actually displayed:
Like, I want the data of the todos, not the URL. Thanks in advance.
You have used HyperlinkedModelSerializer. Try to use ModelSerializer instead.
from .models import Todo
from rest_framework import serializers
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Todo
fields = ['title', 'desc', 'level', 'created']

How I can show http 404 with djangofilterbackend?

django rest framework is showing http 202 ok when I set a wrong value and I want to show http 404 on the api django rest framework, when set a wrong value in djangofilterbackend but I don't know how to implement it.
my views.py:
from rest_framework import viewsets
from .models import Producto
from .serializers import ProductoSerializer
from django_filters.rest_framework import DjangoFilterBackend
class ProductoViewSet(viewsets.ModelViewSet):
filter_backends = [DjangoFilterBackend]
filterset_fields = ['Codigo_Producto']
queryset = Producto.objects.all()
serializer_class = ProductoSerializer
my serializers.py:
from .models import Producto
from rest_framework import serializers, viewsets
class ProductoSerializer(serializers.ModelSerializer):
Nombre_Producto = serializers.SerializerMethodField
class Meta:
model = Producto
fields = [
'id',
'Nombre_Producto',
'Codigo_Producto',
'Precio_sugerido',
'stock',
]
def get_Producto_name(self,obj:Producto):
return obj.Producto.get_full_name()
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from rest_framework import routers
from GestionadorApp.views import ProductoViewSet
# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register('Producto', ProductoViewSet)
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
path('api/', include(router.urls)),
path('admin/', admin.site.urls),
]
The way I found is overriding the filter backend to ensure that the parameters are what I want else raise a error Http404.
to show a solution I had to take part of code from Django Filter Backend
from rest_framework import filters
from django.http import Http404
class ObjektFilterBackend(filters.BaseFilterBackend):
allowed_fields = ['username', 'email']
def filter_queryset(self, request, queryset, view):
flt = {}
for param in request.query_params:
#put your logic here
if request.query_params[param] is None:
raise Http404
for fld in self.allowed_fields:
if param.startswith(fld):
flt[param] = request.query_params[param]
return queryset.filter(**flt)
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [ObjektFilterBackend]
search_fields = ['username', 'email']
In another hand you may override the get_queryset method to ensure data too.
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [DjangoFilterBackend]
search_fields = ['username', 'email']
def get_queryset(self):
username = self.request.query_params.get('username', None)
if username is None:
raise http404
return self.queryset
django rest framework filtering

Django Rest Framework urls.py getting messed up

I am having my models.py file defined as below:-
from django.db import models
from django.contrib.auth.models import User
class Custom_User(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
mobile = models.CharField(max_length=20)
REGISTRATION_CHOICES = (
('Learner', 'Learner'),
('Trainer', 'Trainer'),
)
primary_registration_type = models.CharField(max_length=15, choices=REGISTRATION_CHOICES)
def __str__(self):
return self.user.email
As you can see that my Custom_User model uses Django's User model as its foreign Key.
For the above model I have defined my serialziers.py file like this:-
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
class Custom_UserSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Custom_User
fields = ('__all__')
Now I am using this serializers in my viewsets like below:-
from django.contrib.auth.models import User
from rest_framework import viewsets
from .serializers import *
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
class Custom_UserViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.all()
serializer_class = Custom_UserSerializer
class TrainerViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.filter(primary_registration_type="Trainer")
serializer_class = Custom_UserSerializer
class LearnerViewSet(viewsets.ModelViewSet):
queryset = Custom_User.objects.filter(primary_registration_type="Learner")
serializer_class = Custom_UserSerializer
And Finally inside my urls.py file I register them as below:-
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'users', api_mailing_list_views.UserViewSet)
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet)
router.register(r'trainers', api_mailing_list_views.TrainerViewSet)
router.register(r'learners', api_mailing_list_views.LearnerViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', mailing_list_views.index, name='index'),
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
As I was expecting the urls list to be like below in my browser:
{
"users": "http://localhost:8080/api/users/",
"custom_users": "http://localhost:8080/api/custom_users/",
"trainers": "http://localhost:8080/api/trainers/",
"learners": "http://localhost:8080/api/learners/"
}
But what i get instead is a list of urls like this:-
{
"users": "http://localhost:8080/api/users/",
"custom_users": "http://localhost:8080/api/trainers/",
"trainers": "http://localhost:8080/api/trainers/",
"learners": "http://localhost:8080/api/trainers/"
}
However I am not getting any errors or if I visit the following url:-
http://localhost:8080/api/learners/
which is not showing up in the urls list I still get the filtered list of learners in JSON format.
Thanks for the help in advance.
you need to provide basename during router register as all of them actually from same custom_user model.
router = routers.DefaultRouter()
router.register(r'users', api_mailing_list_views.UserViewSet, basename='users')
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet, basename='custom_user')
router.register(r'trainers', api_mailing_list_views.TrainerViewSet, basename='trainers')
router.register(r'learners', api_mailing_list_views.LearnerViewSet, basename='learners')
Django-rest-framework's router tries to identify the viewset by its model/queryset, since both viewsets use the same model things most likely get mixed up.
From the documentation:
If unset the basename will be automatically generated based on the queryset attribute of the viewset, if it has one. Note that if the viewset does not include a queryset attribute then you must set basename when registering the viewset.
Try providing a basename to the router:
router.register(r'custom_users', api_mailing_list_views.Custom_UserViewSet, basename='custom_users')
router.register(r'trainers', api_mailing_list_views.TrainerViewSet, basename='trainers')
router.register(r'learners', api_mailing_list_views.LearnerViewSet, basename='learners')

How to add django-reversion to an app developed using django and django-rest framework

I have an app developed using Django and Django Rest framework. I would like to add the django-reversion feature to my app.
I have already tried http://django-reversion.readthedocs.org/en/latest/api.html#low-level-api but I have failed to make specific changes to my app.
Below are the modules of the app where I would like to include the Django-reversion to restore objects if they get deleted. How to set the django-reversion configuration for the below modules
admin.py:-
from django.contrib import admin
from.models import Category
admin.site.register(Category)
models.py:-
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=64, unique=True)
def __unicode__(self):
return self.name
serializers.py:-
from rest_framework import serializers
from .models import Category
class CategorySerializer(serializers.ModelSerializer):
courses = serializers.HyperlinkedRelatedField(
many=True
read_only=True
view_name='course-detail'
)
class Meta:
model = Category
fields = ('pk', 'name', 'courses',)
urls.py :-
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from .import views
from django.conf.urls import include
category_list = views.CategoryViewSet.as_view({
'get': 'list',
'post': 'create'
})
category_detail = views.CategoryViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy',
})
urlpatterns = format_suffix_patterns([
url(r'^categories/$',
category_list,
name='category-list'),
url(r'^categories/(?P<pk>[0-9]+)/$',
category_detail,
name='category-detail'),
])
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
views.py :-
from rest_framework import permissions
from rest_framework import viewsets
from .models import Category
from .serializers import CategorySerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = (permissions.IsAuthenticatedorReadOnly,)
The simplest way to create revisions is to use reversion.middleware.RevisionMiddleware. This will automatically wrap every request in a revision, ensuring that all changes to your models will be added to their version history.
To enable the revision middleware, simply add it to your MIDDLEWARE_CLASSES setting as follows:
MIDDLEWARE_CLASSES = (
'reversion.middleware.RevisionMiddleware',
# Other middleware goes here...
)
Any thing more complex with this will require adding API calls through your code in away that wraps specific save calls in ways that you decide.
admin.py
from django.contrib import admin
from.models import Category
import reversion
class BaseReversionAdmin(reversion.VersionAdmin):
pass
admin.site.register(Category, BaseReversionAdmin)
also added reversion to installed_apps and middlewareclasses.
Finally i could see the "recover deleted objects button".
I discovered that since rest_framework.viewsets.ModelViewSet inherits from django.views.generic.View so you can also use the reversion.views.RevisionMixin to create revisions instead of having to use the middleware if you want.
From the question above this would look like the following:
from rest_framework import permissions
from rest_framework import viewsets
from .models import Category
from .serializers import CategorySerializer
from reversion.views import RevisionMixin
class CategoryViewSet(RevisionMixin, viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = (permissions.IsAuthenticatedorReadOnly,)
You can read more about the specifics of how you can use RevisionMixin here: https://django-reversion.readthedocs.io/en/stable/views.html#reversion-views-revisionmixin