Django rest nested APIView routes - python

I'm relatively new to Django & Django rest - previously built only very simple apps.
Currently facing a problem with using a nested routes.
Here are my related configs:
main urls.py:
urlpatterns = [
url(r'^'+root_url+'/swagger', swagger_schema_view),
url(r'^' + root_url + '/', include('payments.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
app's urls.py:
urlpatterns = [
url(r'payments', views.PaymentsView.as_view(), name='index'),
url(r'payments/charge', views.PaymentsChargeView.as_view(), name='charge'),
]
app's views:
import logging
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.authentication import BasicAuthentication
from mysite.csrf_exempt import CsrfExemptSessionAuthentication
from rest_framework.views import APIView
from rest_framework.response import Response
import stripe
try:
from django.conf import settings
except ImportError:
pass
logger = logging.getLogger(__name__)
#method_decorator(csrf_exempt, name='dispatch')
class PaymentsView(APIView):
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
def get(self, request, *args, **kwargs):
print('here GET PaymentsView')
return Response('good')
def post(self, request, *args, **kwargs):
print('here POST PaymentsView')
return Response('good')
#method_decorator(csrf_exempt, name='dispatch')
class PaymentsChargeView(APIView):
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
def get(self, request, *args, **kwargs):
print('here GET PaymentsChargeView')
return Response('good')
def post(self, request, *args, **kwargs):
print('here POST PaymentsChargeView')
return Response('good post')
Problem:
requests both to /payments and /payments/charge GET/POST always processed by PaymentsView (ex: POST to /payments and to /payments/charge gives me 'here POST PaymentsView' in console)

The best practice is to put $ (end-of-string match character) in your url. So the defined url will match and process the correct view function.
url(r'payments$', views.PaymentsView.as_view(), name='index'),
url(r'payments/charge$', views.PaymentsChargeView.as_view(), name='charge'),

change your url order
urlpatterns = [
url(r'payments/charge', views.PaymentsChargeView.as_view(), name='charge'),
url(r'payments', views.PaymentsView.as_view(), name='index'),
]

urlpatterns = [
url(r'^payments', views.PaymentsView.as_view(), name='index'),
url(r'^payments/charge', views.PaymentsChargeView.as_view(), name='charge'),
]

Related

How do i rewrite my code with Routers? Django rest framework

I have this REST API:
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', UserViewSet.as_view({'get': 'list',
'post': 'create',
'delete': 'delete'})),
path('users/<uuid:pk>/video/', UserViewSet.as_view({'post': 'video'}))
]
How can i rewrite this with routers?
Default router with register method creates API -> GET users/ and POST users/ and also DELETE /users/{id} but it's different from current, because i need DELETE /users/ endpoint.
Or, maybe, in this situation it would be more correct to use my code with dictionaries?
assuming that UserViewSet is indeed a viewset, you can use the restframework's default router to register the router for /users/, and then add an action to handle you /video/ route from that viewset.
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'users/', UserViewSet, basename='users')
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
viewsets.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import (
CreateModelMixin, RetrieveModelMixin,
DestroyModelMixin, ListModelMixin
)
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
class UserViewSet(CreateModelMixin, RetrieveModelMixin,
DestroyModelMixin, ListModelMixin, GenericViewSet):
# serializer class
# queryset
# permissions
#action(methods=['post'], parser_classes=(MultiPartParser,), detail=True)
def video(self, request, pk=None, *args, **kwargs):
# Implementation to upload a video
Edit
To create a bulk DELETE of users endpoint, I would create a Mixin class, as there is no django mixin for Deleting on the index of a router..
class BulkDeleteModelMixin:
def destroy(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
self.perform_destroy(queryset)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data,
status=status.HTTP_201_CREATED, headers=headers)
def perform_destroy(self, queryset):
queryset.delete()
And inherit from this class in your viewset
viewsets.py
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import (CreateModelMixin, RetrieveModelMixin, ListModelMixin)
from some_app.mixins import BulkDeleteModelMixin
class UserViewSet(CreateModelMixin, RetrieveModelMixin,
BulkDeleteModelMixin, ListModelMixin, GenericViewSet):

DRF reverse cannot find view name

I am working of django rest framework api_root. It cannot find view even though I name it.
# board/urls.py
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from .views import BoardList, BoardDetail, api_root
app_name = 'board'
urlpatterns = [
path('boards/', BoardList.as_view(), name='board-list'), # board-list
path('boards/<int:pk>', BoardDetail.as_view(), name='board-detail'),
path('', api_root),
]
urlpatterns = format_suffix_patterns(urlpatterns)
# board/views.py
from django.contrib.auth.models import User
from django.shortcuts import render
from rest_framework import generics, permissions, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
from .models import Board
from .serializers import BoardSerializer
from .permissions import IsAuthorOrReadOnly
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'boards': reverse('board-list', request=request, format=format) # board-list
})
class BoardList(generics.ListCreateAPIView):
queryset = Board.objects.all()
serializer_class = BoardSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
It throws error,
Reverse for 'board-list' not found. 'board-list' is not a valid view function or pattern name.
Why it cannot find view name?
Since you included an app_name in your urls.py, you need to specify the view name with the app name, so:
#api_view(['GET'])
def api_root(request, format=None):
return Response({
'boards': reverse('board:board-list', request=request, format=format) # board-list
})

