Using views to change DRF response - django

I would like to alter the response from an API.
However, it does not alter the result properly. I get a KeyError: 'game'.
I am not sure why, as my API response (via URL) seems to have the value game in it. I may be getting confused with the JSON response, and the python object.
I have a sample of the API response below
results from API
{
"pk": 995,
"game": [
{
"name": "Finance",
"gamelevel": 3
},
{
"name": "Data",
"gamelevel": 1
}
]
},
views.py
class TagList(viewsets.ModelViewSet):
queryset = Task.objects.filter(game__isnull=False).all()
serializer_class = TagSortSerializer
def get_queryset(self):
test = self.queryset.values('title', 'game__name')
result = defaultdict(set)
for item in queryset:
parent = {'name': 'NoLevel_1'}
children = []
for game in item['game']:
if game['gamelevel'] == 1:
parent = game
else:
children.append((game['gamelevel'], game['name']))
result[parent['name']].update(children)
result = [
{
'name': parent,
'game_child': [
{'name': name, 'gamelevel': gamelevel} for gamelevel, name in games
],
'gamelevel': 1,
} for parent, games in result.items()
]
return result

You're using the values queryset method to get only a selection of fields from the model, and the only fields you've specified are title and tag__name. So you won't get game or any of the other keys you've used.
You certainly don't want to use values here in the first place; just do a normal query and access fields via dot lookup rather than dictionary.

Related

Django "Method \"POST\" not allowed."

When I test my endpoints using Postman I get the message Method \"POST\" not allowed when I try to create a new truck using my createTruck function. I am able to delete, update, and get trucks from my database. Also I am able to create a new truck in Django Admin.
views.py
#api_view(['POST'])
def createTruck(request):
data = request.data
user = request.user
truck = Truck.objects.create(
user=user,
image=data['image'],
make=data['make'],
prototype=data['prototyp'],
year=data['year'],
serviceInterval=data['serviceInterval'],
nextService=data['nextService'],
seats=data['seats'],
bedLength=data['bedLength'],
color=data['color'],
vin=data['vin'],
currentMileage=data['currentMileage']
)
print("request", request)
serializer = TruckSerializer(truck, many=False)
return Response(serializer.data)
#api_view(['DELETE'])
def deleteTruck(request, pk):
truck = Truck.objects.get(id=pk)
truck.delete()
return Response('Truck Deleted!')
urls.py
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/<str:pk>/', views.getTruck, name="truck"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]
I might be passing in user wrong, but I'm not sure.
The Postman URL is
http://127.0.0.1:8000/api/trucks/create/
The body is
{
"user": 1,
"image": "/images/2016-tundra-600x400.jpeg",
"make": "Toyota",
"prototype": "Tundra",
"year": 2016,
"serviceInterval": 720,
"nextService": 600,
"seats": 2,
"bedLength": "5.50",
"color": "blak",
"vin": "0989098ad2321",
"currentMileage": 20000
}
Just change your URL pattern. It is a URL pattern conflict, that create-truck-pattern tries to get the truck with pk:create. The standard solution is to change the type of pk from str to int. But the other solution is to change URL priority in your patterns and move create-truck on step upper.
Recommended solution:
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/<int:pk>/', views.getTruck, name="truck"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]
other solution:
urlpatterns = [
path('trucks/', views.getTrucks, name="trucks"),
path('trucks/create/', views.createTruck, name="create-truck"),
path('trucks/<str:pk>/', views.getTruck, name="truck"),
path('trucks/delete/<str:pk>/', views.deleteTruck, name="delete-truck"),
path('trucks/update/<str:pk>/', views.updateTruck, name="update-truck"),
]

How to parse a string list query param in DRF serializers?

I'm building a REST API to handle user requests in the following form:
localhost:8000/search/?categories=<category1>,<category2>&parts=<id1>,<id2>
Where , is supposed to be the delimiter for the parser.
My view processes the request and passes the query params to the serializer, but I just cannot get the raw strings parsed to a list of strings.
My attempt so far:
class StringListField(serializers.ListField):
child = serializers.CharField()
class LookupPartsSerializer(serializers.Serializer):
categories = StringListField()
parts = StringListField()
class LookupParts(APIView):
def get(self, request, format=None):
serializer = LookupPartsSerializer(data=request.query_params)
serializer.is_valid()
return Response(serializer.validated_data)
My desired output is like:
{
"categories": [
"<category1>",
"<category2>"
],
"parts": [
"<id1>",
"<id2>"
]
}
But now I'm getting:
{
"categories": [
"<category1>,<category2>"
],
"parts": [
"<id1>,<id2>"
]
}
So basically I'm looking for an option to pass a delimiter argument to the StringListField or add some custom parsing method.
NOTE:
I'm aware, that if I change the query pattern from ?categories=<category1>,<category2>... to ?categories=<category1>&categories=<category2>... then I'd get the desired results, but I'd like to stick to my original approach.
I think the best way is to implement a customer to_representation():
def to_representation(self, instance):
data = super(StringListField, self).to_representation(instance)
data['parts'] = data['parts'].split(',') if isinstance(data['parts'], str) else data['parts']
data['categories'] = data['categories'].split(',') if isinstance(data['categories'], str) else data['categories']
return data
Hope it helps!

