Django 2.7 Rest API Validation Request - django

I use Django 2.7 to build Rest API application, and having problem to validate/clean the request data from client for get detail transaction (Not Save/update). for example the request data trx_no cannot less than 5 char length. where's the validation class i should create? should I validate on Model.py or using forms, or in serializer?
Here's my models.py:
class mst_trx(models.Model):
trx_no = models.CharField(max_length=20,primary_key=True)
Here's my views.py:
class views_index(APIView):
def post(self,request):
action = request.POST['action']
if action == 'detail' :
resp = detail.as_view()(request)
class detail(APIView):
def dispatch(self,request):
##I want to validate first before get data
try:
detail = mst_trx.objects.select_related().get(pk=request.POST['trx_no'])
except mst_trx.DoesNotExist:
raise Http404("Transaction does not exist")
else:
serializer = TrxDetailSerializer(detail)
return serializer.data
And Here's my serializer.py :
class TrxDetailSerializer(serializers.ModelSerializer):
class Meta:
model = mst_trx
fields = ('trx_no')

Validation logic should be in forms.py file
for e.g.
def clean_columnname(self):
columnname = self.cleaned_data['columnname']
if len(columnname) < 1:
raise ValidationError('Please add some content ...')
elif len(columnname) > 500000:
raise ValidationError('Too many characters ...')
return columnname

Related

How to collect all validations errors when using custom class validator

I'm using a custom class validator to validate serializer fields, and I would like to raise ValidationError for all fields so API error response has fields with all errors instead of a single error, instead, now I'm getting the validation error only for single fields. Before I've used method validations inside my serializer and it worked as I need, but this is not the case with class validators.
Here is how validators look like
class TitleValidator:
MIN_TITLE_LENGTH = 20
def __call__(self, attrs: dict):
title = attrs.get("title", "")
if len(title) < self.MIN_TITLE_LENGTH:
raise ValidationError(f"Min title length is {self.MIN_TITLE_LENGTH}")
return title
class SlugsValidator:
def __call__(self, attrs):
slug = attrs.get("slug", "")
if len(slug) < 10:
raise ValidationError("Slug must be at least 10 characters long")
return slug

Django rest framework - Serializer always returns empty object ({})

I am creating first rest api in django using django rest framework
I am unable to get object in json format. Serializer always returns empty object {}
models.py
class Shop(models.Model):
shop_id = models.PositiveIntegerField(unique=True)
name = models.CharField(max_length=1000)
address = models.CharField(max_length=4000)
serializers.py
class ShopSerializer(serializers.ModelSerializer):
class Meta:
model = Shop
fields = '__all__'
views.py
#api_view(['GET'])
def auth(request):
username = request.data['username']
password = request.data['password']
statusCode = status.HTTP_200_OK
try:
user = authenticate(username=username, password=password)
if user:
if user.is_active:
context_data = request.data
shop = model_to_dict(Shop.objects.get(retailer_id = username))
shop_serializer = ShopSerializer(data=shop)
if shop:
try:
if shop_serializer.is_valid():
print('is valid')
print(shop_serializer.data)
context_data = shop_serializer.data
else:
print('is invalid')
print(shop_serializer.errors)
except Exception as e:
print(e)
else:
print('false')
else:
pass
else:
context_data = {
"Error": {
"status": 401,
"message": "Invalid credentials",
}
}
statusCode = status.HTTP_401_UNAUTHORIZED
except Exception as e:
pass
return Response(context_data, status=statusCode)
When i try to print print(shop_data) it always returns empty object
Any help, why object is empty rather than returning Shop object in json format?
Edited:
I have updated the code with below suggestions mentioned. But now, when shop_serializer.is_valid() is executed i get below error
{'shop_id': [ErrorDetail(string='shop with this shop shop_id already exists.', code='unique')]}
With the error it seems it is trying to update the record but it should only get the record and serialize it into json.
You're using a standard Serializer class in this code fragment:
class ShopSerializer(serializers.Serializer):
class Meta:
model = Shop
fields = '__all__'
This class won't read the contend of the Meta subclass and won't populate itself with fields matching the model class. You probably meant to use ModelSerializer instead.
If you really want to use the Serializer class here, you need to populate it with correct fields on your own.
.data - Only available after calling is_valid(), Try to check if serializer is valid than take it's data

