We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Catch-all signals in mezzanine.generic disable fast deletion
Hi Steve,

Mezzanine's BaseGenericRelation connects its signals up without a sender with the following comment:

```
# For some unknown reason the signal won't be triggered
# if given a sender arg, particularly when running
# Cartridge with the field RichTextPage.keywords - so
# instead of specifying self.rel.to as the sender, we
# check for it inside the signal itself.
post_save.connect(self._related_items_changed)
post_delete.connect(self._related_items_changed)
```

Django has two paths for object deletion – a fast path which transforms the query into a `DELETE FROM`, and a slow one that retrieves the items and then deletes them. A condition of the fast path being used is that the model being deleted has no post_delete handler set up, so by listening for all post_delete signals mezzanine.generic effectively disables the fast path globally.

I've just run the tests with the sender specified on 1.4, 1.5 and 1.6 and everything is passing – if you can remember what wasn't working, or give any more info, I'd quite like to look into this further and see if we can't find another workaround.

As always thanks for your great work.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mezzanine/generic/fields.py`
Content:
```
1 from __future__ import division, unicode_literals
2 from future.builtins import str
3 
4 from copy import copy
5 
6 from django.conf import settings
7 from django.contrib.contenttypes.generic import GenericRelation
8 from django.core.exceptions import ImproperlyConfigured
9 from django.db.models import get_model, IntegerField, CharField, FloatField
10 from django.db.models.signals import post_save, post_delete
11 
12 
13 class BaseGenericRelation(GenericRelation):
14     """
15     Extends ``GenericRelation`` to:
16 
17     - Add a consistent default value for ``object_id_field`` and
18       check for a ``related_model`` attribute which can be defined
19       on subclasses as a default for the ``to`` argument.
20 
21     - Add one or more custom fields to the model that the relation
22       field is applied to, and then call a ``related_items_changed``
23       method each time related items are saved or deleted, so that a
24       calculated value can be stored against the custom fields since
25       aggregates aren't available for GenericRelation instances.
26 
27     """
28 
29     # Mapping of field names to model fields that will be added.
30     fields = {}
31 
32     def __init__(self, *args, **kwargs):
33         """
34         Set up some defaults and check for a ``related_model``
35         attribute for the ``to`` argument.
36         """
37         if kwargs.get("frozen_by_south", False):
38             raise Exception("""
39 
40     Your project contains migrations that include one of the fields
41     from mezzanine.generic in its Migration.model dict: possibly
42     KeywordsField, CommentsField or RatingField. These migratons no
43     longer work with the latest versions of Django and South, so you'll
44     need to fix them by hand. This is as simple as commenting out or
45     deleting the field from the Migration.model dict.
46     See http://bit.ly/1hecVsD for an example.
47 
48     """)
49 
50         kwargs.setdefault("object_id_field", "object_pk")
51         to = getattr(self, "related_model", None)
52         if to:
53             kwargs.setdefault("to", to)
54         super(BaseGenericRelation, self).__init__(*args, **kwargs)
55 
56     def contribute_to_class(self, cls, name):
57         """
58         Add each of the names and fields in the ``fields`` attribute
59         to the model the relationship field is applied to, and set up
60         the related item save and delete signals for calling
61         ``related_items_changed``.
62         """
63         for field in cls._meta.many_to_many:
64             if isinstance(field, self.__class__):
65                 e = "Multiple %s fields are not supported (%s.%s, %s.%s)" % (
66                     self.__class__.__name__, cls.__name__, cls.__name__,
67                     name, field.name)
68                 raise ImproperlyConfigured(e)
69         self.related_field_name = name
70         super(BaseGenericRelation, self).contribute_to_class(cls, name)
71         # Not applicable to abstract classes, and in fact will break.
72         if not cls._meta.abstract:
73             for (name_string, field) in self.fields.items():
74                 if "%s" in name_string:
75                     name_string = name_string % name
76                 # In Django 1.6, add_to_class will be called on a
77                 # parent model's field more than once, so
78                 # contribute_to_class needs to be idempotent. We
79                 # don't call get_all_field_names() which fill the app
80                 # cache get_fields_with_model() is safe.
81                 if name_string in [i.name for i, _ in
82                                    cls._meta.get_fields_with_model()]:
83                     continue
84                 if field.verbose_name is None:
85                     field.verbose_name = self.verbose_name
86                 cls.add_to_class(name_string, copy(field))
87             # Add a getter function to the model we can use to retrieve
88             # the field/manager by name.
89             getter_name = "get_%s_name" % self.__class__.__name__.lower()
90             cls.add_to_class(getter_name, lambda self: name)
91             # For some unknown reason the signal won't be triggered
92             # if given a sender arg, particularly when running
93             # Cartridge with the field RichTextPage.keywords - so
94             # instead of specifying self.rel.to as the sender, we
95             # check for it inside the signal itself.
96             post_save.connect(self._related_items_changed)
97             post_delete.connect(self._related_items_changed)
98 
99     def _related_items_changed(self, **kwargs):
100         """
101         Ensure that the given related item is actually for the model
102         this field applies to, and pass the instance to the real
103         ``related_items_changed`` handler.
104         """
105         # Manually check that the instance matches the relation,
106         # since we don't specify a sender for the signal.
107         try:
108             to = self.rel.to
109             if isinstance(to, str):
110                 to = get_model(*to.split(".", 1))
111             if not isinstance(kwargs["instance"], to):
112                 raise TypeError
113         except (TypeError, ValueError):
114             return
115         for_model = kwargs["instance"].content_type.model_class()
116         if issubclass(for_model, self.model):
117             instance_id = kwargs["instance"].object_pk
118             try:
119                 instance = for_model.objects.get(id=instance_id)
120             except self.model.DoesNotExist:
121                 # Instance itself was deleted - signals are irrelevant.
122                 return
123             if hasattr(instance, "get_content_model"):
124                 instance = instance.get_content_model()
125             related_manager = getattr(instance, self.related_field_name)
126             self.related_items_changed(instance, related_manager)
127 
128     def related_items_changed(self, instance, related_manager):
129         """
130         Can be implemented by subclasses - called whenever the
131         state of related items change, eg they're saved or deleted.
132         The instance for this field and the related manager for the
133         field are passed as arguments.
134         """
135         pass
136 
137     def value_from_object(self, obj):
138         """
139         Returns the value of this field in the given model instance.
140         Needed for Django 1.7: https://code.djangoproject.com/ticket/22552
141         """
142         return getattr(obj, self.attname).all()
143 
144 
145 class CommentsField(BaseGenericRelation):
146     """
147     Stores the number of comments against the
148     ``COMMENTS_FIELD_NAME_count`` field when a comment is saved or
149     deleted.
150     """
151 
152     related_model = "generic.ThreadedComment"
153     fields = {"%s_count": IntegerField(editable=False, default=0)}
154 
155     def related_items_changed(self, instance, related_manager):
156         """
157         Stores the number of comments. A custom ``count_filter``
158         queryset gets checked for, allowing managers to implement
159         custom count logic.
160         """
161         try:
162             count = related_manager.count_queryset()
163         except AttributeError:
164             count = related_manager.count()
165         count_field_name = list(self.fields.keys())[0] % \
166                            self.related_field_name
167         setattr(instance, count_field_name, count)
168         instance.save()
169 
170 
171 class KeywordsField(BaseGenericRelation):
172     """
173     Stores the keywords as a single string into the
174     ``KEYWORDS_FIELD_NAME_string`` field for convenient access when
175     searching.
176     """
177 
178     related_model = "generic.AssignedKeyword"
179     fields = {"%s_string": CharField(editable=False, blank=True,
180                                      max_length=500)}
181 
182     def __init__(self, *args, **kwargs):
183         """
184         Mark the field as editable so that it can be specified in
185         admin class fieldsets and pass validation, and also so that
186         it shows up in the admin form.
187         """
188         super(KeywordsField, self).__init__(*args, **kwargs)
189         self.editable = True
190 
191     def formfield(self, **kwargs):
192         """
193         Provide the custom form widget for the admin, since there
194         isn't a form field mapped to ``GenericRelation`` model fields.
195         """
196         from mezzanine.generic.forms import KeywordsWidget
197         kwargs["widget"] = KeywordsWidget
198         return super(KeywordsField, self).formfield(**kwargs)
199 
200     def save_form_data(self, instance, data):
201         """
202         The ``KeywordsWidget`` field will return data as a string of
203         comma separated IDs for the ``Keyword`` model - convert these
204         into actual ``AssignedKeyword`` instances. Also delete
205         ``Keyword`` instances if their last related ``AssignedKeyword``
206         instance is being removed.
207         """
208         from mezzanine.generic.models import AssignedKeyword, Keyword
209         related_manager = getattr(instance, self.name)
210         # Get a list of Keyword IDs being removed.
211         old_ids = [str(a.keyword_id) for a in related_manager.all()]
212         new_ids = data.split(",")
213         removed_ids = set(old_ids) - set(new_ids)
214         # Remove current AssignedKeyword instances.
215         related_manager.all().delete()
216         # Convert the data into AssignedKeyword instances.
217         if data:
218             data = [AssignedKeyword(keyword_id=i) for i in new_ids]
219         # Remove Keyword instances than no longer have a
220         # related AssignedKeyword instance.
221         existing = AssignedKeyword.objects.filter(keyword__id__in=removed_ids)
222         existing_ids = set([str(a.keyword_id) for a in existing])
223         unused_ids = removed_ids - existing_ids
224         Keyword.objects.filter(id__in=unused_ids).delete()
225         super(KeywordsField, self).save_form_data(instance, data)
226 
227     def contribute_to_class(self, cls, name):
228         """
229         Swap out any reference to ``KeywordsField`` with the
230         ``KEYWORDS_FIELD_string`` field in ``search_fields``.
231         """
232         super(KeywordsField, self).contribute_to_class(cls, name)
233         string_field_name = list(self.fields.keys())[0] % \
234                             self.related_field_name
235         if hasattr(cls, "search_fields") and name in cls.search_fields:
236             try:
237                 weight = cls.search_fields[name]
238             except TypeError:
239                 # search_fields is a sequence.
240                 index = cls.search_fields.index(name)
241                 search_fields_type = type(cls.search_fields)
242                 cls.search_fields = list(cls.search_fields)
243                 cls.search_fields[index] = string_field_name
244                 cls.search_fields = search_fields_type(cls.search_fields)
245             else:
246                 del cls.search_fields[name]
247                 cls.search_fields[string_field_name] = weight
248 
249     def related_items_changed(self, instance, related_manager):
250         """
251         Stores the keywords as a single string for searching.
252         """
253         assigned = related_manager.select_related("keyword")
254         keywords = " ".join([str(a.keyword) for a in assigned])
255         string_field_name = list(self.fields.keys())[0] % \
256                             self.related_field_name
257         if getattr(instance, string_field_name) != keywords:
258             setattr(instance, string_field_name, keywords)
259             instance.save()
260 
261 
262 class RatingField(BaseGenericRelation):
263     """
264     Stores the rating count and average against the
265     ``RATING_FIELD_NAME_count`` and ``RATING_FIELD_NAME_average``
266     fields when a rating is saved or deleted.
267     """
268 
269     related_model = "generic.Rating"
270     fields = {"%s_count": IntegerField(default=0, editable=False),
271               "%s_sum": IntegerField(default=0, editable=False),
272               "%s_average": FloatField(default=0, editable=False)}
273 
274     def related_items_changed(self, instance, related_manager):
275         """
276         Calculates and saves the average rating.
277         """
278         ratings = [r.value for r in related_manager.all()]
279         count = len(ratings)
280         _sum = sum(ratings)
281         average = _sum / count if count > 0 else 0
282         setattr(instance, "%s_count" % self.related_field_name, count)
283         setattr(instance, "%s_sum" % self.related_field_name, _sum)
284         setattr(instance, "%s_average" % self.related_field_name, average)
285         instance.save()
286 
287 
288 # South requires custom fields to be given "rules".
289 # See http://south.aeracode.org/docs/customfields.html
290 if "south" in settings.INSTALLED_APPS:
291     try:
292         from south.modelsinspector import add_introspection_rules
293         add_introspection_rules(rules=[((BaseGenericRelation,), [], {})],
294             patterns=["mezzanine\.generic\.fields\."])
295     except ImportError:
296         pass
297
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mezzanine/generic/fields.py b/mezzanine/generic/fields.py
--- a/mezzanine/generic/fields.py
+++ b/mezzanine/generic/fields.py
@@ -7,7 +7,57 @@
 from django.contrib.contenttypes.generic import GenericRelation
 from django.core.exceptions import ImproperlyConfigured
 from django.db.models import get_model, IntegerField, CharField, FloatField