How to integrate Haystack with Django Rest Framework for making GET REST API for searching?

model.py
class Item(models.Model):
name=models.CharField(max_length=50)
company=models.CharField(max_length=100)
search_indexes.py
class ItemIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
name=indexes.CharField(model_attr='name')
company=indexes.CharField(model_attr='company')
def get_model(self):
return Item
def index_queryset(self, using=None):
return self.get_model().objects.all()
serializer.py
class ItemSearchSerializer(serializers.Serializer):
text = serializers.CharField()
name=serializers.CharField()
company=serializers.CharField()
views.py
class ItemSearchViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = ItemSearchSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get_queryset(self):
request = self.request
queryset = EmptySearchQuerySet()
if request.GET.get('q', ''):
query = request.GET.get('q', '')
queryset =SearchQuerySet().filter(content=query);
return queryset
And in url.py I added :
router.register(r'searchquery', views.ItemSearchViewSet, base_name='searchquery')
Now on making GET request from postman like :
http://127.0.0.1:8000/searchquery/?q=app, I am getting the response as desired as show below.
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"text": "apple\ndjflkj",
"id": 14,
"name": "apple",
"mrp": "45.000000",
"company": "djflkj",
"imageid": "jfhi",
"edible": false,
"discount": "0.000000",
"deliverable": true,
"seller_uid": "ljhkh",
"category": "ldjhgjfdk"
},
{
"text": "app\nhuhiu",
"id": 16,
"name": "app",
"mrp": "78.000000",
"company": "huhiu",
"imageid": "iyiugiy",
"edible": false,
"discount": "45.000000",
"deliverable": true,
"seller_uid": "hjh",
"category": "hhl"
}
]
}
But the reponse time is very slow it takes around 2700 ms everytime ,
and I want to make it fast. As response of elastic search is much fast
but I don't know what I am doing wrong. Not sure but may be due to
these reasons I am getting this delay : 1) Haystack is made for
django, so on integrating it with django rest framework , it may be
getting slow. 2) I am using free Bonsai Elastic search heroku add on
and it has just 125 mb memory.
This is how I am connecting to Bonsai elastic search (setting.py)
ES_URL = urlparse('https://******#pine-1455731.us-east1.bonsaisearch.net')
print ES_URL
HAYSTACK_CONNECTIONS = {
'default': {
'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
'URL': ES_URL.scheme + '://' + ES_URL.hostname + ':443',
'INDEX_NAME': 'haystack',
},
}
if ES_URL.username:
HAYSTACK_CONNECTIONS['default']['KWARGS'] = {"http_auth": ES_URL.username + ':' + ES_URL.password}
Any help will be appreciated. I am new to elastic search. I want to do elastic search to search products by name for my android application.
I even don't know whether this is the correct approach to do searching. I thought I would enter name of product I want to search and then i will send a GET request and get all the products which are related.
I did Python Profile please look it here: gist
If any one could suggest me any other way of achieving this I will appreciate your help.
The search response is slow because of this code:
def index_queryset(self, using=None):
return self.get_model().objects.all()
index_queryset is supposed to return query set, you are actually returning all model objects. This method is called for every item which is returned in search.

Show Filters and Ordering in Django Rest Framework Options Request

