Skip to content

Commit

Permalink
Fixes exception when there are signals connected to abstract models.
Browse files Browse the repository at this point in the history
  • Loading branch information
emorozov committed Jan 13, 2023
1 parent 9cc676b commit 8f3007a
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
21 changes: 18 additions & 3 deletions django_extensions/management/commands/list_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import weakref
from collections import defaultdict

from django.apps import apps
from django.core.management.base import BaseCommand
from django.db.models import Model
from django.db.models.signals import (
ModelSignal, pre_init, post_init, pre_save, post_save, pre_delete,
post_delete, m2m_changed, pre_migrate, post_migrate
Expand All @@ -31,12 +31,27 @@
}


def get_all_models() -> set[Model]:
"""
Returns set of all models defined in all apps.
This implementation is required because apps.get_models() is an internal API and
doesn't return abstract models.
"""
result = set()
generation = {Model}
while generation:
generation = {sc for c in generation for sc in c.__subclasses__()}
result.update(generation)

return result


class Command(BaseCommand):
help = 'List all signals by model and signal type'

def handle(self, *args, **options):
all_models = apps.get_models(include_auto_created=True, include_swapped=True)
model_lookup = {id(m): m for m in all_models}
model_lookup = {id(m): m for m in get_all_models()}

signals = [obj for obj in gc.get_objects() if isinstance(obj, ModelSignal)]
models = defaultdict(lambda: defaultdict(list))
Expand Down
17 changes: 14 additions & 3 deletions tests/management/commands/test_list_signals.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
# -*- coding: utf-8 -*-
import re
from io import StringIO

from django.db.models.signals import post_delete
from django.test import TestCase
from django.core.management import call_command

from tests.testapp.models import AbstractInheritanceTestModelParent


def delete_dummy_handler(sender, instance, **kwargs):
pass


class ListSignalsTests(TestCase):
"""Tests for list_signals command."""

def setUp(self):
self.out = StringIO()

post_delete.connect(delete_dummy_handler, sender=AbstractInheritanceTestModelParent)

def test_should_print_all_signals(self):
expected_result = '''django.contrib.sites.models.Site (site)
expected_result = """django.contrib.sites.models.Site (site)
pre_delete
django.contrib.sites.models.clear_site_cache #
pre_save
django.contrib.sites.models.clear_site_cache #
tests.testapp.models.AbstractInheritanceTestModelParent (abstract inheritance test model parent)
post_delete
tests.management.commands.test_list_signals.delete_dummy_handler #
tests.testapp.models.HasOwnerModel (has owner model)
pre_save
tests.testapp.models.dummy_handler #
'''
"""

call_command('list_signals', stdout=self.out)

Expand Down

0 comments on commit 8f3007a

Please sign in to comment.