I haven't found any information in documentation about how to exclude/disable any of the endpoints provided by Djoser. I could just look for one in it's urlpatterns and remove it but I'm wondering if there's any less ugly way to do that.
You can write a custom view that returns an error code and provide it in your own urls.py before you include djoser's URLs, to pre-empt access to specific endpoints.
It's not as ideal as having settings to support this, but until/unless the app supports this, overriding its URLs with your own is a way to do what you're asking without having to maintain a modified local version of the app.
I found a nice solution. Instead of using include("djoser.urls.base"), do something like so:
from djoser.views import UserViewSet
# Taken from Djoser's source
router = DefaultRouter()
router.register(r"users", UserViewSet, basename="users")
def is_route_selected(url_pattern):
urls = [
"usuarios/set_email/",
"usuarios/reset_email_confirm/",
]
for u in urls:
match = url_pattern.resolve(u)
if match:
return False
return True
# Filter router URLs removing unwanted ones
selected_user_routes = list(filter(is_route_selected, router.urls))
# Of course, instead of [] you'd have other URLs from your app here:
urlpatterns = [] + selected_user_routes
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)),
]
the redirect url is
"liveinterviewList/2"
and, ofcourse, I declare that url in url.py
more over, when I type that url in browser manualy, it works well.
what's the matter?
more question.
at this case, I write the user_id on the url.
I think, it is not good way to make url pattern.
but I don't know how I deliver the user_id variable without url pattern.
please give me a hint.
What HariHaraSudhan left out was how to use parameters. For your case, you would want something like:
path(r'liveinterviewList/<int:userId>', ..., name='live-interview'),
And then when you are ready to reverse, use this:
reverse('app:live-interview', kwargs={ 'userId': userId })
where app is the name of the app in which your view lives. If your url lives in the main urls file , you don't need the app: prefix.
Django reverse function accepts the name of the path not the URL.
lets say i have url patterns like this
urlpatterns = [
path('/users/list', name="users-list")
]
In my view i can use like this
def my_view(request):
return redirect(reverse("users-list"));
You should add a name to your path url and use it to redirect.
As the django doc says :
urls :
urlpatterns = [
path('/name', name="some-view-name")
]
def my_view(request):
...
return redirect('some-view-name')
i'm having a issue when i try to register more than 2 routers using Django-REST-FRAMEWORK. Please take a look on my example:
urls.py
from rest_framework import routers
from collaborativeAPP import views
router = routers.DefaultRouter()
router.register(r'get_vocab', views.VocabViewSet)
router.register(r'get_term', views.TermViewSet)
router.register(r'get_discipline', views.DisciplineViewSet)
urlpatterns = patterns(
...
url(r'^service/', include(router.urls))
)
views.py
class VocabViewSet(viewsets.ModelViewSet):
queryset = Vocab.objects.all()
serializer_class = VocabSerializer
class TermViewSet(viewsets.ModelViewSet):
queryset = Term.objects.all()
serializer_class = TermSerializer
class DisciplineViewSet(viewsets.ModelViewSet):
queryset = Vocab.objects.filter(kwdGroup=4)
serializer_class = DisciplineSerializer
the result in my localhost is the following:
http://localhost:8000/service/
HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS
{
"get_vocab": "http://127.0.0.1:8000/service/get_discipline/",
"get_term": "http://127.0.0.1:8000/service/get_term/",
"get_discipline": "http://127.0.0.1:8000/service/get_discipline/"
}
As you can see i have registered 3 routers expecting that they will display 3 urls for each methodname(get_vocab, get_term, get_discipline). The final result is get_discipline is occuring two times and get_vocab url is missing.
Notice that for methods that uses different models it works fine, but in case of get_discipline and get_vocab they use the same model which will create this mess. Should i use a viewset for each model? If so, how can a define different methods in a viewset?
It should occur the following result:
HTTP 200 OK
Content-Type: application/json
Vary: Accept
Allow: GET, HEAD, OPTIONS
{
"get_vocab": "http://127.0.0.1:8000/service/get_vocab/",
"get_term": "http://127.0.0.1:8000/service/get_term/",
"get_discipline": "http://127.0.0.1:8000/service/get_discipline/"
}
What am i missing? I supposed that i could register as many routers as i want. It is supposed to have one router per model? Why doesn't seem to work for viewsets that share a same model?
Try explicitly adding a base_name to each registered viewset:
router = routers.DefaultRouter()
router.register(r'vocabs', views.VocabViewSet, 'vocabs')
router.register(r'terms', views.TermViewSet, 'terms')
router.register(r'disciplines', views.DisciplineViewSet, 'disciplines')
As a side note, your should probably exclude get_ prefix in your urls since that is not RESTful. Each URL should specify a resource, not an action on the resource. Thats what HTTP verbs are used for:
GET http://127.0.0.1:8000/service/vocabs/
# or this to create resource
POST http://127.0.0.1:8000/service/vocabs/
...
Here some more information about router:
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls
Here exist two mandatory arguments for register() method:
prefix : The URL prefix to use for this set of routes.
viewset : The viewset class.
And what abut base_name?
The base to use for the URL names that are created. if you don't set the base_name, it will be automatically generated according to the model or queryset attribute on the viewset.
Here is the URL patterns generated by example above:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
URL pattern: ^accounts/$ Name: 'account-list'
URL pattern: ^accounts/{pk}/$ Name: 'account-detail'
Now if you want create custome route you should write methods on the viewset decorated with #link or #action like this:
#action(permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
The following URL generated:
URL pattern: ^users/{pk}/set_password/$ Name: 'user-set-password'
You used the DefaultRouter and this router is similar to SimpleRouter, but additionally includes a default API root view, that returns a response containing hyperlinks to all the list views. It also generates routes for optional .json style format suffixes.
See this picture of detail table
In previous answer you got the right answer .. me just have given you some more information about router
I am not sure if this is an answer or comment (or a question).
In my case I have two endpoints to a single model and #miki725 answer was not help for me. I was forced to use separated views and separated serializers.
In urls.py I have the 3rd parameter (like #miki725 suggests)
router.register(r'doc_v1', views.DocV1ViewSet, basename='doc_v1')
router.register(r'doc_v2', views.DocV2ViewSet, basename='doc_v2')
But this is not enough. I must use a separated serializers and connect them to the proper detail view via explicitly defined url field:
url = serializers.HyperlinkedIdentityField(view_name='doc_v1-detail')
This is a pain, because (sometimes) both views differ in really minor details like access permissions are. And I have to make almost identical duplicates of view and serializer.
Not good behavior but I have no other solution for now.
i have a tastypie auto generated url
/api/v1/categories/?format=json
i want only this particular url to get data from a view instead of tastypie resource.
the reason i want to do is this because all my clients are using this and don't want to change that.
i tried to put my url under
url(r'^api/', include(v1_api.urls)),
url(r'^api/v1/categories/\?format=json','categories.views.raw_data'),
in urls.py
that doesn't seem to work
change the order:
url(r'^api/v1/categories/\?format=json','categories.views.raw_data'),
url(r'^api/', include(v1_api.urls)),
django looks for matches from top to bottom.
How Django processes a request
it can be done this way from tastypie instead of overriding it in urls.py by using override_urls in your resources
def override_urls(self):
return [url(r"^(?P<resource_name>%s)/$" % self._meta.resource_name,'categories.views.raw_data', name="categories_views_raw_data"),]
I am creating REST based APIs for an app using Tastypie with Django. The problem is default API url in Tastypie contains version info in url patterns i.e.
http://lx:3001/api/v1/vservers/?username=someuser&api_key=someapikey
I want my url to be free from API version info like this:
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
urls.py
v1_api = Api()
v1_api.api_name = ''
v1_api.register(UserResource())
...
url(r'^api/', include(v1_api.urls)),
I am overwriting api_name with an empty string still
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey does not work.
How can I get rid of the version info altogether?
Thanks..
Subclass Api and override urls to remove all the api_name-related bits:
class MyApi(Api):
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r"^/", include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Although tastypie makes supplying api_name optional, failing to provide one simply defaults api_name to "v1". This behavior can be modified by subclassing Api and overriding the urls property within api.py to behave independently of api_name. To achieve the desired URLconf, however, there's still one correction to #dokkaebi's solution worth noting:
pattern_list.append((r"^/", include(self._registry[name].urls)))
should instead read:
pattern_list.append((r'', include(self._registry[name].urls)))
in order to avoid the dreaded // that would direct your clients to
http://lx:3001/api//vservers/?username=someuser&api_key=someapikey
in place of
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
as intended.
For convenience, I've included the modified code below.
Solution
class MyApi(Api):
"""
An API subclass that circumvents api_name versioning.
"""
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r'', include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Convention
One of Django’s core philosophies is that URLs should be beautiful; a clean, elegant URL scheme is an important detail in any high-quality Web application. With respect to the validity of this approach, using a custom request header or an accept header will get the versioning job done without convoluting the scheme with the (subjectively) ugly v1/. That is not to say that the URL versioning strategy is without its share of caveats; however, it's quick to implement and predictable in its response.