diff --git a/djblets/db/fields.py b/djblets/db/fields.py
index 39e286ebfeed64806edd6fe3cc825612410db440..f627b29d5b8890e865c8a13f2220841592eadedf 100644
--- a/djblets/db/fields.py
+++ b/djblets/db/fields.py
@@ -31,13 +31,16 @@ from datetime import datetime
 import base64
 import json
 import logging
+import weakref
 
 from django import forms
 from django.conf import settings
 from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
-from django.db.models import F
+from django.db.models import F, Q
 from django.db.models.expressions import ExpressionNode
+from django.db.models.signals import (m2m_changed, post_delete, post_init,
+                                      post_save)
 from django.utils import six
 from django.utils.encoding import smart_unicode
 
@@ -198,7 +201,7 @@ class JSONField(models.TextField):
         setattr(cls, "get_%s_json" % self.name, get_json)
         setattr(cls, "set_%s_json" % self.name, set_json)
 
-        models.signals.post_init.connect(self.post_init, sender=cls)
+        post_init.connect(self.post_init, sender=cls)
 
     def pre_save(self, model_instance, add):
         return self.dumps(getattr(model_instance, self.attname, None))
@@ -330,14 +333,18 @@ class CounterField(models.IntegerField):
             if value != 0:
                 update_values[attname] = F(attname) + value * multiplier
 
-        if update_values:
+        cls._set_values(model_instance, update_values, reload_object)
+
+    @classmethod
+    def _set_values(cls, model_instance, values, reload_object=True):
+        if values:
             queryset = model_instance.__class__.objects.filter(
                 pk=model_instance.pk)
-            queryset.update(**update_values)
+            queryset.update(**values)
 
             if reload_object:
                 cls._reload_model_instance(model_instance,
-                                           six.iterkeys(update_values))
+                                           six.iterkeys(values))
 
     @classmethod
     def _reload_model_instance(cls, model_instance, attnames):
@@ -370,66 +377,17 @@ class CounterField(models.IntegerField):
         queryset.update(**{self.attname: F(self.attname) - decrement_by})
 
     def contribute_to_class(self, cls, name):
-        def _increment(model_instance, reload_object=True, increment_by=1):
-            """Increments this field by one."""
-            if increment_by != 0:
-                self.increment(cls.objects.filter(pk=model_instance.pk),
-                               increment_by)
-
-                if reload_object:
-                    _reload(model_instance)
+        def _increment(model_instance, *args, **kwargs):
+            self._increment(model_instance, *args, **kwargs)
 
-        def _decrement(model_instance, reload_object=True, decrement_by=1):
-            """Decrements this field by one."""
-            if decrement_by != 0:
-                self.decrement(cls.objects.filter(pk=model_instance.pk),
-                               decrement_by)
-
-                if reload_object:
-                    _reload(model_instance)
+        def _decrement(model_instance, *args, **kwargs):
+            self._decrement(model_instance, *args, **kwargs)
 
         def _reload(model_instance):
-            """Reloads the value in this instance from the database."""
-            self._reload_model_instance(model_instance, [self.attname])
+            self._reload(model_instance)
 
         def _reinit(model_instance):
-            """Re-initializes the value in the database from the initializer."""
-            if not (model_instance.pk or self._initializer or
-                    six.callable(self._initializer)):
-                # We don't want to end up defaulting this to 0 if creating a
-                # new instance unless an initializer is provided. Instead,
-                # we'll want to handle this the next time the object is
-                # accessed.
-                return
-
-            value = 0
-
-            if self._initializer:
-                if isinstance(self._initializer, ExpressionNode):
-                    value = self._initializer
-                elif six.callable(self._initializer):
-                    self._locks[model_instance] = 1
-                    value = self._initializer(model_instance)
-                    del self._locks[model_instance]
-
-            if value is not None:
-                is_expr = isinstance(value, ExpressionNode)
-
-                if is_expr and not model_instance.pk:
-                    value = 0
-                    is_expr = False
-
-                if is_expr:
-                    cls.objects.filter(pk=model_instance.pk).update(**{
-                        self.attname: value,
-                    })
-
-                    self._reload_model_instance(model_instance, [self.attname])
-                else:
-                    setattr(model_instance, self.attname, value)
-
-                    if model_instance.pk:
-                        model_instance.save(update_fields=[self.attname])
+            self._reinit(model_instance)
 
         super(CounterField, self).contribute_to_class(cls, name)
 
@@ -439,18 +397,515 @@ class CounterField(models.IntegerField):
         setattr(cls, 'reinit_%s' % self.name, _reinit)
         setattr(cls, self.attname, self)
 
-        models.signals.post_init.connect(self._post_init, sender=cls)
+        post_init.connect(self._post_init, sender=cls)
 
