Skip to content

Instantly share code, notes, and snippets.

@renskiy
Last active December 26, 2015 14:29
Show Gist options
  • Save renskiy/7166189 to your computer and use it in GitHub Desktop.
Save renskiy/7166189 to your computer and use it in GitHub Desktop.
select_for_update() for Django < 1.4. Tested on Django 1.3.
"""
Adds `select_for_update()` method to Django QuerySet (Django < 1.4).
Usage:
class MyModel(models.Model):
if not hasattr(models.Manager, 'select_for_update'):
objects = ForUpdateManager()
# model definition
"""
__author__ = "Renskiy <[email protected]>"
from functools import wraps
from django.conf import settings
from django.db import models
from django.db.models.query import QuerySet
from django.db.models.sql import Query
def for_update(as_sql):
"""
Decorator: for_update
Used to decorate `as_sql` method of Django SQLCompiler,
adds " FOR UPDATE" to the end of result of decorated method.
"""
@wraps(for_update)
def _wrapper(compiler, *args, **kwargs):
sql, params = as_sql(compiler, *args, **kwargs)
if getattr(compiler.query, 'select_for_update', False):
sql += ' FOR UPDATE'
return sql, params
return _wrapper
class ForUpdateQuery(Query):
"""
Class: ForUpdateQuery
Query which adds support of `select_for_update()`.
"""
def __init__(self, *args, **kwargs):
super(ForUpdateQuery, self).__init__(*args, **kwargs)
self.select_for_update = False
def get_compiler(self, *args, **kwargs):
compiler = super(ForUpdateQuery, self).get_compiler(*args, **kwargs)
as_sql = getattr(compiler.__class__, 'as_sql')
if as_sql.__name__ != 'for_update':
# decorate class method only if it's not decorated yet
decorated_as_sql = for_update(as_sql)
setattr(compiler.__class__, 'as_sql', decorated_as_sql)
return compiler
def clone(self, *args, **kwargs):
kwargs.update(select_for_update=self.select_for_update)
return super(ForUpdateQuery, self).clone(*args, **kwargs)
class ForUpdateQuerySet(QuerySet):
"""
Class: ForUpdateQuerySet
Special query class for replacing Django's standard one, adds select_for_update() method
"""
def select_for_update(self):
db_engine = settings.DATABASES.get(self.db, {}).get('ENGINE', '').lower()
if 'sqlite' in db_engine: # sqlite doesn't support "FOR UPDATE"
return self
query_set = self._clone()
query_set.query._for_write = True
query_set.query.select_for_update = True
return query_set
class ForUpdateManager(models.Manager):
"""
Class: ForUpdateManager
Special model manager for replacing Django's standard one, adds select_for_update() method
"""
def get_query_set(self):
query = ForUpdateQuery(self.model)
return ForUpdateQuerySet(self.model, using=self._db, query=query)
def select_for_update(self):
return self.get_query_set().select_for_update()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment