How to change serializer field name when validation error is triggered - django

I need to change the view of the error displayed when I validate the field.
serializer.py
class ElementCommonInfoSerializer(serializers.ModelSerializer):
self_description = serializers.CharField(required=False, allow_null=True,
validators=[RegexValidator(regex=r'^[a-zA-Z0-9,.!? -/*()]*$',
message='The system detected that the data is not in English. '
'Please correct the error and try again.')]
)
....
class Meta:
model = Elements
fields = ('self_description',......)
This error is displayed
{
"self_description": [
"The system detected that the data is not in English. Please correct the error and try again."
]
}
The key of error dict is field name - self_description. For FE I need to send another format like:
{
"general_errors": [
"The system detected that the data is not in English. Please correct the error and try again."
]
}
How to change this?

One way this could be achieved is via custom exception handler
from copy import deepcopy
from rest_framework.views import exception_handler
def genelalizing_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
# Now add the HTTP status code to the response.
if 'self_description' in response.data:
data = deepcopy(response.data)
general_errors = data.pop('self_description')
data['general_errors'] = general_errors
response.data = data
return response
in settings
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils. genelalizing_exception_handler'
}

Another solution is to rewrite the validate method.
def validate(self, data):
self_description = str((data['self_description']))
analyst_notes = str((data['analyst_notes']))
if re.match(r'^[a-zA-Z0-9,.!? -/*()]*$', self_description) or re.match(r'^[a-zA-Z0-9,.!? -/*()]*$', analyst_notes):
raise serializers.ValidationError({
"general_errors": [
"The system detected that the data is not in English. Please correct the error and try again."
]
})
return data

The solution is very simple.
you can rename the key field by using serializer method (source attribute)
below you can find an example code.
class QuestionSerializer(serializers.ModelSerializer):
question_importance = serializers.IntegerField(source='importance')
question_importance = serializers.IntegerField(required=False)
class Meta:
model = create_question
fields = ('id','question_importance','complexity','active')
Above you can see I have an importance field which is present in django model But here I renamed this field to question_importance by using source attribute .
In your case it will be like below,
class ElementCommonInfoSerializer(serializers.ModelSerializer):
general_errors = serializer.CharField(source="self_description")
general_error = serializers.CharField(required=False, allow_null=True,
validators=[])
class Meta:
model = Elements
fields = ('general_error',......)

Related

Input for field is missing even after i type the correct format on the API

I have coded the following:
models.py
class Job(models.Model):
datetime = models.DateTimeField(default=timezone.now)
combinedparameters = models.CharField(max_length = 1000)
serializers.py
class JobSerializers(serializers.ModelSerializer):
class Meta:
model = Job
fields = ['combinedparameters']
views.py
#api_view(['POST'])
def create_job(request):
job = Job()
jobserializer = JobSerializers(job, data = request.data)
if jobserializer.is_valid():
jobserializer.save()
return Response(jobserializer.data, status=status.HTTP_201_CREATED)
return Response(jobserializer.errors, status=status.HTTP_400_BAD_REQUEST)
Page looks like this:
But if i copy
{'device': 177, 'configuration': {'port_range': 'TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/2,TenGigabitEthernet1/0/3,TenGigabitEthernet1/0/4,TenGigabitEthernet1/0/5', 'port_mode': 'Access', 'port_status': 'Disabled', 'port_param1': 'Test\\n1\\n2\\n3', 'port_param2': 'Test\\n1\\n2\\n3'}}
And click post, got error saying the single quotes have to be double quotes. So i changed it to :
{"device": 177, "configuration": {"port_range": "TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/5", "port_mode": "Access", "port_status": "Disabled", "port_param1": "1\\n2\\n3", "port_param2": "1\\n2\\n3"}}
I clicked post again and this time the following error comes out:
I dont understand why is it happening. The reason why I key in the long format because this is the format i want to save in my database and this format is created upon saving from my html that creates the job
Postman:
Updated Postman:
Your post data object does not contain the required key "combinedparameters". I'm guessing that big object you copy into content is the string you want saved into the CharField combinedparameters? If that's the case you should structure your post data like this:
{
"combinedparameters": "{'device': 177, 'configuration': {'port_range': 'TenGigabitEthernet1/0/1,TenGigabitEthernet1/0/2,TenGigabitEthernet1/0/3,TenGigabitEthernet1/0/4,TenGigabitEthernet1/0/5', 'port_mode': 'Access', 'port_status': 'Disabled', 'port_param1': 'Test\\n1\\n2\\n3', 'port_param2': 'Test\\n1\\n2\\n3'}}"
}

Django rest framework bulk post object

