caruccio
caruccio

Reputation: 304

Auto increment django model field per user

I have this model:

class Invoice(models.Model):
    owner  = models.ForeignKey(settings.AUTH_USER_MODEL)
    data   = models.TextField(default=None, blank=True, null=True)
    number = models.PositiveIntegerField(default=0, null=False)

What I need is to auto-increment the field number for each separated user. The rationale is that each user has a list of Invoice, starting from number=1 to number=latest.number+1.

I do known about F() expressions, but can't figure out how to reference the latest/greatest number for each specific user. Maybe Invoice.objects.filter(owner=request.user).aggregate(Max('number')) is the path, but how do I ensure there is no race conditions between Max() and F()?

Upvotes: 10

Views: 3680

Answers (5)

Saeed Perfect
Saeed Perfect

Reputation: 47

I think it's better to implement save() method into your model and sort by number like this to add 1 to last number that you have:

def save(self, *args, **kwargs):
        if self.pk is None:
            last_invoice = Invoice.objects.filter(owner=self.owner).order_by('-number').first()
            if last_invoice:
                self.number = last_invoice.number + 1
            else:
                self.number = 1
        super().save(*args, **kwargs)

Upvotes: 0

code-0101
code-0101

Reputation: 11

use select_for_update() inside a transaction like this

with transaction.atomic():
    latest_invoice = Invoice.objects.filter(owner=user).select_for_update().aggregate(Max('number'))
    max_number = latest_invoice['number__max']
    next_number = (max_number or 0) + 1
    invoice = Invoice.objects.create(owner=user, data=data, number=next_number)

can find relevant documnetation here:- https://docs.djangoproject.com/en/5.1/ref/models/querysets/#select-for-update

Upvotes: 0

Bhavya Peshavaria
Bhavya Peshavaria

Reputation: 361

A simple solution is you can make the number field as the primary key since its nature would be similar.

class Invoice(models.Model):
    owner  = models.ForeignKey(settings.AUTH_USER_MODEL)
    data   = models.TextField(default=None, blank=True, null=True)
    number = models.IntegerField(primary_key=True)

Or, you can make number as AutoField or BigAutoField.

number = models.AutoField()

Upvotes: 0

Mojtaba Arezoomand
Mojtaba Arezoomand

Reputation: 2380

you can get your first or last object like this:

# For last Object
Model.objects.latest('field') # Field can be id or pk or ...

# For first Object
Model.objects.all().first() # You can also use it on filter

Upvotes: 0

varnothing
varnothing

Reputation: 1309

You can achieve this and similar functions by overriding save method in model and writing your custom logics to it.

class Invoice(models.Model):
    owner  = models.ForeignKey(settings.AUTH_USER_MODEL)
    data   = models.TextField(default=None, blank=True, null=True)
    number = models.PositiveIntegerField(default=0, null=False)

    def save(self, *args, **kwargs):
        if self.pk:
            self.number += 1
        # Write all your logic here, like handeling max value etc
        return super(Invoice, self).save(*args, **kwargs)

Upvotes: 0

Related Questions