I have modified get_queryset() method that looks like this:
def get_queryset(self):
type_of_review = self.request.data['type_of_review']
queryset = Reviews.objects.filter(type_of_review=type_of_review).order_by('-id')[:3]
return queryset
It sorts my models according to the type_of_review field, orders them and retrieves 3 last.
When I was trying to write a unit test to it, I ran into a problem that I cannot find a reliable or working way to pass filter argument into my get_queryset() method.
I tried doing it like this:
def test_list_three_review(self):
self.data = [ReviewsFactory.create() for i in range(10)]
response = self.client.get(self.url2, {"type_of_review": "Team"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
But got an error: test_list_three_review - KeyError: 'type_of_review'
Can somebody explain to me what am I doing wrong?
Will gladly provide any additional information.
Thanks anyway!
OK, so self.client.get takes data which should be a dictionary. I think you want to do the following...
def test_list_three_review(self):
[ReviewsFactory.create() for i in range(10)]
data = {'type_of_review':'Team'}
response = self.client.get(self.url2, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Not exactly sure why you want to include a list of objects in the request since your get_queryset method overrides it anyway.
You are passing data as query string parameter. In-order to parse it in view you could use request.GET.get("type_of_review")
your get_queryset() method will become
def get_queryset(self):
type_of_review = self.request.GET.get('type_of_review')
queryset = Reviews.objects.filter(type_of_review=type_of_review).order_by('-id')[:3]
return queryset
GET request is not for passing json data, if you need to pass data as json anyway you could use POST method
then you can pass data like
def test_list_three_review(self):
self.data = [ReviewsFactory.create() for i in range(10)]
data = {"type_of_review": "Team"}
response = self.client.post(self.url2, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
and your get_queryset method will work as it is.
Related
class PreUpdateDeleteAPIView(APIView):
serializer_class = PreEditSerializer
queryset = Reserve.objects.all()
def post(self, request):
code = request.data()
"""''.join(code)"""
data = {
reverse('update-delete', args = [code] ,request=request)
}
return Response(data)
hello. i have an problem for converting querydict to string. what should i do now?
i have tried json.dump and several answers in SOF. but i didnt get my answer yet!
request.data is a property that returns a QueryDict, so you work with:
code = request.data
It however makes no sense to specify args=[code], since code is a dictionary-like structure.
Likely you need some value that corresponds to some key from the request.data, so something like:
code = request.data['code']
The ModelViewSets in DRF have been really helpful, but I'm trying to extend one of them to be able to return a list of objects at a GET request, and process a list on a POST request. So far, it seems like I need to use the #list_route decorator to add this functionality.
I've used it just fine to add custom routes in other viewsets, however this is the first time I'm trying to add one that accepts more than one method. Here's what I have so far:
class PickViewset(viewsets.ModelViewSet):
queryset = Pick.objects.all()
serializer_class = PickSerializer
def get_queryset(self):
#gets the correct queryset
#list_route(methods=['get', 'post'])
def update_picks(self, request, league, week, format = None):
if request.method == 'POST':
#process/save objects here
else:
#otherwise return the requested list
I think this works and that my issue is in urls.py- here's the related stuff from there:
#bind the pick methods explicitly
update_picks = PickViewset.as_view({'get': 'update_picks'})
url(r'^api/picks/(?P<league>[\w ]+)/(?P<week>[0-9]+)/$', update_picks, name='update_picks')
This works fine for GET requests, and if i change the update_picks definition to
update_picks = PickViewset.as_view({'get': 'update_picks'})
then I can step into the POST code from the Viewset. What do I need to do to have both GET and POST requests routed to the update_picks action, where they can then be differentiated with the request method?
I tried adding a , {'post': 'update_picks'} to the as_view(), but that doesn't work.
I also tried adding
get_picks = PickViewset.as_view({'get': 'update_picks'})
with new URL pattern
url(r'^api/picks/(?P<league>[\w ]+)/(?P<week>[0-9]+)/$', get_picks, name='get_picks'),
but that didn't work either.
I looked into having separate list_routes with the same URL, but that doesn't seem to be supported, though I could have missed something in the docs.
Thanks for any help!
The actions argument to the ViewSet is a dict, all methods go in that dict:
get_picks = PickViewset.as_view({
'get': 'update_picks',
'post': 'update_picks',
})
I'm trying to get an instance of a serializer in my overwritten list method and then pass it in through perform_create. Basically what this code does is it checks if the queryset is empty and if it is, we do a perform_create. The problem is that I'm trying to get an instance of the serializer so I can pass it in to the perform_create method. I don't believe the line serializer = self.get_serializer(data=request.data)
correctly grabs the serializer as it shows nothing when I try to log it. Any help is appreciated, thanks.
class ExampleViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwner)
def list(self, request):
queryset = self.get_queryset()
name = self.request.query_params.get('name', None)
# print(request.data)
if name is not None:
queryset = queryset.filter(name=name)
if (queryset.count() == 0):
serializer = self.get_serializer(data=request.data)
print(serializer)
return self.perform_create(serializer)
return HttpResponse(serializers.serialize('json', queryset))
elif name is None:
return HttpResponse(serializers.serialize('json', queryset))
As far as I can see, with
serializer = self.get_serializer(data=request.data)
you are trying to access POST data while responding to a GET request.
DRF ViewSets offer the methods:
list (called upon an HTTP GET request)
create (called upon an HTTP POST request)
retrieve (called upon an HTTP GET request)
update (called upon an HTTP PUT request)
partial_update (called upon an HTTP PATCH request)
destroy (called upon an HTTP DELETE request)
Also see this explicit example binding HTTP verbs to ViewSet methods
So if
you are POSTing data, the list method isn't called at all (as suggested by #Ivan in the very first comment you got above).
The solution is to move the code to the appropriate method, i.e create
Otherwise
your client is GETting, the list method is called, but request.data will be empty at best.
The solution is to make the client provide the parameters for the creation as GET parameters, along with name.
That way the view will find them in self.request.query_params
In case you have a form, simply change the way it sends its data by making it use HTTP GET. See here for further info
I made a little endpoint, adapting DRF ReadOnlyModelViewSet, defined as follows:
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = []
serializer_class = MySerializer
def get_queryset(self):
print 'Debug: I am starting...\n\n\n\n'
# do a lot of things filtering data from Django models by some information on neo4j and saving data in the queryset...
return self.queryset
When I call MyApi via URL, it returns results without any problems, but sometimes it returns doubled result!! It's very strange...It's not a systematic error but happens only sometimes.
I use the line print 'Debug: I am starting...\n\n\n\n' in Apache log to investigate the problem. When that doubling happens, I read in the log:
Debug: I am starting...
Debug: I am starting...
It seems like get_queryset is called more than one time. It's very strange. I didn't report the detail of the logic inside that method, I think the problem is elsewhere or that is a bug...How can I solve?
You have defined queryset as a class attribute.
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = []
That means that each time you append to self.queryset, you are appending to the same list. Your get_queryset method is only called once, but self.queryset already has entries in it at the beginning of the method. To see the problem in action, print self.queryset in your method at the very beginning, before you change it.
You would be better to do something like:
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = None # this line is probably not required, but some code checking tools like it to be defined.
def get_queryset(self):
self.queryset = []
...
return self.queryset
If you are using a custom permission like DjangoModelPermissions. You need to check that the get_queryset method of your View is not being called.
For example, DjangoModelPermissions call this method here:
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
If I use this permission just as it is. The method get_queryset will be called twice.
So I changed it to just this:
queryset = getattr(view, 'queryset', None)
As a result it is just called once. Hope this helps.
You are defining queryset = [] as class attributes and class attributes are "shared by all instances". So, if you python process append some data to your queryset attribute, the subsequent view instances would have that data, only if they are created by the same process.
Had the same problem and just ran a trackback
Turns out that the second run is the rest_framework's rendering files calling view.get_queryset().
Try calling it from the command line and then checking the results.
I am trying to write something elegant where I am not relying on Request object in my code. All the examples are using:
(r'^hello/(?P.*)$', 'foobar.views.hello')
but it doesn't seem like you can post to a URL like that very easily with a form. Is there a way to make that URL respond to ..../hello?name=smith
Absolutely. If your url is mapped to a function, in this case foobar.views.hello, then that function might look like this for a GET request:
def hello(request):
if request.method == "GET":
name_detail = request.GET.get("name", None)
if name_detail:
# got details
else:
# error handling if required.
Data in encoded forms, i.e. POST parameters, is available if you HTTP POST from request.POST.
You can also construct these yourself if you want, say, query parameters on a POST request. Just do this:
PARAMS = dict()
raw_qs = request.META.get('QUERY_STRING', '') # this will be the raw query string
if raw_qs:
for line in raw_qs.split("&"):
key,arg = line.split("=")
PARAMS[key] = arg
And likewise for form-encoded parameters in non POST requests, do this:
FORM_PARAMS = QueryDict(request.raw_post_data)
However, if you're trying to use forms with Django, you should definitely look at django.forms. The whole forms library will just generally make your life easier; I've never written a html form by hand using Django because this part of Django takes all the work out of it. As a quick summary, you do this:
forms.py:
class FooForm(forms.Form):
name = fields.CharField(max_length=200)
# other properties
or even this:
class FooForm(forms.ModelForm):
class Meta:
model = model_name
Then in your request, you can pass a form out to the template:
def pagewithforminit(request):
myform = FooForm()
return render_to_response('sometemplate.html', {'nameintemplate': myform},
context_instance=RequestContext(request))
And in the view that receives it:
def pagepostingto(request):
myform = FooForm(request.POST)
if myform.is_valid(): # check the fields for you:
# do something with results. if a model form, this:
myform.save()
# creates a model for you.
See also model forms. In short, I strongly recommend django.forms.
You can't catch GET parameters in a URL pattern. As you can see in django.core.handlers.base.BaseHandler.get_response, only the part of the URL that ends up in request.path_info is used to resolve an URL:
callback, callback_args, callback_kwargs = resolver.resolve(
request.path_info)
request.path_info does not contain the GET parameters. For handling those, see Ninefingers answer.