Serializing with django rest framework - python

I have the model named Artist and I want expose this model with Django Rest Framework to create an API and this data can be consumed.
I've created a class based view in artists/views.py named ArtistViewSet
#CBV for rest frameworks
from rest_framework import viewsets
class ArtistViewSet(viewsets.ModelViewSet):
model = Artist
I also have an url named api/ in the urls.py file (view third url named api/) which the user could access to the view above mentioned.
# coding=utf-8
from django.conf.urls import patterns, include, url
from django.conf import settings
from django.contrib import admin
admin.autodiscover()
from rest_framework import routers
from artists.views import ArtistViewSet
#I create a router by default
router = routers.DefaultRouter()
#Register the model 'artists' in ArtistViewSet
router.register(r'artists', ArtistViewSet)
urlpatterns = patterns('',
(r'^grappelli/', include('grappelli.urls')), # grappelli URLS
url(r'^admin/', include(admin.site.urls)),
#Include url api/ with all urls of router
url(r'^api/', include(routers.urls)),
)
When I go to my browser and type http://localhost:8000/api/ I get this message error:
What did can be happened me?

In Django REST framework 2.4+ (including 3.0+), the model attribute for views has been deprecated and removed. This means that you should be defining your view as
from rest_framework import viewsets
class ArtistViewSet(viewsets.ModelViewSet):
queryset = Artist.objects.all()
Which should give you the result you are expecting. Now, you asked in the comments
I cannot understand the role of base_name. I mean, this base name is the url that I've created? My viewset ArtistViewSet does not have a queryset attribute, due to this, according to documentation, it's necessary put the base_name argument, but i don't know how to do it.
The base_name that can be optionally defined when registering a ViewSet is used when naming the automatically generated routes. By default, the format is [base]-list and [base]-detail, where [base] is the base_name that can be defined. When you do not specify your own base_name, it is automatically generated based on the model name. As the queryset method must be defined for ViewSet instances, this is where the model (and later model name) is retrieved. As you did not provide the queryset argument, Django REST framework triggers an error because it cannot generate a base_name.
To quote from the documentation on routers
Note: The base_name argument is used to specify the initial part of the view name pattern.
The documentation goes on to further explain exactly why you are getting the issue, even including an example, and how to fix it.

Related

DRF: API root view doesn't list my APIs for some reasons

The bounty expires in 4 days. Answers to this question are eligible for a +50 reputation bounty.
varnie wants to draw more attention to this question.
I have the following urls.py for an app named service where I register API endpoints:
from .views import AccessViewSet, CheckViewSet
app_name = "api"
router = DefaultRouter()
router.register(r"access/(?P<endpoint>.+)", AccessViewSet, basename="access")
router.register(r"check/(?P<endpoint>.+)", CheckViewSet, basename="check")
urlpatterns = [
path("", include(router.urls)),
]
Below is my project's urls.py where I use it:
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
import service.urls as service_urls
urlpatterns = [
# ...
path("service/", include('service.urls')),
]
The APIs themselves are functioning properly, but I am having trouble making them work with DRF's default API root view. The view is displaying an empty list of available endpoints. I'm not sure, but this issue may be related to the regular expressions I'm using when registering endpoints, such as r"access/(?P<endpoint>.+). If this is indeed the problem, how can I resolve it?"
DefaultRouter needs to run reverse(viewname) on your views to generate urls for the default view. It won't know about your dynamic parameter endpoint when it runs.
If it's acceptable to change your url structure to {endpoint}/access/... and {endpoint}/check/... you can:
router.register(r"access", AccessViewSet, basename="access")
router.register(r"check", CheckViewSet, basename="check")
urlpatterns = [
re_path(r"(?P<endpoint>.+)/", include(router.urls)),
]
After which, each {endpoint}/ view should have a working default API view.
If you need to preserve your url structure, you'll have to manually generate a list of available routes like this:
from collections import OrderedDict
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.reverse import reverse
class ApiRoot(APIView):
"""My API root view"""
def get(self, request, format=None):
"""List of available API endpoints"""
api_methods = [
"access-list",
"check-list",
]
endpoints = [
"endpoint-a",
"endpoint-b",
]
routes = OrderedDict()
for endpoint in endpoints:
for method in api_methods:
routes[endpoint + method] = reverse(
method, endpoint=endpoint, request=request, format=format
)
return Response(routes)
Notice the endpoint=endpoint in the reverse call.
If the code above doesn't help, please, expand your question with more details of what you're trying to archive in the first place. I have a feeling you might want to replace your viewsets with an EndpointViewSet with custom ViewSet actions for check and access.
Finally, if you're looking for routes like {endpoint_id}/access/{access_id}/... and {endpoint_id}/check/{check_id}/... check out drf-nested-routers - a drop-in replacement for DRF's Routers that supports nesting.

