Django Rest Framework routing with primary key prefix - django

I'm using DRF DefaultRouter as follows.
# urls.py
router = DefaultRouter()
router.register('book', BookingViewSet, basename='booking')
router.register('book/<int:bk>/reservation', ReservationViewSet, basename='reservation')
urlpatterns = [
path('', include(router.urls)),
]
# view
class ReservationViewSet(viewsets.ModelViewSet):
serializer_class = ReservationSerializer
queryset = Reservation.objects.all() # for testing only
But when I visit the URL /book/1/reservation/ it says no url pattern found.
lending/ ^book/<int:bk>/reservation/$ [name='reservation-list']
lending/ ^book/<int:bk>/reservation\.(?P<format>[a-z0-9]+)/?$ [name='reservation-list']
lending/ ^book/<int:bk>/reservation/(?P<pk>[^/.]+)/$ [name='reservation-detail']
lending/ ^book/<int:bk>/reservation/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='reservation-detail']
The current path, lending/book/1/reservation/, didn’t match any of these.
I'm using bk to capture book id.

That's because it implements the <int:bk> as regex, so without any interpretation. Probably the simplest way to do this is with two routers:
router1 = DefaultRouter()
router1.register('book', BookingViewSet, basename='booking')
router2 = DefaultRouter()
router2.register('reservation', ReservationViewSet, basename='reservation')
urlpatterns = [
path('', include(router1.urls)),
path('book/<int:bk>/', include(router2.urls)),
]
In the ReservationViewSet you can then for example filter with bk:
class ReservationViewSet(viewsets.ModelViewSet):
serializer_class = ReservationSerializer
def get_queryset(self):
return Reservation.objects.filter(book_id=self.kwargs['bk'])

Just wanted to add this to the accepted answer ... You can also do this with the same router, just embed the pk in the URL in regex form as the error message suggests:
# urls.py
router = DefaultRouter()
router.register('book', BookingViewSet, basename='booking')
router.register('book/(?P<bk>[^/.]+)/reservation', ReservationViewSet, basename='reservation')
urlpatterns = [
path('', include(router.urls)),
]
You can then access the bk argument in your view using self.kwargs['bk'].

Related

Django optional URL captured value for rest APIView

