I am attempting to do a search using Django Haystack and then upon retrieving results I need to pass these results to my Django Rest Framework serializer.
The Django Rest Framework serializers.ModelSerializer requires that a queryset of objects gets sent for the serializer to be able to serialize these objects along with their database fields.
When I create my API view and use search to get results haystack returns a searchqueryset.
How could I get this searchqueryset into a django queryset without doing something like:
article_queryset = Article.objects.filter(id__in=[i.object for i in searchqueryset])
As you could imagine, sometimes search can return excess of 1000 search results which means that the above would be very inefficient.
Right now the Django rest framework allows me to paginate my returned objects. I am paginating by 30 objects on each page. How would it be possible for me to do the same with my Haystack searchqueryset?
Any advice or ideas on how to use Haystack along with Django Rest Framework would be great. Examples of how others have done a similar thing would be cool too :)
You could use Haystack's SearchView (instead of a DRF view) which exposes the page object for pagination. Then you can just pass that to your serializer.
E.G. Awhile back I was working on something that required the json object of the rendered results on the current HTML page, so I wrote a filter tag to do this (we were using FacetedSearchView for the UI, and DRF for a more general purpose RESTful API). Called like so in the template:
var articlesJson = {{ page.object_list|article_jsonify }}
Actual filter tag:
import collections
from django.template import Library
from django.utils.safestring import mark_safe
from rest_framework.renderers import JSONRenderer
register = Library()
def article_jsonify(object):
if isinstance(object, collections.Iterable):
many = True
ids = [obj.object.id for obj in object]
else:
many = False
ids = [object.id]
articles = Article.objects.filter(pk__in=ids)
serializer = ArticleSerializer(articles, many=many)
content = JSONRenderer().render(serializer.data)
return mark_safe(content)
register.filter('article_jsonify', article_jsonify)
You could also write a view inherited from generics.ListAPIView and override the get_queryset method, where you would pass the request's query parameter to a SearchQuerySet, then output it using the Serializer. More info here:
http://www.django-rest-framework.org/api-guide/filtering
Of course you might not be able to use the ModelSerializer this way, unless you do something like you mentioned. However DRF has an example on using the paginator on a queryset like so:
http://www.django-rest-framework.org/api-guide/pagination#paginating-querysets
UPDATE
I ended up eventually using a DRF view that uses a Haystack SearchQuerySet as the queryset, and then passing it to a Paginator. Here is a simplified example (I'm sure it can be streamlined some), but I hope it helps someone gets started.
class ArticleList(ListCreateAPIView):
"""
List, Create files
"""
model = Article
def get(self, request, *args, **kwargs):
# simplified filtering of an SQS
q = request.get('q', '')
sqs = SearchQuerySet().filter(content=Clean(q))
paginator = Paginator(sqs, 20)
page = request.QUERY_PARAMS.get('page')
try:
articles = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page
articles = paginator.page(1)
except PageNotAnInteger:
# If page is out of range, deliver last page
articles = paginator.page(paginator.num_pages)
serializer_context = {'request': request}
serializer = PaginatedArticleSerializer(articles, context=serializer_context)
return Response(serializer.data)
class ArticleSerializer(serializers.ModelSerializer):
"""
Base Article Serializer
"""
class Meta:
model = Article
class PaginatedArticleSerializer(pagination.PaginationSerializer):
"""
Serializes page objects of article querysets.
"""
start_index = serializers.SerializerMethodField('get_start_index')
end_index = serializers.SerializerMethodField('get_end_index')
num_pages = serializers.Field(source='paginator.num_pages')
class Meta:
object_serializer_class = ArticleSerializer
def get_start_index(self, page):
return page.start_index()
def get_end_index(self, page):
return page.end_index()
def get_curr_page(self, page):
return page.number
Related
I am building a simple Photos-Albums app with Django Rest Framework (DRF). I would like to be able to delete multiple albums at once by supplying an array of ids. I am using viewsets.ModelViewSet for the basic views.
class AlbumViewSet(viewsets.ModelViewSet):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
I have managed to create a view to show the albums before the delete operation by adding this function to my views.py file.
#api_view(['GET', 'DELETE'])
def delete_albums(request):
"""
GET: list all albums
DELETE: delete multiple albums
"""
if request.method == 'GET':
albums = Album.objects.all()
serializer = AlbumSerializer(albums, many=True)
return Response(serializer.data)
elif request.method == 'DELETE':
ids = request.data
albums = Album.objects.filter(id__in=ids)
for album in albums:
album.delete()
serializer = AlbumSerializer(albums, many=True)
return Response(serializer.data)
If I run curl -X delete -H "Content-Type: application/json" http://localhost:8000/albums/delete -d '{"data":[1,2,3]}' then it will delete albums with ids 1,2,3.
This is OK, except that:
It's not class-based, and I'd prefer to have everything class-based if possible
I would prefer to have a form in the view that lets me input an array e.g. [1,2,3], hit a delete button, and then see the results of the query in the browser, much like when one posts a new object through the interface.
Can anyone please outline how to achieve that? Thanks!
You can use the action decorator
from rest_framework.decorators import action
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.request import Request
#import your model and serializer classes
class AlbumViewSet(viewsets.ModelViewSet):
queryset = Album.objects.all()
serializer_class = AlbumSerializer
#action(methods=["DELETE"], details =False, )
def delete(self, request:Request):
delete_id =request.data
delete_albums = self.queryset.filter(id__in=delete_id)
delete_albums.delete()
return Response( self.serializer_class(delete_albums,many=True).data)
assuming your modelViewSet api point was /api/albums
you could now make a delete request to /api/albums/delete
You could check out the full document on viewsets at ViewSet
and on how to use action decorator provided by the django rest framework
I need to create User instances through the Django Rest API. The normal Django built-in User needs to be extended, most commonly with a UserProfile model. Because of this, User objects had to be represented with a separate table for User and UserProfile in the API.
I assume I could make a single function-based view that combines serialized data from both User and UserProfile serializers, but I'm really interested to know if it's possible to do the same thing with a class-based view. It would need to have two querysets and serializer_classes, is that even possible?
You can use a simple APIView to achieve this:
from rest_framework.views import APIView
class GetProfileEmployeeView(APIView):
def get(self, request, format=None):
# I associate Django users to a matching Employee record via e-mail address
emp_profile = Employee.objects.get(email=self.request.user.email)
serializer = EmployeeSerializer(emp_profile)
return Response(serializer.data)
Then, in your urls.py add an endpoint that points to this view:
urlpatterns = [
url(r'^profile/?$', utility_views.GetProfileEmployeeView.as_view()),
]
When users GET that endpoint they'll get back their entire user profile. You can also go crazy and manually craft a response that's composed of data from multiple model objects:
def get(self, request, format=None):
employee = Employee.objects.get(id=self.request.employee.id)
company = toolbox.get_employee_company(employee)
profile_co = CompanySerializer(company).data
profile_co['licenses'] = AccountSerializer(Account.objects.get(company=company)).data['license_count']
profile = {}
profile['id'] = employee.id
profile['first_name'] = employee.first_name
profile['last_name'] = employee.last_name
profile['email'] = employee.email
profile['is_admin'] = employee.is_admin
profile['company'] = profile_co
return Response(profile)
I have simple blog app which have author=models.ForeignKey(User, editable=False) field. Blog posts are created from django admin site, and I use save_model to get author which is request.user.
Now I want that user (is_staff) can see only his own posts, when he browse model posts. But by default all blog posts are displayed, so how can I hide blog posts created by other users? Of course superusers need to see all of them.
Override the get_queryset method on the ModelAdmin subclass. The documentation has an example of exactly what you're asking for: displaying only objects related to the current user.
In case anyone is lazy. Here's a sample code I use.
So first you create a QuerySet Manager, I usually have mine in models.py, which does something like this (in this case I'm calling the model Blog):
class BlogManager(models.Manager):
def get_queryset(self, request):
query = Blog.objects.filter(author=request.user)
if request.user.is_superuser:
query = UserProfile.objects.all()
return query
Make sure your admin.py then has this:
def get_queryset(self, request):
queryset = BlogManager.get_queryset(self, request)
return queryset
So it might look like:
class BlogAdmin(admin.ModelAdmin):
#add any other code here
#Using that queryset manager created before
def get_queryset(self, request):
queryset = BlogManager.get_queryset(self, request)
return queryset
admin.site.register(BlogAdmin)
Hope this helps anyone!
I am using Rest Framework Ember along with Django Rest Framework as my JSON API backend for my Ember application.
https://github.com/ngenworks/rest_framework_ember
I have gotten sideloading to work correctly with the resource_name = False flag.
Here is my code below:
class DocumentViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows documents to be viewed or edited.
"""
queryset = Document.objects.all()
serializer_class = DocumentSerializer
# Side loading code for documents
resource_name = False
# renderer_classes = (JSONRenderer, BrowsableAPIRenderer)
def list(self, request, *args, **kwargs):
# import IPython
# IPython.embed()
data = {'document': []}
for doc in self.get_queryset():
data['document'].append(doc)
data['contacts'] = doc.contacts.all()
serializer = DocumentContactSerializer(data)
return Response(serializer.data)
This works as I'd like it to work.
The problem now is that since I've implemented this and overwritten the list() method on the ModelViewSet whenever a new object is created on a POST I am getting this error:
'NoneType' object has no attribute '__getitem__'
If I comment out the resource_name = False then POST works as expected again.
Would you know what could be causing this?
I just ran into the very same problem. Our set-up is also Ember + DRF. And I have found a solution.
You could override the create method like this:
def create(self, request):
self.resource_name = 'document'
data = request.DATA # returns the right querydict now
# do what you want
In this way, you're keeping the side load by using resource_name = false in cases other than create.
I'm trying to build out a bulk update view for a specific model using Django Rest Framework. In the short term, it only needs to update one field (toggling an invite from submitted=False to submitted=True), but I'd like it to be able to provide more functionality in the future. Whenever I test the view, however, a new object is being created instead of the current one being modified.
I feel like this must be a simple mistake on my part, but I can't figure out what's going on. The serializer object appears to be ignoring the value for "id" passed in through JSON, which may be contributing to the issue. Current code is:
class InviteBulkUpdateView(generics.UpdateAPIView):
def get_queryset(self):
order = self.kwargs['order']
invite = get_objects_for_user(self.request.user, 'sourcing.view_invite')
return invite.filter(order=order)
serializer_class = InviteInputSerializer
def put(self, request, *args, **kwargs):
data = request.DATA
serializer = InviteInputSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
class InviteInputSerializer(serializers.ModelSerializer):
class Meta:
model = Invite
fields = ('id', 'order', 'team', 'submitted')
Can anybody shed some light onto what I might be doing wrong?
Just in case somebody is looking for a library to handle this, I wrote a Django-REST-Framework-bulk which allows to do that in a couple of lines (the example only does bulk update but the library also allows bulk create and delete):
from rest_framework_bulk import ListCreateBulkUpdateAPIView
class FooView(ListCreateBulkUpdateAPIView):
model = FooModel
You're not passing object instances to your serializer. (Thus it will create new instances rather than update.) See the docs on dealing with multiple objects in serializers where you'll see your QuerySet passed in.
Django has update method to handle that. You may want to read full info from django documentation.
Here is a sample code where you can use to update given field for multiple records:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import APIException
class Room_Update_ViewSet(APIView):
def put(self, request,*args, **kwargs):
hotel_id = self.kwargs.get('hotel_id')
room_ids = self.request.query_params.get('room_ids')
room_ids = list(map(int, room_ids.split(',')))
try:
Room.objects.filter(hotel_id=hotel_id,id__in=room_ids).update(booked_status=False)
instances = Room.objects.filter(hotel_id=hotel_id,id__in=room_ids)
serializer = RoomSerializer(instance=instances, many=True)
return Response(serializer.data,status=status.HTTP_200_OK)
except Exception as e:
print("Error udating rooms-->",e)
raise APIException