-from django.db.models.signals import post_save, post_delete
+from django.db.models.signals import post_save, post_delete, class_prepared
+from django.utils import six
+
+
+class LazyModelOperations(object):
+    """
+    This class connects itself to Django's class_prepared signal. Pass a
+    function and a model or model name to its add() method, and the function
+    will be called with the model as its only parameter once the model has been
+    loaded. If the model is already loaded, the function is called immediately.
+
+    Adapted from code found at the top of django/db/models/fields/related.py.
+    """
+    def __init__(self):
+        self.pending_operations = {}
+        class_prepared.connect(self.signal_receiver)
+
+    @staticmethod
+    def model_key(model_or_name):
+        """
+        Returns an (app_label, model_name) tuple from a model or string.
+        """
+        if isinstance(model_or_name, six.string_types):
+            app_label, model_name = model_or_name.split(".")
+        else:
+            # it's actually a model class
+            app_label = model_or_name._meta.app_label
+            model_name = model_or_name._meta.object_name
+        return app_label, model_name
+
+    def add(self, function, model_or_name):
+        model_key = self.model_key(model_or_name)
+
+        # If the model is already loaded, pass it to the function immediately.
+        # Otherwise, delay execution until the class is prepared.
+        model = get_model(*model_key, seed_cache=False, only_installed=False)
+        if model:
+            function(model)
+        else:
+            self.pending_operations.setdefault(model_key, []).append(function)
+
+    def signal_receiver(self, sender, **_):
+        """
+        Receive class_prepared, and pass the freshly prepared model to each
+        function waiting for it.
+        """
+        key = (sender._meta.app_label, sender.__name__)
+        for function in self.pending_operations.pop(key, []):
+            function(sender)
+
+lazy_model_ops = LazyModelOperations()
 
 
 class BaseGenericRelation(GenericRelation):
