There are SimpleRouter with ViewSet:
router = SimpleRouter()
router.register(r'product', ProductViewSet)
When i trying to write some tests for get endpoint:
If i use url = reverse('product-list'), then i have url product/ and this is what i want.
But after all product API i want to test product/pk/ api but i don't find the proper reverse in django rest framework Docs, can you help me?
from django.urls import reverse
url = reverse("product-detail", kwargs={"pk": 4})
Related
I'm using REST in Django, And I couldn't understand what is the main difference between classic URL and instantiating DefaultRouter() for registering URL by ViewSet.
I have a model:
class Article(models.Model):
title = models.CharField()
body = models.TextField()
author = models.ForeignKey()
Serializing model like this:
from blog.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'body', 'author']
View Class:
from blog.models import Article
from rest_framework import viewsets
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
and URLS:
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Is it possible to use classic URL in URLS.py instead of instantiating the object for a ViewSet like this:
urlpatterns = [
path('api/', 'views.someAPI'),
]
I just know HTTP method in ViewSet translate methods to retrieve, list and etc...
The Question is can we use traditional(Classic) URL style in this situation, Should we ?
Thanks for your help.
Well, in a nutshell as a django developer it is notorious how it is hard to deal with normal urls in django in some cases. Every now and again we get confused with the id type of the detail page that in some case are strings or integers with its regex, and so on.
For example:
urlpatterns = [
url(r'^(?P<content_type_name>[a-zA-z-_]+)$', views.content_type, name = 'content_type'),
]
# or
urlpatterns = [
url(r'^(?P<content_type_name>comics|articles|videos)$', views.content_type, name='content_type'),
]
Not mentioning that in almost every case its needed to have two urls like:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
THE MAIN DIFFERENCE
However, using DRF routers the example above is done automatically:
# using routers -- myapp/urls.py
router.register(r"store", StoreViewSet, basename="store")
How django will understand it:
^store/$ [name='store-list']
^store\.(?P<format>[a-z0-9]+)/?$ [name='store-list']
^store/(?P<pk>[^/.]+)/$ [name='store-detail']
^store/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='store-detail']
See how much job and headache you have saved with a line of code only?
To contrast, according to DRF documentation the routers is a type of standard to make it easy to declare urls. A pattern brought from ruby-on-rails.
Here is what the documentation details:
Resource routing allows you to quickly declare all of the common
routes for a given resourceful controller. Instead of declaring
separate routes for your index... a resourceful route declares them in
a single line of code.
— Ruby on Rails Documentation
Django rest framework documentation:
Some Web frameworks such as Rails provide functionality for
automatically determining how the URLs for an application should be
mapped to the logic that deals with handling incoming requests.
REST framework adds support for automatic URL routing to Django, and
provides you with a simple, quick and consistent way of wiring your
view logic to a set of URLs.
For more details follow the django rest framework documentation.
Hello guys so I came to realise that DRF ModelViewSet is a quick way to make rest views, I am trying to use parameters within the router url but the ViewSet does not detect the param
Here is how my viewset looks
class ClientRequests(ModelsViewSet):
serializer_class = serializers.ClientRequestSerializer
def get_queryset(self):
return models.ClientRequest.objects.filter(cliend_id=self.request.kwargs.get('client_id')
now I register the router as below
router = DefaultRouter()
router.register('/<int:client_id/requests', views.ClientRequests, basename='clients request api endpoint')
urlpatterns=[
path('', include(router.urls))
Is this the right way to use restframework router and how can I pass paramters to the url when using router
Use regex notation
router.register(
r'(?P<client_id>\d+)/requests',
views.ClientRequests,
basename='clients request api endpoint'
)
I have recently inherited an API built with Django and DRF. I need to add some endpoints to the API but have never worked with Django or DRF before so I am trying to come up to speed as quickly as possible.
I am wondering how to do custom endpoints that don't just translate data too/from the backend database. A for instance might be an endpoint that reads data from the DB then compiles a report and returns it to the caller in JSON. But I suppose that right now the simplest method would be one that when the endpoint is hit just prints 'Hello World' to the log and returns a blank page.
I apologize if this seems basic. I've been reading through the docs and so far all I can see is stuff about serializers when what I really need is to be able to call a custom block of code.
Thanks.
if you want your REST endpoint to have all: GET, POST, PUT, DELETE etc. functionality then you have to register a route in your urls.py:
urls.py:
from rest_framework import routers
from django.urls import path, include
from . import views
router = routers.DefaultRouter()
router.register(r'hello', views.HelloWorldViewSet)
urlpatterns = [
# Wire up our API using automatic URL routing.
# rest_framework api routing
path('api/', include(router.urls)),
# This requires login for put/update while allowing get (read-only) for everyone.
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
now the url: /hello/ points to the HelloWorldViewSet.
in your views.py add the HelloWorldViewSet that will inherits from the rest_framework.viewsets.ViewSet class. You can override the ViewSet default class behavior by defining the following "actions": list(), create(), retrieve(), update(), partial_update(), destroy(). For displaying "hello world" on GET you only need to override list():
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
class HelloWorldViewSet(viewsets.ViewSet):
def list(self, response):
return Response('Hello World')
So, in your more advanced list() function you have to interact with the database, to retrieve the data you want, process it and create the report as a json serializable dictionary and return it as a Response object.
If you don't want to override the standard list action, you could instead add a new action to the HelloWorldViewSet let's call it report:
so in your views.py:
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import action
class HelloWorldViewSet(viewsets.ViewSet):
#action(detail=False)
def report(self, request, **kwargs):
return Response('Hello World')
I hope this is what you were looking for.
Note that you don't need django-rest-framework if you are not interested in POST, PUT, PATCH, DELETE, etc... you can simply add a path to your urls.py that points to a Django view function that returns a Django JsonResponse object containing your report.
One good option for you would be DRF actions. Docs here
The action allows you to choose a relevant view for what you wanna do, so you can just pop things into the API you inherited. No additional setup needed, they show up next to the regular routes.
Tastypie looked promising, now not so much:
http://django-tastypie.readthedocs.org/en/latest/non_orm_data_sources.html
Should I use SimpleAPI or is there a better solution?
Hmm i'm using this https://github.com/mitar/django-tastypie-mongoengine
I once used django-piston. You should try it out, it's easy to create a rest api and it's integrated with django.
I've read it can be done with MongoEngine, but never tryed.
The finally solution was to use django-tastypie + django-tastypie-mongoengine:
https://github.com/mitar/django-tastypie-mongoengine
Once you have that installed, in your app add a resource.py with code like this:
from models.account import MAccount
from models.company import MCompany
class AccountResource(resources.MongoEngineResource):
class Meta:
serializer = CustomSerializer()
queryset = MAccount.objects.all()
allowed_methods = ('get', 'post', 'put','delete')
resource_name = 'account'
authorization= tastypie_authorization.Authorization()
Then if your urls.py file add this code:
v1_api = api.Api(api_name='v1')
v1_api.register(resources.AccountResource())
urlpatterns += patterns('', (r'^m/api/', include(v1_api.urls)))
Finally, you should be able to hit an API like
/m/api/v1/account/?format=json
So, kind of a in a weird situation here. I've got a Django project using TastyPie to power its API and some views/templates that will be used to power plugins for various sites. Rather than building this plugins as standard Django templates, I've been asked to use our API to process requests to the plugins, which means I'm calling a view on my server from another view, and for some reason that isn't working with any of my views. For reference:
#views.py
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template.context import Context, RequestContext
import json, urllib, urllib2
SITE_NAME = "http://localhost:8000/"
API_PATH = "api/v1/"
def aggregate(request):
template = 'lemonwise/plugins/aggregate.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib.urlopen(url).read())['objects'][0]
return render_to_response(template, product)
def reviews(request):
template = 'lemonwise/plugins/reviews.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?format=json&sku=', sku])
#Comment the next line out and the url is passed correctly
response = urllib2.build_opener().open(url).read()
return HttpResponse(url)
#page = opener.open(url).read()
#return HttpResponse(url)
#product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
#return HttpResponse(url)
#reviews = [json.loads(urllib.urlopen(SITE_NAME + uri)) for uri in product['reviews']]
#return render_to_response(template, {'reviews': reviews})
def survey(request):
template = 'lemonwise/plugins/survey.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
return render_to_response(template, product)
def mosthelpfulpositive(request):
template = 'lemonwise/plugins/mosthelpfulpositive.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
uri = product['most_helpful_positive']
most_helpful_positive = json.loads(urllib.urlopen(SITE_NAME + uri))
return render_to_response(template, most_helpful_positive)
def mosthelpfulnegative(request):
template = 'lemonwise/plugins/mosthelpfulnegative.html'
sku = request.GET.get('sku')
url = ''.join([SITE_NAME, API_PATH, 'product/?sku=', sku])
product = json.loads(urllib2.build_opener().open(url).read())['objects'][0]
uri = product['most_helpful_negative']
most_helpful_negative = json.loads(urllib.urlopen(SITE_NAME + uri))
return render_to_response(template, most_helpful_negative)
And the corresponding urls.py (in a different app):
#urls.py
from django.conf import settings
from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from tastypie.api import Api
from lemonwise.reviews.api import *
admin.autodiscover()
v1_api = Api(api_name='v1')
v1_api.register(UserResource())
v1_api.register(MerchantResource())
v1_api.register(ProductFamilyResource())
v1_api.register(ProductResource())
v1_api.register(BooleanAttributeResource())
v1_api.register(SlideAttributeResource())
v1_api.register(ProductAttributeResource())
v1_api.register(SubmissionResource())
v1_api.register(ReviewResource())
v1_api.register(ReviewProductAttributeResource())
v1_api.register(CommentResource())
v1_api.register(BestUseResource())
v1_api.register(HelpfulVoteResource())
#Acess the api via http://127.0.0.1:8000/api/v1/user/?format=json
urlpatterns = patterns('',
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
# Lemonwise apps
url(r'^reviews/', include('lemonwise.reviews.urls')),
#API
url(r'^api/', include(v1_api.urls)),
)
if settings.DEBUG:
urlpatterns += staticfiles_urlpatterns()
Any idea on how to fix this? I can't find any writings on the subject.
EDIT: More specifically, what's happening is that when I load any of these views they hang trying to read the api page. ALso, my server shows no indications of any requests being processed. (Though I can load the api pages directly.)
Now your comment has clarified the situation, I can take a guess at what the problem is. It is that you are using the built-in development server, which is single-threaded. So while it's processing the original request, it's not able to process the internal request for another URL - so it hangs indefinitely.
The solution, as Mao points out, is to think of a better architecture. If you can't do that, you may have some luck with using something like gunicorn instead of the built-in server.
Particular to Tastypie you can use your Resources in views, if you need to, accessing the objects directly and calling the get methods and building the bundle.
You can find out how: http://django-tastypie.readthedocs.org/en/latest/cookbook.html
Go to the section titled - Using Your Resource In Regular Views
Agreed, your development server likely only has one instance. Launch another one on a different port. ie/
SITE_NAME = "http://localhost:8001/"
Okay, so I already said that having views making HTTPRequests against other views is kind of silly, but I do see what your problem currently is. You are calling json.loads(urllib.urlopen(SITE_NAME + uri)), but urllib.urlopen returns a file-like object, not a string, so it should actually be json.load(urllib.urlopen(SITE_NAME + uri)).
I just encountered the same problem i.e. Calling an API from the same server it is hosted. As Daniel suggested, I was able to solve this by using multi threads in gunicorn:
gunicorn --certfile=crt --keyfile=key --bind 0.0.0.0:8000 app.wsgi --threads 5