Revision 2c21381e snf-cyclades-app/synnefo/db/managers.py
b/snf-cyclades-app/synnefo/db/managers.py | ||
---|---|---|
27 | 27 |
# those of the authors and should not be interpreted as representing official |
28 | 28 |
# policies, either expressed or implied, of GRNET S.A. |
29 | 29 |
|
30 |
from django.db import connections |
|
31 | 30 |
from django.db.models import Manager |
32 | 31 |
from django.db.models.query import QuerySet |
33 | 32 |
|
34 | 33 |
|
35 |
class ForUpdateManager(Manager): |
|
36 |
"""Model manager implementing SELECT .. FOR UPDATE statement |
|
37 |
|
|
38 |
This manager implements select_for_update() method in order to use |
|
39 |
row-level locking in the database and guarantee exclusive access, since |
|
40 |
this method is only implemented in Django>=1.4. |
|
41 |
|
|
42 |
Non-blocking reads are not implemented, and each query including a row |
|
43 |
that is locked by another transaction will block until the lock is |
|
44 |
released. Also care must be taken in order to avoid deadlocks or retry |
|
45 |
transactions that abort due to deadlocks. |
|
46 |
|
|
47 |
Example: |
|
48 |
networks = Network.objects.select_for_update().filter(public=True) |
|
49 |
|
|
50 |
""" |
|
51 |
|
|
52 |
def select_for_update(self, *args, **kwargs): |
|
53 |
return ForUpdateQuerySet(self.model, using=self.db) |
|
54 |
|
|
55 |
|
|
56 |
class ForUpdateQuerySet(QuerySet): |
|
57 |
"""QuerySet implmenting SELECT .. FOR UPDATE statement |
|
58 |
|
|
59 |
This QuerySet overrides filter and get methods in order to implement |
|
60 |
select_for_update() statement, by appending 'FOR UPDATE' to the end |
|
61 |
for the SQL query. |
|
62 |
|
|
63 |
""" |
|
64 |
|
|
65 |
def filter(self, *args, **kwargs): |
|
66 |
query = super(ForUpdateQuerySet, self).filter(*args, **kwargs) |
|
67 |
return for_update(query) |
|
68 |
|
|
69 |
def get(self, *args, **kwargs): |
|
70 |
query = self.filter(*args, **kwargs) |
|
71 |
query = list(query) |
|
72 |
num = len(query) |
|
73 |
if num == 1: |
|
74 |
return query[0] |
|
75 |
if not num: |
|
76 |
raise self.model.DoesNotExist("%s matching query does not exist. " |
|
77 |
"Lookup parameters were %s" % |
|
78 |
(self.model._meta.object_name, |
|
79 |
kwargs)) |
|
80 |
raise self.model.MultipleObjectsReturned( |
|
81 |
"get() returned more than one %s -- it returned %s! " |
|
82 |
"Lookup parameters were %s" % |
|
83 |
(self.model._meta.object_name, num, kwargs)) |
|
84 |
|
|
85 |
|
|
86 |
def for_update(query): |
|
87 |
"""Rewrite query using SELECT .. FOR UPDATE.""" |
|
88 |
if 'sqlite' in connections[query.db].settings_dict['ENGINE'].lower(): |
|
89 |
# SQLite does not support FOR UPDATE |
|
90 |
return query |
|
91 |
sql, params = query.query.get_compiler(query.db).as_sql() |
|
92 |
return query.model._default_manager.raw(sql.rstrip() + ' FOR UPDATE', |
|
93 |
params) |
|
94 |
|
|
95 |
|
|
96 |
class ProtectedDeleteManager(ForUpdateManager): |
|
34 |
class ProtectedDeleteManager(Manager): |
|
97 | 35 |
"""Manager for protecting Backend deletion. |
98 | 36 |
|
99 | 37 |
Call Backend delete() method in order to prevent deletion |
Also available in: Unified diff