Register your Django router - django

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',
)

Related

Django Rest Framework routing with primary key prefix

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'].

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

tastypie return empty resource_uri in get

This is the resource
class TagResource(ModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
simple get request
http://localhost:8000/api/v1/tag/1/?format=json
returns with empty resource_uri
{"created": "2014-03-26T15:14:11.928068",
"id": 1, "name": "test",
"resource_uri": "", "user": ""}
Why is that ?
I tried
def hydrate_resource_uri(self, bundle):
return bundle.get_resource_uri()
It didn't work and i'm pretty sure it's not supposed to require special care.
What am i missing ?
I know this is old, but I know your problem, I just had it on mine, you have a "namespace" on the API URL include or on any URL includes further up in your URL tree.
I had the same problem, and it was because I forgot to register my resource in the urls.py.
Ensure you have something like this in your urls.py file:
myapi.register(TagResource())
I have created a blog for highlighting the same problem. link to my blog
Tastypie give couple of options to create a Model Resource.
ModelResource
NamespacedModelResource
When namespace is included in urls.py then Tastypie logic of generating resource_uri fails as it also expects the same namespace in the api urls. To overcome this problem either one has to remove the namespace from module level urls.py or implement namespace with Tastypie. First solution looks easy but it may break your application. The below code will help you use second approach.
Api.py
from tastypie.resources import NamespacedModelResource
class EntityResource(NamespacedModelResource):
class Meta:
queryset = Entity.objects.all()
allowed_methods = ['get']
resource_name = 'entity'
authentication = SessionAuthentication()
authorization = Authorization()
mymodule/url.py
from tastypie.api import NamespacedApi
from mymodule.api import EntityResource
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='mymodule')
v1_api.register(EntityResource())
urlpatterns = patterns('',
url(r'^api/', include(v1_api.urls)),
)
Make sure you use same namespace for module and its api urls. The above code will surely generate proper resource_uri.
Try to remove the object_class, I think that if this is ModelResource, you don't need this.
This may be because, for some reason, the api_name is missing.
Try to add it in the resource meta.
For instance, if your resource uri is /api/v1/YourResourceName, try to add in your resource Meta:
api_name = 'v1'
Hope it helps.
Only if someone else get this problem caused by a namespace in urls.py. You have to use NamespacedModelResource instead of ModelResource:
from tastypie.resources import NamespacedModelResource
class TagResource(NamespacedModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
and then into your module's urls.py
from tastypie.api import NamespacedApi
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='your_module')
v1_api.register(TagResource())
urlpatterns = patterns(
'',
url(r'^api/', include(v1_api.urls)),
)
Check this post.

merge 2 django (very similar) urlconfs in one

I have an app called "products" that manages "products" and "categories". And I have products/views.py (with generic views) that goes like this:
Objects = {
'products': {'model':Product, 'form':ProductForm}
'categories': {'model':Category, 'form':CategoryForm}
}
and something like this:
def list(request, obj):
model = Objects[obj]['model']
queryset = model.objects.all()
return object_list(request, queryset=queryset)
and then my project urls.py is something like this:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^products/', include('products.product_urls.py'), {obj:'product'}),
(r'^categories/', include('products.category_urls.py'), {obj:'category'}),
)
and then I have the two urls.py for category and product like this:
1) products/product_urls.py
urlpatterns = patterns('',
url(r'^$', 'products.views', name='products-list'),
)
2) and a very similar line in products/category_urls.py
urlpatterns = patterns('',
url(r'^$', 'products.views', name='categories-list'),
)
As you can see, products/product_urls.py and products/category_urls.py are really very similar except for the url names.
My question is: is there a smart technique to "merge" products/product_urls.py and products/category_urls.py into a single module and still have different names for the urls depending on the "object" they're working on. i.e. have a single products/urls.py that'll manage both objects: product and category
You can maybe include the same url module twice, but use namespaced urls!
To me, this seems obvious:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^products/$', 'products.views.list', {'obj':'product'}, name='products-list'),
url(r'^categories/$', 'products.views.list', {'obj':'category'}, name='categories-list'),
)
The url function only differs from the tuple approach in that you can use keyword args (like name) in it.
Your code seems like it would break if you were to really try it verbatim. This, along with the perceived obviousness, makes me wonder if your actual use case is more complicated and requires a different answer.
Furthermore, the object-list generic view already employs the functionality you're trying to create with your Objects approach. (See the queryset argument; also, create-object's form_class arg). An example:
from django.conf.urls.defaults import *
from models import Product, Category
from django.views.generic.list_detail import object_list
urlpatterns = patterns('',
url(r'^products/$',
object_list,
{'queryset': Product.objects.all()},
name='products-list'),
url(r'^categories/$',
object_list,
{'queryset': Category.objects.all()},
name='categories-list'),
)