Index: django/db/models/sql/query.py
===================================================================
--- django/db/models/sql/query.py	(wersja 11366)
+++ django/db/models/sql/query.py	(kopia robocza)
@@ -13,7 +13,7 @@
 from django.utils.datastructures import SortedDict
 from django.utils.encoding import force_unicode
 from django.db.backends.util import truncate_name
-from django.db import connection
+from django.db import connection, DatabaseError
 from django.db.models import signals
 from django.db.models.fields import FieldDoesNotExist
 from django.db.models.query_utils import select_related_descend
@@ -29,8 +29,14 @@
 except NameError:
     from sets import Set as set     # Python 2.3 fallback
 
-__all__ = ['Query', 'BaseQuery']
+__all__ = ['Query', 'BaseQuery', 'LockNotAvailable']
 
+class LockNotAvailable(DatabaseError):
+    '''
+    Raised when a query fails because a lock was not available.
+    '''
+    pass
+
 class BaseQuery(object):
     """
     A single SQL query.
@@ -74,6 +80,8 @@
         self.order_by = []
         self.low_mark, self.high_mark = 0, None  # Used for offset/limit
         self.distinct = False
+        self.select_for_update = False
+        self.select_for_update_nowait = False
         self.select_related = False
         self.related_select_cols = []
 
@@ -211,6 +219,8 @@
         obj.order_by = self.order_by[:]
         obj.low_mark, obj.high_mark = self.low_mark, self.high_mark
         obj.distinct = self.distinct
+        obj.select_for_update = self.select_for_update
+        obj.select_for_update_nowait = self.select_for_update_nowait
         obj.select_related = self.select_related
         obj.related_select_cols = []
         obj.aggregates = deepcopy(self.aggregates)
@@ -341,6 +351,7 @@
 
         query.clear_ordering(True)
         query.clear_limits()
+        query.select_for_update = False
         query.select_related = False
         query.related_select_cols = []
         query.related_select_fields = []
@@ -459,6 +470,10 @@
                         result.append('LIMIT %d' % val)
                 result.append('OFFSET %d' % self.low_mark)
 
+        if self.select_for_update and self.connection.features.has_select_for_update:
+            nowait = self.select_for_update_nowait and self.connection.features.has_select_for_update
+            result.append("%s" % self.connection.ops.for_update_sql(nowait=nowait))
+
         params.extend(self.extra_params)
         return ' '.join(result), tuple(params)
 
@@ -2366,7 +2381,12 @@
             else:
                 return
         cursor = self.connection.cursor()
-        cursor.execute(sql, params)
+        try:
+            cursor.execute(sql, params)
+        except DatabaseError, e:
+            if self.connection.features.has_select_for_update_nowait and self.connection.ops.signals_lock_not_available(e):
+                raise LockNotAvailable(*e.args)
+            raise
 
         if not result_type:
             return cursor
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(wersja 11366)
+++ django/db/models/base.py	(kopia robocza)
@@ -566,6 +566,12 @@
         self._collect_sub_objects(seen_objs)
 
         # Actually delete the objects.
+        print seen_objs.items()
+        if seen_objs.items() == [(type(self), { self.pk : self })]:
+            delete_objects(seen_objs)
+        else:
+            raise Exception("related objects found")
+        # Actually delete the objects.
         delete_objects(seen_objs)
 
     delete.alters_data = True
Index: django/db/models/manager.py
===================================================================
--- django/db/models/manager.py	(wersja 11366)
+++ django/db/models/manager.py	(kopia robocza)
@@ -152,6 +152,9 @@
     def order_by(self, *args, **kwargs):
         return self.get_query_set().order_by(*args, **kwargs)
 
+    def select_for_update(self, *args, **kwargs):
+        return self.get_query_set().select_for_update(*args, **kwargs)
+        
     def select_related(self, *args, **kwargs):
         return self.get_query_set().select_related(*args, **kwargs)
 
Index: django/db/models/__init__.py
===================================================================
--- django/db/models/__init__.py	(wersja 11366)
+++ django/db/models/__init__.py	(kopia robocza)
@@ -11,6 +11,7 @@
 from django.db.models.fields.subclassing import SubfieldBase
 from django.db.models.fields.files import FileField, ImageField
 from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
+from django.db.models.sql.query import LockNotAvailable
 from django.db.models import signals
 
 # Admin stages.
Index: django/db/models/query.py
===================================================================
--- django/db/models/query.py	(wersja 11366)
+++ django/db/models/query.py	(kopia robocza)
@@ -381,6 +381,7 @@
         del_query = self._clone()
 
         # Disable non-supported fields.
+        del_query.query.select_for_update = False
         del_query.query.select_related = False
         del_query.query.clear_ordering()
 
@@ -533,6 +534,19 @@
         else:
             return self._filter_or_exclude(None, **filter_obj)
 
+    def select_for_update(self, **kwargs):
+        """
+        Returns a new QuerySet instance that will select objects with a
+        FOR UPDATE lock.
+        """
+        # Default to false for nowait
+        nowait = kwargs.pop('nowait', False)
+        obj = self._clone()
+        obj.query.select_for_update = True
+        obj.query.select_for_update_nowait = nowait
+        transaction.commit_unless_managed()
+        return obj
+
     def select_related(self, *fields, **kwargs):
         """
         Returns a new QuerySet instance that will select related objects.