I'm using the Django Rest Framework I noticed on the web browseable part of the API there is a button called 'options' when clicked it shows the following...
HTTP 200 OK Vary: Accept Content-Type: text/html Allow: HEAD, GET, OPTIONS
{
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"renders": [
"application/json",
"text/html"
],
"name": "Products",
"description": "API endpoint."
}
my question is, is there anyway I could list out here all the filter options an other stuff for this url?
You can make OPTIONS return whatever you want, by overriding the .metadata() method on the view.
See here: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L340
Update as of 2015: We now have a customizable metadata API that makes this easier: http://www.django-rest-framework.org/api-guide/metadata/
You can totally do this. Here's a custom metadata class that I've been keeping up to date here on StackOverflow. This simply lists all the available filters, their types, and their choices. It also lists the ordering fields that are available on a class:
class SimpleMetadataWithFilters(SimpleMetadata):
def determine_metadata(self, request, view):
metadata = super(SimpleMetadataWithFilters, self).determine_metadata(request, view)
filters = OrderedDict()
if not hasattr(view, 'filter_class'):
# This is the API Root, which is not filtered.
return metadata
for filter_name, filter_type in view.filter_class.base_filters.items():
filter_parts = filter_name.split('__')
filter_name = filter_parts[0]
attrs = OrderedDict()
# Type
attrs['type'] = filter_type.__class__.__name__
# Lookup fields
if len(filter_parts) > 1:
# Has a lookup type (__gt, __lt, etc.)
lookup_type = filter_parts[1]
if filters.get(filter_name) is not None:
# We've done a filter with this name previously, just
# append the value.
attrs['lookup_types'] = filters[filter_name]['lookup_types']
attrs['lookup_types'].append(lookup_type)
else:
attrs['lookup_types'] = [lookup_type]
else:
# Exact match or RelatedFilter
if isinstance(filter_type, RelatedFilter):
model_name = (filter_type.filterset.Meta.model.
_meta.verbose_name_plural.title())
attrs['lookup_types'] = "See available filters for '%s'" % \
model_name
else:
attrs['lookup_types'] = ['exact']
# Do choices
choices = filter_type.extra.get('choices', False)
if choices:
attrs['choices'] = [
{
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
}
for choice_value, choice_name in choices
]
# Wrap up.
filters[filter_name] = attrs
metadata['filters'] = filters
if hasattr(view, 'ordering_fields'):
metadata['ordering'] = view.ordering_fields
return metadata
Put that somewhere in your project, then set your DEFAULT_METADATA_CLASS, and you should be all set, with a new key on your OPTIONS requests like so:
"filters": {
"sub_opinions": {
"type": "RelatedFilter"
},
"source": {
"type": "MultipleChoiceFilter",
"choices": [
{
"display_name": "court website",
"value": "C"
},
]
}
...more...
}
This will also display choices, mirroring the way it's handled elsewhere in DRF.

Right way to pass Django objects to ExtJS

Django has a built in serialization functionality which allows you to serialize any query result set into JSON:
json_serializer = serializers.get_serializer("json")()
json_serializer.serialize(queryset, ensure_ascii=False)
This produces output such as:
[
{
"pk": 1,
"model": "app_name.model_name",
"fields": {
"field_name": "value",
(...)
}
}
]
If you want to pass this JSON object over to an ExtJS driven application you run into a problem, because ExtJS expects its JSON to be formatted differently:
{
"total": 100,
"success": true,
"objects": [
{
"id": 1,
"field_name": "value",
(...)
}
]
}
There are 2 main differences: the additional meta-data (success, total) and the IDs of the objects which are provided together with other fields in Ext, but not in Django.
There are many possible ways to make one or the other format conform with the second, but what do you consider to be the best way to make this work? Is it a special serializer on the Django side, or maybe a special reader on the ExtJS side...
What do you think is the best way to solve this problem?
Better idea: use a custom serialiser.
settings.py
SERIALIZATION_MODULES = {
'extjson': 'extjs.serialiser.json'
}
extjs\serialiser\json.py
from django.core.serialisers.json import Serialiser
class ExtJSONSerialiser(Serializer)
"""
Serializes a QuerySet to basic Python objects.
"""
def end_object(self, obj):
self._current.update({"id": smart_unicode(obj._get_pk_val(), strings_only=True),})
self.objects.append(self._current)
self._current = None
def getvalue(self):
return {
'total': len(self.objects),
'success': True,
'objects': self.objects,
}
yourcode.py
json_serializer = serializers.get_serializer("extjson")()
json_serializer.serialize(queryset, ensure_ascii=False)
I found a solution which is able to serialize objects, which contain QuerySet as attributes. It's comes from traddicts.org blog, but you can now find it on GitHub:
https://github.com/datamafia/django-query-json-serializer/blob/master/JSONSerializer.py
I further modified the code to work recursively, so in effect I can do the following:
users = User.objects.all()
response = {}
response['success'] = True
response['users'] = users
json_serialize(response)
json_serialize(response, serialize_related=True)
json_serialize(response, serialize_related=True, ignored=['users.groups'])
json_serialize(response, serialize_related=True, ignored=['users.groups.permissions'])
I like your answer Thomas, but I needed something which would be even more flexible.