I'm new to the Django Framework and one thing bothers me.
I want a simple Rest Call:
www.abc.com/users/1/cantonments/1/
If i use 'pk' in the url pattern everything works out of the box (pk, pk1, pk2....).
But i have some permission functionality which expects the parameters in kwargs in the form 'upk' and 'cpk' for user and cantonment. So if i change pk to upk everything breaks. Somehow the url needs ONE pk.
This works:
url(r'^users/(?P<pk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
This doesnt:
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
Is there any way to have an url pattern that does not need one entry with pk?
P.S. The error:
Expected view CantonmentDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.
EDIT:
My view is simple:
# Authenticated User can show Cantonment Detail
class CantonmentDetail(generics.RetrieveAPIView):
serializer_class = serializers.CantonmentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Cantonment.objects.filter(pk=self.kwargs['cpk'])
Edit2:
I changed get_queryset to get object and it works.
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
obj = queryset.get(pk=self.kwargs['cpk'])
return obj
Edit3:
Using
lookup_url_kwarg = "cpk"
in the class works as well.
You can send optional pk using get method with your url like
www.abc.com/users/1/cantonments/?&upk=1
and url should be
url(r'^users/(?P<pk>[0-9]+)/cantonments/$',
views.CantonmentDetail.as_view()),
and views.py
def view_name(request, pk=None):
upk = request.GET.get('upk')
May be in your view you are accessing pk variable
urls.py
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
views.py
class your_class_name(ListView):
def view_name(self):
upk=self.kwargs['upk']
cpk=self.kwargs['cpk']
print upk, cpk
...
Hope this is helps you
The upk doesn't make any difference to the lookup (because a primary key identifies a single object by design).
So for the view, the lookup_field needs to be set to 'cpk' and everything works.
Did you changed your view with the new names of the variables?
If you have url like this:
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
You shouls update your view like this:
def view_name(request, upk=None, cpk=None):
...
Related
Short question:
What's the appropriate way to access a url path parameter (not a url query parameter) in a Django DRF serializer?
Context:
A have a Django DRF api, where I have a url similar to :
/api/blogposts/:blogid/comments
that is handled by a generic view:
class MyCommentsView(generics.ListCreateAPIView):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
(blog and comment are just example resources; i am working with different ones)
This view allows via a POST call to create new comments.
For legacy reasons, the request will contain the blog_id also as a request body parameter.
In my Serializer I want to check if the :blogid in the url is identical to the blog_id in the body. Currently I do the following, but the path splitting and element selecting feels very fragile (e.g. if I restructure my url in urls.py I need to adjust my custom parsing) . So, is there a better way?
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [..., 'blog_id', ... ]
...
def validate_blog_id(self, value):
# Better way for next line?
blogid_in_url = int(self.context['request'].path.split('/')[-2])
if blogid_in_url != value:
...
Note:
my question is about a url path parameter, not a url query parameter (like blogid in api/blogposts?blogid=55). I know a url query parameter can be accessed via self.context['request'].query_params.
I can't say it is a good way but suggest alternatively:
You can access your url parameters in your serializer like this:
self.context.get('request').parser_context.get('kwargs').get(
'blog_id') # your url parameter name here
Another way, you can override create method of ListCreateAPIView;
def create(self, request, *args, **kwargs):
blog_id = kwargs.get('blog_id') # your url parameter name here
serializer = self.get_serializer(data=request.data,
context={'blog_id': blog_id})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
I want to show User profile in url /account/profile.
I have a detail class based view,
class UserDetail(generic.DetailView):
model = User
slug_field = 'username'
slug_url_kwarg = 'username'
template_name = 'myuser/user_detail.html'
I have a error:
AttributeError at /accounts/profile/
Generic detail View UserDetail must be called with either an object pk or a slug.
How can I charge the username without pass it like parameter in the url?
(ie: /account/prifle/username), the user is already authenticated.
I see something similar here: http://programtalk.com/vs2/?source=python/12247/horas/apps/profiles/views.py but doesn't work to me.
I tried modify get_queryset, dispatch, and nothing work, I don't know where can modify to get the right result.
Any idea? Thanks
That code is very bizarre and you should not follow it. Overriding get_object is the right idea in your case though; but that method should always actually return an object.
class UserDetail(generic.DetailView):
model = User
def get_object(self, *args, **kwargs):
return self.request.user
I have an endpoint that, using the url kwargs, I want to filter and return a querySet, like so: /api/myendpoint/{id}/ should return all myendpoint objects with a field X that matches {id}. I provide the get_query_set method in my view class, which is a ReadOnlyModelViewSet, that returns a queryset with all the objects that fit the criteria (this part I know works, because I print out the result before returning it and it looks correct).
The problem I seem to be having, is that the queryset getting returned by get_queryset, seems to be again filtered by django before being displayed on the endpoint. It is getting filtered such that pk=={id}. This is not the functionality that I want, because I am using {id} to filter on a different field. It seems that it is being treated as a DetailView, but I want it to be treated like a collection view (if such a thing exists). How do I return multiple objects for this view, while still using the url kwargs? I am a django beginner, so sorry if this is obvious.
urls.py:
from rest_framework_nested_import routers
router = routers.DefaultRouter()
router.register(r'', MyView, base_name='myendpoint')
urlpatterns = patterns('',
url(r'^', include(router.urls)))
views.py:
class MyView(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
if 'pk' in self.kwargs:
return MyObj.objects.filter(field=self.kwargs['pk'])
For returning the collection you should not pass in the /{pk}/ since that will try and get a single object with that id, which you have seen.
The proper URL you should be using to get a list of objects is /api/myendpoint/. You can filter the list of objects by using queryset property or get_queryset function. However, we still need to let django know what field and value to filter by.
That's where filtering by query parameter comes in. You could also filter in the URL string, but it's a tad more complicated because you'd need to modify your router URLs.
class MyView(viewsets.ReadOnlyModelViewSet):
def get_queryset(self):
queryset = MyObj.objects.all()
filter_value = self.request.query_params.get('field_name', None)
if filter_value is not None:
queryset = queryset.filter(field_name=filter_value)
return queryset
With this code you can hit /api/myendpoint/?field_name=somevalue and it will return a queryset of MyObj model filtering field_name by somevalue.
I use a variable in the base of my API url, identical to the setup found in the docs for Django REST Framework:
/api/<brand>/states/<state_pk>/
Everything after the base brand slug is a standard API format, and so I use ModelViewSets to generate all my list and detail views for my objects. Everything in the API is filtered by the brand, so this setup makes sense.
simplified project/urls.py
urlpatterns = patterns(
'',
url(r'^v2/(?P<brand_slug>\w+)/', include(router.urls, namespace='v2')),
)
simplified api/urls.py
router = routers.DefaultRouter()
router.register(r'states', StateViewSet)
router.register(r'cities', CityViewSet)
I also need hypermedia links for all models, and this is where I've run into problems. The REST framework doesn't know how to grab this brand variable and use it to generate correct links. Attempting to solve this problem by following the docs leaves me with 2 setbacks:
While the docs explain how to overwrite the HyperlinkRelatedField class, they never say where to put THAT class so that it works with my Serializers.
There's no mention on how to actually get the brand variable from the URL into the HyperlinkRelatedField class.
What are the missing elements here?
So, I figured it out.
Getting the URL variable into the Serializer
To do this, you need to overwrite the get_serializer_context() method for your ModelViewSet, and send in the variable from your kwargs
class BrandedViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
context['brand_slug'] = self.kwargs.get('brand_slug')
return context
Then, you can just extend all of your ModelViewSets with that class:
class StateViewSet(BrandedViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
What's nice is that even though you've injected the Serializer with this variable, it's ALSO accessible from the HyperlinkedRelatedField class, via self.context, and that's how the next part is possible.
Building a Custom Hypermedia link with extra URL variables
The docs were correct in overwriting get_url():
class BrandedHyperlinkMixin(object):
def get_url(self, obj, view_name, request, format):
""" Extract brand from url
"""
if hasattr(obj, 'pk') and obj.pk is None:
return None
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
kwargs['brand_slug'] = self.context['brand_slug']
return reverse(
view_name, kwargs=kwargs, request=request, format=format)
Except, you'll notice I'm grabbing the variable from the context I set in part 1. I was unable to get the context from the object as the docs suggested, and this method turned out to be simpler.
The reason it's a mixin is because we need to extend TWO classes for this to work on all the url hyperlinks and not just the related field hyperlinks.
class BrandedHyperlinkedIdentityField(BrandedHyperlinkMixin,
serializers.HyperlinkedIdentityField):
pass
class BrandedHyperlinkedRelatedField(BrandedHyperlinkMixin,
serializers.HyperlinkedRelatedField):
pass
class BrandedSerializer(serializers.HyperlinkedModelSerializer):
serializer_related_field = BrandedHyperlinkedRelatedField
serializer_url_field = BrandedHyperlinkedIdentityField
Now we can safely extend our serializer and the hyperlinks show the brand variable!
class StateSerializer(BrandedSerializer):
class Meta:
model = State
fields = ('url', 'slug', 'name', 'abbrev', )
This is a continuation of my other question about how to include additional queryset (with request) in an app (Userena) view. If I do what #limelights suggested, this is what my code looks like:
view:
from django.views.generic import list_detail
def requestuserswampers(request):
qs = Thing.objects.filter(user=request.user)
return list_detail.object_list(
request,
queryset = Thing.objects.all(),
template_object_name = 'thing',
extra_context = {'swamp_things': qs},
)
url:
url(r'^accounts/(?P<username>(?!signout|signup|signin)[\.\w-]+)/$',
requestuserswampers,
name='userena_profile_detail'),
This generates a TemplateDoesNotExist error: Template does not exist at myapp/swamp_things.html.
If I try to include the template name and location using template_name = 'userena/profile_detail.html', on the other hand, the right template is rendered, but some of the context is now missing, like the user information that is normally rendered in the default userena "profile_detail" template..
How do I add an extra queryset to the Userena profile detail view which allows for request so that I can filter objects based on the logged in user? Thanks for your ideas!
I didn't realize that it was accepted practice to re-write another app's view, figuring that it was antithetical to DRY principles. But since I had not discovered another method of achieving what I needed to do, and since it was endorsed by another senior user in the comments above, I went ahead and tried to re-write the Userena view. Just needed to add my queryset into the extra_context:
def profile_detail(request, username,
template_name=userena_settings.USERENA_PROFILE_DETAIL_TEMPLATE,
extra_context=None, **kwargs):
user = get_object_or_404(get_user_model(),
username__iexact=username)
profile_model = get_profile_model()
try:
profile = user.get_profile()
except profile_model.DoesNotExist:
profile = profile_model.objects.create(user=user)
if not profile.can_view_profile(request.user):
return HttpResponseForbidden(_("You don't have permission to view this profile."))
if not extra_context: extra_context = dict()
extra_context['profile'] = user.get_profile()
extra_context['hide_email'] = userena_settings.USERENA_HIDE_EMAIL
#### Added the line below
extra_context['swamp_things'] = Thing.objects.filter(user=user)
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)