Auto increment django model field per user
Asked Answered
D

4

10

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()?

Dupuy answered 25/11, 2015 at 17:8 Comment(6)
Which database are you using?Flavorful
@Flavorful Postgres 9.4Dupuy
@MTaqi an auto increment field will increment regardless of which user the Invoice belongs to. I need to be able to have Invoice.number=1 for all users. Thinks of your own telco invoice numbering: it starts from 1 and increments until you call to cancel the service, for each client.Dupuy
It'd be nice to have something like models.PositiveIntegerField(auto_increment_for('owner'))Dupuy
Why not have a date field, which will tell you the latest invoice as well for each user.Beulahbeuthel
@LegoStormtroopr Because I need invoices to be numbered as a sequence. That seams to be the most natural way.Dupuy
S
0

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)
Surrebutter answered 26/11, 2015 at 12:10 Comment(5)
Ok, but that won't prevent a race condition, when 2 concurrent clients try to save at (almost) the same time. That is why I believe it must be done using F() expression. That way the increment operation is done at the database level.Dupuy
you can handle IntegrityError and wait for random time to avoid race condition. ref: https://mcmap.net/q/606706/-handling-race-condition-in-model-saveSurrebutter
May be F() is better option. I haven't used it ever.Surrebutter
Here is the best solution to your problem: check latest saved invoice: https://mcmap.net/q/1169454/-auto-increament-the-invoice-number-in-django-backend-for-new-invoiceSurrebutter
This is not a solution for multithreading applications, neither are any of both options in the comments.Financial
T
0

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
Tetrapterous answered 12/5, 2021 at 9:32 Comment(0)
P
0

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()
Paapanen answered 9/7, 2022 at 7:30 Comment(0)
P
0

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

Parotid answered 1/9, 2024 at 13:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.