0

In my model, I have calculated property current_tobe_payed

I want to generate CSV report of all rows where my property current_tobe_payed is less than zero

See my view below:

def export_leaseterm_csv(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'
    writer = csv.writer(response)
    leaseterms = serializers.serialize( "python", LeaseTerm.objects.all())
    [obj for obj in leaseterms if obj.current_tobe_payed > 0]

    for leaseterm in  obj:
        writer.writerow(leaseterm['fields'].values())
    return response

However, I am getting an error:

'dict' object has no attribute 'current_tobe_payed'

How can I solve this issue?
(also I want to enter only certain fields into CSV and not all the table.)

UPDATE:

See my model below:

class LeaseTerm(CommonInfo):
    version = IntegerVersionField( )
    start_period = models.ForeignKey(Period, related_name='start_period' )
    end_period = models.ForeignKey(Period, related_name='end_period')
    lease = models.ForeignKey(Lease)
    increase  = models.DecimalField(max_digits=7, decimal_places=2)
    amount  = models.DecimalField(max_digits=7, decimal_places=2)
    is_terminated = models.BooleanField(default=False)

    # _total = None
    _current_period  = None
    _total_current = None

    _total_payment = None
    _total_current_payment = None

    _total_discount = None
    _total_current_discount = None

    _current_tobe_payed = None
    _current_balance = None


    def _get_total(self):
        from payment.models import LeasePayment
        from conditions.models import LeaseDiscount
        total_payment_dict = LeasePayment.objects.filter(leaseterm_id=self.id, is_active = True ).aggregate(Sum('amount'))

        if total_payment_dict ['amount__sum']:
            total_payment =  total_payment_dict['amount__sum'] 
        else:  
            total_payment =  0

        total_discount_dict = LeaseDiscount.objects.filter(leaseterm_id=self.id, is_active = True ).aggregate(Sum('amount'))

        if total_discount_dict ['amount__sum']:
            total_discount =  total_discount_dict['amount__sum'] 
        else:  
            total_discount =  0

        # current = Period.objects.filter( is_active = True, _is_current = True )

        current_date=datetime.datetime.now().date()
        current_period_dict = Period.objects.filter(start_date__lte=current_date,end_date__gte=current_date, is_active = True ).aggregate(Max('order_value'))

        #self._current_period = current_period
        if current_period_dict['order_value__max']:
            current_period =  current_period_dict['order_value__max'] 
        else:  
            current_period =  0

        current_discount_dict = LeaseDiscount.objects.filter(leaseterm_id=self.id, 
            is_active = True, period_date__gte=self.start_period,
             period_date__lte=current_period).aggregate(Sum('amount'))

        if current_discount_dict ['amount__sum']:
            current_discount =  current_discount_dict['amount__sum'] 
        else:  
            current_discount =  0

        current_periods_number = current_period - self.start_period.order_value + 1

        current_tobe_payed =  current_periods_number * self.amount -  current_discount

        current_balance =  total_payment - current_tobe_payed

        self._current_period = current_period
        self._total_payment = total_payment
        self._total_discount = total_discount
        self._current_tobe_payed  = current_tobe_payed 
        self._current_balance = current_balance


    @property
    def current_tobe_payed(self):
        if self._current_tobe_payed is None:
            self._get_total()
        return self._current_tobe_payed

    @property
    def current_balance(self):
        if self._current_balance is None:
            self._get_total()
        return self._current_balance

    @property
    def current_period(self):
        if self._current_period is None:
            self._get_total()
        return self._current_period   

    @property
    def total_payment(self):
        if self._total_payment is None:
            self._get_total()
        return self._total_payment

    @property
    def total_discount(self):
        if self._total_discount is None:
            self._get_total()
        return self._total_discount

    def clean(self):
            model = self.__class__

            if self.lease_id and (self.is_terminated == False) and (self.is_active == True) and model.objects.filter(lease=self.lease, is_active=True ).exclude(id=self.id).count() == 1:
                raise  ValidationError('!Lease has a active condition already, Terminate prior to creation of new one'.format(self.lease))

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(LeaseTerm, self).save(*args, **kwargs)

    def __unicode__(self):
        return u'%s %i %s %s ' % ("term:",self.id, self.start_period, self.end_period)
Prakhar Trivedi
  • 8,218
  • 3
  • 28
  • 35
Ilya Bibik
  • 3,924
  • 4
  • 23
  • 48
  • Hi boris. Can you post your model. Sometimes it's possible to filter on a property with a bit of help from database functions – e4c5 Dec 20 '16 at 03:58
  • The error says `dict object has no attribute current_tobe_payed`. The serializer returns a list of dictionary. So obviously when you try to iterate over that list, every item which is dict wont have the said property, rather it would be a key. – Rajesh Yogeshwar Dec 20 '16 at 04:18
  • It is not a model field, so it can't be serialized out of the box. A model property needs to be serialized in its own class. See this http://stackoverflow.com/questions/17066074/modelserializer-using-model-property – nael Dec 20 '16 at 04:22

3 Answers3

2

That's rather lengthy calculation that you have in your get_total method. I count five queries inside that the following bit of code will result in those five queries being executed for each row on your table.

[obj for obj in leaseterms if obj.current_tobe_payed > 0]

So that means you are doing 5000 queries if you have just a 1000 rows in your table. With 10,000 rows, this list comprehension would take a very long time to run.

Solution. Convert your property to a model field.

to_be_payed  = models.DecimalField(max_digits=7, decimal_places=2)

I am often telling deves not to save the results of simple calculations into a db column. but yours isn't a simple calculation but a complex one so it deserves a field. YOu can update this field in the save method

def save(self, *args, **kwargs):
    self.to_be_payed = self.get_total()
    super(LeaseTerm, self).save(*args, **kwargs)

If as you say, the amount to be paid depends on changes to a Payment instance, what you can do is to have a post_save signal on the Payment model to trigger the related LeaseTerm object(s) to be updated. Doing such an update would still be cheaper than doing this calculation 5000 times

e4c5
  • 52,766
  • 11
  • 101
  • 134
  • The problem with it is that this value is dependant on other tables. So if the payment was performed in Payment table the balance should increase but the Lease Term model will not know that it has to be updated . – Ilya Bibik Dec 20 '16 at 04:20
1

You are using a serializer which returns a python dictionary object. It is not an instance of a model. I suggest the following:

EDITED SOLUTION

def export_leaseterm_csv(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'

    writer = csv.writer(response)

    # get all the LeaseTerm instances
    leaseterms = LeaseTerm.objects.all()

    # filter based on current_tobe_payed
    tobe_payed_terms = [obj for obj in leaseterms if obj.current_tobe_payed > 0]
    tobe_payed_dict = serializers.serialize( "python", tobe_payed_terms)
    # serialize these objects and write to values to the csv
    for term in tobe_payed_dict:
         writer.writerow(term['fields'].values())



    return response
nael
  • 1,441
  • 19
  • 36
  • Thanks but Getting 'LeaseTerm' object is not iterable from line term_dict = serializers.serialize( "python", term) – Ilya Bibik Dec 20 '16 at 04:25
  • 1
    @BorisTheAnimal a modification to this solution. Instead of looping over `tobe_payed_terms`, just pass the list `tobe_payed_terms` itself to the serializers.serialize(). – Rajesh Yogeshwar Dec 20 '16 at 04:30
  • Thank you Rajesh, I updated the solution per your comment. – nael Dec 20 '16 at 04:38
  • Thank you Rajesh and nmushel it does work for filtering this way but the calculated values are not being populated into csv. Only the actual db fields are. – Ilya Bibik Dec 20 '16 at 04:43
  • @BorisTheAnimal debug it, try printing the values before they are getting written to csv. Verify if the calculated values exist or not. If it does not exist, write a custom serializer and make sure that it serializes your custom property. – Rajesh Yogeshwar Dec 20 '16 at 04:46
  • It may be filtering but it's doing N*5 queries were N is the number of rows in the LeaseTerm table – e4c5 Dec 21 '16 at 00:00
0

at the end I did it without signal and without sterilizer

Amount of records in this table will never grow more then 100th .This report is executed only by one person in company once a week. During the testing if the performance will be insufficient I will denormalize other then that I prefer to have it normalized as long as I can.

 def export_leaseterm_csv(request):

    response = HttpResponse(content_type='text/csv')

    response['Content-Disposition'] = 'attachment; filename="leaseterm.csv"'
    writer = csv.writer(response)

    writer.writerow([
        "lease",
        "tenant",
        "amount",
        "current balance",
    ])


    leaseterms = LeaseTerm.objects.filter(is_terminated = False, is_active = True )



    tobe_payed_terms = [obj for obj in leaseterms if obj.current_balance < 0]

    for term in tobe_payed_terms:



             writer.writerow([
                term.lease,
                term.tenant,
                term.amount,
                term.current_balance,

            ])

    return response
Ilya Bibik
  • 3,924
  • 4
  • 23
  • 48