Search code examples
djangodjango-modelsdjango-admindjango-modeladmin

How to introduce a model class method in Django admin


I have a model that has class methods. In testing the class methods work and alter the model instances according to my needs. The issue is using this class method in the admin. When an application cannot pay a late payment fee is applied creating another transaction altering the balance. The method in models is decorated with a @classmethod decorator:

class Transactions(models.Model):

    application = models.ForeignKey(Application, 
    related_name='application_id', blank=True, null=True)
    transaction_type = models.CharField(max_length=56, 
    choices=TRANSACTION_TYPE, null=True)
    transaction_source = models.CharField(max_length=56, 
    choices=TRANSACTION_SOURCE, null=True)
    transaction_method = models.CharField(max_length=56, 
    choices=TRANSACTION_METHOD, null=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    created_date = models.DateField()
    posted_date = models.DateField()
    external_reference = models.CharField(max_length=100, null=True, 
    verbose_name='External Reference')

    def __unicode__(self):
        return self.application.forename + " " + 
        self.application.surname + "(" + str(self.application.id) + ")"

    @classmethod
    def late_payment_fee(cls, app, desired_action):
        """
        This function enacts a late payment fee to an application as a 
        transaction
        :param app: application the fee is going to be applied to
        :param desired_action: either True or False, when reversing the 
        fee the transaction shouldn't be deleted,
        just another transaction of the opposite effect in order to 
        help loans collection with tracking, True will
        enact a feee, False will reverse the fee
        :return: a transaction which is stored in the database
        """
        today = str(date.today())
        if desired_action:
            trans_type = MISSEDREPAYMENTFEE
            amount = float(12)
        else:
            trans_type = MISSEDREPAYMENTFEEREVERSAL
            amount = float(-12)
        cls.create_trasaction(app, trans_type, INTERNALBOOKING, 
        INTERNALBOOKING, amount, today, today, None)

I need to get it so when status is altered, or when a tickbox is checked in the admin for an application it fires the class method. I have Googled overriding models in admin but cannot find anything. Here's the admin:

class ApplicationAdmin(ImportExportModelAdmin):
    resource_class = ApplicationResource
    search_fields = ['forename', 'surname']
    list_filter = ('status', 'underwritingresult', 'test', 'signed', 
    'mandateapproved', 'dealership', 'brand')
    list_select_related = ('instalment',)
    list_display = ('id', 'SPV_ID', 'forename', 'surname'......
    inlines = [
        InstalmentInline,
        AddressInline
    ]
    exclude = ['customer', 'accountnumber', 'sortcode']
    readonly_fields = ('Account_Number', 'Sort_Code', 'SPV_ID')

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = []
        if obj.locked :
            for field in self.model._meta.fields:
                readonly_fields.append(field.name)
        else:
            readonly_fields = ('Account_Number', 'Sort_Code', 'SPV_ID')
        return readonly_fields

    def Account_Number(self, obj):
        return Decrypt(obj.accountnumber)

    def Sort_Code(self, obj):
        return Decrypt(obj.sortcode)

    def SPV_ID(self, obj):
        return obj.spv.id

    def has_delete_permission(self, request, obj=None):
        return False

Many thanks for reading this.


Solution

  • I've now found the solution (sorry if my initial question was not very clear). It turned out that I needed to override the save function in the Application model. However, another issue was raised when doing this. If a user changed something benign like the name of the customer then the class method would fire and create new transactions even though there was no real change. Because of this, we have to override the init and the save function in the model but still actually save. For this model, we are interested to see if the status has changed or a late fee has been applied. In order to do this, we have to store the initial status values by overriding the init method in the model:

    __initial_status = None
    __initial_fee_status = None
    
    def __init__(self, *args, **kwargs):
        """
        This function overrides the __init__ function in order to
        save initial_status facilitating comparisons in the save function
        :param args: allows the function to accept an arbitrary number of 
        arguments and/or keyword arguments
        :param kwargs: same as above
        """
        super(Application, self).__init__(*args, **kwargs)
        self.__initial_status = self.status
        self.__initial_fee_status = self.feeadded 
    

    Now we have them stored we can pass them through our save function. If there is a change we can utilize the class method:

    def save(self, *args, **kwargs):
        """
        This function overrides the standard save function. The application
        is still saved, however, other functions are fired when it is 
        saved.
        :return: creates reversal commission and dealership transactions
        if the status is CANCELLED
        """
        if self.signed and self.status != self.__initial_status:
            if self.status == Application.CANCELLED:
                Transactions.create_commision_trasaction(app=self, 
                reverse=True)
                Transactions.create_dealership_trasaction(app=self, 
                reverse=True)
            elif self.status == Application.OK:
                Transactions.create_commision_trasaction(app=self, 
                reverse=False)
                Transactions.create_dealership_trasaction(app=self, 
                reverse=False)
    
        if self.signed and self.feeadded != self.__initial_fee_status:
            if self.feeadded:
                Transactions.late_payment_fee(app=self, desired_action=True)
            elif self.feeadded is False:
                Transactions.late_payment_fee(app=self, desired_action=False)
    
        super(Application, self).save(*args, **kwargs)