Using django-cas-ng to authenticate on admin site

I'm using django-cas-ng framework to authenticate users. The main problem is that the admin page still uses the default login view.
Methods used this far:
1.- Using env var
From docs:
CAS_ADMIN_PREFIX: The URL prefix of the Django administration site. If undefined, the CAS middleware will check the view being rendered to see if it lives in django.contrib.admin.views.
2.- Redirecting url on app/urls.py:
url(r'^arta/admin/login$', django_cas_ng.views.login, name='cas_ng_login')
Everything is just ignored and the admin login form is shown.
The goal of this is to only authenticate with CAS and redirect the current /app/admin/login to CAS
If anyone is interested in the answer, the solution was overriding AdminSite. Django admin module overrides it's own url redirects, so editing them on /app/urls.py was useless.
Creating an /app/admin.py and extending AdminSite like:
from django.contrib.admin import AdminSite
from functools import update_wrapper
from django.urls import NoReverseMatch, reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.http import Http404, HttpResponseRedirect
import django_cas_ng.views
class Admin(AdminSite):
def admin_view(self, view, cacheable=False):
"""
Decorator to create an admin view attached to this ``AdminSite``. This
wraps the view and provides permission checking by calling
``self.has_permission``.
You'll want to use this from within ``AdminSite.get_urls()``:
class MyAdminSite(AdminSite):
def get_urls(self):
from django.conf.urls import url
urls = super(MyAdminSite, self).get_urls()
urls += [
url(r'^my_view/$', self.admin_view(some_view))
]
return urls
By default, admin_views are marked non-cacheable using the
``never_cache`` decorator. If the view can be safely cached, set
cacheable=True.
"""
def inner(request, *args, **kwargs):
if not self.has_permission(request):
if request.path == reverse('cas_ng_logout', current_app=self.name):
index_path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(index_path)
# Inner import to prevent django.contrib.admin (app) from
# importing django.contrib.auth.models.User (unrelated model).
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
request.get_full_path(),
reverse('cas_ng_login', current_app=self.name)
)
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
# We add csrf_protect here so this function can be used as a utility
# function for any view, without having to repeat 'csrf_protect'.
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
return update_wrapper(inner, view)
def get_urls(self):
from django.conf.urls import url, include
# Since this module gets imported in the application's root package,
# it cannot import models from other applications at the module level,
# and django.contrib.contenttypes.views imports ContentType.
from django.contrib.contenttypes import views as contenttype_views
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return self.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper, view)
# Admin-site-wide views.
urlpatterns = [
url(r'^$', wrap(self.index), name='index'),
url(r'^login/$', django_cas_ng.views.login, name='login'),
url(r'^logout/$', django_cas_ng.views.logout, name='logout'),
url(r'^password_change/$', wrap(self.password_change, cacheable=True), name='password_change'),
url(r'^password_change/done/$', wrap(self.password_change_done, cacheable=True),
name='password_change_done'),
url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', wrap(contenttype_views.shortcut),
name='view_on_site'),
]
# Add in each model's views, and create a list of valid URLS for the
# app_index
valid_app_labels = []
for model, model_admin in self._registry.items():
urlpatterns += [
url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
]
if model._meta.app_label not in valid_app_labels:
valid_app_labels.append(model._meta.app_label)
# If there were ModelAdmins registered, we should have a list of app
# labels for which we need to allow access to the app_index view,
if valid_app_labels:
regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
urlpatterns += [
url(regex, wrap(self.app_index), name='app_list'),
]
return urlpatterns
site = Admin()
Then you override the methods that you want, in this case were admin_view and get_urls
The interesting lines are:
→ admin_view
if request.path == reverse('cas_ng_logout', current_app=self.name):
----
reverse('cas_ng_login', current_app=self.name)
→ get_urls
url(r'^login/$', django_cas_ng.views.login, name='login'),
url(r'^logout/$', django_cas_ng.views.logout, name='logout')
That will let you redirect the login and logout steps into CAS
For being now (April, 2022), we can use builtin middleware of django-cas-ng (v4.x) as below:
MIDDLEWARE_CLASSES = (
...
'django_cas_ng.middleware.CASMiddleware',
...
)
You may add these url pattern in url.py as well, don't change the url name.
urlpatterns = [
...
path('accounts/login/', cas_ng_views.LoginView.as_view(), name='cas_ng_login'),
path('accounts/logout/', cas_ng_views.LogoutView.as_view(), name='cas_ng_logout'),
path('accounts/callback/',cas_ng_views.CallbackView.as_view(), name='cas_ng_proxy_callback'),
...
]