remove authentication and permission for specific url path

I'm working with DRF and came across this issue. I have a third-party view which I'm importing in my urls.py file like this :
from some_package import some_view
urlpatterns = [
path('view/',some_view)
]
but the issue I'm facing is since I have enabled default permission classes in my settings.py like this:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.IsAuthenticated',
),
}
now when I call that view using the url , it gives me authentication error as I'm not providing token .Is there a way I can bypass authentication error without having to make changes in view directly,I know that we can remove permission for that particular view , but for that I'll have to make changes to that some_view function code. But I don't want to do that,let's say we don't have access to that function we can only pass data and receive response. How can I bypass authentication without having to change that functions code .
I tried searching but couldn't find what I'm looking for.
I was assuming that there might be someway we can do that from urls.py like specifying any parameter or something like that which make that particular view to bypass authentication without having to change functions code.
somthing like this :
from some_package import some_view
urlpatterns = [
path('view/',some_view,"some_parameter") #passing someparameter from here or something like that
]
Is it possible what I'm looking for ? Thanks in advance :)
So, the most appropriate way for third-party views is to use decorators by defining them inside your urls.py:
Case 1
I assume that some_view is a class inherited from rest_framework.views.APIView:
urls.py
from django.urls import path
from rest_framework.decorators import permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from some_package import some_view
urlpatterns = [
path('', authentication_classes([])(permission_classes([AllowAny])(some_view)).as_view())
]
Case 2
I assume that some_view is a simple Django view function and you need to define it for GET method:
urls.py
from django.urls import path
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from some_package import some_view
urlpatterns = [
path('', api_view(['GET'])(authentication_classes([])(permission_classes([AllowAny])(some_view))))
]
Case 3
I assume that some_view is an api_view decorated DRF view function. This is the hardest and most probably the most impossible part because you have to undecorate the previous api_view decorator. If view function is decorated with api_view, then it is already converted into Django view function so neither permision_classes nor authentication_classes can be appended to class:
You can override the default authentication class to skip auth for specific urls.
For example:
class CustomIsAuthenticated(IsAuthenticated):
def has_permission(self, request, view):
# make a list of public urls
if request.path in PUBLIC_URLS:
return True
return super().has_permission(request, view)
You have to create a list of PUBLIC_URLS which bypass authentication.
Now use this class in the DEFAULT_PERMISSION_CLASSES in rest framework settings. And remove the default IsAuthenticated class.
Although I recommend the decorators approach.
check docs: https://www.django-rest-framework.org/api-guide/permissions/#allowany
Decorators approach is more verbose and by looking at the function you can make out if it is public or not.
#Bedilbek's answer worked for me, but if you'd prefer to use different syntax and define the permission_classes and authentication_classes inside your view instead of urls.py, you can do the following:
from rest_framework import permissions
from rest_framework.views import APIView
class SomeView(APIView):
permission_classes = [permissions.AllowAny]
authentication_classes = []

Django Path To Resource Failure?