@@ -93,8 +143,15 @@
             # Cartridge with the field RichTextPage.keywords - so
             # instead of specifying self.rel.to as the sender, we
             # check for it inside the signal itself.
-            post_save.connect(self._related_items_changed)
-            post_delete.connect(self._related_items_changed)
+
+            def connect_save(sender):
+                post_save.connect(self._related_items_changed, sender=sender)
+
+            def connect_delete(sender):
+                post_delete.connect(self._related_items_changed, sender=sender)
+
+            lazy_model_ops.add(connect_save, self.rel.to)
+            lazy_model_ops.add(connect_delete, self.rel.to)
 
     def _related_items_changed(self, **kwargs):
         """
@@ -102,16 +159,6 @@
         this field applies to, and pass the instance to the real
         ``related_items_changed`` handler.
         """
-        # Manually check that the instance matches the relation,
-        # since we don't specify a sender for the signal.
-        try:
-            to = self.rel.to
-            if isinstance(to, str):
-                to = get_model(*to.split(".", 1))
-            if not isinstance(kwargs["instance"], to):
-                raise TypeError
-        except (TypeError, ValueError):
-            return
         for_model = kwargs["instance"].content_type.model_class()
         if issubclass(for_model, self.model):
             instance_id = kwargs["instance"].object_pk