I want insert bulk json via django rest_framework however, I get a error message ;
typed following line ;
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got list."
]
}
my serializer.py file include following code block;
class DrfSerializerV2(serializers.ModelSerializer):
def __init__(self,*args,**kwargs):
many=kwargs.pop('many',True)
super(DrfSerializerV2,self).__init__(many=True,*args,**kwargs)
class Meta:
model = DrfData
fields=['id','name','duration','rating','typ']
and my views.py file ;
class ActionViewSet(viewsets.ModelViewSet):
queryset = DrfData.objects.filter(typ="action")
serializer_class = serializers.DrfSerializerV2
finally send json file via post;
[
{"name":"m1","duration":120,"rating":4.7},
{"name":"m2","duration":120,"rating":4.5}
]
however, I can't handle this error message.
if I send sperataly, there is no problem.
but, I can't resolve this error for bulk insert

Django serializer get related field

I have a querySet that I want to turn into a json and send to the client.
It only needs some of the fields, as defined.
However, the 'sender' shows up as a id because it is a foreignKey.
What would be needed to return the senders username instead? (sender.username didn't work). (Not using drf)
messages = Message.objects.all()
messages_json = serializers.serialize("json", messages, fields=('id','sender', 'text', 'timestamp'))
Not tested but using QuerySet.values and __ relations-walking lets you get the data in one hit:
import json
messages = Message.objects.all()
payload_data = messages.values('id', 'sender__username', 'text', 'timestamp')
# tidy up the dict key names, assuming the client needs that doing
for x in payload_data:
x['sender'] = x['sender__username']
del x['sender__username']
messages_json = json.dumps(payload_data)
I think I've found a way so I'll answer it myself
from django.core.serializers.python import Serializer
class MySerialiser(Serializer):
def end_object( self, obj ):
self._current['username'] = obj.sender.username
self.objects.append(self._current)
serializer = MySerialiser()
messages_ser = serializer.serialize(messages, fields=('id','sender','text' ))
messages_json = json.dumps(messages_ser)
messages_ser is a orderedDict, so turning it into json only works when there are no nested objects/dicts

Getting Primary Key from Tastypie Resource URI

I have a Django - tastypie Resource as follows. It has multiple fields that has Many to Many Relations.
I am trying to get the fields "workflow_initiators", "workflow_submitters" and "workflow_approvers" and add the user to a respective group namely initiators, submitters and approvers.
My JSON Request as follows :
{
"workflow_approvers": [
"/client/api/v1/user/44/",
"/client/api/v1/user/6/"
],
"workflow_dept": [
"/client/api/v1/departments/1/",
"/client/api/v1/departments/2/"
],
"workflow_initators": [
"/client/api/v1/user/44/",
"/client/api/v1/user/6/"
],
"workflow_name": "Hello Workflow",
"workflow_submitters": [
"/client/api/v1/user/43/",
"/client/api/v1/user/6/"
],
}
I want to get the primary key from resource uri of tastypie in a hydrate() or a obj_create() method. In order to get the pk i used a function get_pk_from_uri(). But it throws an error of the following
error : global name 'get_pk_from_uri' is not defined
My Resource as follows :
class WorkflowResource(ModelResource):
workflow_dept = fields.ToManyField(DepartmentsResource, 'workflow_dept', related_name='departments', full=True)
workflow_initators = fields.ToManyField(UserResource, 'workflow_initators', related_name='user')
workflow_submitters = fields.ToManyField(UserResource, 'workflow_submitters', related_name='user')
workflow_approvers = fields.ToManyField(UserResource, 'workflow_approvers', related_name='user')
def obj_create(self, bundle, **kwargs):
submitters = bundle.data.get('workflow_submitters', [])
for submitter in submitters:
print(get_pk_from_uri(submitter)) # Throws Error
#Adding User to Group Logic
# g = Group.objects.get(name='submitters')
# g.user_set.add(your_user)
class Meta:
queryset = WorkflowIndex.objects.filter(is_active=True)
resource_name = 'workflows'
list_allowed_methods = ['get', 'post']
detail_allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
serializer = Serializer()
default_format = 'application/json'
authentication = Authentication()
authorization = DjangoAuthorization()
always_return_data = True
Is there any other method to get the primary key and other fields from resource uri ? I did see get_via_uri() method but was unsure on how to implement the same.
Kindly guide me in resolving this issue.
References :
Stackoverflow - Get model object from tastypie uri
Tastypie Documents - get_via_uri()
You should go back to this post: Get model object from tastypie uri?
The get_pk_from_uri(uri) method that you can see in this answer is not part of the source code of Tastypie, as you can check here.
I suppose the guy wrote it by himself, and you should do the same sothat you won't get the error : global name 'get_pk_from_uri' is not defined error. I didn't tested his method thought.
Solution
David was correct. The get_pk_from_uri() is not an in built tasty-pie method, which i was mistaken.
P.S : I'm just making the answer clear so that someone find it useful.
When needed to extract the resource name or pk from the resource uri of tastypie. we will be able to access them from **kwargs of the below section. The Kwargs contains the follows
kwargs
{u'api_name': 'v1', u'pk': '1', u'resource_name': 'workflows'}
Add the following code to your resources.py or utils.py and include it in your API's to a get this method get_pk_from_uri
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix) - 1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']

Convert POST to PUT with Tastypie

Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.