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)
Related
When I do validation in Django query set, the below code execute successfully and returns validation error if query fails to meet certain parameters
if second_condition:
raise ValidationError("1 error")
else:
serializer.save()
Meanwhile the below code fails to give response 1 error as expected.
if second_condition:
return Response("1 error")
else:
serializer.save()
Why is so ?
note : avoid indentation format
The complete code without formating given below
class CommentCreate(generics.CreateAPIView):
serializer_class = CommentSerializer
queryset = Comment.objects.all()
permission_classes = [IsAuthenticated]
def perform_create(self, serializer):
request_user = self.request.user
pk = self.kwargs.get('pk')
product = get_object_or_404(Product, pk=pk)
if Po.objects.filter(Q(user=request_user) & Q(item_id=pk) & Q(delivered=True)).exists():
if Comment.objects.filter(Q(author=request_user) & Q(product=product)).exists():
raise ValidationError("You have already commented")
else:
serializer.save(author=request_user, product=product)
else:
raise ValidationError("Purchase this item,prior to commenting")
This is because exceptions bubble up the stack to the point where the exception gets caught by DRF and is handled as expected, while the return only returns the value from the hook perform_create. You can return Response-like instances from the main view handler (whatever that is in your django or DRF).
More on the hooks here, a didn't check what the return value means there.
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.
I got form:
class SearchForm(Form):
owner = ModelMultipleChoiceField(queryset=User.objects.all(), required=False)
and after customizing get_queryset() of related view it works as expected but I got objects without owner. I want to add additional new choice on top of the list (0,'Without owner') so I could then filter only objects without owner.
How to add this option?
UPDATE:
I add the choice in form.__init__ and wrote custom clean method for it but if I choose added option something raises ValidationError before getting to my clean method.I'm guessing I have to override form.is_valid but I'm not sure how to do it so I can still use default is_valid method.
My code
def __init__(self, *args, **kwargs):
super(ClientListSearchForm, self).__init__(*args, **kwargs)
self.fields['owner'].choices = \
list(self.fields['owner'].choices)+[('0', 'n/a')]
def clean_owner(self):
logger.debug('CLEAN_OWNER:')
data = self.cleaned_data.get('owner')
logger.debug('data: %s' % data)
if data == 0:
logger.debug('Data zero - not assigned')
return data
users = User.objects.all()
if all(e in users for e in data):
logger.debug('Data in users - validating ok')
return data
else:
raise ValidationError('Incorrect owner')
I tried:
def is_valid(self):
try:
super(ClientListSearchForm, self).is_valid()
except ValidationError as e:
logger.debug('val error: %s' % e.args)
but it's nor validating nor caching exception
UPDATE2 Added custom validator
def userWithEmpty(value):
users = User.objects.values_list('pk').all()
v =list()
for va in value:
v.append(int(va))
u = list()
for us in users:
u.append(int(us[0]))
if not (all(e in u for e in v)or v ==0):
raise ValidationError('Invalid Value: %s' % value)
Is there a better way to convert every value in iterable than my for loops?
Didn’t post it as answer because there is a lot of place for improvement. Waiting for rants about what I'm doing wrong- and I will appreciate it all...
It STOPPED WORKING eee - there is something before validator from validators=[]
I ended up with other approach
class ModelMultipleChoiceWithEmptyField(ModelMultipleChoiceField):
def __init__(self, *args, **kwargs):
super(ModelMultipleChoiceWithEmptyField, self).__init__(*args, **kwargs)
self.choices = list(self.choices) + [('0', 'Brak')]
def clean(self, value):
if self.required and not value:
raise ValidationError(self.error_messages['required'], code='required')
if value == [u'0']:
return value
return super(ModelMultipleChoiceWithEmptyField,self).clean(value)
It's much cleaner and it works. Fell free to reuse and improve
I need to return a render_to_response in case an exception happens inside my FormView derived class.
I'm doing the following (code excerpt, but the rest of the code doesn't cause problems):
class ProjectCreateView(FormView):
"""Create view."""
form_class = ProjectCreateForm
template_name = 'projects/project_form.html'
group = None
def dispatch(self, request, *args, **kwargs):
"""Populate group attribute."""
self.group = get_object_or_404(Group, slug=self.kwargs['slug'])
return super(ProjectCreateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
"""Add current group to context."""
context = super(ProjectCreateView, self).get_context_data(**kwargs)
context['group'] = self.group
return context
def form_valid(self, form):
"""Form and logic behind project creation."""
project_file = form.cleaned_data['project_file']
thumbnail = form.cleaned_data['thumbnail']
description = form.cleaned_data['description']
try:
<something to try>
except IntegrityError as e:
data = {'error':{'msg':_('project_overwrite_error')}}
return render_to_response('generic_error.html',data,context_instance=RequestContext(self.request))
except Exception as e:
return HttpResponse(e)
return HttpResponseRedirect(self.group.get_absolute_url())
If I debug with a breakpoint inside the dispatch() method, everything works fine, and my error template is loaded correctly, and context processors work fine.
When I run it outside debug I get a DatabaseError, which seems to be caused by the access to the django DB session data.
What am I doing wrong? Should I use render_to_response differently inside a generic view?
I don't know what is happening but I've solved with a workaround:
dummy = self.request.user.is_authenticated()
calling is_authenticated before returning render_to_response solves the problem, because request.user is "casted" to the actual user instance, instead of remaining a SimpleLazyObject.
I hope to understand why it happens one day...
I am trying to create a custom form field in Django.
class CustomTypedMultipleChoiceField(MultipleChoiceField):
def __init__(self, *args, **kwargs):
self.coerce = kwargs.pop('coerce', lambda val: val)
self.empty_value = kwargs.pop('empty_value', [])
super(CustomTypedMultipleChoiceField, self).__init__(*args, **kwargs)
def to_python(self, value):
"""
Validates that the values are in self.choices and can be coerced to the
right type.
"""
value = super(CustomTypedMultipleChoiceField, self).to_python(value)
if value == self.empty_value or value in self.empty_values:
return self.empty_value
new_value = []
for choice in value:
try:
new_value.append(self.coerce(choice))
except (ValueError, TypeError, ValidationError):
raise ValidationError(self.error_messages['invalid_choice'] % {'value': choice})
return new_value
def validate(self, value):
if value != self.empty_value:
super(CustomTypedMultipleChoiceField, self).validate(value)
elif self.required:
raise ValidationError(self.error_messages['required'])
I am getting the error CustomTypedMultipleChoiceField has no attribute empty_values. This is the exact same code that Django in built TypedMultipleChoiceField is built with. So I dont understand why I am getting this error.
I also thought of sub-classing the TypedMultipleChoiceField, but I wanted its error to be different in to_python method and didn't want to return the value thing, so opted for this method.
Please help me.
I don't know if it's a typo or you intended that way but actually empty_values (in plural) is not defined in your code anywhere. I also take a look at the source code of the super class MultipleChoiceField and is not defined there either.
What I could find in the super super class of your class (ChoiceField) was a reference to validator.EMPTY_VALUES and of course, it is in capital letters.
The line more alike yours in the source code was this one:
if value == self.empty_value or value in validators.EMPTY_VALUES:
Take a look deep in your code and see if that was what you intended to do.
Hope this helps!