Django rest framework : POST on many to many items - django

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)

Related

IntegrityError null value in column "name" of relation "tag" violates not-null constraint

I have created an API and am currently testing my post request with Postman. However, I keep getting this error.
views.py
def post(self, request, *args, **kwargs):
serializer = TagSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(name=request.POST.get("name"),
language=request.POST.get("language"))
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ('id', 'name', 'language')
def create(self, validated_data):
return Tag.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def __str__(self):
return self.name or ''
The name field in your tag model is not nullable. So it looks like you are trying to create a Tag model without a name value.
My guess is your request.POST is probably empty. Did you check it ? The recommanded way is to use request.data instead.
https://www.django-rest-framework.org/api-guide/requests/
By the way, if you want to use serializer, you can do all the process calling its functions.
So, you are supposed to check if the serializer is valid by calling
serializer.is_valid(raise_exception=True) and then create the object with save(). And if you want something more custom, you can override the .save() method of your serializer
def post(self, request, *args, **kwargs):
serializer = TagSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save() # the result contains the tag instance
It may be also more efficient if you use CreateAPIView directly instead
https://www.django-rest-framework.org/api-guide/generic-views/
ps: It can't see where you are going with serializer.save(tag_input).

MultipartParser is not validating data in Django Rest Framework

I am using a multipart/form-data in a form which have a manytomany relation as well as multiple file upload. But the validated data doesn't contains the array data
Views.py
class ExpenseCreateAPIView(CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
parser_classes = ( MultiPartParser,)
def post(self, request, *args, **kwargs):
owner = request.user.pk
d = request.data.copy()
d['owner'] = owner
serializer = ExpenseSerializer(data=d)
print("exp")
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)
Serializers.py
class ExpenseSerializer(serializers.ModelSerializer):
transactions = ExpenseTransactionsSerializer(many=True, required=False)
bill = ExpenseFilesSerializer(many=True, required=False)
class Meta:
model = Expense
fields = "__all__"
def create(self, validated_data):
print("validated data", validated_data)
items_objects = validated_data.pop('transactions', None)
files_objects = validated_data.pop('bill', None)
prdcts = []
files = []
for item in items_objects:
i = ExpenseTransactions.objects.create(**item)
prdcts.append(i)
if files_objects == None:
pass
else:
for item in files_objects:
i = ExpenseFiles.objects.create(**item)
files.append(i)
instance = Expense.objects.create(**validated_data)
instance.transactions.set(prdcts)
instance.bill.set(files)
return instance
How else should I use the MultiPartParser class in the views ?
I keep getting the error:
TypeError: 'NoneType' object is not iterable
at
for item in items_objects:
Make sure you have transactions in validated_data. If transactions can empty or not required, just change:
validated_data.pop('transactions', None)
to
validated_data.pop('transactions', [])
It means that if transactions not in validated_data then pop [] (an empty list), instead of None, then empty list can keep iterate in next your code.
I think you need to try validated_data.get instead of validated_data.pop

Django keeps giving error 'ArticleView' should either include a `serializer_class` attribute, or override the `get_serializer_class()` method

I already solved the problem by adding serializer_classes but it keeps giving me the same error. It did the same to me before even if i solved it . Did i miss something here?
Views.py
class ArticleView(CreateAPIView):
serializer = ArticleCreateSerializer
permission_classes = (IsAuthenticated,)
def post(self, request, format=None):
try:
serializer_context = {
'request': request
}
serializer_data = request.data.get('article',{})
serializer = self.get_serializer_class(data=serializer_data, context=serializer_context, )
if serializer.is_valid():
serializer.save()
return Response({'success': True})
else:
return Response({'success': False})
except:
return Response({'success': False})
Serializers.py
class ArticleCreateSerializer(serializers.ModelSerializer):
caption = serializers.CharField(required=False)
author = UserSerializer(read_only=True)
class Meta:
model = Article
fields = ('id','author','caption')
def create(self, validated_data):
author = self.context['request'].user.profile
article = Article.objects.create(author=author,**validated_data)
return article
Does anyone knows why??
Change your serializer = ArticleCreateSerializer with serializer_class = ArticleCreateSerializer.
Follow DRF guide on Generic views.

Return different serializer after create() in CreateAPIView in Django REST Framework