App works with ModelChoiceField but does not work with ModelMultipleChoiceField

I am trying to retrieve user input data in a django page. But I am unable to choose to multichoice field. I have tried multiple alternatives to no relief.
self.fields['site'].queryset=forms.ModelMultipleChoiceField(queryset=sites.objects.all())
self.fields['site'] = forms.ModelChoiceField(queryset=sites.objects.filter(project_id=project_id))
self.fields['site'].queryset = forms.MultipleChoiceField(widget=forms.SelectMultiple, choices=[(p.id, str(p)) for p in sites.objects.filter(project_id=project_id)])
forms.py
class SearchForm(forms.Form):
class Meta:
model= images
fields=['site']
def __init__(self,*args,**kwargs):
project_id = kwargs.pop("project_id") # client is the parameter passed from views.py
super(SearchForm, self).__init__(*args,**kwargs)
self.fields['site'] = forms.ModelChoiceField(queryset=sites.objects.filter(project_id=project_id))
views.py
def site_list(request, project_id):
form = SearchForm(project_id=project_id)
site_list = sites.objects.filter(project__pk=project_id).annotate(num_images=Count('images'))
template = loader.get_template('uvdata/sites.html')
if request.method == "POST":
image_list=[]
form=SearchForm(request.POST,project_id=project_id)
#form=SearchForm(request.POST)
#site_name=request.POST.get('site')
if form.is_valid():
site_name=form.cleaned_data.get('site')
print(site_name)
I expect to get a multiselect field but I end up getting this error:
Exception Value:
'site'
Exception Location: /home/clyde/Downloads/new/automatic_annotator_tool/django_app/search/forms.py in init, line 18
(line 18:self.fields['site'].queryset = forms.MultipleChoiceField(widget=forms.SelectMultiple, choices=[(p.id, str(p)) for p in sites.objects.filter(project_id=project_id)]))
You are not defining your form correctly. The documentation shows you how to do this.
In your case it would be something like this:
class SearchForm(forms.Form):
site = forms.ModelMultipleChoiceField(queryset=Sites.object.none())
def __init__(self,*args,**kwargs):
project_id = kwargs.pop("project_id")
super(SearchForm, self).__init__(*args,**kwargs)
self.fields['site'].queryset = Sites.objects.filter(project_id=project_id))
You also appear to be confusing regular Form and ModelForm, as Meta.model is only used in ModelForm whereas you are using a regular Form. I suggest you read up on the difference in the documentation before you proceed.

Make BooleanField required in Django Rest Framework

