I am fairly new to Django rest framework and I had a couple questions that would really clear up a lot of stuff for me.
I was looking at docs for simple CRUD generic views like ListAPIView, Retrieve... etc.
For my list view I created it like this:
class CourseListApiView(ListAPIView):
queryset = Course.objects.all()
serializer_class = CourseListSerializer
Which makes sense because of the queryset returns Course.objects.all() so all the courses appear.
What I am not clear about is how the queryset in RetrieveApi works
class CourseRetrieveAPIView(RetrieveAPIView):
queryset = Course.objects.all()
serializer_class = CourseRetrieveSerializer
This is my retrieve view, it takes pk from my link and returns a corresponding course. What is unclear to me is why the queryset is Course.objects.all(), not a filtered query that gets the kwargs from the URL and filters my Courses. I tried it my way and got the same results, my view was:
class CourseRetrieveAPIView(RetrieveAPIView):
serializer_class = CourseRetrieveSerializer
def get_queryset(self):
queryset = Course.objects.filter(pk=self.kwargs.get('pk'))
return queryset
This makes more sense since the queryset is Course.objects.filter(pk=self.kwargs.get('pk')) instead of Course.objects.all() which to me doesn't make sense since I am filtering my courses by the pk in the URL
Hope my question made sense. Leave a comment if you need any clarification. I know the answer will be pretty obvious but I am very new to the framework
You will have to go through the codebase of rest_framework. A function named get_object uses two class variables named lookup_field and lookup_url_kwarg which have a default value of pk and None respectively.
Excerpt from the GenericAPIView in rest_framework/generics.py
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
As you can see the lookup_url_kwarg is set to be equal to lookup_field if nothing is specified. If you change this value to a field of your requirement then the filter in get_object_or_404 changes.
Now coming back to your issue, when you are specifying the filter manually with url kwargs you are not using the functionality provided by the RetrieveAPIView. Instead what you are doing is filtering out your result with pk from url kwargs in get_queryset and then sending that QuerySet result to get_object which will again do the same thing for you.
Related
I am implementing DRF's Pagination over my existing web service (RESTful API). Now I learned from the docs of DRF Pagination that pagination is authomatically applied to ListCreateAPIView , only need to add some of the lines in settings.py file.
So I did the changes according to the documentation and for my webservice I wanted my queryset to be dynamic.
Below are the changes done:
urls.py
url(r'^users/(?P<pk>[0-9]+)/workouts/get/$',
ListCreateAPIView.as_view(WorkoutList.get_queryset(), serializer_class=WorkoutSerializer), name='list'),
views.py
class WorkoutList(generics.ListCreateAPIView):
queryset = Workout.objects.all()
serializer_class = WorkoutSerializer
permission_classes = (UserPermissions,)
def get_queryset(self):
workout_instance = WorkoutList()
workout_instance.get_queryset()
query_params = self.request.QUERY_PARAMS.dict()
if 'date' in query_params and 'exclude_app_install_time' in query_params:
query_set = Workout.objects.filter(created__contains=date).exclude(
app_install_time=query_params['exclude_app_install_time'])
else:
query_set = {}
return query_set
def list(self, request, *args, **kwargs):
workouts = self.get_queryset()
serializer = WorkoutSerializer(workouts, many=True)
return Response(serializer.data)
PS : I have stackoverflowed (pun intented) the problem but couldn't find the right solution(s).
Also I want to implement OffsetLimitPagination in the DRF. A small example link will be helpful
You're doing a couple of very strange things here.
If you subclass a view, you should use that subclass in the urls, not a strange mash-up of the original class and a method from the subclass. So:
url(r'^users/(?P<pk>[0-9]+)/workouts/get/$',
WorkoutList.as_view(serializer_class=WorkoutSerializer), name='list'),
Once you've fixed that, you'll get into an infinite recursion inside your get_queryset method. Again, when you subclass, if you want to call the original implementation you use super; you don't initialize another instance of the current class and try to call that method, because it'll be the same method.
def get_queryset(self):
query_set = super(WorkoutList, self).get_queryset()
Edit I guess the pagination doesn't work because you are starting from a blank Workout query rather than using the returned value from the super call. So you should do:
def get_queryset(self):
query_set = super(WorkoutList, self).get_queryset()
query_params = self.request.QUERY_PARAMS.dict()
if 'date' in query_params and 'exclude_app_install_time' in query_params:
query_set = query_set.filter(created__contains=date).exclude(
app_install_time=query_params['exclude_app_install_time'])
return query_set
I've been learning Django over the past couple of weeks, and there is one thing that really seems to confuse me. Which model's attributes does Django use to define the <pk> that is used in urls.py?
For example, If I have:
urlpatterns = [
url(r'^(?P<pk>\d+)/$', ProductDetailView.as_view(), name="product-detail"),
]
I had assumed previously that the pk would be derived from the model instance that is being used in the given view, in this case, ProductDetailView.as_view(). However, I'm starting to question that logic as you can pass multiple models into a view, of course.
Part 2
Also, what if I wanted to use the pk of one model instance, while only using a different model instance in the view?
For example, what if I had two models, Products & Stores which both hold a many-to-many relationship (eg. a product could be in multiple stores, and a store can hold many products). Then I wanted to have a url where I have a StoreListView listing all the stores that hold a given product, so my url would be something like:
url(r'^(?P<pk>\d+)/$', StoreListView.as_view(), name="store-list")
Where the pk is of the Product instance but the view is of the Store instance
To finalize the question, again, how does Django define pk?
Django doesn't define anything. You define it. ProductDetailView must have a model attribute; it is that attribute that defines what model to use.
Unfortunately, part 2 of your question doesn't really make sense; a view is not an instance of a model.
Have a look at the various methods and attributes of the detail view here, particularly the get_object method:
http://ccbv.co.uk/projects/Django/1.9/django.views.generic.detail/DetailView
def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
By default this requires `self.queryset` and a `pk` or `slug` argument
in the URLconf, but subclasses can override this to return any object.
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
if queryset is None:
queryset = self.get_queryset()
# Next, try looking up by primary key.
pk = self.kwargs.get(self.pk_url_kwarg)
slug = self.kwargs.get(self.slug_url_kwarg)
if pk is not None:
queryset = queryset.filter(pk=pk)
# Next, try looking up by slug.
if slug is not None and (pk is None or self.query_pk_and_slug):
slug_field = self.get_slug_field()
queryset = queryset.filter(**{slug_field: slug})
# If none of those are defined, it's an error.
if pk is None and slug is None:
raise AttributeError("Generic detail view %s must be called with "
"either an object pk or a slug."
% self.__class__.__name__)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404(_("No %(verbose_name)s found matching the query") %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
It's Django, so its fairly customizable, so it doesn't need to be called 'PK'. You could override that by using the pk_url_kwarg. By default the id field is the pk, unless you specify it in your model definition.
Django's DetailView uses the SingleObjectMixin mixin which has a method called get_object which will look for a pk_url_kwarg
pk = self.kwargs.get(self.pk_url_kwarg)
By default this is set to 'pk'
I am trying to use a Django UpdateView to display an update form for the user. https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-editing/
I only want the user to be able to edit their own form.
How can I filter or restrict the the objects in the model to only show objects belonging to the authenticated user?
When the user only has one object I can use this:
def get_object(self, queryset=None):
return self.request.user.profile.researcher
However, I now need the user to be able to edit multiple objects.
UPDATE:
class ExperimentList(ListView):
model = Experiment
template_name = 'part_finder/experiment_list.html'
def get_queryset(self):
self.researcher = get_object_or_404(Researcher, id=self.args[0])
return Experiment.objects.filter(researcher=self.researcher)
class ExperimentUpdate(UpdateView):
model = Experiment
template_name = 'part_finder/experiment_update.html'
success_url='/part_finder/'
fields = ['name','short_description','long_description','duration', 'city','address', 'url']
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
return qs.filter(researcher=self.request.user.profile.researcher)
URL:
url(r'^experiment/update/(?P<pk>[\w\-]+)/$', login_required(ExperimentUpdate.as_view()), name='update_experiment'),
UpdateView is only for one object; you'd need to implement a ListView that is filtered for objects belonging to that user, and then provide edit links appropriately.
To prevent someone from simply putting the URL for an edit view explicitly, you can override get_object (as you are doing in your question) and return an appropriate response.
I have successfully been able to generate the list view and can get
the update view to work by passing a PK. However, when trying to
override the UpdateView get_object, I'm still running into problems.
Simply override the get_queryset method:
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
# replace this with whatever makes sense for your application
return qs.filter(user=self.request.user)
If you do the above, then you don't need to override get_object.
The other (more complicated) option is to use custom form classes in your UpdateView; one for each of the objects - or simply use a normal method-based-view with multiple objects.
As the previous answer has indicated, act on the list to show only the elements belonging to the user.
Then in the update view you can limit the queryset which is used to pick the object by overriding
def get_queryset(self):
qs = super(YourUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)
I am trying to use the ordering_fields with a ListAPIView in Django REST Framework. When I override the get method to return self.get_queryset(), ordering is lost.
How can I maintain the ordering specified in the ORDERING_PARAMs from the url (ordering) while returning the ListAPIView's self.get_queryset()?
I am aware of less elegant solutions where I can grab the ordering fields from the url using:
ordering_params = self.request.QUERY_PARAMS.get(
'ordering', None
)
ordering_params_list = ordering_params.split(',')
# some other code to remove invalid fields not in `ordering_fields` ...
But I'd like to use Django REST Framework to maintain the ordering_fields
usually
get_queryset just to return the dataset without filter.
filter_queryset: it will handle the ordering_fields, search_fields, filter_fields..
default list method will get the queryset like this:
queryset = self.filter_queryset(self.get_queryset())
If you don't want to call filter_queryset, You can call the orderingFilter by yourself.
from rest_framework import filters
queryset = filters.OrderingFilter().filter_queryset(self.request, queryset, self)
To keep the ordering of queryset, you need to wrap it with filter_queryset method:
queryset = self.filter_queryset(self.get_queryset())
I managed to get data from the tables MemberDeclareRecept and Member with the following config files. Here I am looking for the MemberDeclareRecept.pk. But how can I get all the data if I search the Member.CoId instead?
The MemberSearchByCode view gives all the members in the table but I can't get the specific member.
Here are my models
class Member(models.Model):
Name = models.CharField(max_length=40,null=True)
FirstName = models.CharField(max_length=40,null=True)
DateBirth = models.DateField(,null=True)
CoId = models.CharField(max_length=6,null=True)
class MemberDeclareRecept(models.Model):
SyMember=models.ForeignKey(Member,verbose_name='Name member ',null=True,related_name='Member')
DateDeclareRecept=models.DateField(('Date received',null=True)
And the serializers that are being used
class MemberDeclareSerializer(serializers.ModelSerializer):
member = serializers.StringRelatedField(read_only=True)
SyRecept=serializers.StringRelatedField(read_only=True)
class Meta:
model = MemberDeclareRecept
fields=('id','member','SyRecept')
And the views that I am currently using
class MemberDeclareDetail(generics.ListCreateAPIView):
queryset=MemberDeclareRecep.objects.all()
serializer_class =MemberDeclareSerializer
def get_object(self,pk):
try:
return self.queryset.get(pk=pk)
except MemberDeclareRecep.DoesNotExist:
raise Http404
def get(self, request, pk,format=None):
entries = self.get_object(pk)
serializer = MemberDeclareSerializer(entries)
return Response(serializer.data)
class MemberSearchByCode(generics.ListAPIView):
serializer_class =MemberSerializer
def get_queryset(self):
member=self.request.QUERY_PARAMS.get(Co_id',None)
if membre is not None:
queryset = queryset.filter(member__Name=member
return queryset
It appears as though you've found an answer, based on the comment, and it's included below.
class MemberSearch(generics.ListAPIView):
serializer_class=MemberDeclareSerializer
def get_queryset(self):
member = self.kwargs['Co_id']
return member_declare_recept.objects.filter(member__Co_id=member)
It is important to note that this is not filtering the queryset based on query parameters, this is filtering it based on parameters present in the url. If you were filtering it based on query parameters, which is useful if you will need to get a list of all objects at once, the url that you would be using would be like
/api/members/?company=[co_id]
Which would make the company id optional. In your case though, the id is being embedded within the url itself. This is typically referred to as hierarchal urls, and it's generally not recommended, but your urls end up like
/api/company/[co_id]/members
Which is preferable for some, and even required in certain cases.
Now, if you wanted to use the query parameter instead of the url parameter for filtering, only a slight change would be required in your code.
class MemberSearch(generics.ListAPIView):
serializer_class=MemberDeclareSerializer
def get_queryset(self):
company_id = self.request.query_parameters.get('company', None)
if not company_id:
return member_declare_recept.objects.all()
return member_declare_recept.objects.filter(member__Co_id=company_id)
This has the additional advantage of also being support directly through django-filter and the DjangoFilterBackend.