Index: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(wersja 11366)
+++ django/db/backends/mysql/base.py	(kopia robocza)
@@ -113,6 +113,8 @@
     update_can_self_select = False
     allows_group_by_pk = True
     related_fields_match_type = True
+    has_select_for_update = True
+    has_select_for_update_nowait = False
 
 class DatabaseOperations(BaseDatabaseOperations):
     def date_extract_sql(self, lookup_type, field_name):
@@ -208,6 +210,8 @@
         # MySQL doesn't support microseconds
         return unicode(value.replace(microsecond=0))
 
+    signals_deadlock = lambda self, e: e.args[0] == ER.LOCK_DEADLOCK
+
     def year_lookup_bounds(self, value):
         # Again, no microseconds
         first = '%s-01-01 00:00:00'
Index: django/db/backends/oracle/base.py
===================================================================
--- django/db/backends/oracle/base.py	(wersja 11366)
+++ django/db/backends/oracle/base.py	(kopia robocza)
@@ -41,6 +41,8 @@
     needs_datetime_string_cast = False
     uses_custom_query_class = True
     interprets_empty_strings_as_nulls = True
+    has_select_for_update = True
+    has_select_for_update_nowait = True
     uses_savepoints = True
     can_return_id_from_insert = True
 
@@ -226,6 +228,12 @@
                                            'column': column_name})
         return output
 
+    def signals_deadlock(self, exception):
+        return exception.args[0].code == 60
+
+    def signals_lock_not_available(self, exception):
+        return exception.args[0].code == 54
+
     def start_transaction_sql(self):
         return ''
 
Index: django/db/backends/__init__.py
===================================================================
--- django/db/backends/__init__.py	(wersja 11366)
+++ django/db/backends/__init__.py	(kopia robocza)
@@ -102,6 +102,8 @@
     # If True, don't use integer foreign keys referring to, e.g., positive
     # integer primary keys.
     related_fields_match_type = False
+    has_select_for_update = False
+    has_select_for_update_nowait = False
 
 class BaseDatabaseOperations(object):
     """
@@ -187,6 +189,16 @@
         """
         return []
 