Make new custom view at django admin

Sorry, I am still new at django. I want to make custom view at admin site that is not related to my model. I have read the documentation (https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_urls), but does not work. Reading some tutorials does not work too...
Here is what I tried:
admin.py
from django.contrib import admin
from django.urls import path
from .models import Question
from django.http import HttpResponse
class CustomAdminView(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path(r'^my_view/$', self.admin_site.admin_view(self.my_view))
]
urls = my_urls + urls
return urls
def my_view(self, request):
return HttpResponse("Hello, world.")
admin.site.register(Question)
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url
admin.autodiscover()
urlpatterns = [
path(r'polls/',include('polls.urls')),
path('admin/', admin.site.urls),
]
when I go to admin/my_view the result is 404 not found.
I tried by extending the AdminView too.
admin.py
from django.contrib.admin import AdminSite
from django.urls import path
from .models import Question
from django.http import HttpResponse
class CustomAdminView(AdminSite):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path(r'my_view/', self.admin_view(self.my_view))
]
urls = my_urls + urls
return urls
def my_view(self, request):
return HttpResponse("Hello, world.")
custom_admin = CustomAdminView()
custom_admin.register(Question)
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include, url
from polls.admin import custom_admin
admin.autodiscover()
urlpatterns = [
path(r'polls/',include('polls.urls')),
path('admin/', custom_admin.urls),
]
I don't get 404 error on admin/my_view. But, the default models(user, and others) are not displayed. There is only my 'Question' model there. The previous one still has the default models.
How can I make the custom admin view with the right way?
Thanks.
It is solved. I am using my second admin.py and urls.py snippets and register django's default model, based on this answer: Django (1.10) override AdminSite
admin.py
from django.contrib.admin import AdminSite
from django.http import HttpResponse
from django.urls import path
from .models import Question
from django.contrib.auth.models import Group, User #add these moduls
from django.contrib.auth.admin import GroupAdmin, UserAdmin #and these
class CustomAdminView(AdminSite):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path(r'my_view/', self.admin_view(self.my_view))
]
urls = my_urls + urls
return urls
def my_view(self, request):
return HttpResponse("Hello, world.")
custom_admin = CustomAdminView()
custom_admin.register(Question)
#register the default model
custom_admin.register(Group, GroupAdmin)
custom_admin.register(User, UserAdmin)

NoReverseMatch at /accounts/home_page/

#Reverse for 'user_profile_view' with arguments '(u'Emmanuel',)' and keyword arguments '{}' not found. 0 pattern(s) tried: [] :
#project/urls.py:
from django.conf.urls import url, include
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('registration.backends.default.urls')),
url(r'', include('app.urls', namespace='app')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#app/urls.py
from .views import *
from . import views
from django.conf import settings
from django.conf.urls import url
from django.views.generic import TemplateView
from django.conf.urls.static import static
app_name = 'app'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^accounts/home_page/$', home_page),
url(r'^accounts/home_page/(?P<username>[\w-]+)/$', UserProfileView.as_view(), name='user_profile_view'),
# url(r'^accounts/profile/$', views.user_profile, name='user_profile'),
# url(r'^accounts/profile/edit/$', views.edit_user_profile,
# name='edit_user_profile'),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#views.py
class UserProfileView(View):
#method_decorator(login_required)
def get(self, request, user):
if request.user.username == user:
profile = get_object_or_404(User, user=request.user)
return render(request, 'registration/home.html', {'profile': profile})
else:
raise Http404
#login_required
def home_page(request):
return HttpResponseRedirect(
reverse('user_profile_view', args=[request.user.username], current_app='app'))
So what I'm actually trying to do is allow a user login with is name appearing in the url but since the LOGIN_REDIRECT_URL = '/accounts/home_page' cannot take a dynamic parameter like I am using a redirect. But I get this error please what am I doing wrong.
Thanks in advance!!!
#views.py
class UserProfileView(View):
#method_decorator(login_required)
def get(self, request, username):
if request.user.username == username:
profile = get_object_or_404(UserExtended, user=request.user)
return render(request, 'registration/home.html', {'profile': profile})
else:
raise Http404
#login_required
def home_page(request):
return HttpResponseRedirect(
reverse('app:user_profile_view',
args=[request.user.username], current_app='app'))
So I changed the when Followed what #ShanWang said I got the error:
TypeError Exception Value: get() got an unexpected keyword argument 'username'
that as because my def get(self, request, username): was def get(self, request, user): so I changed that and
if request.user.username == user:
to
if request.user.username == username:
That did the trick

Categories