-    def _post_init(self, instance=None, **kwargs):
-        if not instance or instance in self._locks:
-            # Prevent the possibility of recursive lookups where this
-            # same CounterField on this same instance tries to initialize
-            # more than once. In this case, this will have the updated
-            # value shortly.
+    def _increment(self, model_instance, reload_object=True, increment_by=1):
+        """Increments this field by one."""
+        if increment_by != 0:
+            cls = model_instance.__class__
+            self.increment(cls.objects.filter(pk=model_instance.pk),
+                           increment_by)
+
+            if reload_object:
+                self._reload(model_instance)
+
+    def _decrement(self, model_instance, reload_object=True, decrement_by=1):
+        """Decrements this field by one."""
+        if decrement_by != 0:
+            cls = model_instance.__class__
+            self.decrement(cls.objects.filter(pk=model_instance.pk),
+                           decrement_by)
+
+            if reload_object:
+                self._reload(model_instance)
+
+    def _reload(self, model_instance):
+        """Reloads the value in this instance from the database."""
+        self._reload_model_instance(model_instance, [self.attname])
+
+    def _reinit(self, model_instance):
+        """Re-initializes the value in the database from the initializer."""
+        if not (model_instance.pk or self._initializer or
+                six.callable(self._initializer)):
+            # We don't want to end up defaulting this to 0 if creating a
+            # new instance unless an initializer is provided. Instead,
+            # we'll want to handle this the next time the object is
+            # accessed.
             return
 
+        value = 0
+
+        if self._initializer:
+            if isinstance(self._initializer, ExpressionNode):
+                value = self._initializer
+            elif six.callable(self._initializer):
+                self._locks[model_instance] = 1
+                value = self._initializer(model_instance)
+                del self._locks[model_instance]
+
+        if value is not None:
+            is_expr = isinstance(value, ExpressionNode)
+
+            if is_expr and not model_instance.pk:
+                value = 0
+                is_expr = False
+
+            if is_expr:
+                cls = model_instance.__class__
+                cls.objects.filter(pk=model_instance.pk).update(**{
+                    self.attname: value,
+                })
+
+                self._reload_model_instance(model_instance, [self.attname])
+            else:
+                setattr(model_instance, self.attname, value)
+
+                if model_instance.pk:
+                    model_instance.save(update_fields=[self.attname])
+
+    def _post_init(self, instance=None, **kwargs):
+        # Prevent the possibility of recursive lookups where this
+        # same CounterField on this same instance tries to initialize
+        # more than once. In this case, this will have the updated
+        # value shortly.
+        if instance and instance not in self._locks:
+            self._do_post_init(instance)
+
+    def _do_post_init(self, instance):
         value = self.value_from_object(instance)
 
         if value is None:
             reinit = getattr(instance, 'reinit_%s' % self.name)
             reinit()