When I try a GET request on one of my API endpoints it can't find the endpoint.
urls.py file looks like this
from django.urls import path, include
from django.contrib import admin
from api.resources import NoteResource
note_resource = NoteResource()
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(note_resource.urls)),
]
api.resources looks like this
from tastypie.resources import ModelResource
from api.models import Note
class NoteResource(ModelResource):
class Meta:
queryset = Note.objects.all()
resource_name = 'note'
Any idea why this is happening?
Solution: It appears that http://127.0.0.1:8000/api/note/ works properly.. why would this be?
You should also have one url entry in note_resource.urls for only /api request. Something similar to
path('api/', APIClass).
But, you never need that endpoint. Because, /api does not represent any actual request in your system.
I rather suggest to have following endpoints :
path('api/notes/',include(note_resource.urls))
in your main urls.py.
So that you can have multiple urls in main urls.py file representing each app.
path('api/<APP_NAME>(s)/',include(<APP_NAME>.urls))
And, you will manage other endpoints in your app urls.py file:
# Create a new Note
path(
'create/',
NoteCreate.as_view()
),

Modifying and creating sites in djangos admin app

I have to include multiple changes to djangos admin panel, so I decided to fork the django admin app into my own django project.
As I was working with this admin app I recognized, that the site registration and template handling differs from the apps, that are normally created in django.
For instance, I want to keep the old admin index.html template and view, for backup and safety reasons but the landing page should be replaced by a custom page.
For that of course I need to change admin/templates/index.html and /admin/sites.py respectively.
I copied the old index function in admin/sites.py to old_index.py and created a old_index.html in the template folder.
But if I try to reference to old_index.html in my new index.html with
old index
I got an NoReverseMatch-Exception thrown. Unfortunately I did not found more information about how the django admin app itself register new views and sites, so an example or description would be helpful.
Creating separate views for the admin app in the distinct other apps in my project is no real option, due the high amount of changes, that need to be done.
The main urls.py
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'django_project.views.home', name='home'),
url(r'^polls/', include('other_app.urls', namespace="other_app")),
url(r'^admin/', include(admin.site.urls)),
)
The admin app itself does not provide a urls.py file and the views.py is exactely the same as in django.contrib.admin I just copied the function index to a new function called old_index, referencing to a template old_index.html.
Maybe the point did not get so clear, as I expected. I copied the whole admin app in my project and want to add a custom defined site to it, regardless where. But I failed to understand how sites and views are registered in the admin app itself, because the way is different from the custom apps you create normally in django.
So, is it possible (and how) to add a custom site in the django.contrib.admin app?
I think you need to create your own AdminSite for custom purposes and keep default as it is. More about this you can find here: https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#adminsite-objects and here https://docs.djangoproject.com/en/dev/ref/contrib/admin/#multiple-admin-sites-in-the-same-urlconf
Update:
You need to edit get_urls method of AdminSite class - add:
url(r'^$', wrap(self.old_index), name='old_index')
to urlpatterns variable. And rename old index method to old_index.

Django, name parameter in urlpatterns

I'm following a tutorial where my urlpatterns are:
urlpatterns = patterns('',
url(r'^passwords/$', PasswordListView.as_view(), name='passwords_api_root'),
url(r'^passwords/(?P<id>[0-9]+)$', PasswordInstanceView.as_view(), name='passwords_api_instance'),
...other urls here...,
)
The PasswordListView and PasswordInstanceView are supposed to be class based views.
I could not figure out the meaning of the name parameter. Is it a default parameter passed to the view?
No. It is just that django gives you the option to name your views in case you need to refer to them from your code, or your templates. This is useful and good practice because you avoid hardcoding urls on your code or inside your templates. Even if you change the actual url, you don't have to change anything else, since you will refer to them by name.
e.x with views:
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse #this is deprecated in django 2.0+
from django.urls import reverse #use this for django 2.0+
def myview(request):
passwords_url = reverse('passwords_api_root') # this returns the string `/passwords/`
return HttpResponseRedirect(passwords_url)
More here.
e.x. in templates
<p>Please go here</p>
More here.

Categories

Resources