I'm currently working at my first Django REST Api and I have a question about the routing.
I have a JSON and the short form looks like this:
{
"name": "Summer Festival",
"date": "2020-01-05",
"deadline": "2020-01-01",
"address": "Fantasy Road 50",
"postcode": 12346,
"location": "New York",
(...)
}
I use this ViewSet:
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
and this serializer:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = "__all__"
My urls.py:
router = routers.DefaultRouter()
router.register("events", views.EventViewSet)
urlpatterns = [
path('', include(router.urls)),
path(
'events/location=<str:address>&from=<str:from>&to=<str:to>',
views.EventViewSet.as_view({"get": "list"})),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
The scenario:
On my frontend is a form where you can pick your postcode and a date range.
When the user just inserts his postcode he gets all events up today and the url should look like this:
http://127.0.0.1:8000/events/postcode=12345
When the user picks a from date and postcode it should like this:
http://127.0.0.1:8000/events/postcode=12345&from=2019-01-01
With a daterange + postcode like this:
http://127.0.0.1:8000/events/postcode=12345&from=2019-01-01&to=2019-02-01
The url should be dynamically build.
The second path in my urls.py code snippet is a workaround but this urls isn't dynamically created. My first idea was to just set the values null but the result is just a large url with no information.
I already tried to work with the lookup field but you just can use it with a single value.
Is there another builtin django or django rest approach to achieve this dynamic url with dynamic query parameters?
Django take help of URL dispatchers to router request to appropriate view.
Django provide both(static and dynamic) options for URL.
In django if you want to generate dynamic URL you take help of regular expression
https://docs.djangoproject.com/en/3.0/topics/http/urls/#using-regular-expressions
Related
I have an Comment which have an Article foreign key (so Article have an "array" of comments). I need to build an url to fetch theese comments using article's pk, but when I am trying to do smth like "articles/int:article_pk/comments/" or "articles/{article_pk}/comments/" drf router crates static route with path "articles/{article_pk}/comments/". How can I implement getting a comments using article pk?
urls.py
router = DefaultRouter()
router.register('articles', articleAPI.ArticleAPI, basename='articles')
router.register('articles/comments', CommentAPI, basename='comments')
You can use this same url to get comments as well.
router.register('articles', articleAPI.ArticleAPI, basename='articles')
add a new method to Article ModelViewSet
#action(methods=['get'], detail=True,
url_path='comments', url_name='article-comments')
article = self.get_object()
serializer = CommentSerializer(queryset=article.comment_set.all(), many=True) # comment_set resembles the related name for article foreign key
return Response(serializer.data)
In postman hit the url articles/<article_id>/comments/ with GET method to get the comment list
Its not working because that is not how routers are intended to be used. You don't specify the key when registering, you use the viewset to define it. Read the docs at [1].
[1]: https://www.django-rest-framework.org/api-guide/routers/#simplerouter
A router can indeed not do that. You will need to capture the article_pk in the path and then work with two routers, so:
article_router = DefaultRouter()
article_router.register('articles', articleAPI.ArticleAPI, basename='articles')
comment_router = DefaultRouter()
comment_router.register('comments', CommentAPI, basename='comments')
urlpatterns = [
path('', include(article_router.urls)),
path('articles/<int:article_pk>/', include(comment_router.urls)),
]
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.
I'm having an issue with a new django 4 app not processing or not allowing POST method. I used to bypass this in Django version 3 by adding a trailing slash "/" to the end of url in Postman API Tester like http://127.0.0.1:8000/api/didipay/ instead of http://127.0.0.1:8000/api/didipay . But now in my Django 4 API I completed the url with the slash but POST method is still not processing the data. It gives me a "500 internal server error" and I don't understand where it is coming from. The GET method is rather giving an empty array which is normal because I've not inserted any data yet in the database. I'm using venv, Django 4 Rest framework, serializer, viewSet and these is my models and API urls configurations:
//Serialize.py
from rest_framework.decorators import permission_classes
from didipay_app.models import Didipay_app
from rest_framework import viewsets, permissions
from .serializers import Didipay_appSerializer
class Didipay_appViewSet(viewsets.ModelViewSet):
queryset = Didipay_app.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = Didipay_appSerializer
// Project folder's urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('didipay_app.urls')),
]
// App folder's urls.py
from rest_framework import routers, urlpatterns
from .api import Didipay_appViewSet
router = routers.DefaultRouter()
router.register('api/didipay', Didipay_appViewSet, 'didipay')
urlpatterns = router.urls
The application's web page says:
HTTP 200 OK Allow: GET, HEAD, OPTIONS Content-Type: application/json
Vary: Accept
{
"api/didipay": "http://127.0.0.1:8000/api/didipay/" }
And POST is even missing from the allowed method, and I don't understand why, since I granted all permissions in the Serialize.py file (No authentication systhem yet).
I will be very greatful for your help regarding this issue. Thank you in advance.
I got it fixed by changing fields = '__ all __' in the Model by all the table attributes in fields = ('id', 'first_name', 'last_name', 'phone', 'email', 'country', 'currency', 'status', 'payingType') because restarting the computer made console erroring out this
TypeError: The fields option must be a list or tuple or "__ all __"
which was not shown before restarting the system. I also suspect having set fields = '__ all __' instead of fields = '__ all __' (there is an issue about using __ in stackoverflow editor because it is hidden when used like in real code editor, but the overall idea is that there should not be spaces around "all" when using "__".
I am trying to filter products either by brand or category but the url path will only execute path('<slug:brand_slug>/', views.product_list,name='product_list_by_brand'), since it appears first and would not execute the second.
Is there a way I can probably merge both paths or cause both paths to work independently without taking order into consideration.
from . import views
app_name = 'shop'
urlpatterns = [
path('', views.product_list, name='product_list'),
path('<slug:brand_slug>/', views.product_list,name='product_list_by_brand'),
path('<slug:category_slug>/', views.product_list,name='product_list_by_category'),
]
Thank you in advance for your response.
the problem is your upper url pattern is overriding second URL because of same slug and some other reasons.
FIX
change your URL pattern
path('<slug:brand_slug>/brand/', views.product_list,name='product_list_by_brand'),
path('<slug:category_slug>/', views.product_list,name='product_list_by_category'),
Another Thing You Can Do!
modify your function and url pattern.
def product_list(request, slug):
mode = request.GET.get("mode")
if mode.lower() == "brand":
''' your brand code '''
pass
else:
''' your category code '''
pass
path('<slug:slug>/', views.product_list,name='product_list_by_category'),
if you want to execute brand code your URL pattern would look like this.
127.0.0.1:8000/yourslug?mode=brand
and with this url pattern it will execute category code.
127.0.0.1:8000/yourslug
so by default it will execute category code.
I am reading http://www.django-rest-framework.org/api-guide/routers#usage and can't understand what a base_name is. Also i try to add a custom action and the router won't pick it up
I have this views.py
#authentication_classes((SessionAuthentication, TokenAuthentication))
#permission_classes((IsAuthenticated,))
class utente(CreateModelMixin, RetrieveAPIView, GenericViewSet, ViewSet):
model = MyUser
serializer_class = MyUserSerializer
def retrieve(self, request, *args, **kwargs):
self.object = MyUser.objects.get(
pk = request.user.pk
)
serializer = MyUserSerializerGET(self.object)
return Response(serializer.data)
#action(permission_classes=[IsAuthenticated])#POST action
def customaction(self, request):
return Response( None )
pass
and this urls.py
admin.autodiscover()
router_v1 = routers.DefaultRouter(trailing_slash=True)
router_v1.register(r'register', my_register, 'wtf' )
router_v1.register(r'utente', utente, 'wtf2' )
#router_v1.register(r'utente/customaction', utente.as_view({'post' : 'customaction'}) )
api_urls_v1 = router_v1.urls
api_urls = patterns('',
url(r'^v1/', include(api_urls_v1)),
)
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'wecup.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^login/', 'rest_framework.authtoken.views.obtain_auth_token'),
url(r'^logout/', my_logout ),
url(r'^api/', include(api_urls)),
)
when i open http://127.0.0.1:8000/api/v1/
HTTP 200 OK Content-Type: application/json Vary: Accept Allow: GET, HEAD, OPTIONS
{
"register": "http://127.0.0.1:8000/api/v1/register/",
"utente": "http://127.0.0.1:8000/api/v1/utente/"
where is customaction?
}
You've got two different questions here, so I'll address them each separately.
Base Name
First, a base_name is simply the name the ViewSet will use when generating your named urls. By default, this will simply be your model or perhaps your queryset, though you may need to set it automatically if you've played with your ViewSet's get_queryset method.
If you don't implement your own url names, then the base_name will be used to implement them for you. For example, given that your Model is MyUser, your named urls would be something like 'myuser-list' or 'myuser-detail'.
Documentation, if interested, is here.
#action and custom methods
You're using a DefaultRouter, which allows you to access the API root view at http://127.0.0.1:8000/api/v1/, as you've shown. This root view only shows list views. Using #action creates a detail view. In your case, your customaction view can be found at ^utente/{pk}/customaction/$. It will not show up in the API root because it is not a list view.
General information on #action and custom methods can be found here.
Also, if for some reason you do want to make customaction a list-level view, you'll need to make some modifications. You could either string up a custom route yourself, without using the #action decorator (which is specifically for detail views). An example of that can be found here.
Your other option is to use the new-ish drf-extensions package. A discussion of using the package to implement collection level controllers in ViewSets can be found here.