How to add a key to my DRF API response value - python-2.7

My API returns just the value. I believe it's my fault but am not very versatile with Python/Django. Any help is appreciated.
What is returned
"This is the message"
What I want:
{
"message": "This is a message"
}
All I want to do is to add a word as a key
views.py
from rest_framework.response import Response
from id.models import Id
from rest_framework import generics
from id.serializers import IdSerializer
from django.http import Http404
from IPython import embed
class OfferView(generics.RetrieveAPIView):
serializer_class = IdSerializer
lookup_field = 'id'
def get_queryset(self):
id = self.kwargs['id']
try:
return Id.objects.filter(id=id)
except Mamo.DoesNotExist:
raise Http404
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset, many=True)
try:
if serializer.data[0]['offer_id'] is not None:
result = serializer.data[0]['main_offer']
elif serializer.data[0]['offer_id'] is None:
result = serializer.data[0]['extra_offer']
else:
result = serializer.data[0]['exceptional_offer']
return Response(result)
except IndexError:
raise Http404

Just change
Note: The thing is what you will form that you will get, you were just returning string and that was the problem. Now let's convert that to a dictionary (more specifically the JSON here in REST).
serializer.data is a dictionary. You can form a single dictionary containing the keys & values (JSON serializable) you want. Here I just tried to fulfill the need.
result = serializer.data[0]['exceptional_offer']
to
result = {"message": serializer.data[0]['exceptional_offer']}
and the similar for others as well.
Better way:
Just change the following (last lines, I mean try...except)
try:
if serializer.data[0]['offer_id'] is not None:
result = serializer.data[0]['main_offer']
elif serializer.data[0]['offer_id'] is None:
result = serializer.data[0]['extra_offer']
else:
result = serializer.data[0]['exceptional_offer']
return Response(result)
except IndexError:
raise Http404
to the below one (writing the same piece of code is like code duplication).
try:
if serializer.data[0]['offer_id'] is not None:
# result = serializer.data[0]['main_offer']
key = 'main_offer'
elif serializer.data[0]['offer_id'] is None:
# result = serializer.data[0]['extra_offer']
key = 'extra_offer'
else:
# result = serializer.data[0]['exceptional_offer']
key = 'exceptional_offer'
result = {'message': serializer.data[0][key]}
return Response(result)
except IndexError:
raise Http404

So, I figured out am just returning the result and not creating a JSON Object.
Adding result = {'message': serializer.data[0]…} to all the variable result in the if..else conditions solves it for me.

Related

Try and except for Query set

I want to do something similar to try and except for single object .. in my situation I got a Query set and I want to do something like :
try:
qs = model.objects.filter(id=1)
except qs.DoesNotExist:
raise Http_404()
How can I do that ?
You can use the get_list_or_404(…) function [Django-doc] for this:
from django.shortcuts import get_list_or_404
qs = get_list_or_404(model, id=1)
but since you are filtering on a primary key, it makes not much sense to use .filter(…) [Django-doc] here, you can retrieve the single Model object with get_object_or_404(…) [Django-doc]:
from django.shortcuts import get_object_or_404
obj = get_object_or_404(model, id=1)
There is no way to caught an exception since none is raisen.
Why don't you try something like that?
qs = model.objects.filter(id=1)
if len(qs) == 0:
raise Http_404()
Or, if you are not using qs after that, the more efficient if qs.count() == 0:.
views.py
#api_view(['GET'])
def getObjList(request):
if request.method == 'GET':
serialized_data =
serializers.FstObjSerializer(models.FstObjModel.objects.filter(id=2),
many=True)
if serialized_data.data:
return Response(serialized_data, status=status.HTTP_200_OK)
else:
return Response({"error":"any message you want"},
status=status.HTTP_400_BAD_REQUEST)
serializers:
class FstObjSerializer(serializers.ModelSerializer):
class Meta:
model = models.FstObjModel
fields = '__all__'
You can do the above

Django: 'Q' object is not iterable