+
+
+class RelationCounterField(CounterField):
+    """A field that provides an atomic count of a relation.
+
+    RelationCounterField is a specialization of CounterField that tracks
+    how many objects there are on the other side of a ManyToManyField or
+    ForeignKey relation.
+
+    RelationCounterField takes the name of a relation (either a field name,
+    for a forward ManyToManyField relation, or the "related_name" for
+    the reverse relation of another model's ForeignKey or ManyToManyField.
+    (Note that using a forward ForeignKey relation is considered invalid,
+    as the count can only be 1 or 0.)
+
+    The counter will be initialized with the number of objects on the
+    other side of the relation, and this will be kept updated so long as
+    all updates to the table are made using standard create/save/delete
+    operations on models.
+
+    Note that updating a relation outside of a model's regular API (such as
+    through raw SQL or something like an update() call) will cause the
+    counters to get out of sync. They would then need to be reset using
+    ``reinit_{field_name}``.
+    """
+    # Stores state across all instances of a RelationCounterField.
+    #
+    # Django doesn't make it easy to track updates to the other side of a
+    # relation, meaning we have to do it ourselves. This dictionary will
+    # weakly track InstanceState objects (which are tied to the lifecycle of
+    # a particular model instancee). These objects are used to look up model
+    # instances and their RelationCounterFields, given a model name, model
+    # instance ID, and a relation name.
+    _instance_states = weakref.WeakValueDictionary()
+
+    # Most of the hard work really lives in RelationTracker below. Here, we
+    # store all registered instances of RelationTracker. There will be one
+    # per model_cls/relation_name pair.
+    _relation_trackers = {}
+
+    class InstanceState(object):
+        """Tracks state for a RelationCounterField assocation.
+
+        State instances are bound to the lifecycle of a model instance.
+        They keep track of the model instance (using a weak reference) and
+        all RelationCounterFields tied to the relation name provided.
+
+        These are used for looking up the proper instance and
+        RelationCounterFields on the other end of a reverse relation, given
+        a model, relation name, and IDs, through the _instance_states
+        dictionary.
+        """
+        def __init__(self, model_instance, fields):
+            self.model_instance_ref = weakref.ref(model_instance)
+            self.fields = fields
+            self.to_clear = set()
+
+        @property
+        def model_instance(self):
+            return self.model_instance_ref()
+
+        def reinit_fields(self):
+            """Reinitializes all associated fields' counters."""
+            model_instance = self.model_instance
+
+            for field in self.fields:
+                field._reinit(model_instance)
+
+        def increment_fields(self, by=1):
+            """Increments all associated fields' counters."""
+            RelationCounterField.increment_many(
+                self.model_instance,
+                dict([(field.attname, by) for field in self.fields]))
+
+        def decrement_fields(self, by=1):
+            """Decrements all associated fields' counters."""
+            RelationCounterField.decrement_many(
+                self.model_instance,
+                dict([(field.attname, by) for field in self.fields]))
+
+        def zero_fields(self):
+            """Zeros out all associated fields' counters."""
+            RelationCounterField._set_values(
+                self.model_instance,
+                dict([(field.attname, 0) for field in self.fields]))
+
+        def reload_fields(self):
+            """Reloads all associated fields' counters."""
+            RelationCounterField._reload_model_instance(
+                self.model_instance,
+                [field.attname for field in self.fields])
+
+        def __repr__(self):
+            return '<RelationCounterField.InstanceState for %s.pk=%s>' % (
+                self.model_instance.__class__.__name__,
+                self.model_instance.pk)
+
+    class RelationTracker(object):
+        """Tracks relations and updates state for all affected CounterFields.
+
+        This class is responsible for all the hard work of updating
+        RelationCounterFields refererring to a relation, based on updates
+        to that relation. It's really the meat of RelationCounterField.
+
+        Each RelationTracker is responsible for a given model/relation name
+        pairing, across all instances of a model and across all
+        RelationCounterFields following that relation name.
+
+        The main reason the code lives here instead of in each
+        RelationCounterField is to keep state better in sync and to ensure
+        we're only ever dealing with one set of queries per relation name.
+        We're also simplifying signal registration, helping to make things
+        less error-prone.
+        """
+        def __init__(self, model_cls, rel_field_name):
+            self._rel_field_name = rel_field_name
+            self._rel_field, rel_model, is_rel_direct, is_m2m = \
+                model_cls._meta.get_field_by_name(rel_field_name)
+
+            self._is_rel_reverse = not is_rel_direct
+
+            if not is_m2m and is_rel_direct:
+                # This combination doesn't make any sense. There's only ever
+                # one item on this side, so no point in counting. Let's just
+                # complain about it.
+                raise ValueError(
+                    "RelationCounterField cannot work with the forward end of "
+                    "a ForeignKey ('%s')"
+                    % rel_field_name)
+
+            dispatch_uid = id(self)
+
+            if is_m2m:
+                # This is going to be one end or the other of a ManyToManyField
+                # relation.
+                if is_rel_direct:
+                    # This is a ManyToManyField, and we can get the 'rel'
+                    # attribute through it.
+                    m2m_field = self._rel_field
+                    self._related_name = m2m_field.rel.related_name
+                else:
+                    # This is a RelatedObject. We need to get the field through
+                    # this.
+                    m2m_field = self._rel_field.field
+                    self._related_name = m2m_field.attname
+
+                # Listen for all M2M updates on the through table for this
+                # ManyToManyField. Unfortunately, we can't look at a
+                # particular instance, but we'll use state tracking to do the
+                # necessary lookups and updates in the handler.
+                m2m_changed.connect(
+                    self._on_m2m_changed,
+                    weak=False,
+                    sender=m2m_field.rel.through,
+                    dispatch_uid=dispatch_uid)
+            else:
+                # This is a ForeignKey or similar. It must be the reverse end.
+                assert not is_rel_direct
+
+                model = self._rel_field.model
+                self._related_name = self._rel_field.field.attname
+
+                # Listen for deletions and saves on that model type. In the
+                # handler, we'll look up state for the other end of the
+                # relation (the side owning this RelationCounterField), so that
+                # we can update the counts.
+                #
+                # Unfortunately, we can't listen on the particular instance, so
+                # we use the state tracking.
+                post_delete.connect(
+                    self._on_related_delete,
+                    weak=False,
+                    sender=model,
+                    dispatch_uid=dispatch_uid)
+                post_save.connect(
+                    self._on_related_save,
+                    weak=False,
+                    sender=model,
+                    dispatch_uid=dispatch_uid)
+
+        def _on_m2m_changed(self, instance, action, reverse, model, pk_set,
+                            **kwargs):
+            """Handler for when a M2M relation has been updated.
+
+            This will figure out the necessary operations that may need to be
+            performed, given the update.
+
+            For post_add/post_remove operations, it's pretty simple. We see
+            if there are any instances (by way of stored state) for any of the
+            affected IDs, and we re-initialize them.
+
+            For clear operations, it's more tricky. We have to fetch all
+            instances on the other side of the relation before any database
+            changes are made, cache them in the InstanceState, and then update
+            them all in post_clear.
+            """
+            if reverse != self._is_rel_reverse:
+                # This doesn't match the direction we're paying attention to.
+                # Ignore it.
+                return
+
+            is_post_clear = (action == 'post_clear')
+            is_post_add = (action == 'post_add')
+            is_post_remove = (action == 'post_remove')
+
+            if is_post_clear or is_post_add or is_post_remove:
+                state = RelationCounterField._get_state(
+                    instance.__class__, instance.pk, self._rel_field_name)
+
+                if state:
+                    if is_post_add:
+                        state.increment_fields(by=len(pk_set))
+                    elif is_post_remove:
+                        state.decrement_fields(by=len(pk_set))
+                    elif is_post_clear:
+                        state.zero_fields()
+
+                    if not pk_set and is_post_clear:
+                        # See the note below for 'pre_clear' for an explanation
+                        # of why we're doing this.
+                        pk_set = state.to_clear
+                        state.to_clear = set()
+
+                if pk_set:
+                    # If any of the models have their own
+                    # RelationCounterFields, make sure they've been updated to
+                    # handle this side of things.
+                    if is_post_add:
+                        update_by = 1
+                    else:
+                        update_by = -1
+
+                    # Update all RelationCounterFields on the other side of the
+                    # relation that are referencing this relation.
+                    self._update_counts(model, pk_set, '_related_name',
+                                        update_by)
+
+                    for pk in pk_set:
+                        state = RelationCounterField._get_state(
+                            model, pk, self._related_name)
+
+                        if state:
+                            state.reload_fields()
+            elif action == 'pre_clear':
+                # m2m_changed doesn't provide any information on affected IDs
+                # for clear events (pre or post). We can, however, look up
+                # these IDs ourselves, and if they match any existing
+                # instances, we can re-initialize their counters in post_clear
+                # above.
+                #
+                # We do this by fetching the IDs (without instantiating new
+                # models) and storing it in the associated InstanceState. We'll
+                # use those IDs above in the post_clear handler.
+                state = RelationCounterField._get_state(
+                    instance.__class__, instance.pk, self._rel_field_name)
+
+                if state:
+                    mgr = getattr(instance, self._rel_field_name)
+                    state.to_clear.update(mgr.values_list('pk', flat=True))
+
+        def _on_related_delete(self, instance, **kwargs):
+            """Handler for when a ForeignKey relation is deleted.
+
+            This will check if a model entry that has a ForeignKey relation
+            to this field's parent model entry has been deleted from the
+            database. If so, any associated counter fields on this end will be
+            decremented.
+            """
+            state = self._get_reverse_foreign_key_state(instance)
+
+            if state:
+                state.decrement_fields()
+            else:
+                self._update_unloaded_fkey_rel_counts(instance, -1)
+
+        def _on_related_save(self, instance=None, created=False, raw=False,
+                             **kwargs):
+            """Handler for when a ForeignKey relation is created.
+
+            This will check if a model entry has been created that has a
+            ForeignKey relation to this field's parent model entry. If so, any
+            associated counter fields on this end will be decremented.
+            """
+            if raw or not created:
+                return
+
+            state = self._get_reverse_foreign_key_state(instance)
+
+            if state:
+                state.increment_fields()
+            else:
+                self._update_unloaded_fkey_rel_counts(instance, 1)
+
+        def _update_unloaded_fkey_rel_counts(self, instance, by):
+            """Updates unloaded model entry counters for a ForeignKey relation.
+
+            This will get the ID of the model being referenced by the
+            matching ForeignKey in the provided instance. If set, it will
+            update all RelationCounterFields on that model that are tracking
+            the ForeignKey.
+            """
+            rel_pk = getattr(instance, self._rel_field.field.attname)
+
+            if rel_pk is not None:
+                self._update_counts(self._rel_field.parent_model,
+                                    [rel_pk], '_rel_field_name', by)
+
+        def _update_counts(self, model_cls, pks, rel_attname, update_by):
+            """Updates counts on all model entries matching the given criteria.
+
+            This will update counts on all RelationCounterFields on all entries
+            of the given model in the database that are tracking the given
+            relation.
+            """
+            values = dict([
+                (field.attname, F(field.attname) + update_by)
+                for field in model_cls._meta.local_fields
+                if (isinstance(field, RelationCounterField) and
+                    getattr(field._relation_tracker, rel_attname) ==
+                        self._rel_field_name)
+            ])
+
+            if values:
+                if len(pks) == 1:
+                    q = Q(pk=list(pks)[0])
+                else:
+                    q = Q(pk__in=pks)
+
+                model_cls.objects.filter(q).update(**values)
+
+        def _get_reverse_foreign_key_state(self, instance):
+            """Returns an InstanceState for the other end of a ForeignKey relation.
+
+            This is used when listening to changes on models that establish a
+            ForeignKey to this counter field's parent model. Given the instance
+            on that end, we can get the state for this end.
+            """
+            return RelationCounterField._get_state(
+                self._rel_field.parent_model,
+                getattr(instance, self._rel_field.field.attname),
+                self._rel_field_name)
+
+    @classmethod
+    def _store_state(cls, instance, field):
+        """Stores state for a model instance and field.
+
+        This constructs an InstanceState instance for the given model instance
+        and RelationCounterField. It then associates it with the model instance
+        and stores a weak reference to it in _instance_states.
+        """
+        assert instance.pk is not None
+
+        key = (instance.__class__, instance.pk, field._rel_field_name)
+
+        if key in cls._instance_states:
+            cls._instance_states[key].fields.append(field)
+        else:
+            state = cls.InstanceState(instance, [field])
+            setattr(instance, '_%s_state' % field.attname, state)
+            cls._instance_states[key] = state
+
+    @classmethod
+    def _get_state(cls, model_cls, instance_id, rel_field_name):
+        """Returns an InstanceState instance for the given parameters.
+
+        If no InstanceState instance can be found that matches the
+        parameters, None will be returned.
+        """
+        return cls._instance_states.get(
+            (model_cls, instance_id, rel_field_name))
+
+    def __init__(self, rel_field_name, *args, **kwargs):
+        def _initializer(model_instance):
+            if model_instance.pk:
+                return getattr(model_instance, rel_field_name).count()
+            else:
+                return 0
+
+        kwargs['initializer'] = _initializer
+
+        super(RelationCounterField, self).__init__(*args, **kwargs)
+
+        self._rel_field_name = rel_field_name
+        self._relation_tracker = None
+
+    def _do_post_init(self, instance):
+        """Handles initialization of an instance of the parent model.
+
+        This will begin the process of storing state about the model
+        instance and listening to signals coming from the model on the
+        other end of the relation.
+        """
+        super(RelationCounterField, self)._do_post_init(instance)
+
+        cls = instance.__class__
+
+        # We may not have a ID yet on the instance (as it may be a
+        # newly-created instance not yet saved to the database). In this case,
+        # we need to listen for the first save before storing the state.
+        if instance.pk is None:
+            dispatch_uid = '%s-%s-first-save' % (id(instance), self.attname)
+
+            post_save.connect(
+                lambda **kwargs: self._on_first_save(
+                    instance, dispatch_uid=dispatch_uid, **kwargs),
+                weak=False,
+                sender=cls,
+                dispatch_uid=dispatch_uid)
+        else:
+            RelationCounterField._store_state(instance, self)
+
+        if not self._relation_tracker:
+            key = (cls, self._rel_field_name)
+            self._relation_tracker = \
+                RelationCounterField._relation_trackers.get(key)
+
+            if not self._relation_tracker:
+                self._relation_tracker = \
+                    self.RelationTracker(cls, self._rel_field_name)
+                RelationCounterField._relation_trackers[key] = \
+                    self._relation_tracker
+
+    def _on_first_save(self, model_instance, instance, dispatch_uid, **kwargs):
+        """Handler for the first save on a newly created instance.
+
+        This will disconnect the signal and store the state on the instance.
+        """
+        if model_instance == instance:
+            RelationCounterField._store_state(instance, self)
+
+            post_save.disconnect(sender=instance.__class__,
+                                 dispatch_uid=dispatch_uid)
diff --git a/djblets/db/tests/test_relation_counter_field.py b/djblets/db/tests/test_relation_counter_field.py
new file mode 100644
index 0000000000000000000000000000000000000000..56ed3f0c9b08258f0ba0ad9be47a130fced1f4d0
--- /dev/null
+++ b/djblets/db/tests/test_relation_counter_field.py
@@ -0,0 +1,721 @@
+from __future__ import unicode_literals
+
+from django.db import models
+
+from djblets.db.fields import RelationCounterField
+from djblets.testing.testcases import TestCase, TestModelsLoaderMixin
+
+
+class ReffedModel(models.Model):
+    m2m_reffed_counter = RelationCounterField('m2m_reffed')
+    reffed_key_counter = RelationCounterField('key_reffed')
+
+    # These are here to ensure that RelationCounterField's smarts don't
+    # over-increment/decrement other counters.
+    m2m_reffed_counter_2 = RelationCounterField('m2m_reffed')
+    reffed_key_counter_2 = RelationCounterField('key_reffed')
+
+
+class M2MRefModel(models.Model):
+    m2m = models.ManyToManyField(ReffedModel, related_name='m2m_reffed')
+    counter = RelationCounterField('m2m')
+    counter_2 = RelationCounterField('m2m')
+
+
+class KeyRefModel(models.Model):
+    key = models.ForeignKey(ReffedModel, related_name='key_reffed',
+                            null=True)
+
+
+class BadKeyRefModel(models.Model):
+    key = models.ForeignKey(ReffedModel, related_name='bad_key_reffed')
+    counter = RelationCounterField('key')
+    counter_2 = RelationCounterField('key')
+
+
+class RelationCounterFieldTests(TestModelsLoaderMixin, TestCase):
+    """Tests for djblets.db.fields.RelationCounterField."""
+    tests_app = 'djblets.db.tests'
+
+    # ManyToManyField.add will do 1 filter(), 1 bulk_create().
+    # RelationCounterField will do 1 increment(), 1 reload() for self;
+    # 1 increment() total for all objects.
+    M2M_ADD_BASE_QUERY_COUNT = 2 + 3
+
+    # RelationCounterField will do 1 reload() per item.
+    M2M_ADD_LOADED_ITEM_QUERY_COUNT = 1
+
+    # ManyToManyField.add will do 1 filter(), 1 delete()
+    # RelationCounterField will do 1 decrement(), 2 update()
+    M2M_REMOVE_BASE_QUERY_COUNT = 2 + 3
+
+    # RelationCounterField will do 1 reload() per item
+    M2M_REMOVE_LOADED_ITEM_QUERY_COUNT = 1
+
+    # ManyToManyField.clear will do 2 filters(), 1 delete().
+    # RelationCounterField will do 1 decrement(), 1 reload(), 1 update().
+    M2M_CLEAR_BASE_QUERY_COUNT = 3 + 3
+
+    # RelationCounterField will do 1 reload().
+    M2M_CLEAR_LOADED_ITEM_QUERY_COUNT = 1
+
+    # Django will do 1 delete().
+    # RelationCounterField will do 1 decrement(), 1 reload().
+    KEY_REMOVE_ITEM_QUERY_COUNT = 1 + 2
+
+    # Django will do 1 create().
+    # RelationCounterField will do 1 update().
+    KEY_CREATE_UNLOADED_REL_QUERY_COUNT = 2
+
+    # RelationCounterField will do 1 count(), 1 reload()
+    REINIT_QUERY_COUNT = 2
+
+    def setUp(self):
+        super(RelationCounterFieldTests, self).setUp()
+
+        # Make sure the state is clear due to dropped references before
+        # each run.
+        self.assertFalse(RelationCounterField._instance_states)
+
+    #
+    # Forward-relation ManyToManyField tests
+    #
+
+    def test_m2m_forward_initialize(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and initialization
+        """
+        with self.assertNumQueries(1):
+            model = M2MRefModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+
+    def test_m2m_forward_and_reinit(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and re-initialization
+        """
+        with self.assertNumQueries(2):
+            model = M2MRefModel.objects.create()
+            added_model = ReffedModel.objects.create()
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(added_model)
+
+        with self.assertNumQueries(self.REINIT_QUERY_COUNT):
+            model.counter = None
+            model.reinit_counter()
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+
+    def test_m2m_forward_and_create(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and creating object
+        """
+        with self.assertNumQueries(1):
+            model = M2MRefModel.objects.create()
+
+        with self.assertNumQueries(1 +
+                                   self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            rel_model_1 = model.m2m.create()
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+
+        with self.assertNumQueries(1 +
+                                   self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            rel_model_2 = model.m2m.create()
+            self.assertEqual(model.counter, 2)
+            self.assertEqual(model.counter_2, 2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 1)
+
+    def test_m2m_forward_and_add(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and adding object
+        """
+        with self.assertNumQueries(3):
+            model = M2MRefModel.objects.create()
+            rel_model_1 = ReffedModel.objects.create()
+            rel_model_2 = ReffedModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_1)
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_2)
+            self.assertEqual(model.counter, 2)
+            self.assertEqual(model.counter_2, 2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 1)
+
+    def test_m2m_forward_and_add_many(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and adding multiple objects
+        """
+        with self.assertNumQueries(3):
+            model = M2MRefModel.objects.create()
+            rel_model_1 = ReffedModel.objects.create()
+            rel_model_2 = ReffedModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   2 * self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_1, rel_model_2)
+            self.assertEqual(model.counter, 2)
+            self.assertEqual(model.counter_2, 2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 1)
+
+    def test_m2m_forward_and_remove(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and removing object
+        """
+        with self.assertNumQueries(2):
+            model = M2MRefModel.objects.create()
+            rel_model = ReffedModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model)
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+            self.assertEqual(rel_model.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model.m2m_reffed_counter_2, 1)
+
+        with self.assertNumQueries(self.M2M_REMOVE_BASE_QUERY_COUNT +
+                                   self.M2M_REMOVE_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.remove(rel_model)
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model.m2m_reffed_counter_2, 0)
+
+    def test_m2m_forward_and_clear(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and clearing all objects
+        """
+        with self.assertNumQueries(3):
+            model = M2MRefModel.objects.create()
+            rel_model_1 = ReffedModel.objects.create()
+            rel_model_2 = ReffedModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_1)
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_2)
+            self.assertEqual(model.counter, 2)
+            self.assertEqual(model.counter_2, 2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 1)
+
+        with self.assertNumQueries(self.M2M_CLEAR_BASE_QUERY_COUNT +
+                                   2 * self.M2M_CLEAR_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.clear()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+    def test_m2m_forward_and_clear_unloaded(self):
+        """Testing RelationCounterField with forward ManyToManyField relation
+        and clearing all unloaded objects
+        """
+        with self.assertNumQueries(3):
+            model = M2MRefModel.objects.create()
+            rel_model_1 = ReffedModel.objects.create()
+            rel_model_2 = ReffedModel.objects.create()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_1)
+            self.assertEqual(model.counter, 1)
+            self.assertEqual(model.counter_2, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m.add(rel_model_2)
+            self.assertEqual(model.counter, 2)
+            self.assertEqual(model.counter_2, 2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 1)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 1)
+
+        # Get rid of these for now, so that the state will drop. This will
+        # ensure we're re-fetching on pre_clear.
+        rel_model_id_1 = rel_model_1.pk
+        rel_model_id_2 = rel_model_2.pk
+        del rel_model_1
+        del rel_model_2
+
+        with self.assertNumQueries(self.M2M_CLEAR_BASE_QUERY_COUNT):
+            model.m2m.clear()
+            self.assertEqual(model.counter, 0)
+            self.assertEqual(model.counter_2, 0)
+
+        with self.assertNumQueries(2):
+            rel_model_1 = ReffedModel.objects.get(
+                pk=rel_model_id_1)
+            rel_model_2 = ReffedModel.objects.get(
+                pk=rel_model_id_2)
+            self.assertEqual(rel_model_1.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_1.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter, 0)
+            self.assertEqual(rel_model_2.m2m_reffed_counter_2, 0)
+
+    #
+    # Reverse-relation ManyToManyField tests
+    #
+
+    def test_m2m_reverse_initialize(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and initialization
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+
+    def test_m2m_reverse_and_reinit(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and re-initialization
+        """
+        with self.assertNumQueries(2):
+            model = ReffedModel.objects.create()
+            rel_model = M2MRefModel.objects.create()
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model)
+
+        with self.assertNumQueries(self.REINIT_QUERY_COUNT):
+            model.m2m_reffed_counter = None
+            model.reinit_m2m_reffed_counter()
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+
+    def test_m2m_reverse_and_create(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and creating object
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+
+        with self.assertNumQueries(1 +
+                                   self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            rel_model_1 = model.m2m_reffed.create()
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+
+        with self.assertNumQueries(1 +
+                                   self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            rel_model_2 = model.m2m_reffed.create()
+            self.assertEqual(model.m2m_reffed_counter, 2)
+            self.assertEqual(model.m2m_reffed_counter_2, 2)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 1)
+            self.assertEqual(rel_model_2.counter_2, 1)
+
+    def test_m2m_reverse_and_add(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and adding object
+        """
+        with self.assertNumQueries(3):
+            model = ReffedModel.objects.create()
+            rel_model_1 = M2MRefModel.objects.create()
+            rel_model_2 = M2MRefModel.objects.create()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_1.counter, 0)
+            self.assertEqual(rel_model_1.counter_2, 0)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_1)
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_2)
+            self.assertEqual(model.m2m_reffed_counter, 2)
+            self.assertEqual(model.m2m_reffed_counter_2, 2)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 1)
+            self.assertEqual(rel_model_2.counter_2, 1)
+
+    def test_m2m_reverse_and_remove(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and removing object
+        """
+        with self.assertNumQueries(2):
+            model = ReffedModel.objects.create()
+            rel_model = M2MRefModel.objects.create()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model.counter, 0)
+            self.assertEqual(rel_model.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model)
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model.counter, 1)
+            self.assertEqual(rel_model.counter_2, 1)
+
+        with self.assertNumQueries(self.M2M_REMOVE_BASE_QUERY_COUNT +
+                                   self.M2M_REMOVE_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.remove(rel_model)
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model.counter, 0)
+            self.assertEqual(rel_model.counter_2, 0)
+
+    def test_m2m_reverse_and_clear(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and clearing all objects
+        """
+        with self.assertNumQueries(3):
+            model = ReffedModel.objects.create()
+            rel_model_1 = M2MRefModel.objects.create()
+            rel_model_2 = M2MRefModel.objects.create()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_1.counter, 0)
+            self.assertEqual(rel_model_1.counter_2, 0)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_1)
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_2)
+            self.assertEqual(model.m2m_reffed_counter, 2)
+            self.assertEqual(model.m2m_reffed_counter_2, 2)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 1)
+            self.assertEqual(rel_model_2.counter_2, 1)
+
+        with self.assertNumQueries(self.M2M_CLEAR_BASE_QUERY_COUNT +
+                                   2 * self.M2M_CLEAR_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.clear()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_1.counter, 0)
+            self.assertEqual(rel_model_1.counter_2, 0)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+    def test_m2m_reverse_and_clear_unloaded(self):
+        """Testing RelationCounterField with reverse ManyToManyField relation
+        and clearing all unloaded objects
+        """
+        with self.assertNumQueries(3):
+            model = ReffedModel.objects.create()
+            rel_model_1 = M2MRefModel.objects.create()
+            rel_model_2 = M2MRefModel.objects.create()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+            self.assertEqual(rel_model_1.counter, 0)
+            self.assertEqual(rel_model_1.counter_2, 0)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_1)
+            self.assertEqual(model.m2m_reffed_counter, 1)
+            self.assertEqual(model.m2m_reffed_counter_2, 1)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+        with self.assertNumQueries(self.M2M_ADD_BASE_QUERY_COUNT +
+                                   self.M2M_ADD_LOADED_ITEM_QUERY_COUNT):
+            model.m2m_reffed.add(rel_model_2)
+            self.assertEqual(model.m2m_reffed_counter, 2)
+            self.assertEqual(model.m2m_reffed_counter_2, 2)
+            self.assertEqual(rel_model_1.counter, 1)
+            self.assertEqual(rel_model_1.counter_2, 1)
+            self.assertEqual(rel_model_2.counter, 1)
+            self.assertEqual(rel_model_2.counter_2, 1)
+
+        # Get rid of these for now, so that the state will drop. This will
+        # ensure we're re-fetching on pre_clear.
+        rel_model_id_1 = rel_model_1.pk
+        rel_model_id_2 = rel_model_2.pk
+        del rel_model_1
+        del rel_model_2
+
+        with self.assertNumQueries(self.M2M_CLEAR_BASE_QUERY_COUNT):
+            model.m2m_reffed.clear()
+            self.assertEqual(model.m2m_reffed_counter, 0)
+            self.assertEqual(model.m2m_reffed_counter_2, 0)
+
+        with self.assertNumQueries(2):
+            rel_model_1 = M2MRefModel.objects.get(
+                pk=rel_model_id_1)
+            rel_model_2 = M2MRefModel.objects.get(
+                pk=rel_model_id_2)
+            self.assertEqual(rel_model_1.counter, 0)
+            self.assertEqual(rel_model_1.counter_2, 0)
+            self.assertEqual(rel_model_2.counter, 0)
+            self.assertEqual(rel_model_2.counter_2, 0)
+
+    #
+    # Forward-relation ForeignKey tests
+    #
+
+    def test_fkey_forward_initialize(self):
+        """Testing RelationCounterField with forward ForeignKey relation
+        and initialization disallowed
+        """
+        with self.assertNumQueries(0):
+            self.assertRaisesMessage(
+                ValueError,
+                "RelationCounterField cannot work with the forward "
+                "end of a ForeignKey ('key')",
+                lambda: BadKeyRefModel())
+
+    #
+    # Reverse-relation ForeignKey tests
+    #
+
+    def test_fkey_reverse_initialize(self):
+        """Testing RelationCounterField with reverse ForeignKey relation
+        and initialization
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+    def test_fkey_reverse_and_reinit(self):
+        """Testing RelationCounterField with reverse ForeignKey relation
+        and re-initialization
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            KeyRefModel.objects.create(key=model)
+
+        with self.assertNumQueries(self.REINIT_QUERY_COUNT):
+            model.reffed_key_counter = None
+            model.reinit_reffed_key_counter()
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+    def test_fkey_reverse_and_add(self):
+        """Testing RelationCounterField with reverse ForeignKey relation and
+        adding object
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 2)
+            self.assertEqual(model.reffed_key_counter_2, 2)
+
+    def test_fkey_reverse_and_add_unloaded_by_id(self):
+        """Testing RelationCounterField with reverse ForeignKey relation and
+        adding unloaded object by ID
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        model_id = model.pk
+        del model
+
+        with self.assertNumQueries(self.KEY_CREATE_UNLOADED_REL_QUERY_COUNT):
+            KeyRefModel.objects.create(key_id=model_id)
+
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.get(pk=model_id)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+    def test_fkey_reverse_and_delete(self):
+        """Testing RelationCounterField with reverse ForeignKey relation and
+        deleting object
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            rel_model = KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+        with self.assertNumQueries(self.KEY_REMOVE_ITEM_QUERY_COUNT):
+            rel_model.delete()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+    def test_fkey_reverse_and_save_existing(self):
+        """Testing RelationCounterField with reverse ForeignKey relation and
+        saving existing object doesn't modify counts
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            rel_model = KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+        with self.assertNumQueries(1):
+            rel_model.save()
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+    def test_fkey_reverse_delete_unloaded(self):
+        """Testing RelationCounterField with reverse ForeignKey relation
+        and deleting unloaded object
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            rel_model = KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+        model_id = model.pk
+        del model
+
+        with self.assertNumQueries(self.KEY_REMOVE_ITEM_QUERY_COUNT):
+            rel_model.delete()
+
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.get(pk=model_id)
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+    def test_fkey_reverse_and_delete_with_all_unloaded(self):
+        """Testing RelationCounterField with reverse ForeignKey relation and
+        deleting object with all instances unloaded
+        """
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.create()
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
+
+        with self.assertNumQueries(1 + self.REINIT_QUERY_COUNT):
+            rel_model = KeyRefModel.objects.create(key=model)
+            self.assertEqual(model.reffed_key_counter, 1)
+            self.assertEqual(model.reffed_key_counter_2, 1)
+
+        model_id = model.pk
+        del model
+        del rel_model
+
+        with self.assertNumQueries(self.KEY_REMOVE_ITEM_QUERY_COUNT):
+            KeyRefModel.objects.all().delete()
+
+        with self.assertNumQueries(1):
+            model = ReffedModel.objects.get(pk=model_id)
+            self.assertEqual(model.reffed_key_counter, 0)
+            self.assertEqual(model.reffed_key_counter_2, 0)
