Search code examples
python-3.xdjangodjango-modelsdjango-rest-frameworkdjango-views

Django Rest Framework - ListCreateAPIView - How to filter + aggregate + create multiple objects inside def perform_create


Any help on this issue would be highly appreciated please!

Basically I have a model called Transaction and is linked to the User model using a ForeignKey.

I would like to filter all transactions made today, per user, and then I want to aggregate their sales by summing the transactions per user, and then based on this sum, I would want to calculate 10% of it and create a new transaction (basically today's interest earned)

I have tried to make it work inside inside serializers.py

class UserTransactionFilterSerializer(serializers.Serializer):
    
username = serializers.CharField(read_only = True)
id = serializers.IntegerField(read_only = True)
agg_amt_btc = serializers.SerializerMethodField(read_only=True)
trans = serializers.SerializerMethodField(read_only = True)

# Gets transaction data using UserTransactionSerializer
def get_trans(self, obj):
    user = obj
    print(user)
    today = timezone.now().date()
    my_trans_qs = user.transaction_set.filter(trans_date__date=today)
    return TransactionUserInlineSerializer(my_trans_qs, many=True, context = self.context).data

# Aggregates the trans amount (sum and avg)
def get_agg_amt_btc(self, obj):
    user = obj
    print(user)
    today = timezone.now().date()
    my_trans_qs = user.transaction_set.filter(trans_date__date=today)
    return my_trans_qs.aggregate(sum_btc = Sum('trans_amt_btc'), avg_btc = Avg('trans_amt_btc'))


def create(self,obj):
    today = timezone.now().date()
    for user in User.objects.filter(transaction__trans_date__date=today).distinct():

        total_transactions = user.transaction_set.aggregate(sum_btc = Sum('trans_amt_btc'))['sum_btc']

        print(user)
        transaction_amount = total_transactions * Decimal('0.1')
        print(transaction_amount)
        
        with transaction.atomic():
            Transaction.objects.create(owner=user, trans_amt_btc=transaction_amount)

    return user

Which works! but it bypasses all validations when I do:

Transaction.objects.create(owner=user,trans_amt_btc=transaction_amount) 

The problem is that in the above objects.create method, some fields are not mentioned.

For example there is a field trans_status which is found in the Transaction model (shown below) which has null=False and blank=False But, when the object gets created, it has these fields as null instead of giving me an error

Any idea how I can implement this inside a view where I am sure the validation can happen?

Here are the fields of my Transaction model looks like for your reference:

owner = models.ForeignKey(User, default = 1, null = True, on_delete = models.SET_NULL) #related_name defaults to transaction_set

trans_date = models.DateTimeField(auto_now_add=True, blank=False)
trans_amt_btc = models.DecimalField(decimal_places=12, max_digits=24,blank=False, null=False)

trans_type = models.CharField(choices= TRANSACTION_TYPE_CHOICES, max_length=24, blank=False, null=False)
trans_reason = models.CharField(choices= TRANSACTION_REASON_CHOICES, max_length=24, blank=False, null=False)
trans_mode = models.CharField(choices= TRANSACTION_MODE_CHOICES, max_length=24, blank=False, null=False)
trans_status = models.CharField(choices= TRANSACTION_STATUS_CHOICES, max_length=24, blank=False, default="APPROVED")

note = models.CharField(max_length=120, blank=False, null=False)

Below is my attempt at adding it in the view, where I hope I can perform the required validations which is not working:

class UserTransactionCommissionView(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserTransactionFilterSerializer

def perform_create(self,serializer):
    today = timezone.now().date()
    for user in list(User.objects.filter(transaction__trans_date__date=today).distinct()):

        total_transactions = user.transaction_set.aggregate(sum_btc = Sum('trans_amt_btc'))['sum_btc']

        print(total_transactions)
        
        total_transactions = serializer.validated_data.get('trans_amt_btc')

        
        transaction_amount = total_transactions * Decimal('0.1')
        print(transaction_amount)

        serializer.save(owner=user)

        
        # with transaction.atomic():
        #     Transaction.objects.create(owner=user, trans_amt_btc=transaction_amount)

    return user

Solution

  • Adding it in the view is the right way to go, but I think you're just missing another serializer. You should create the below serializer in order to have all the needed fields:

    class TransactionSerializer(serializers.ModelSerializer):
        class Meta:
            model = Transaction
            fields = ['owner', 'trans_amt_btc'] # Add other required fields here
    

    Now you need to use that serializer inside the perform_create as such:

    def perform_create(self, serializer):
        today = timezone.now().date()
        for user in User.objects.filter(transaction__trans_date__date=today).distinct():
            total_transactions = user.transaction_set.filter(trans_date__date=today).aggregate(sum_btc=Sum('trans_amt_btc'))['sum_btc']
            if total_transactions:
                transaction_amount = total_transactions * Decimal('0.1')
                transaction_data = {
                    'owner': user,
                    'trans_amt_btc': transaction_amount
                     # Add other fields here
                }
                transaction_serializer = TransactionSerializer(data=transaction_data) # <= Call the new serializer
                if transaction_serializer.is_valid(): # <= Do the validation
                    transaction_serializer.save()
                else:
                    raise serializers.ValidationError(transaction_serializer.errors)