I have following APIView:
class SubmitFormAPIView(APIView):
def put(self, request, pk):
# some other codes
form = Form.objects.get(id=pk)
tu_filter, target_user = self._validate_target_user(request, form)
user_status, created = UserFormStatus.objects.get_or_create(
tu_filter,
form_id=pk,
user_id=request.user.pk
)
# Some other codes.
def _validate_target_user(request, form):
if some_conditions:
return Q(), None
else:
try:
target_user_id = int(request.GET.get('target_user_id))
except ValueError:
raise ValidationError()
target_user = get_user_model().objects.get(id=target_user_id)
return Q(target_user_id=target_user_id), target_user
but when django wants to execude get_or_create method, raises following error:
TypeError: 'Q' object is not iterable
Note: If _validate_target_user() returns Q(), None, no errors raised and view works fine. The error will be raised when return Q(target_user_id=target_user_id), target_user is returned.
I know, question information is not completed, just I want to know, what may cause this error?
From the source of get_or_create(...), def get_or_create(self, defaults=None, **kwargs):
which indicating that the get_or_create(...) doesn't not accept any args unlike the get() or filter(...) methods.
Since your are executing the function as below, Python thinks that the tu_filter is the value for default parameter, which is expected to be an iterable.
get_or_create(
tu_filter,
form_id=pk,
user_id=request.user.pk
)
Instead of returning a Q object, you can also just pass a dictionary of filters instead, like
{ 'target_user_id': target_user_id }
The you can run the get_or_create with **tu_filter as arguments, bypassing the need for Q.
class SubmitFormAPIView(APIView):
def put(self, request, pk):
# some other codes
form = Form.objects.get(id=pk)
tu_filter, target_user = self._validate_target_user(request, form)
user_status, created = UserFormStatus.objects.get_or_create(
**tu_filter,
form_id=pk,
user_id=request.user.pk
)
# Some other codes.
def _validate_target_user(request, form):
if some_conditions:
return {}, None
else:
try:
target_user_id = int(request.GET.get('target_user_id))
except ValueError:
raise ValidationError()
target_user = get_user_model().objects.get(id=target_user_id)
return { 'target_user_id': target_user_id }, target_user
Edit: As to what causes the error, my guess would be that using Q as part of your get_or_create statement is unclear to Django, because it doesn't know what to do with it in case the object needs to be created. A better approach would therefor be:
UserFormStats.objects.filter(tu_filter).get_or_create(form_id=pk, user_id=request.user.pk)

Django Queryset - rename original values and re-use original value names

I'm completing a challenge for a job and I'm a little confused with this endpoint's response.
I have the following models:
Attribute
AttributeValue
ProductAttribute
I need to get all attributes that are linked to a given product ID. I have managed to get the values but I can't give them the correct names in the response. The relevant code is in the get_attributes_from_product function:
# src/api/viewsets/attribute.py
from django.db.models import F
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from api import errors
from api.models import Attribute, AttributeValue, ProductAttribute
from api.serializers import AttributeSerializer, AttributeValueSerializer, AttributeValueExtendedSerializer
import logging
logger = logging.getLogger(__name__)
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
"""
list: Return a list of attributes
retrieve: Return a attribute by ID.
"""
queryset = Attribute.objects.all()
serializer_class = AttributeSerializer
#action(detail=False, url_path='values/<int:attribute_id>')
def get_values_from_attribute(self, request, *args, **kwargs):
"""
Get Values Attribute from Attribute ID
"""
attribute_id = int(kwargs['attribute_id'])
# Filter queryset to find all values for attribute
response = AttributeValue.objects.filter(attribute_id=attribute_id).values(
'attribute_value_id', 'value')
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
"""
Get all Attributes with Product ID
"""
product_id = int(kwargs['product_id'])
# Filter all attributes in product
response = ProductAttribute.objects.filter(product_id=product_id).annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value')).values(
attribute_name=F('attribute_value__attribute_id__name'),
attribute_value_id=F('attribute_value_id'),
attribute_value=F('attribute_value__value')
)
# Return response
if response.exists():
return Response(response, 200)
else:
return Response(response, 204)
If I change attribute_value_id=F('attribute_value_id') and attribute_value=F('attribute_value__value') to attribute_value_id1=F('attribute_value_id') and attribute_value1=F('attribute_value__value') the response is successful and all the values are correct, but obviously the key names are wrong.
It should return the following keys: attribute_name, attribute_value_id and attribute_value.
The django ORM will not overwrite existing model attributes with the names of annotated fields.
In order to use names that collide with existing model attributes, you need to use a
serializer class or just format the queryset rows before returning the response.
An example of using a serializer can be found in the django rest-framework
documentation.
Without using a queryset, you can use a list of dict objects in the response. This
is a shortcut though, and using a serializer would probably be better.
class AttributeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
def render_product_attribute_row(self, row):
row["attribute_value_id"] = row.pop("tmp_attribute_value_id")
row["attribute_value"] = row.pop("tmp_attribute_value")
return row
#action(detail=False, url_path='inProduct/<int:product_id>')
def get_attributes_from_product(self, request, *args, **kwargs):
product_id = int(kwargs['product_id'])
queryset = ProductAttribute.objects.filter(product_id=product_id)
queryset = queryset.annotate(
original_attribute_value_id=F('attribute_value_id'),
original_attribute_value=F('attribute_value__value'),
)
queryset = queryset.values(
attribute_name=F('attribute_value__attribute_id__name'),
tmp_attribute_value_id=F('attribute_value_id'),
tmp_attribute_value=F('attribute_value__value'),
)
if queryset.exists():
status_code = 200
else:
status_code = 204
response = [self.render_product_attribute_row(row) for row in queryset]
return Response(response, status_code)

Customize Status code response from Django Rest Framework serializer

The scenario is quite straight-forward:
Say i have a user model where email should be unique. I did a custom validation for this like.
def validate_email(self, value):
if value is not None:
exist_email = User.objects.filter(email=value).first()
if exist_email:
raise serializers.ValidationError("This Email is already taken")
return value
from rest_framework response when input validation occur we should return status_code_400 for BAD_REQUEST but in this scenario we should or we need to return status_code_409 for conflicting entry. What is the best way to customize status_code response from serializer_errors validation.
I think is better to define custom exception_handler like:
settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'myproject.common.custom_classes.handler.exception_handler',
}
handler.py
def exception_handler(exc, context):
# Custom exception hanfling
if isinstance(exc, UniqueEmailException):
set_rollback()
data = {'detail': exc.detail}
return Response(data, status=exc.status_code)
elif isinstance(exc, (exceptions.APIException, ValidationError)):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if hasattr(exc, 'error_dict') and isinstance(exc, ValidationError):
exc.status_code = HTTP_400_BAD_REQUEST
data = exc.message_dict
elif isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
msg = _('Not found.')
data = {'detail': six.text_type(msg)}
set_rollback()
return Response(data, status=status.HTTP_404_NOT_FOUND)
return None
exceptions.py
class UniqueEmailException(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = 'Error Message'
And finally the validator:
def validate_email(self, value):
if value is not None:
exist_email = User.objects.filter(email=value).first()
if exist_email:
raise UniqueEmailException()
return value
I would go for intercepting ValidationError exception and return the Response object with 409 status code:
try:
serializer.is_valid(raise_exception=True)
except ValidationError, msg:
if str(msg) == "This Email is already taken":
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_409_CONFLICT
)
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_400_BAD_REQUEST
)
Short answer:
You can't return custom response codes from a serializer.
This is because the serializer is just that: A Serializer. It doesn't, or shouldn't, deal with HTTP at all. It's just for formatting data, usually as JSON, but it'll usually do HTML for showing your API, and one or two other formats.
Long answer:
One way to accomplish this is to raise something (doesn't matter what, but make it descriptive) in your serializer, and add code to your view to catch the error. Your view can return a custom response code with a custom response body as you like it.
Like this:
add something like this to your view class:
def create(self, request, *args, **kwargs):
try:
return super().create(request, *args, **kwargs)
except ValidationError as x:
return Response(x.args, status=status.HTTP_409_CONFLICT)

Django Multilingual Text Field using JSON

I recently ask this question Custom Django MultilingualTextField model field but I found no good reason why I should not do this, so I create a model Field that support multilingual text, auto return text in current language. This basically is the field that store custom Language object to database in json format. Here is the code:
Github: https://github.com/james4388/django-multilingualfield
Ussage:
from django.db import models
from multilingualfield import MLTextField, MLHTMLField
class MyModel(models.Model):
text = MLTextField()
html = MLHTMLField()
Used it like normal text field, translation is auto bases on system language (translation.get_language)
>>>from django.utils import translation
>>>translation.active('en')
>>>m = MyModal.objects.create(text='Hello world',html='<b>Hello world</b>');
>>>m.text
Hello world
>>>translation.active('fr')
>>>m.text #Auto fallback to first language (if any).
Hello world
>>>m.text.value('Bonjour')
>>>m.text.value('Ciao','es')
>>>m.text
Bonjour
>>>m.save()
>>>m.text.get_available_language()
['en', 'fr', 'es']
>>>m.text.remove_language('en')
Field.py
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models, DatabaseError, transaction
from django.utils.translation import ugettext_lazy as _, get_language
from django.utils import six
try:
import json
except ImportError:
from django.utils import simplejson as json
def get_base_language(lang):
if '-' in lang:
return lang.split('-')[0]
return lang
def get_current_language(base=True):
l = get_language()
if base:
return get_base_language(l)
return l
from .widgets import MultilingualWidget, MultilingualHTMLWidget
from .forms import MultilingualTextFormField, MultilingualHTMLFormField
from .language import LanguageText
class MultilingualTextField(six.with_metaclass(models.SubfieldBase, models.Field)):
"""
A field that support multilingual text for your model
"""
default_error_messages = {
'invalid': _("'%s' is not a valid JSON string.")
}
description = "Multilingual text field"
def __init__(self, *args, **kwargs):
self.lt_max_length = kwargs.pop('max_length',-1)
self.default_language = kwargs.get('default_language', get_current_language())
super(MultilingualTextField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {
'form_class': MultilingualTextFormField,
'widget': MultilingualWidget
}
defaults.update(**kwargs)
return super(MultilingualTextField, self).formfield(**defaults)
def validate(self, value, model_instance):
if not self.null and value is None:
raise ValidationError(self.error_messages['null'])
try:
self.get_prep_value(value)
except:
raise ValidationError(self.error_messages['invalid'] % value)
def get_internal_type(self):
return 'TextField'
def db_type(self, connection):
return 'text'
def to_python(self, value):
if isinstance(value, six.string_types):
if value == "" or value is None:
if self.null:
return None
if self.blank:
return ""
try:
valuejson = json.loads(value)
Lang = LanguageText(max_length=self.lt_max_length,default_language=self.default_language)
Lang.values = valuejson
return Lang
except ValueError:
try:
Lang = LanguageText(value,language=None,max_length=self.lt_max_length,default_language=self.default_language)
return Lang
except:
msg = self.error_messages['invalid'] % value
raise ValidationError(msg)
return value
def get_db_prep_value(self, value, connection=None, prepared=None):
return self.get_prep_value(value)
def get_prep_value(self, value):
if value is None:
if not self.null and self.blank:
return ""
return None
if isinstance(value, six.string_types):
value = LanguageText(value,language=None,max_length=self.lt_max_length,default_language=self.default_language)
if isinstance(value, LanguageText):
value.max_length = self.lt_max_length
value.default_language = self.default_language
return json.dumps(value.values)
return None
def get_prep_lookup(self, lookup_type, value):
if lookup_type in ["exact", "iexact"]:
return self.to_python(self.get_prep_value(value))
if lookup_type == "in":
return [self.to_python(self.get_prep_value(v)) for v in value]
if lookup_type == "isnull":
return value
if lookup_type in ["contains", "icontains"]:
if isinstance(value, (list, tuple)):
raise TypeError("Lookup type %r not supported with argument of %s" % (
lookup_type, type(value).__name__
))
# Need a way co combine the values with '%', but don't escape that.
return self.get_prep_value(value)[1:-1].replace(', ', r'%')
if isinstance(value, dict):
return self.get_prep_value(value)[1:-1]
return self.to_python(self.get_prep_value(value))
raise TypeError('Lookup type %r not supported' % lookup_type)
def value_to_string(self, obj):
return self._get_val_from_obj(obj)
Forms.py
from django import forms
from django.utils import simplejson as json
from .widgets import MultilingualWidget, MultilingualHTMLWidget
from .language import LanguageText
class MultilingualTextFormField(forms.CharField):
widget = MultilingualWidget
def __init__(self, *args, **kwargs):
kwargs['widget'] = MultilingualWidget
super(MultilingualTextFormField, self).__init__(*args, **kwargs)
def clean(self, value):
"""
The default is to have a TextField, and we will decode the string
that comes back from this. However, another use of this field is
to store a list of values, and use these in a MultipleSelect
widget. So, if we have an object that isn't a string, then for now
we will assume that is where it has come from.
"""
value = super(MultilingualTextFormField, self).clean(value)
if not value:
return value
if isinstance(value, basestring):
try:
valuejson = json.loads(value)
Lang = LanguageText()
Lang.values = valuejson
return Lang
except ValueError:
try:
Lang = LanguageText(value,language=None)
return Lang
except:
raise forms.ValidationError(
'JSON decode error: %s' % (unicode(exc),)
)
else:
return value
Language object in language.py
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models, DatabaseError, transaction
from django.utils.translation import ugettext_lazy as _, get_language
try:
import json
except ImportError:
from django.utils import simplejson as json
def get_base_language(lang):
if '-' in lang:
return lang.split('-')[0]
return lang
def get_current_language(base=True):
l = get_language()
if base:
return get_base_language(l)
return l
class LanguageText(object):
'''
JSON text field blah blah blah
'''
values = {}
default_language = None
max_length = -1
def __init__(self, value=None, language=None, default_language=None, max_length=-1):
self.max_length = max_length
self.default_language = default_language
self.values = {}
if value is not None:
self.value(value,language)
def __call__(self, value=None, language=None):
self.value(value,language)
return self
def get_available_language(self):
return self.values.keys()
def get_current_language(self, base=False):
return get_current_language(base)
def remove_language(self, lang):
try:
return self.values.pop(lang)
except:
pass
def has_language(self, lang):
return self.values.has_key(lang)
def get(self, language=None, fallback=True):
if language is None:
curr_lang = get_current_language(False)
else:
curr_lang = language
curr_lang_base = get_current_language(True)
if curr_lang in self.values:
return self.values[curr_lang]
if not fallback:
return None
if curr_lang_base in self.values:
return self.values[curr_lang_base]
if self.default_language in self.values:
return self.values[self.default_language]
try:
first_lang = self.values.keys()[0]
return self.values[first_lang]
except:
pass
return None
def value(self, value=None, language=None):
if value is None: #Get value
return self.get(language)
else: #Set value
if language is None:
language = get_current_language(False)
if self.max_length != -1:
value = value[:self.max_length]
self.values[language] = value
return None
def __unicode__(self):
return self.value()
def __str__(self):
return unicode(self.value()).encode('utf-8')
def __repr__(self):
return unicode(self.value()).encode('utf-8')
widgets.py
from django import forms
from django.utils import simplejson as json
from django.conf import settings
from .language import LanguageText
from django.template import loader, Context
class MultilingualWidget(forms.Textarea):
def __init__(self, *args, **kwargs):
forms.Widget.__init__(self, *args, **kwargs)
def render(self, name, value, attrs=None):
if value is None: #New create or edit none
vjson = '{}'
aLang = []
Lang = '[]'
Langs = json.dumps(dict(settings.LANGUAGES))
t = loader.get_template('multilingualtextarea.html')
c = Context({"data":value,"vjson":vjson,"lang":Lang,"langs":Langs,"langobjs":settings.LANGUAGES,"fieldname":name})
return t.render(c)
if isinstance(value, LanguageText):
vjson = json.dumps(value.values)
aLang = value.get_available_language()
Lang = json.dumps(aLang)
Langs = json.dumps(dict(settings.LANGUAGES))
t = loader.get_template('multilingualtextarea.html')
c = Context({"data":value,"vjson":vjson,"lang":Lang,"langs":Langs,"langobjs":settings.LANGUAGES,"fieldname":name})
return t.render(c)
return "Invalid data '%s'" % value
So I would like to know is this a good approach? Why shouldn't I do this? Plz help
Code looks good to me.
The only thing that could impact performance is the frequent json encoding/decoding... yet, that shouldn't have a major impact unless you are facing thousands of users on a server with minimal resources.
The previous question you linked to contains some comments noting that adding additional languages might be easier using other means. But in the end - that's a mixture between personal preferences and maintainability. If it fits your project goals, I can't see any reason not to do it the way you've coded it.
Providing proof that your implementation is the best is near to impossible. That is, unless you prove it yourself by creating a different, non-json based implementation and benchmark both on your production server. You'll notice differences will be rather minimal on regular machines. Yet, only the individual numbers will provide actual proof and can help you decide if it's "tuned" and "resource -friendly" enough for your project's purposes. I think it will fit your needs... but that's only my 2 cents.