{"golden_diff": "diff --git a/mezzanine/generic/fields.py b/mezzanine/generic/fields.py\n--- a/mezzanine/generic/fields.py\n+++ b/mezzanine/generic/fields.py\n@@ -7,7 +7,57 @@\n from django.contrib.contenttypes.generic import GenericRelation\n from django.core.exceptions import ImproperlyConfigured\n from django.db.models import get_model, IntegerField, CharField, FloatField\n-from django.db.models.signals import post_save, post_delete\n+from django.db.models.signals import post_save, post_delete, class_prepared\n+from django.utils import six\n+\n+\n+class LazyModelOperations(object):\n+    \"\"\"\n+    This class connects itself to Django's class_prepared signal. Pass a\n+    function and a model or model name to its add() method, and the function\n+    will be called with the model as its only parameter once the model has been\n+    loaded. If the model is already loaded, the function is called immediately.\n+\n+    Adapted from code found at the top of django/db/models/fields/related.py.\n+    \"\"\"\n+    def __init__(self):\n+        self.pending_operations = {}\n+        class_prepared.connect(self.signal_receiver)\n+\n+    @staticmethod\n+    def model_key(model_or_name):\n+        \"\"\"\n+        Returns an (app_label, model_name) tuple from a model or string.\n+        \"\"\"\n+        if isinstance(model_or_name, six.string_types):\n+            app_label, model_name = model_or_name.split(\".\")\n+        else:\n+            # it's actually a model class\n+            app_label = model_or_name._meta.app_label\n+            model_name = model_or_name._meta.object_name\n+        return app_label, model_name\n+\n+    def add(self, function, model_or_name):\n+        model_key = self.model_key(model_or_name)\n+\n+        # If the model is already loaded, pass it to the function immediately.\n+        # Otherwise, delay execution until the class is prepared.\n+        model = get_model(*model_key, seed_cache=False, only_installed=False)\n+        if model:\n+            function(model)\n+        else:\n+            self.pending_operations.setdefault(model_key, []).append(function)\n+\n+    def signal_receiver(self, sender, **_):\n+        \"\"\"\n+        Receive class_prepared, and pass the freshly prepared model to each\n+        function waiting for it.\n+        \"\"\"\n+        key = (sender._meta.app_label, sender.__name__)\n+        for function in self.pending_operations.pop(key, []):\n+            function(sender)\n+\n+lazy_model_ops = LazyModelOperations()\n \n \n class BaseGenericRelation(GenericRelation):\n@@ -93,8 +143,15 @@\n             # Cartridge with the field RichTextPage.keywords - so\n             # instead of specifying self.rel.to as the sender, we\n             # check for it inside the signal itself.\n-            post_save.connect(self._related_items_changed)\n-            post_delete.connect(self._related_items_changed)\n+\n+            def connect_save(sender):\n+                post_save.connect(self._related_items_changed, sender=sender)\n+\n+            def connect_delete(sender):\n+                post_delete.connect(self._related_items_changed, sender=sender)\n+\n+            lazy_model_ops.add(connect_save, self.rel.to)\n+            lazy_model_ops.add(connect_delete, self.rel.to)\n \n     def _related_items_changed(self, **kwargs):\n         \"\"\"\n@@ -102,16 +159,6 @@\n         this field applies to, and pass the instance to the real\n         ``related_items_changed`` handler.\n         \"\"\"\n-        # Manually check that the instance matches the relation,\n-        # since we don't specify a sender for the signal.\n-        try:\n-            to = self.rel.to\n-            if isinstance(to, str):\n-                to = get_model(*to.split(\".\", 1))\n-            if not isinstance(kwargs[\"instance\"], to):\n-                raise TypeError\n-        except (TypeError, ValueError):\n-            return\n         for_model = kwargs[\"instance\"].content_type.model_class()\n         if issubclass(for_model, self.model):\n             instance_id = kwargs[\"instance\"].object_pk\n", "issue": "Catch-all signals in mezzanine.generic disable fast deletion\nHi Steve,\n\nMezzanine's BaseGenericRelation connects its signals up without a sender with the following comment:\n\n```\n# For some unknown reason the signal won't be triggered\n# if given a sender arg, particularly when running\n# Cartridge with the field RichTextPage.keywords - so\n# instead of specifying self.rel.to as the sender, we\n# check for it inside the signal itself.\npost_save.connect(self._related_items_changed)\npost_delete.connect(self._related_items_changed)\n```\n\nDjango has two paths for object deletion \u2013\u00a0a fast path which transforms the query into a `DELETE FROM`, and a slow one that retrieves the items and then deletes them. A condition of the fast path being used is that the model being deleted has no post_delete handler set up, so by listening for all post_delete signals mezzanine.generic effectively disables the fast path globally.\n\nI've just run the tests with the sender specified on 1.4, 1.5 and 1.6 and everything is passing \u2013\u00a0if you can remember what wasn't working, or give any more info, I'd quite like to look into this further and see if we can't find another workaround.\n\nAs always thanks for your great work.\n\n", "before_files": [{"content": "from __future__ import division, unicode_literals\nfrom future.builtins import str\n\nfrom copy import copy\n\nfrom django.conf import settings\nfrom django.contrib.contenttypes.generic import GenericRelation\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db.models import get_model, IntegerField, CharField, FloatField\nfrom django.db.models.signals import post_save, post_delete\n\n\nclass BaseGenericRelation(GenericRelation):\n    \"\"\"\n    Extends ``GenericRelation`` to:\n\n    - Add a consistent default value for ``object_id_field`` and\n      check for a ``related_model`` attribute which can be defined\n      on subclasses as a default for the ``to`` argument.\n\n    - Add one or more custom fields to the model that the relation\n      field is applied to, and then call a ``related_items_changed``\n      method each time related items are saved or deleted, so that a\n      calculated value can be stored against the custom fields since\n      aggregates aren't available for GenericRelation instances.\n\n    \"\"\"\n\n    # Mapping of field names to model fields that will be added.\n    fields = {}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Set up some defaults and check for a ``related_model``\n        attribute for the ``to`` argument.\n        \"\"\"\n        if kwargs.get(\"frozen_by_south\", False):\n            raise Exception(\"\"\"\n\n    Your project contains migrations that include one of the fields\n    from mezzanine.generic in its Migration.model dict: possibly\n    KeywordsField, CommentsField or RatingField. These migratons no\n    longer work with the latest versions of Django and South, so you'll\n    need to fix them by hand. This is as simple as commenting out or\n    deleting the field from the Migration.model dict.\n    See http://bit.ly/1hecVsD for an example.\n\n    \"\"\")\n\n        kwargs.setdefault(\"object_id_field\", \"object_pk\")\n        to = getattr(self, \"related_model\", None)\n        if to:\n            kwargs.setdefault(\"to\", to)\n        super(BaseGenericRelation, self).__init__(*args, **kwargs)\n\n    def contribute_to_class(self, cls, name):\n        \"\"\"\n        Add each of the names and fields in the ``fields`` attribute\n        to the model the relationship field is applied to, and set up\n        the related item save and delete signals for calling\n        ``related_items_changed``.\n        \"\"\"\n        for field in cls._meta.many_to_many:\n            if isinstance(field, self.__class__):\n                e = \"Multiple %s fields are not supported (%s.%s, %s.%s)\" % (\n                    self.__class__.__name__, cls.__name__, cls.__name__,\n                    name, field.name)\n                raise ImproperlyConfigured(e)\n        self.related_field_name = name\n        super(BaseGenericRelation, self).contribute_to_class(cls, name)\n        # Not applicable to abstract classes, and in fact will break.\n        if not cls._meta.abstract:\n            for (name_string, field) in self.fields.items():\n                if \"%s\" in name_string:\n                    name_string = name_string % name\n                # In Django 1.6, add_to_class will be called on a\n                # parent model's field more than once, so\n                # contribute_to_class needs to be idempotent. We\n                # don't call get_all_field_names() which fill the app\n                # cache get_fields_with_model() is safe.\n                if name_string in [i.name for i, _ in\n                                   cls._meta.get_fields_with_model()]:\n                    continue\n                if field.verbose_name is None:\n                    field.verbose_name = self.verbose_name\n                cls.add_to_class(name_string, copy(field))\n            # Add a getter function to the model we can use to retrieve\n            # the field/manager by name.\n            getter_name = \"get_%s_name\" % self.__class__.__name__.lower()\n            cls.add_to_class(getter_name, lambda self: name)\n            # For some unknown reason the signal won't be triggered\n            # if given a sender arg, particularly when running\n            # Cartridge with the field RichTextPage.keywords - so\n            # instead of specifying self.rel.to as the sender, we\n            # check for it inside the signal itself.\n            post_save.connect(self._related_items_changed)\n            post_delete.connect(self._related_items_changed)\n\n    def _related_items_changed(self, **kwargs):\n        \"\"\"\n        Ensure that the given related item is actually for the model\n        this field applies to, and pass the instance to the real\n        ``related_items_changed`` handler.\n        \"\"\"\n        # Manually check that the instance matches the relation,\n        # since we don't specify a sender for the signal.\n        try:\n            to = self.rel.to\n            if isinstance(to, str):\n                to = get_model(*to.split(\".\", 1))\n            if not isinstance(kwargs[\"instance\"], to):\n                raise TypeError\n        except (TypeError, ValueError):\n            return\n        for_model = kwargs[\"instance\"].content_type.model_class()\n        if issubclass(for_model, self.model):\n            instance_id = kwargs[\"instance\"].object_pk\n            try:\n                instance = for_model.objects.get(id=instance_id)\n            except self.model.DoesNotExist:\n                # Instance itself was deleted - signals are irrelevant.\n                return\n            if hasattr(instance, \"get_content_model\"):\n                instance = instance.get_content_model()\n            related_manager = getattr(instance, self.related_field_name)\n            self.related_items_changed(instance, related_manager)\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Can be implemented by subclasses - called whenever the\n        state of related items change, eg they're saved or deleted.\n        The instance for this field and the related manager for the\n        field are passed as arguments.\n        \"\"\"\n        pass\n\n    def value_from_object(self, obj):\n        \"\"\"\n        Returns the value of this field in the given model instance.\n        Needed for Django 1.7: https://code.djangoproject.com/ticket/22552\n        \"\"\"\n        return getattr(obj, self.attname).all()\n\n\nclass CommentsField(BaseGenericRelation):\n    \"\"\"\n    Stores the number of comments against the\n    ``COMMENTS_FIELD_NAME_count`` field when a comment is saved or\n    deleted.\n    \"\"\"\n\n    related_model = \"generic.ThreadedComment\"\n    fields = {\"%s_count\": IntegerField(editable=False, default=0)}\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Stores the number of comments. A custom ``count_filter``\n        queryset gets checked for, allowing managers to implement\n        custom count logic.\n        \"\"\"\n        try:\n            count = related_manager.count_queryset()\n        except AttributeError:\n            count = related_manager.count()\n        count_field_name = list(self.fields.keys())[0] % \\\n                           self.related_field_name\n        setattr(instance, count_field_name, count)\n        instance.save()\n\n\nclass KeywordsField(BaseGenericRelation):\n    \"\"\"\n    Stores the keywords as a single string into the\n    ``KEYWORDS_FIELD_NAME_string`` field for convenient access when\n    searching.\n    \"\"\"\n\n    related_model = \"generic.AssignedKeyword\"\n    fields = {\"%s_string\": CharField(editable=False, blank=True,\n                                     max_length=500)}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Mark the field as editable so that it can be specified in\n        admin class fieldsets and pass validation, and also so that\n        it shows up in the admin form.\n        \"\"\"\n        super(KeywordsField, self).__init__(*args, **kwargs)\n        self.editable = True\n\n    def formfield(self, **kwargs):\n        \"\"\"\n        Provide the custom form widget for the admin, since there\n        isn't a form field mapped to ``GenericRelation`` model fields.\n        \"\"\"\n        from mezzanine.generic.forms import KeywordsWidget\n        kwargs[\"widget\"] = KeywordsWidget\n        return super(KeywordsField, self).formfield(**kwargs)\n\n    def save_form_data(self, instance, data):\n        \"\"\"\n        The ``KeywordsWidget`` field will return data as a string of\n        comma separated IDs for the ``Keyword`` model - convert these\n        into actual ``AssignedKeyword`` instances. Also delete\n        ``Keyword`` instances if their last related ``AssignedKeyword``\n        instance is being removed.\n        \"\"\"\n        from mezzanine.generic.models import AssignedKeyword, Keyword\n        related_manager = getattr(instance, self.name)\n        # Get a list of Keyword IDs being removed.\n        old_ids = [str(a.keyword_id) for a in related_manager.all()]\n        new_ids = data.split(\",\")\n        removed_ids = set(old_ids) - set(new_ids)\n        # Remove current AssignedKeyword instances.\n        related_manager.all().delete()\n        # Convert the data into AssignedKeyword instances.\n        if data:\n            data = [AssignedKeyword(keyword_id=i) for i in new_ids]\n        # Remove Keyword instances than no longer have a\n        # related AssignedKeyword instance.\n        existing = AssignedKeyword.objects.filter(keyword__id__in=removed_ids)\n        existing_ids = set([str(a.keyword_id) for a in existing])\n        unused_ids = removed_ids - existing_ids\n        Keyword.objects.filter(id__in=unused_ids).delete()\n        super(KeywordsField, self).save_form_data(instance, data)\n\n    def contribute_to_class(self, cls, name):\n        \"\"\"\n        Swap out any reference to ``KeywordsField`` with the\n        ``KEYWORDS_FIELD_string`` field in ``search_fields``.\n        \"\"\"\n        super(KeywordsField, self).contribute_to_class(cls, name)\n        string_field_name = list(self.fields.keys())[0] % \\\n                            self.related_field_name\n        if hasattr(cls, \"search_fields\") and name in cls.search_fields:\n            try:\n                weight = cls.search_fields[name]\n            except TypeError:\n                # search_fields is a sequence.\n                index = cls.search_fields.index(name)\n                search_fields_type = type(cls.search_fields)\n                cls.search_fields = list(cls.search_fields)\n                cls.search_fields[index] = string_field_name\n                cls.search_fields = search_fields_type(cls.search_fields)\n            else:\n                del cls.search_fields[name]\n                cls.search_fields[string_field_name] = weight\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Stores the keywords as a single string for searching.\n        \"\"\"\n        assigned = related_manager.select_related(\"keyword\")\n        keywords = \" \".join([str(a.keyword) for a in assigned])\n        string_field_name = list(self.fields.keys())[0] % \\\n                            self.related_field_name\n        if getattr(instance, string_field_name) != keywords:\n            setattr(instance, string_field_name, keywords)\n            instance.save()\n\n\nclass RatingField(BaseGenericRelation):\n    \"\"\"\n    Stores the rating count and average against the\n    ``RATING_FIELD_NAME_count`` and ``RATING_FIELD_NAME_average``\n    fields when a rating is saved or deleted.\n    \"\"\"\n\n    related_model = \"generic.Rating\"\n    fields = {\"%s_count\": IntegerField(default=0, editable=False),\n              \"%s_sum\": IntegerField(default=0, editable=False),\n              \"%s_average\": FloatField(default=0, editable=False)}\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Calculates and saves the average rating.\n        \"\"\"\n        ratings = [r.value for r in related_manager.all()]\n        count = len(ratings)\n        _sum = sum(ratings)\n        average = _sum / count if count > 0 else 0\n        setattr(instance, \"%s_count\" % self.related_field_name, count)\n        setattr(instance, \"%s_sum\" % self.related_field_name, _sum)\n        setattr(instance, \"%s_average\" % self.related_field_name, average)\n        instance.save()\n\n\n# South requires custom fields to be given \"rules\".\n# See http://south.aeracode.org/docs/customfields.html\nif \"south\" in settings.INSTALLED_APPS:\n    try:\n        from south.modelsinspector import add_introspection_rules\n        add_introspection_rules(rules=[((BaseGenericRelation,), [], {})],\n            patterns=[\"mezzanine\\.generic\\.fields\\.\"])\n    except ImportError:\n        pass\n", "path": "mezzanine/generic/fields.py"}], "after_files": [{"content": "from __future__ import division, unicode_literals\nfrom future.builtins import str\n\nfrom copy import copy\n\nfrom django.conf import settings\nfrom django.contrib.contenttypes.generic import GenericRelation\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.db.models import get_model, IntegerField, CharField, FloatField\nfrom django.db.models.signals import post_save, post_delete, class_prepared\nfrom django.utils import six\n\n\nclass LazyModelOperations(object):\n    \"\"\"\n    This class connects itself to Django's class_prepared signal. Pass a\n    function and a model or model name to its add() method, and the function\n    will be called with the model as its only parameter once the model has been\n    loaded. If the model is already loaded, the function is called immediately.\n\n    Adapted from code found at the top of django/db/models/fields/related.py.\n    \"\"\"\n    def __init__(self):\n        self.pending_operations = {}\n        class_prepared.connect(self.signal_receiver)\n\n    @staticmethod\n    def model_key(model_or_name):\n        \"\"\"\n        Returns an (app_label, model_name) tuple from a model or string.\n        \"\"\"\n        if isinstance(model_or_name, six.string_types):\n            app_label, model_name = model_or_name.split(\".\")\n        else:\n            # it's actually a model class\n            app_label = model_or_name._meta.app_label\n            model_name = model_or_name._meta.object_name\n        return app_label, model_name\n\n    def add(self, function, model_or_name):\n        model_key = self.model_key(model_or_name)\n\n        # If the model is already loaded, pass it to the function immediately.\n        # Otherwise, delay execution until the class is prepared.\n        model = get_model(*model_key, seed_cache=False, only_installed=False)\n        if model:\n            function(model)\n        else:\n            self.pending_operations.setdefault(model_key, []).append(function)\n\n    def signal_receiver(self, sender, **_):\n        \"\"\"\n        Receive class_prepared, and pass the freshly prepared model to each\n        function waiting for it.\n        \"\"\"\n        key = (sender._meta.app_label, sender.__name__)\n        for function in self.pending_operations.pop(key, []):\n            function(sender)\n\nlazy_model_ops = LazyModelOperations()\n\n\nclass BaseGenericRelation(GenericRelation):\n    \"\"\"\n    Extends ``GenericRelation`` to:\n\n    - Add a consistent default value for ``object_id_field`` and\n      check for a ``related_model`` attribute which can be defined\n      on subclasses as a default for the ``to`` argument.\n\n    - Add one or more custom fields to the model that the relation\n      field is applied to, and then call a ``related_items_changed``\n      method each time related items are saved or deleted, so that a\n      calculated value can be stored against the custom fields since\n      aggregates aren't available for GenericRelation instances.\n\n    \"\"\"\n\n    # Mapping of field names to model fields that will be added.\n    fields = {}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Set up some defaults and check for a ``related_model``\n        attribute for the ``to`` argument.\n        \"\"\"\n        if kwargs.get(\"frozen_by_south\", False):\n            raise Exception(\"\"\"\n\n    Your project contains migrations that include one of the fields\n    from mezzanine.generic in its Migration.model dict: possibly\n    KeywordsField, CommentsField or RatingField. These migratons no\n    longer work with the latest versions of Django and South, so you'll\n    need to fix them by hand. This is as simple as commenting out or\n    deleting the field from the Migration.model dict.\n    See http://bit.ly/1hecVsD for an example.\n\n    \"\"\")\n\n        kwargs.setdefault(\"object_id_field\", \"object_pk\")\n        to = getattr(self, \"related_model\", None)\n        if to:\n            kwargs.setdefault(\"to\", to)\n        super(BaseGenericRelation, self).__init__(*args, **kwargs)\n\n    def contribute_to_class(self, cls, name):\n        \"\"\"\n        Add each of the names and fields in the ``fields`` attribute\n        to the model the relationship field is applied to, and set up\n        the related item save and delete signals for calling\n        ``related_items_changed``.\n        \"\"\"\n        for field in cls._meta.many_to_many:\n            if isinstance(field, self.__class__):\n                e = \"Multiple %s fields are not supported (%s.%s, %s.%s)\" % (\n                    self.__class__.__name__, cls.__name__, cls.__name__,\n                    name, field.name)\n                raise ImproperlyConfigured(e)\n        self.related_field_name = name\n        super(BaseGenericRelation, self).contribute_to_class(cls, name)\n        # Not applicable to abstract classes, and in fact will break.\n        if not cls._meta.abstract:\n            for (name_string, field) in self.fields.items():\n                if \"%s\" in name_string:\n                    name_string = name_string % name\n                # In Django 1.6, add_to_class will be called on a\n                # parent model's field more than once, so\n                # contribute_to_class needs to be idempotent. We\n                # don't call get_all_field_names() which fill the app\n                # cache get_fields_with_model() is safe.\n                if name_string in [i.name for i, _ in\n                                   cls._meta.get_fields_with_model()]:\n                    continue\n                if field.verbose_name is None:\n                    field.verbose_name = self.verbose_name\n                cls.add_to_class(name_string, copy(field))\n            # Add a getter function to the model we can use to retrieve\n            # the field/manager by name.\n            getter_name = \"get_%s_name\" % self.__class__.__name__.lower()\n            cls.add_to_class(getter_name, lambda self: name)\n            # For some unknown reason the signal won't be triggered\n            # if given a sender arg, particularly when running\n            # Cartridge with the field RichTextPage.keywords - so\n            # instead of specifying self.rel.to as the sender, we\n            # check for it inside the signal itself.\n\n            def connect_save(sender):\n                post_save.connect(self._related_items_changed, sender=sender)\n\n            def connect_delete(sender):\n                post_delete.connect(self._related_items_changed, sender=sender)\n\n            lazy_model_ops.add(connect_save, self.rel.to)\n            lazy_model_ops.add(connect_delete, self.rel.to)\n\n    def _related_items_changed(self, **kwargs):\n        \"\"\"\n        Ensure that the given related item is actually for the model\n        this field applies to, and pass the instance to the real\n        ``related_items_changed`` handler.\n        \"\"\"\n        for_model = kwargs[\"instance\"].content_type.model_class()\n        if issubclass(for_model, self.model):\n            instance_id = kwargs[\"instance\"].object_pk\n            try:\n                instance = for_model.objects.get(id=instance_id)\n            except self.model.DoesNotExist:\n                # Instance itself was deleted - signals are irrelevant.\n                return\n            if hasattr(instance, \"get_content_model\"):\n                instance = instance.get_content_model()\n            related_manager = getattr(instance, self.related_field_name)\n            self.related_items_changed(instance, related_manager)\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Can be implemented by subclasses - called whenever the\n        state of related items change, eg they're saved or deleted.\n        The instance for this field and the related manager for the\n        field are passed as arguments.\n        \"\"\"\n        pass\n\n    def value_from_object(self, obj):\n        \"\"\"\n        Returns the value of this field in the given model instance.\n        Needed for Django 1.7: https://code.djangoproject.com/ticket/22552\n        \"\"\"\n        return getattr(obj, self.attname).all()\n\n\nclass CommentsField(BaseGenericRelation):\n    \"\"\"\n    Stores the number of comments against the\n    ``COMMENTS_FIELD_NAME_count`` field when a comment is saved or\n    deleted.\n    \"\"\"\n\n    related_model = \"generic.ThreadedComment\"\n    fields = {\"%s_count\": IntegerField(editable=False, default=0)}\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Stores the number of comments. A custom ``count_filter``\n        queryset gets checked for, allowing managers to implement\n        custom count logic.\n        \"\"\"\n        try:\n            count = related_manager.count_queryset()\n        except AttributeError:\n            count = related_manager.count()\n        count_field_name = list(self.fields.keys())[0] % \\\n                           self.related_field_name\n        setattr(instance, count_field_name, count)\n        instance.save()\n\n\nclass KeywordsField(BaseGenericRelation):\n    \"\"\"\n    Stores the keywords as a single string into the\n    ``KEYWORDS_FIELD_NAME_string`` field for convenient access when\n    searching.\n    \"\"\"\n\n    related_model = \"generic.AssignedKeyword\"\n    fields = {\"%s_string\": CharField(editable=False, blank=True,\n                                     max_length=500)}\n\n    def __init__(self, *args, **kwargs):\n        \"\"\"\n        Mark the field as editable so that it can be specified in\n        admin class fieldsets and pass validation, and also so that\n        it shows up in the admin form.\n        \"\"\"\n        super(KeywordsField, self).__init__(*args, **kwargs)\n        self.editable = True\n\n    def formfield(self, **kwargs):\n        \"\"\"\n        Provide the custom form widget for the admin, since there\n        isn't a form field mapped to ``GenericRelation`` model fields.\n        \"\"\"\n        from mezzanine.generic.forms import KeywordsWidget\n        kwargs[\"widget\"] = KeywordsWidget\n        return super(KeywordsField, self).formfield(**kwargs)\n\n    def save_form_data(self, instance, data):\n        \"\"\"\n        The ``KeywordsWidget`` field will return data as a string of\n        comma separated IDs for the ``Keyword`` model - convert these\n        into actual ``AssignedKeyword`` instances. Also delete\n        ``Keyword`` instances if their last related ``AssignedKeyword``\n        instance is being removed.\n        \"\"\"\n        from mezzanine.generic.models import AssignedKeyword, Keyword\n        related_manager = getattr(instance, self.name)\n        # Get a list of Keyword IDs being removed.\n        old_ids = [str(a.keyword_id) for a in related_manager.all()]\n        new_ids = data.split(\",\")\n        removed_ids = set(old_ids) - set(new_ids)\n        # Remove current AssignedKeyword instances.\n        related_manager.all().delete()\n        # Convert the data into AssignedKeyword instances.\n        if data:\n            data = [AssignedKeyword(keyword_id=i) for i in new_ids]\n        # Remove Keyword instances than no longer have a\n        # related AssignedKeyword instance.\n        existing = AssignedKeyword.objects.filter(keyword__id__in=removed_ids)\n        existing_ids = set([str(a.keyword_id) for a in existing])\n        unused_ids = removed_ids - existing_ids\n        Keyword.objects.filter(id__in=unused_ids).delete()\n        super(KeywordsField, self).save_form_data(instance, data)\n\n    def contribute_to_class(self, cls, name):\n        \"\"\"\n        Swap out any reference to ``KeywordsField`` with the\n        ``KEYWORDS_FIELD_string`` field in ``search_fields``.\n        \"\"\"\n        super(KeywordsField, self).contribute_to_class(cls, name)\n        string_field_name = list(self.fields.keys())[0] % \\\n                            self.related_field_name\n        if hasattr(cls, \"search_fields\") and name in cls.search_fields:\n            try:\n                weight = cls.search_fields[name]\n            except TypeError:\n                # search_fields is a sequence.\n                index = cls.search_fields.index(name)\n                search_fields_type = type(cls.search_fields)\n                cls.search_fields = list(cls.search_fields)\n                cls.search_fields[index] = string_field_name\n                cls.search_fields = search_fields_type(cls.search_fields)\n            else:\n                del cls.search_fields[name]\n                cls.search_fields[string_field_name] = weight\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Stores the keywords as a single string for searching.\n        \"\"\"\n        assigned = related_manager.select_related(\"keyword\")\n        keywords = \" \".join([str(a.keyword) for a in assigned])\n        string_field_name = list(self.fields.keys())[0] % \\\n                            self.related_field_name\n        if getattr(instance, string_field_name) != keywords:\n            setattr(instance, string_field_name, keywords)\n            instance.save()\n\n\nclass RatingField(BaseGenericRelation):\n    \"\"\"\n    Stores the rating count and average against the\n    ``RATING_FIELD_NAME_count`` and ``RATING_FIELD_NAME_average``\n    fields when a rating is saved or deleted.\n    \"\"\"\n\n    related_model = \"generic.Rating\"\n    fields = {\"%s_count\": IntegerField(default=0, editable=False),\n              \"%s_sum\": IntegerField(default=0, editable=False),\n              \"%s_average\": FloatField(default=0, editable=False)}\n\n    def related_items_changed(self, instance, related_manager):\n        \"\"\"\n        Calculates and saves the average rating.\n        \"\"\"\n        ratings = [r.value for r in related_manager.all()]\n        count = len(ratings)\n        _sum = sum(ratings)\n        average = _sum / count if count > 0 else 0\n        setattr(instance, \"%s_count\" % self.related_field_name, count)\n        setattr(instance, \"%s_sum\" % self.related_field_name, _sum)\n        setattr(instance, \"%s_average\" % self.related_field_name, average)\n        instance.save()\n\n\n# South requires custom fields to be given \"rules\".\n# See http://south.aeracode.org/docs/customfields.html\nif \"south\" in settings.INSTALLED_APPS:\n    try:\n        from south.modelsinspector import add_introspection_rules\n        add_introspection_rules(rules=[((BaseGenericRelation,), [], {})],\n            patterns=[\"mezzanine\\.generic\\.fields\\.\"])\n    except ImportError:\n        pass\n", "path": "mezzanine/generic/fields.py"}]}