I've got a model with a boolean field that I'd like to deserialize with the Django rest framework and I want the serializer to complain when a field is missing in the post request. Yet, it doesn't. It silently interprets a missing boolean as False.
class UserProfile(models.Model):
"""
Message between two users
"""
user = models.OneToOneField(User, verbose_name="django authentication user", related_name='user_profile')
newsletter = models.BooleanField(null=False)
research = models.BooleanField(null=False)
The model is created with a Serialiser like this:
class UserProfileSerializer(serializers.ModelSerializer):
research = BooleanField(source='research', required=True)
newsletter = BooleanField(source='newsletter', required=True)
class Meta:
model = UserProfile
fields = ('research', 'newsletter')
In my view I'm also creating a user, so I have some manual steps:
def post(self, request, format=None):
userprofile_serializer = UserProfileSerializer(data=request.DATA)
reg_serializer = RegistrationSerializer(data=request.DATA)
phone_serializer = PhoneSerializer(data=request.DATA)
errors = {}
if userprofile_serializer.is_valid() and reg_serializer.is_valid() and phone_serializer.is_valid():
user = reg_serializer.save()
data = reg_serializer.data
user_profile = userprofile_serializer.object
user_profile.user = user
userprofile_serializer.save()
return Response(data, status=status.HTTP_201_CREATED)
errors.update(reg_serializer.errors)
# ...
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
However, the following test case fails, because the rest framework doesn't complain about the missing param but instead inserts a False in from_native
def test_error_missing_flag(self):
data = {'username': "test", 'password': "123test", 'email': 'test#me.com',
'newsletter': 'true', 'uuid': self.uuid}
response = self.client.post(reverse('app_register'), data)
# should complain that 'research' is not found
self.assertTrue('research' in response.data)
If I replace my 'research' field with an Integer field that the serializer fails as expected. Any ideas?
There was an issue with Boolean fields and the required argument. Should now be fixed in master.
See this issue: https://github.com/tomchristie/django-rest-framework/issues/1004
Add
your_field = serializers.NullBooleanField(required=False)
in serializer.
That's it. It'll work :)
For anyone who has read #Tom's accepted answer from 2013 and finds that this still doesn't work, it's because this behavior is intended for HTML form inputs. Here's the original issue.
To use serializers.BooleanField with a JSON payload, convert your request.POST to a Python dict by doing request.POST.dict() and pass it to your serializer while initializing.
Create a new custom class:
from rest_framework import serializers
class RequirableBooleanField(serializers.BooleanField):
default_empty_html = serializers.empty
Now, you can use:
research = RequirableBooleanField(required=True)
or
research = RequirableBooleanField(required=False)

Possible to do with django forms?

I have a form where an administrators enters a list of comma-separated email addresses, and the form does validation on each email address before adding it to the db. I was able to do this just fine with using my own (non-django) form. I tried to migrate this over to using modelforms, but ran into a few problems. Here is the code I currently have --
# model
class EmailList(models.Model):
email = models.EmailField(blank=True)
network = models.ForeignKey(Network)
class EmailListForm(ModelForm):
class Meta:
model = EmailList
def clean_email(self):
if self.cleaned_data['email']:
valid_emails = []
for x in self.cleaned_data['email'].split(','):
x = x.strip().lower()
valid_emails.append(x)
return valid_emails
# in views
def email(request):
if request.POST.get('email'):
for email in form.cleaned_data.get('email'): ### ??
if form.is_valid(): ### ??
EmailList.objects.create(email = email, network=Network.objects.get(id=request.POST['network']))
return redirect('.')
I am having trouble because I can't call on the cleaned_data() until the form is validated, but the form will not validate as a whole (only its iterations will). Is it possible to construct this function using django's forms? If so, how would I accomplish this task? Thank you.
In this case I wouldn't use a ModelForm because they are for the case that you want to represent one Model instance by one Form. Here you want to produce multiple instances with one form. So just write a common form with a custom field (there is acutally an example just for this in the Django docs) and maybe a custom save method:
from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"Normalize data to a list of strings."
# Return an empty list if no input was given.
if not value:
return []
return value.split(',')
def validate(self, value):
"Check if value consists only of valid emails."
# Use the parent's handling of required fields, etc.
super(MultiEmailField, self).validate(value)
for email in value:
validate_email(email)
class EmailListForm(forms.Form):
emails = forms.MulitEmailField()
network = forms.ModelChoiceField(queryset=Network.objects.all())
# save the emails to the db
def save(self):
for email in self.cleaned_data.get('emails'):
EmailList.objects.create(email = email,
network=self.network)
# in views
def email(request):
if request.method == 'POST':
form = EmailListForm(request.POST)
if form.is_valid():
form.save()
return redirect(...somewhere...)
else:
form = EmailListForm()
return render(request, 'some/template.html', { 'form': form }