3

I have an invoice app that has an ID. I'd like to have it rollover by year. That is, every year, it would start back at 001. My return string would be 2014-001, 2014-002, 2015-001...

However, Django doesn't seem to support composite keys. Is there a way to do this?

class Invoice(models.Model):
    description = models.CharField(max_length=200)
    date = models.DateField('Date Issued')
    client = models.ForeignKey('organizations.Contact')
    UNPAID = 'Unpaid'
    PAID = 'Paid'
    STATUS_CHOICES = (
        (UNPAID, 'Unpaid'),
        (PAID, 'Paid'),
    )
    status = models.CharField(max_length=6,
                              choices=STATUS_CHOICES, default=UNPAID)
    notes = models.TextField(blank=True)

    def __str__(self):
        return (self.date.__str__().replace("-", "") +
                "-" + self.id.__str__().zfill(3))

edit new code:

def save(self, *args, **kwargs):
    if self.invoice_id is "":
        current_date = self.date.year
        previous_invoice = Invoice.objects.filter(invoice_id__contains=current_date).order_by('-id').first()
        if previous_invoice is not None:
            num = int(previous_invoice.invoice_id[5:])
        else:
            num = 1
        self.invoice_id = '{year}-{num:03d}'.format(year=current_date, num=num)
    super(Invoice, self).save(*args, **kwargs)

1 Answers1

1

You can use a CharField for the id, setting it as primary key and generating it automatically upon saving.

Something similar to the following should work:

class Invoice(models.Model):
    id = models.CharField(max_length=8, null=False, primary_key=True)
    ...

def save(self, *args, **kwargs):
    if self.pk is None:
        current_year = datetime.now().year
        previous_invoice = Invoice.objects.filter(id__contains=str(year)).order_by('-id').first()
        if previous_invoice is not None:
            num = int(previous_invoice.id[5:])
        else:
            num = 1
        self.pk = '{year}-{num:03d}'.format(year=current_year, num=num)
    super(Invoice, self).save(*args, **kwargs) 

EDIT: Add missing primary_key attribute in id field definition.

dukebody
  • 7,025
  • 3
  • 36
  • 61
  • If `self.pk` is not `None`, you'll get a `NameError` on `previous_invoice`. – David Cain Apr 06 '15 at 17:29
  • Well spotted. Thanks! – dukebody Apr 06 '15 at 17:31
  • 2
    This is generally the right idea, except that if you have a lot of concurrent requests, you'll probably occasionally bump into a race condition wherein you will try to save/generate an invoice number which already exists. (e.g., two concurrent processes get the same previous invoice number, then the first one successfully inserts the next invoice number. Then the second, running slightly after, fails to insert the same -- next -- invoice number) It probably is sufficient to have a retry when the exception occurs. – bmhkim Apr 06 '15 at 17:53
  • 2
    Also, it's not strictly necessary for the invoice number to be the database primary key (`id` field. Also, having a string primary key *may* have ramifications, e.g. http://stackoverflow.com/questions/3936182/). You can always have the invoice number as a separate field, set it as unique in Meta, and have both, at the cost of carrying a bit more data. – bmhkim Apr 06 '15 at 18:00
  • Thanks for the response. I tried doing this, but it seems the id is getting saved as null? –  Apr 06 '15 at 18:14
  • 1
    @Wilson I updated the answer, please try again with primary_key=True in the field id. – dukebody Apr 07 '15 at 06:11
  • @dukebody For compatibility reasons I decided not to use it as the PK. However, im still having the same issue where invoice_id is showing up as null when I create a new invoice. Could you take a look at my code? Thanks so much –  Apr 08 '15 at 21:01
  • 1
    Figured it out. Had to do is "". Also, had to do num = previous_invoice + 1 :) –  Apr 08 '15 at 21:23