#29786: Add option to lock rows with select_for_update() immediately
-------------------------------------+-------------------------------------
     Reporter:  ovalseven8           |                    Owner:  nobody
         Type:  New feature          |                   Status:  new
    Component:  Database layer       |                  Version:  2.1
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Old description:

> In my web application I usually follow the "fat models, lightweight
> views" philosophy.
>
> To put it in a nutshell, I have two models in my app called "Event" and
> "Registration (for Event)":
>

> {{{
> class Event(models.Model):
>     capacity = models.PositiveSmallIntegerField()
>
>     def get_number_of_registered_tickets():
>         return
> EventRegistration.objects.filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'),
> 0))['total']
>
>     def reserve_tickets(self, number_tickets):
>         Event.objects.filter(id=self.id).select_for_update()
>         if self.get_number_of_registered_tickets() + number_tickets <=
> self.capacity:
>             # create EventRegistration
>         else:
>             # handle error
>

> class EventRegistration(models.Model):
>     time = models.DateTimeField(auto_now_add=True)
>     event = models.ForeignKey(Event, on_delete=models.CASCADE)
>     number_tickets =
> models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
> }}}
>
> Now, let's say for a specific event only one ticket is left and two users
> want to buy that ticket concurrently. Under unfortunate circumstances
> (when I do not use locking), it would be possible that both get the
> ticket and now I have a problem. So, I need to make sure that the
> registration for an event happens in sequence. That's the reason why I
> use **select_for_update()** in the method `reserve_tickets()` above.
>
> Unfortunately, the row is only locked when the QuerySet is evaluates
> what's not the case here. So, I need more or less a "dirty hack" like
> printing the queryset or creating a list etc.
>
> While this works, I do not think it's a nice solution. Especially for
> locking rows, I think it would be a good think if Django had a
> "lock/evaluate immediately" option.
>
> Different ideas how the solution API could look like:
> `select_for_update().evaluate()`
> `select_for_update(evaluate_immediately=True)`
>
> Of course, there're more possibilities. I think it would be a nice little
> feature and lazy evaluation is something you perhaps do not want for
> locking.
>
> So my question is if you're open for that, if yes I could look into it.

New description:

 In my web application I usually follow the "fat models, lightweight views"
 philosophy.

 To put it in a nutshell, I have two models in my app called "Event" and
 "Registration (for Event)":


 {{{
 class Event(models.Model):
     capacity = models.PositiveSmallIntegerField()

     def get_number_of_registered_tickets():
         return
 
EventRegistration.objects.filter(event__exact=self).aggregate(total=Coalesce(Sum('number_tickets'),
 0))['total']

     @transaction.atomic
     def reserve_tickets(self, number_tickets):
         Event.objects.filter(id=self.id).select_for_update()
         if self.get_number_of_registered_tickets() + number_tickets <=
 self.capacity:
             # create EventRegistration
         else:
             # handle error


 class EventRegistration(models.Model):
     time = models.DateTimeField(auto_now_add=True)
     event = models.ForeignKey(Event, on_delete=models.CASCADE)
     number_tickets =
 models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
 }}}

 Now, let's say for a specific event only one ticket is left and two users
 want to buy that ticket concurrently. Under unfortunate circumstances
 (when I do not use locking), it would be possible that both get the ticket
 and now I have a problem. So, I need to make sure that the registration
 for an event happens in sequence. That's the reason why I use
 **select_for_update()** in the method `reserve_tickets()` above.

 Unfortunately, the row is only locked when the QuerySet is evaluates
 what's not the case here. So, I need more or less a "dirty hack" like
 printing the queryset or creating a list etc.

 While this works, I do not think it's a nice solution. Especially for
 locking rows, I think it would be a good think if Django had a
 "lock/evaluate immediately" option.

 Different ideas how the solution API could look like:
 `select_for_update().evaluate()`
 `select_for_update(evaluate_immediately=True)`

 Of course, there're more possibilities. I think it would be a nice little
 feature and lazy evaluation is something you perhaps do not want for
 locking.

 So my question is if you're open for that, if yes I could look into it.

--

Comment (by ovalseven8):

 Add decorator

-- 
Ticket URL: <https://code.djangoproject.com/ticket/29786#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/068.01d8ad3bb987c82f96eef870b02023e8%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to