I'm using Django 2.2 and Django REST Framework.
I have to serializers for the same model.
class OrderListSerializer(serializers.ModelSerializer):
plan = PlanBaseSerializer(read_only=True, many=False)
class Meta:
model = Order
fields = [
'id', 'name', 'plan', 'pricing',
'created', 'completed',
]
class OrderCreateSerializer(serializers.ModelSerializer):
plan_pricing = serializers.IntegerField(required=True, write_only=True)
class Meta:
model = Order
fields = [
'plan_pricing'
]
def create(self, validated_data):
plan_pricing_ = validated_data.pop('plan_pricing', None)
try:
plan_pricing = PlanPricing.objects.get(pk=plan_pricing_)
except PlanPricing.DoesNotExists:
raise ValidationError('Plan pricing not available')
validated_data['plan'] = plan_pricing.plan
validated_data['amount'] = plan_pricing.price
return super().create(validated_data)
OrderListSerializer serializer is used for listing orders or order detail view and OrderCreateSerializer is used for creating a new order instance.
The view is
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
This is working fine as the order object is creating as expected. But the returned value contains no data.
I want to use OrderListSerializer to render saved order details after creating the order.
How to change the serializer class after creating the object?
Also, I have to trigger a signal after the object has been successfully created. What is the best place to trigger a signal?
Change CreateOrderView as below,
class CreateOrderView(generics.CreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = self.perform_create(serializer)
instance_serializer = OrderListSerializer(instance)
return Response(instance_serializer.data)
serializer.save() returns the instance that just created or updated. So we use that istance to pass to the OrderListSerializer and returning the corresponding response.
You could overwrite create(), and return whatever you want:
from rest_framework import response, status
(...)
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
return response.Response(status=status.HTTP_201_CREATED)
(...)
There are several ways you can use here. First,
class CreateOrderView(generics.ListCreateAPIView):
serializer_class = OrderCreateSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def list(self, *args, **kwargs):
serializer_class = OrderListSerializer
serializer = serializer_class(self.get_queryset())
return Response(serializer.data)
The alternative would be a conditional if statement, where
if self.request.method=='POST':
self.serializer_class = OrderCreateSerializer
elif self.request.method=='GET':
self.serializer_class = OrderListSerializer

Getting and saving value from POST with nested serializer

class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = EventInfo
fields = ('email', 'pin')
class EventSerializer(DataSerializer, GeoModelAPIView):
# other fields
event_info = InfoSerializer(read_only=True)
def create(self, validated_data):
event_info = validated_data.pop('event_info', {})
event = super().create(validated_data)
EventInfo.objects.create(event=event, **event_info)
return event
Model
class EventInfo(models.Model):
pin = models.CharField(max_length=60, null=False, blank=False)
email = models.EmailField()
event = models.ForeignKey(Event)
POST
{
# other data
"event_info": {
"email": "example#example.com",
"pin": "1234567890"
}
}
So I have a model that is not visible on the browsable API, but I want to be able to save data from POST request to that model. Using this code I can create the objects and it correctly links the info to a correct Event model. However the email and pin fields won't get saved. What I have figured out is that the 'event_info' data from the POST is not visible on the validated_data.
The validation goes to the DataSerializer's validation method but I guess that I should somehow bypass the validation for just the 'event_info' data?
Edit:
class EventViewSet(BulkModelViewSet, JSONAPIViewSet):
queryset = Event.objects.filter(deleted=False)
queryset = queryset.select_related('location')
queryset = queryset.prefetch_related(list of related fields)
serializer_class = EventSerializer
filter_backends = (EventOrderingFilter, filters.DjangoFilterBackend)
filter_class = EventFilter
ordering_fields = (fields to order by)
ordering = ('-last_modified_time',)
def __init__(self, **kwargs):
super().__init__(**kwargs)
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
def get_serializer_context(self):
context = super(EventViewSet, self).get_serializer_context()
context.setdefault('skip_fields', set()).update(set([
'headline',
'secondary_headline']))
return context
#atomic
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
def perform_create(self, serializer):
if isinstance(serializer.validated_data, list):
event_data_list = serializer.validated_data
else:
event_data_list = [serializer.validated_data]
super().perform_create(serializer)