+    def for_update_sql(self, nowait=False):
+        """
+        Return FOR UPDATE SQL clause to lock row for update
+        """
+        if nowait:
+            nowaitstr = ' NOWAIT'
+        else:
+            nowaitstr = ''
+        return 'FOR UPDATE' + nowaitstr
+
     def fulltext_search_sql(self, field_name):
         """
         Returns the SQL WHERE clause to use in order to perform a full-text
Index: django/db/backends/postgresql_psycopg2/base.py
===================================================================
--- django/db/backends/postgresql_psycopg2/base.py	(wersja 11366)
+++ django/db/backends/postgresql_psycopg2/base.py	(kopia robocza)
@@ -17,6 +17,7 @@
 try:
     import psycopg2 as Database
     import psycopg2.extensions
+    from psycopg2 import errorcodes
 except ImportError, e:
     from django.core.exceptions import ImproperlyConfigured
     raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e)
@@ -30,6 +31,8 @@
 
 class DatabaseFeatures(BaseDatabaseFeatures):
     needs_datetime_string_cast = False
+    has_select_for_update = True
+    has_select_for_update_nowait = True
     can_return_id_from_insert = False
 
 class DatabaseOperations(PostgresqlDatabaseOperations):
@@ -42,6 +45,11 @@
     def return_insert_id(self):
         return "RETURNING %s", ()
 
+    signals_deadlock = lambda self, e: e.pgcode == errorcodes.DEADLOCK_DETECTED
+
+    signals_lock_not_available = lambda self, e: e.pgcode == errorcodes.LOCK_NOT_AVAILABLE
+    
+
 class DatabaseWrapper(BaseDatabaseWrapper):
     operators = {
         'exact': '= %s',
Index: docs/ref/models/querysets.txt
===================================================================
--- docs/ref/models/querysets.txt	(wersja 11366)
+++ docs/ref/models/querysets.txt	(kopia robocza)
@@ -875,6 +875,54 @@
     Entry.objects.defer("body").only("headline", "body")
 
 
+``select_for_update(nowait=False)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Returns a queryset that will lock rows until the end of the transaction, 
+generating a SELECT ... FOR UPDATE statement on supported databases.
+
+For example::
+
+    entries = Entry.objects.select_for_update().filter(author=request.user)
+
+All matched entries will be locked until the end of the transaction block, 
+meaning that other transactions will be prevented from changing or acquiring 
+locks on them.
+
+Usually, if another transaction has already acquired a lock on one of the 
+selected rows, the query will block until the lock is released. If this is 
+not the behaviour you want, call ``select_for_update(nowait=True)``. This will 
+make the call non-blocking. If a conflicting lock is already acquired by 
+another transaction, ``django.db.models.LockNotAvailable`` will be raised when 
+the queryset is evaluated.
+
+Using blocking locks on a database can lead to deadlocks. This occurs when two 
+concurrent transactions are both waiting on a lock the other transaction 
+already holds. To deal with deadlocks, wrap your views that use 
+``select_for_update(nowait=False)`` with the 
+``django.views.decorators.deadlock.handle_deadlocks`` decorator. 
+
+For example::
+
+    from django.db import transaction
+    from django.views.decorators.deadlock import handle_deadlocks
+
+    @handle_deadlocks(max_retries=2)
+    @transaction.commit_on_success
+    def my_view(request):
+        ...
+
+If the database engine detects a deadlock involving ``my_view`` and decides 
+to abort its transaction, it will be automatically retried. If deadlocks keep 
+occurring after two repeated attempts, 
+``django.views.decorators.DeadlockError`` will be raised, which can be 
+propagated to the user or handled in a middleware.
+
+Currently the ``postgresql_psycopg2``, ``oracle``, and ``mysql``
+database backends support ``select_for_update()`` but MySQL has no
+support for the ``nowait`` argument. Other backends will simply
+generate queries as if ``select_for_update()`` had not been used.
+
 QuerySet methods that do not return QuerySets
 ---------------------------------------------
 
Index: docs/ref/databases.txt
===================================================================
--- docs/ref/databases.txt	(wersja 11366)
+++ docs/ref/databases.txt	(kopia robocza)
@@ -325,6 +325,15 @@
 column types have a maximum length restriction of 255 characters, regardless
 of whether ``unique=True`` is specified or not.
 
+Row locking with ``QuerySet.select_for_update()``
+-------------------------------------------------
+
+MySQL does not support the NOWAIT option to the SELECT ... FOR UPDATE 
+statement. However, you may call the ``select_for_update()`` method of a 
+queryset with ``nowait=True``. In that case, the argument will be silently 
+discarded and the generated query will block until the requested lock can be 
+acquired.
+
 .. _sqlite-notes:
 
 SQLite notes
