After some hours of research, I still can't find the answer to the issue I am facing.
I am trying to add some simple validation to a field in one of my models.
In serializers.py:
class BrandSerializer(serializers.ModelSerializer):
class Meta:
model = Brand
fields = '__all__'
def validate_name(self, value):
"""
Check that the brand name has at least 20 characters
"""
if len(value) < 20:
raise serializers.ValidationError(
"The name of the brand should be longer than 20 characters")
return value
I am using function based views:
#api_view(['POST'])
def brandCreate(request):
data = request.data
try:
brand = Brand.objects.create(
name=data['name'],
)
serializer = BrandSerializer(brand, many=False)
return Response(serializer.data)
except:
message = {'detail': 'Brand is not created'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
And this is my model:
class Brand(models.Model):
name = models.CharField(max_length=80)
logo = models.ImageField(null=True, blank=True)
def __str__(self):
return str(self.name)
After sending a POST request with postman, the record is successfully created, even though the name is shorter than 20 characters!
Any hints on what I am missing here? Thank you!
You're not using it correctly. You need to call is_valid method.
#api_view(['POST'])
def brandCreate(request):
data = request.data
serializer = BrandSerializer(data=data)
if serializer.is_valid():
serialiazer.save()
return Response(serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Related
I created a custom create field in the tournament serializer to create and update nested field.
I have been trying to make that work for a long time, but I can't get rid of errors.
When I try to post data on the tournament update it returns this error:
NOT NULL constraint failed: api_tournament.organizer_id
Here, api is the name of the app.
models.py
class tournament(models.Model):
tournament_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=60)
organizer = models.ForeignKey(
client, null=False, blank=False, on_delete=models.CASCADE)
logo = models.ImageField(null=True, blank=False)
game = models.CharField(max_length=20, choices=GAMES)
fees = models.IntegerField()
def __str__(self):
return self.title
views.py
# tournament
#api_view(['GET'])
def tournamentList(request):
tournaments = tournament.objects.all()
serializer = tournamentSerializer(tournaments, many=True)
return Response(serializer.data)
#api_view(['GET'])
def tournamentDetail(request, tournamentId):
singleTournament = tournament.objects.get(tournament_id=tournamentId)
serializer = tournamentSerializer(singleTournament, many=False)
return Response(serializer.data)
#api_view(['POST'])
def tournamentCreate(request):
serializer = tournamentSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
#api_view(['POST'])
def tournamentUpdate(request, tournamentId):
singleTournament = tournament.objects.get(tournament_id=tournamentId)
serializer = tournamentSerializer(
instance=singleTournament, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
#api_view(['DELETE'])
def tournamentDelete(request, tournamentId):
singleTournament = tournament.objects.get(tournament_id=tournamentId)
singleTournament.delete()
return Response("Deleted Item")
serializers.py
class tournamentSerializer(serializers.ModelSerializer):
organizer = clientSerializer(many=False, read_only=False)
class Meta:
model = tournament
fields = "__all__"
def create(self, validated_data):
organizer_data = validated_data.pop('organizer')
new_tournament = tournament.objects.create(**validated_data)
client.objects.create(organizer_data)
return new_tournament
"I created cusotm create field in the tournament serializer to create and update nested field, trying to make that work for a long time now but can't get rid of errors."
This error has nothing to do with create/update nested fields. But if you really go down that road:
When you initialize a serializer with an instance and call save(), it's not going to call the create method. It'll call the serializer update method. Here is the source code for proof.
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
return self.instance
But the serializer update method does not seem to be in the code you provided. It'll default to the ModelSerializer's update, which would raise an error about nested writes. Since that's not what happened, the bug must be earlier. This probably happened inside serializer.is_valid().
"When I try to post data on the tournament update it gives the error as
NOT NULL constraint failed: api_tournament.organizer_id
"
The error says that a constraint preventing null values has been violated. From the looks of it, the request.data might not have an "organizer_id" or "organizer" key. If you want to update a tournament without posting all the data during an update, either give the serializer field argument required=False or initialize the serializer with the argument partial=True
I have a POST method which is going to be used to retrieve a JSON object, which is then going to be used to retrieve the first_name, last_name, and username -- although I can't figure out how to get the fields (i.e. username) after I serialize it. What's the best way to go about that?
views.py
#api_view(['POST'])
def createUser(request):
# Making a Connection w/ MongoClient
client = MongoClient('mongodb+srv://test_user:0FP33TLJVWrjl8Vy#cluster0.5sacp.mongodb.net/sample_clubs?retryWrites=true&w=majority')
# Getting the Database
db = client['sample_clubs']
# Getting the Collection/Table
collection = db['users']
serializer = MyUserSerializer(data=request.data)
# Gives bug if next 2 lines aren't here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
serializers.py
class MyUserSerializer(serializers.ModelSerializer):
def get_first_name(self, obj):
# obj is model instance
return obj.first_name
def get_last_name(self, obj):
# obj is model instance
return obj.last_name
def get_user_name(self, obj):
# obj is model instance
return obj.user_name
class Meta:
model = MyUser
fields = ['first_name', 'last_name', 'username']
# fields = '__all__'
models.py
class MyUser(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
username = models.CharField(max_length=200)
def __str__(self):
return self.username
A serializer's save method in DRF will return the instance that has been saved. So you can simply call any of its field like this:
if serializer.is_valid():
obj = serializer.save()
print(obj.user_name)
The data will also be available through the serializer's validated data:
if serializer.is_valid():
print(serializer.validated_data.get('user_name')
You can also use the raw JSON that's been generated by serializer:
# note that serializer.data won't be available if 'is_valid()` returns False
print(serializer.data["user_name"])
Also, you shouldn't return serializer.data outside of the is_valid scope. If is_valid() is False, then there won't be any data so you will run to an error. The proper way would be this:
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
If you only want to return the user_name as response:
if serializer.is_valid():
obj = serializer.save()
return Response({"username": obj.user_name})
return Response(serializer.errors)
I have a TransactionType model and I've implemented a viewset method to create transaction type as shown also below. Currently I can only post single credit_account or debit_account items as shown in this payload:
{"name":"Repair and Maintenance","credit_account":16,"debit_account":38}
I would to post multiple credit_accounts and debit_accounts such that my payload looks something like this:
{"name":"Repair and Maintenance","credit_account":[16,4,5],"debit_account":[38,7]}
Which is the efficient way of do this?
class TransactionType(models.Model):
name = models.CharField(max_length=255)
organization = models.IntegerField(null=True, blank=False)
credit_account = models.ManyToManyField(Account,related_name='credit_account', verbose_name="Account to Credit")
debit_account = models.ManyToManyField(Account,related_name='debit_account',verbose_name="Account to Debit")
def __str__(self):
return '{}'.format(self.name)
viewset method
def create(self, request, format=None):
name = request.data['name']
try:
trans_type_obj = TransactionType.objects.create(name=name,
credit_account=Account.objects.get(id=request.data['credit_account'
]),
debit_account=Account.objects.get(id=request.data['debit_account'
]), organization=get_auth(request))
serializer = CreateTransactionTypeSerializer(trans_type_obj)
except Exception, e:
raise e
return Response(data=serializer.data,
status=status.HTTP_201_CREATED)
Use ManyToManyField.add() as below,
def create(self, request, format=None):
name = request.data['name']
try:
trans_type_obj = TransactionType.objects.create(name=name, organization=get_auth(request))
trans_type_obj.credit_account.add(*[credit_obj for credit_obj in Account.objects.filter(id__in=request.data['credit_account'])])
trans_type_obj.debit_account.add(*[debit_obj for debit_obj in Account.objects.filter(id__in=request.data['debit_account'])])
serializer = CreateTransactionTypeSerializer(trans_type_obj)
except Exception, e:
raise e
return Response(data=serializer.data,
status=status.HTTP_201_CREATED)
UPDATE-1
as #Daniel Roseman said, it's also possible to do the same without list comperhension as
trans_type_obj.credit_account.add(*Account.objects.filter(id__in=request.data['credit_account']))
trans_type_obj.debit_account.add(*Account.objects.filter(id__in=request.data['debit_account']))
def create(self, request,*args, **kwargs):
name = request.data.pop('name')
credits = request.data.pop('credit_account')
debits = request.data.pop('debit_account')
try:
trans_type_obj = TransactionType.objects.create(name=name, organization=get_auth(request))
for item in credits:
trans_type_obj.credit_account.add(item)
for item in debits:
trans_type_obj.debit_account.add(item)
serializer = TransactionTypeSerializer(trans_type_obj)
except Exception as e:
raise e
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
you can create one serializer with following fields
class TransactionTypeSerializer(serializers.ModelSerializer):
credit_account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), many=True)
debit_account = serializers.PrimaryKeyRelatedField(queryset=Account.objects.all(), many=True)
class Meta:
model = TransactionType
fields = __all__
now in views
def create(self, request, *args, **kwargs):
serializer = TransactionTypeSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
I am trying to perform a POST request to create an article and I am getting this error Request failed with status code 400 Bad Request: /api/articles/create/.
An article needs 3 attributes to be created:
(1) title
(2) body
(3) author (the current user)
The router works fine since the POST request goes into the post method of the ArticleCreateView class. But I'm guessing that serializer.is_valid() is returning False for some reason.
Also print(request.data) returns {'title': 'hello', 'body': 'stuff'}
Another mystery about Django is, how does the serializer know if I want to get, create or update something? In all the examples I've seen, the serializer magically seems to know this.
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
class Article(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=100)
body = models.TextField(max_length=100)
date_updated = models.DateTimeField(auto_now=True)
date_created = models.DateTimeField(auto_now_add=True)
According to your serializer, the validation process needs author details, which is not passing through POST payload.
So, make the author field in the serializer a not required field by adding required=False or use a ReadOnlyField() or specify read_only_fields in Meta class. Also pass context data as #nishant mentioned
# views.py
class ArticleCreateView(CreateAPIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ArticleSerializer(data=request.data, context={"request": request}) # change here
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors)
# serializer.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
read_only_fields = ('author',)
def create(self, validated_data):
author = self.context['request'].user
title = validated_data.get('title')
body = validated_data.get('body')
return Article.objects.create(author=author, title=title, body=body)
add this
serializer = ArticleSerializer(data=request.data, context={'request':request})
I am trying to make a simple api for marine vessels and ships and I am using django-rest-framework as the library to generate the api. With models that has simple fields like char fields and integers in the models level everything is working properly (create, delete, update, list, get).
The problem is with image fields whenever I try to post a record that has images it does not detect it. And it always causes an error "sequence item 1: expected string, NoneType found"
Below is my model, serializer, and view files.
serializer
class VesselSerializer(serializers.ModelSerializer):
image = serializers.ImageField(source='image')
class Meta:
model = Vessel
fields = ('image', 'id', 'vesselType', 'name')
class VesselTypeSerilizer(serializers.ModelSerializer):
class Meta:
model = VesselType
models
def vessel_file_name(instance, filename):
return '/'.join(['vessels', instance.id, filename]) #the error is in this line
class VesselType(models.Model):
name = models.CharField(max_length=50)
class Vessel(models.Model):
name = models.CharField(max_length=50)
vesselType = models.ForeignKey(VesselType)
image = models.ImageField(upload_to=vessel_file_name, null=True)
def __unicode__(self):
return u'%s' % self.name
views
class VesselList(generics.ListCreateAPIView):
queryset = Vessel.objects.all()
serializer_class = VesselSerializer
fields = ('url', 'id', 'image', 'vesselType')
def post(self, request, format=None):
print 'entered here'
print '%' * 10
print request.DATA
print request.FILES
print '%' * 10
serializer = VesselSerializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class VesselDetails(generics.RetrieveUpdateDestroyAPIView):
queryset = Vessel.objects.all()
serializer_class = VesselSerializer
It should be noted as well that the request.FILES and request.DATA when printed are displayed correctly yet that error appears which indicates that there is no file name although the name appears in request.FILES.
I am stuck in this problem for several hours and I can't seem to find what the problem is or what I am doing wrong. Any help would be appreciated.
The problem is that when vessel_file_name is called, the instance object is not saved to the db and instance.id is None.