1đź‘Ť
This is a bit of a hack. The raw SQL will make your code less portable, but it’ll get rid of the race condition on the counter increment. In theory, this should increment the counter any time you do a query. I haven’t tested this, so you should make sure the list gets interpolated in the query properly.
class VisitorDayTypeCounterManager(models.Manager):
def get_query_set(self):
qs = super(VisitorDayTypeCounterManager, self).get_query_set()
from django.db import connection
cursor = connection.cursor()
pk_list = qs.values_list('id', flat=True)
cursor.execute('UPDATE table_name SET counter = counter + 1 WHERE id IN %s', [pk_list])
return qs
class VisitorDayTypeCounter(models.Model):
...
objects = VisitorDayTypeCounterManager()
29đź‘Ť
As of Django 1.1 you can use the ORM’s F() expressions.
from django.db.models import F
product = Product.objects.get(name='Venezuelan Beaver Cheese')
product.number_sold = F('number_sold') + 1
product.save()
For more details see the documentation:
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#django.db.models.F
- [Django]-Django admin interface: using horizontal_filter with inline ManyToMany field
- [Django]-Import "rest_framework" could not be resolved. But I have installed djangorestframework, I don't know what is going wrong
- [Django]-Get javascript variable's value in Django url template tag
12đź‘Ť
If you truly want the counter to be accurate you could use a transaction but the amount of concurrency required will really drag your application and database down under any significant load. Instead think of going with a more messaging style approach and just keep dumping count records into a table for each visit where you’d want to increment the counter. Then when you want the total number of visits do a count on the visits table. You could also have a background process that ran any number of times a day that would sum the visits and then store that in the parent table. To save on space it would also delete any records from the child visits table that it summed up. You’ll cut down on your concurrency costs a huge amount if you don’t have multiple agents vying for the same resources (the counter).
- [Django]-How to debug failing tests in Django?
- [Django]-Django dynamically get view url and check if its the current page
- [Django]-InvalidBasesError: Cannot resolve bases for [<ModelState: 'users.GroupProxy'>]
6đź‘Ť
You can use patch from http://code.djangoproject.com/ticket/2705 for support database level locking.
With patch this code will be atomic:
visitors = VisitorDayTypeCounter.objects.get(day=curday).for_update()
visitors.counter += 1
visitors.save()
- [Django]-Django Aggregation: Summation of Multiplication of two fields
- [Django]-Select distinct values from a table field
- [Django]-Advantages to using URLField over TextField?
5đź‘Ť
Two suggestions:
Add a unique_together to your model, and wrap the creation in an exception handler to catch duplicates:
class VisitorDayTypeCounter(models.Model):
visitType = models.CharField(max_length=60)
visitDate = models.DateField('Visit Date')
counter = models.IntegerField()
class Meta:
unique_together = (('visitType', 'visitDate'))
After this, you could stlll have a minor race condition on the counter’s update. If you get enough traffic to be concerned about that, I would suggest looking into transactions for finer grained database control. I don’t think the ORM has direct support for locking/synchronization. The transaction documentation is available here.
- [Django]-Register every table/class from an app in the Django admin page
- [Django]-Django: Filter a Queryset made of unions not working
- [Django]-Django "Remember Me" with built-in login view and authentication form
1đź‘Ť
Why not use the database as the concurrency layer ? Add a primary key or unique constraint the table to visitType and visitDate. If I’m not mistaken, django does not exactly support this in their database Model class or at least I’ve not seen an example.
Once you’ve added the constraint/key to the table, then all you have to do is:
- check if the row is there. if it is, fetch it.
- insert the row. if there’s no error you’re fine and can move on.
- if there’s an error (i.e. race condition), re-fetch the row. if there’s no row, then it’s a genuine error. Otherwise, you’re fine.
It’s nasty to do it this way, but it seems quick enough and would cover most situations.
- [Django]-Could not find a version that satisfies the requirement pkg-resources==0.0.0
- [Django]-How do I match the question mark character in a Django URL?
- [Django]-Pip install the latest version
0đź‘Ť
Your should use database transactions to avoid this kind of race condition. A transaction lets you perform the whole operation of creating, reading, incrementing and saving the counter on an “all or nothing” base. If anything goes wrong it will roll back the whole thing and you can try again.
Check out the Django docs. There is a transaction middle ware, or you can use decorators around views or methods to create transactions.
- [Django]-How to add verbose_name to forms
- [Django]-How to omit object name from Django's TabularInline admin view?
- [Django]-How can my django model DateField add 30 days to the provided value?