For following Django code using django_rest_framework:
class PollMessageView(generics.ListAPIView):
serializer_class = MessageSerializer
lookup_url_kwarg = 'count'
def get_queryset(self):
count = self.kwargs.get(self.lookup_url_kwarg)
queryset = Message.objects.all()[:count]
return queryset
urlpatterns = [
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
How do I make the count parameter optional in the URL pattern? For example, if I visit /poll_message/ without a count, it would still call the PollMessageView (and I can set a default number for count if it's missing)?
create a new path as ,
urlpatterns = [
path('poll_message/', PollMessageView.as_view(), name="poll_message-wo-count"),
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
# URLconf
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.page),
path('blog/page<int:num>/', views.page),
]
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...
In detail reference - https://docs.djangoproject.com/en/3.0/topics/http/urls/#specifying-defaults-for-view-arguments
You can specify defaults for view arguments, but the example on the website is for functional view, not sure how would that work for class based view or in this case, the rest framework class based view.
However, you can add another path with extra options to achieve the same thing
path('poll_message/', PollMessageView.as_view(), {
"count": 5
}, name="poll_message"),

How to make a reverse in DefaultRouter()

I'm setting up a new tests, and i want to make an reverse.
router = DefaultRouter()
router.register('profile', views.UserProfileViewSet, base_name='profile')
urlpatterns = [
url(r'', include(router.urls))
]
UserProfileViewSet
class UserProfileViewSet(viewsets.ModelViewSet):
"""Handles creating, creating and updating profiles."""
serializer_class = serializers.UserProfileSerializer
permission_classes = (permissions.UpdateOwnProfile,)
authentication_classes = (TokenAuthentication,)
queryset = get_user_model().objects.all()
So , i want make a reverse in tests.py. my shot is:
CREAT_USER_URL = reverse('profile-create')
And I simply get:
Reverse for 'profile-create' not found. 'profile-create' is not a valid view function or pattern name.
How should I set up a reverse in this case.
You should use profile-list instead of profile-create
CREAT_USER_URL = reverse('profile-list')
There is no URL as {base_name}-create, If you wanna use create endpoint, use {base_name}-list.
For more information, refer to this table

Register your Django router

When I use route.register without base_name like;route.register(r'codes', SmsCodeViewset)
An error occurred;
AssertionError: basename argument not specified, and could not automatically determine the name from the viewset, as it does not have a .queryset attribute.
When I use route.register(r'codes', SmsCodeViewset, bose_name="") there is no error, may I ask why?
I was checking my endpoints and I don't have base name in any of them.
from app import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'states', views.StateSet)
urlpatterns = [
url(r'^', include(router.urls)),
]
class StateSet(viewsets.ModelViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
we should provide base name to the route when we add it like below
router.register(
r'codes',
SmsCodeViewset,
base_name='sms-code',
)

Using multiple slugs in a url

I want to keep my urls dynamic as well as clean.
Therefore I'm using slugs.
My problem right now is, that I get the following error:
redefinition of group name 'slug' as group 2; was group 1 at position 42
I think I get that error, because I have two slugs in my chain.
For reference I have a ListView into ListView into an UpdateView, alls importet from django.views.generic. The first list view gives me the first slug and the update view the second.
Here is the url pattern (spread across the apps):
First list view:
urlpatterns = [
url(r'^$', RestaurantListView.as_view(), name='restaurant-list'),
url(r'^(?P<slug>[\w-]+)/menus/', include('menu.urls', namespace='menu')),
]
Second list view:
urlpatterns = [
url(r'^$', MenuListView.as_view(), name='menu-list'),
url(r'^(?P<slug>[\w-]+)/', MenuUpdateView.as_view(), name='menu-detail'),
]
In the templates I get the objects via:
<li><a href='{{obj.get_absolute_url}}'> {{obj}} </a></li>
Which I have defined in the respective models:
def get_absolute_url(self):
return reverse('restaurants:menu:menu-detail', kwargs={'slug': self.slug})
and
def get_absolute_url(self):
return reverse('restaurants:menu:menu-list', kwargs={'slug': self.slug})
So the resulting pattern at the end is:
restaurants/(?P<slug>[\w-]+)/menus/(?P<slug>[\w-]+)/
How can I fix it so that I don't get the error anymore?
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
for more details https://docs.djangoproject.com/en/1.11/topics/http/urls/

r'^(?P<pk>\d+)$ URL 404ing

I'm trying to use this link <a href="Annotation/{{ vod.id }}"> to load another page in my website base on primary key of videos. My url file is as follows:
urlpatterns = [
url(r'^$', ListView.as_view(queryset=Vod.objects.all().order_by("-date")[:25], template_name="Annotation/home.html")),
url(r'^(?P<pk>\d+)$', ListView.as_view(queryset=Vod.objects.get(pk=1).posts.all().order_by("-date"), template_name="Annotation/post.html")),
]
I get the standard 404 from the links generated using the aforementioned link.
Thanks!
edit: Added the base URLs
url(r'^admin/', admin.site.urls),
url(r'^Annotation', include('Annotation.urls')),
url(r'^Profile', include('Profile.urls')),
This is the URL for Profile.urls
url(r'^$', views.index, name='index'),
edit2: Changed URL and added view I'm trying to use.
url(r'^(?P<key>[0-9]+)$', views.post, name="post")
Here's views.post
def post(request, key):
try:
target_vod = Vod.objects.get(pk=key)
except Target.DoesNotExist:
raise Http404("Vod does not exist")
target_posts = Vod.objects.get(pk=key).posts.all().order_by("-date")
context = {'target_vod': target_vod, 'target_posts': target_posts}
return render(request, 'Annotation/post.html', context)
I have seperated the code and put it into a views.py. This should work.
urls.py
url(r'^(?P<pk>[0-9]+)$', AnnoList.as_view(), template_name="Annotation/post.html")),
Then in views.py:
class AnnoList(ListView):
template_name = 'Annotation/post.html'
def get_queryset(self):
self.vod = get_object_or_404(Vod, pk=self.args[0])
return Posts.objects.filter(vod=self.vod).all().order_by("-date")
I assuming here that you have two different tables and that Posts has a foreign key to Vod.