From f8dad1744c37dc1717a94c6d652d4d98d2c5d0f2 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 15:51:51 -0500 Subject: [PATCH 1/8] #3892: Convert CABLE_TERMINATION_TYPES to a Q object --- netbox/dcim/api/serializers.py | 4 ++-- netbox/dcim/constants.py | 21 ++++++++++++---- netbox/dcim/forms.py | 8 ++----- .../0090_cable_termination_models.py | 24 +++++++++++++++++++ netbox/dcim/models/__init__.py | 4 ++-- netbox/dcim/tests/test_api.py | 2 +- 6 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 netbox/dcim/migrations/0090_cable_termination_models.py diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 664eb88f1..f0382a3f5 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -609,10 +609,10 @@ class InventoryItemSerializer(TaggitSerializer, ValidatedModelSerializer): class CableSerializer(ValidatedModelSerializer): termination_a_type = ContentTypeField( - queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES) + queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS) ) termination_b_type = ContentTypeField( - queryset=ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES) + queryset=ContentType.objects.filter(CABLE_TERMINATION_MODELS) ) termination_a = serializers.SerializerMethodField(read_only=True) termination_b = serializers.SerializerMethodField(read_only=True) diff --git a/netbox/dcim/constants.py b/netbox/dcim/constants.py index 2a4e2e88e..3a6f8e5e9 100644 --- a/netbox/dcim/constants.py +++ b/netbox/dcim/constants.py @@ -1,3 +1,5 @@ +from django.db.models import Q + from .choices import InterfaceTypeChoices @@ -43,10 +45,21 @@ CONNECTION_STATUS_CHOICES = [ ] # Cable endpoint types -CABLE_TERMINATION_TYPES = [ - 'consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', 'rearport', - 'circuittermination', 'powerfeed', -] +CABLE_TERMINATION_MODELS = Q( + Q(app_label='circuits', model__in=( + 'circuittermination', + )) | + Q(app_label='dcim', model__in=( + 'consoleport', + 'consoleserverport', + 'frontport', + 'interface', + 'powerfeed', + 'poweroutlet', + 'powerport', + 'rearport', + )) +) COMPATIBLE_TERMINATION_TYPES = { 'consoleport': ['consoleserverport', 'frontport', 'rearport'], diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 2f2abaedf..45ed5e136 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -3374,9 +3374,7 @@ class CableCSVForm(forms.ModelForm): ) side_a_type = forms.ModelChoiceField( queryset=ContentType.objects.all(), - limit_choices_to={ - 'model__in': CABLE_TERMINATION_TYPES, - }, + limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', help_text='Side A type' ) @@ -3395,9 +3393,7 @@ class CableCSVForm(forms.ModelForm): ) side_b_type = forms.ModelChoiceField( queryset=ContentType.objects.all(), - limit_choices_to={ - 'model__in': CABLE_TERMINATION_TYPES, - }, + limit_choices_to=CABLE_TERMINATION_MODELS, to_field_name='model', help_text='Side B type' ) diff --git a/netbox/dcim/migrations/0090_cable_termination_models.py b/netbox/dcim/migrations/0090_cable_termination_models.py new file mode 100644 index 000000000..b5f240f3e --- /dev/null +++ b/netbox/dcim/migrations/0090_cable_termination_models.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.8 on 2020-01-15 20:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0089_deterministic_ordering'), + ] + + operations = [ + migrations.AlterField( + model_name='cable', + name='termination_a_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + migrations.AlterField( + model_name='cable', + name='termination_b_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + ] diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 8d066e921..46fd3a1b9 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -1939,7 +1939,7 @@ class Cable(ChangeLoggedModel): """ termination_a_type = models.ForeignKey( to=ContentType, - limit_choices_to={'model__in': CABLE_TERMINATION_TYPES}, + limit_choices_to=CABLE_TERMINATION_MODELS, on_delete=models.PROTECT, related_name='+' ) @@ -1950,7 +1950,7 @@ class Cable(ChangeLoggedModel): ) termination_b_type = models.ForeignKey( to=ContentType, - limit_choices_to={'model__in': CABLE_TERMINATION_TYPES}, + limit_choices_to=CABLE_TERMINATION_MODELS, on_delete=models.PROTECT, related_name='+' ) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index dd0a6511f..13d3a48f2 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -30,7 +30,7 @@ class ChoicesTest(APITestCase): # Cable self.assertEqual(choices_to_dict(response.data.get('cable:length_unit')), CableLengthUnitChoices.as_dict()) self.assertEqual(choices_to_dict(response.data.get('cable:status')), CableStatusChoices.as_dict()) - content_types = ContentType.objects.filter(model__in=CABLE_TERMINATION_TYPES) + content_types = ContentType.objects.filter(CABLE_TERMINATION_MODELS) cable_termination_choices = { "{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types } From 9c4ab79beae3968dbf91de388eaf9bc44cf7a3f8 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:00:54 -0500 Subject: [PATCH 2/8] #3892: Convert CUSTOMFIELD_MODELS to a Q object --- netbox/extras/constants.py | 53 ++++++++++++------- netbox/extras/migrations/0022_custom_links.py | 2 +- ..._links_squashed_0034_configcontext_tags.py | 2 +- .../0036_contenttype_filters_to_q_objects.py | 18 +++++++ netbox/extras/models.py | 6 +-- 5 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 04d065da7..7d4b32eb9 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -1,23 +1,38 @@ +from django.db.models import Q + + # Models which support custom fields -CUSTOMFIELD_MODELS = [ - 'circuits.circuit', - 'circuits.provider', - 'dcim.device', - 'dcim.devicetype', - 'dcim.powerfeed', - 'dcim.rack', - 'dcim.site', - 'ipam.aggregate', - 'ipam.ipaddress', - 'ipam.prefix', - 'ipam.service', - 'ipam.vlan', - 'ipam.vrf', - 'secrets.secret', - 'tenancy.tenant', - 'virtualization.cluster', - 'virtualization.virtualmachine', -] +CUSTOMFIELD_MODELS = Q( + Q(app_label='circuits', model__in=[ + 'circuit', + 'provider', + ]) | + Q(app_label='dcim', model__in=[ + 'device', + 'devicetype', + 'powerfeed', + 'rack', + 'site', + ]) | + Q(app_label='ipam', model__in=[ + 'aggregate', + 'ipaddress', + 'prefix', + 'service', + 'vlan', + 'vrf', + ]) | + Q(app_label='secrets', model__in=[ + 'secret', + ]) | + Q(app_label='tenancy', model__in=[ + 'tenant', + ]) | + Q(app_label='virtualization', model__in=[ + 'cluster', + 'virtualmachine', + ]) +) # Custom links CUSTOMLINK_MODELS = [ diff --git a/netbox/extras/migrations/0022_custom_links.py b/netbox/extras/migrations/0022_custom_links.py index cd204f50a..e977eff8f 100644 --- a/netbox/extras/migrations/0022_custom_links.py +++ b/netbox/extras/migrations/0022_custom_links.py @@ -33,7 +33,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customfield', name='obj_type', - field=models.ManyToManyField(limit_choices_to=extras.models.get_custom_field_models, related_name='custom_fields', to='contenttypes.ContentType'), + field=models.ManyToManyField(related_name='custom_fields', to='contenttypes.ContentType'), ), migrations.AlterField( model_name='exporttemplate', diff --git a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py index 4a591efa0..32ed2d5fc 100644 --- a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py +++ b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py @@ -115,7 +115,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='customfield', name='obj_type', - field=models.ManyToManyField(limit_choices_to=extras.models.get_custom_field_models, related_name='custom_fields', to='contenttypes.ContentType'), + field=models.ManyToManyField(related_name='custom_fields', to='contenttypes.ContentType'), ), migrations.AlterField( model_name='exporttemplate', diff --git a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py new file mode 100644 index 000000000..36a8acc05 --- /dev/null +++ b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.8 on 2020-01-15 21:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extras', '0035_deterministic_ordering'), + ] + + operations = [ + migrations.AlterField( + model_name='customfield', + name='obj_type', + field=models.ManyToManyField(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'devicetype', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), related_name='custom_fields', to='contenttypes.ContentType'), + ), + ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 147963589..2df261996 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -192,16 +192,12 @@ class CustomFieldModel(models.Model): return OrderedDict([(field, None) for field in fields]) -def get_custom_field_models(): - return model_names_to_filter_dict(CUSTOMFIELD_MODELS) - - class CustomField(models.Model): obj_type = models.ManyToManyField( to=ContentType, related_name='custom_fields', verbose_name='Object(s)', - limit_choices_to=get_custom_field_models, + limit_choices_to=CUSTOMFIELD_MODELS, help_text='The object(s) to which this field applies.' ) type = models.CharField( From 09bee75cb36b97e5f0f15b3b0434c46d435f7207 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:04:41 -0500 Subject: [PATCH 3/8] #3892: Convert CUSTOMLINK_MODELS to a Q object --- netbox/extras/constants.py | 54 +++++++++++-------- netbox/extras/migrations/0022_custom_links.py | 2 +- ..._links_squashed_0034_configcontext_tags.py | 2 +- .../0036_contenttype_filters_to_q_objects.py | 8 ++- netbox/extras/models.py | 6 +-- 5 files changed, 43 insertions(+), 29 deletions(-) diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 7d4b32eb9..d32ceb300 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -35,27 +35,39 @@ CUSTOMFIELD_MODELS = Q( ) # Custom links -CUSTOMLINK_MODELS = [ - 'circuits.circuit', - 'circuits.provider', - 'dcim.cable', - 'dcim.device', - 'dcim.devicetype', - 'dcim.powerpanel', - 'dcim.powerfeed', - 'dcim.rack', - 'dcim.site', - 'ipam.aggregate', - 'ipam.ipaddress', - 'ipam.prefix', - 'ipam.service', - 'ipam.vlan', - 'ipam.vrf', - 'secrets.secret', - 'tenancy.tenant', - 'virtualization.cluster', - 'virtualization.virtualmachine', -] +CUSTOMLINK_MODELS = Q( + Q(app_label='circuits', model__in=[ + 'circuit', + 'provider', + ]) | + Q(app_label='dcim', model__in=[ + 'cable', + 'device', + 'devicetype', + 'powerpanel', + 'powerfeed', + 'rack', + 'site', + ]) | + Q(app_label='ipam', model__in=[ + 'aggregate', + 'ipaddress', + 'prefix', + 'service', + 'vlan', + 'vrf', + ]) | + Q(app_label='secrets', model__in=[ + 'secret', + ]) | + Q(app_label='tenancy', model__in=[ + 'tenant', + ]) | + Q(app_label='virtualization', model__in=[ + 'cluster', + 'virtualmachine', + ]) +) # Models which can have Graphs associated with them GRAPH_MODELS = ( diff --git a/netbox/extras/migrations/0022_custom_links.py b/netbox/extras/migrations/0022_custom_links.py index e977eff8f..f56229f08 100644 --- a/netbox/extras/migrations/0022_custom_links.py +++ b/netbox/extras/migrations/0022_custom_links.py @@ -22,7 +22,7 @@ class Migration(migrations.Migration): ('group_name', models.CharField(blank=True, max_length=50)), ('button_class', models.CharField(default='default', max_length=30)), ('new_window', models.BooleanField()), - ('content_type', models.ForeignKey(limit_choices_to=extras.models.get_custom_link_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), ], options={ 'ordering': ['group_name', 'weight', 'name'], diff --git a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py index 32ed2d5fc..e1ca96cb1 100644 --- a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py +++ b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py @@ -106,7 +106,7 @@ class Migration(migrations.Migration): ('group_name', models.CharField(blank=True, max_length=50)), ('button_class', models.CharField(default='default', max_length=30)), ('new_window', models.BooleanField()), - ('content_type', models.ForeignKey(limit_choices_to=extras.models.get_custom_link_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), ], options={ 'ordering': ['group_name', 'weight', 'name'], diff --git a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py index 36a8acc05..1f7015452 100644 --- a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py +++ b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py @@ -1,6 +1,7 @@ -# Generated by Django 2.2.8 on 2020-01-15 21:00 +# Generated by Django 2.2.8 on 2020-01-15 21:04 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -15,4 +16,9 @@ class Migration(migrations.Migration): name='obj_type', field=models.ManyToManyField(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'devicetype', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), related_name='custom_fields', to='contenttypes.ContentType'), ), + migrations.AlterField( + model_name='customlink', + name='content_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'device', 'devicetype', 'powerpanel', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + ), ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 2df261996..a24e5ab9b 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -367,10 +367,6 @@ class CustomFieldChoice(models.Model): # Custom links # -def get_custom_link_models(): - return model_names_to_filter_dict(CUSTOMLINK_MODELS) - - class CustomLink(models.Model): """ A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template @@ -379,7 +375,7 @@ class CustomLink(models.Model): content_type = models.ForeignKey( to=ContentType, on_delete=models.CASCADE, - limit_choices_to=get_custom_link_models + limit_choices_to=CUSTOMLINK_MODELS ) name = models.CharField( max_length=100, From f81e7d30e2293d809522b67329a1b8362b8d5b90 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:08:19 -0500 Subject: [PATCH 4/8] #3892: Convert GRAPH_MODELS to a Q object --- netbox/extras/api/serializers.py | 2 +- netbox/extras/constants.py | 14 +++++++++----- .../0036_contenttype_filters_to_q_objects.py | 7 ++++++- netbox/extras/models.py | 2 +- netbox/extras/tests/test_api.py | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index e637a6282..7f49f4204 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -30,7 +30,7 @@ from .nested_serializers import * class GraphSerializer(ValidatedModelSerializer): type = ContentTypeField( - queryset=ContentType.objects.filter(**model_names_to_filter_dict(GRAPH_MODELS)), + queryset=ContentType.objects.filter(GRAPH_MODELS), ) class Meta: diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index d32ceb300..6d8a49247 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -70,11 +70,15 @@ CUSTOMLINK_MODELS = Q( ) # Models which can have Graphs associated with them -GRAPH_MODELS = ( - 'circuits.provider', - 'dcim.device', - 'dcim.interface', - 'dcim.site', +GRAPH_MODELS = Q( + Q(app_label='circuits', model__in=[ + 'provider', + ]) | + Q(app_label='dcim', model__in=[ + 'device', + 'interface', + 'site', + ]) ) # Models which support export templates diff --git a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py index 1f7015452..e58b6aed7 100644 --- a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py +++ b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.8 on 2020-01-15 21:04 +# Generated by Django 2.2.8 on 2020-01-15 21:07 from django.db import migrations, models import django.db.models.deletion @@ -21,4 +21,9 @@ class Migration(migrations.Migration): name='content_type', field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'device', 'devicetype', 'powerpanel', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), ), + migrations.AlterField( + model_name='graph', + name='type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'interface', 'site'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + ), ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index a24e5ab9b..fb97cfd8f 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -423,7 +423,7 @@ class Graph(models.Model): type = models.ForeignKey( to=ContentType, on_delete=models.CASCADE, - limit_choices_to=model_names_to_filter_dict(GRAPH_MODELS) + limit_choices_to=GRAPH_MODELS ) weight = models.PositiveSmallIntegerField( default=1000 diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 9a7edebd2..8957f24f9 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -29,7 +29,7 @@ class ChoicesTest(APITestCase): self.assertEqual(choices_to_dict(response.data.get('export-template:template_language')), TemplateLanguageChoices.as_dict()) # Graph - content_types = ContentType.objects.filter(**model_names_to_filter_dict(GRAPH_MODELS)) + content_types = ContentType.objects.filter(GRAPH_MODELS) graph_type_choices = { "{}.{}".format(ct.app_label, ct.model): ct.name for ct in content_types } From d9437a08f0fdcbc1d1a74b3878cf6ba1e4ea312b Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:11:44 -0500 Subject: [PATCH 5/8] #3892: Convert EXPORTTEMPLATE_MODELS to a Q object --- netbox/extras/constants.py | 70 +++++++++++-------- netbox/extras/migrations/0022_custom_links.py | 2 +- ..._links_squashed_0034_configcontext_tags.py | 2 +- .../0036_contenttype_filters_to_q_objects.py | 7 +- netbox/extras/models.py | 6 +- 5 files changed, 50 insertions(+), 37 deletions(-) diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 6d8a49247..b1e5ebf41 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -82,35 +82,47 @@ GRAPH_MODELS = Q( ) # Models which support export templates -EXPORTTEMPLATE_MODELS = [ - 'circuits.circuit', - 'circuits.provider', - 'dcim.cable', - 'dcim.consoleport', - 'dcim.device', - 'dcim.devicetype', - 'dcim.interface', - 'dcim.inventoryitem', - 'dcim.manufacturer', - 'dcim.powerpanel', - 'dcim.powerport', - 'dcim.powerfeed', - 'dcim.rack', - 'dcim.rackgroup', - 'dcim.region', - 'dcim.site', - 'dcim.virtualchassis', - 'ipam.aggregate', - 'ipam.ipaddress', - 'ipam.prefix', - 'ipam.service', - 'ipam.vlan', - 'ipam.vrf', - 'secrets.secret', - 'tenancy.tenant', - 'virtualization.cluster', - 'virtualization.virtualmachine', -] +EXPORTTEMPLATE_MODELS = Q( + Q(app_label='circuits', model__in=[ + 'circuit', + 'provider', + ]) | + Q(app_label='dcim', model__in=[ + 'cable', + 'consoleport', + 'device', + 'devicetype', + 'interface', + 'inventoryitem', + 'manufacturer', + 'powerpanel', + 'powerport', + 'powerfeed', + 'rack', + 'rackgroup', + 'region', + 'site', + 'virtualchassis', + ]) | + Q(app_label='ipam', model__in=[ + 'aggregate', + 'ipaddress', + 'prefix', + 'service', + 'vlan', + 'vrf', + ]) | + Q(app_label='secrets', model__in=[ + 'secret', + ]) | + Q(app_label='tenancy', model__in=[ + 'tenant', + ]) | + Q(app_label='virtualization', model__in=[ + 'cluster', + 'virtualmachine', + ]) +) # Report logging levels LOG_DEFAULT = 0 diff --git a/netbox/extras/migrations/0022_custom_links.py b/netbox/extras/migrations/0022_custom_links.py index f56229f08..366863018 100644 --- a/netbox/extras/migrations/0022_custom_links.py +++ b/netbox/extras/migrations/0022_custom_links.py @@ -38,7 +38,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='exporttemplate', name='content_type', - field=models.ForeignKey(limit_choices_to=extras.models.get_export_template_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), ), migrations.AlterField( model_name='webhook', diff --git a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py index e1ca96cb1..bfa22b7db 100644 --- a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py +++ b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py @@ -120,7 +120,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='exporttemplate', name='content_type', - field=models.ForeignKey(limit_choices_to=extras.models.get_export_template_models, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), ), migrations.AlterField( model_name='webhook', diff --git a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py index e58b6aed7..49fcfb782 100644 --- a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py +++ b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.8 on 2020-01-15 21:07 +# Generated by Django 2.2.8 on 2020-01-15 21:11 from django.db import migrations, models import django.db.models.deletion @@ -21,6 +21,11 @@ class Migration(migrations.Migration): name='content_type', field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'device', 'devicetype', 'powerpanel', 'powerfeed', 'rack', 'site'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), ), + migrations.AlterField( + model_name='exporttemplate', + name='content_type', + field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'consoleport', 'device', 'devicetype', 'interface', 'inventoryitem', 'manufacturer', 'powerpanel', 'powerport', 'powerfeed', 'rack', 'rackgroup', 'region', 'site', 'virtualchassis'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), + ), migrations.AlterField( model_name='graph', name='type', diff --git a/netbox/extras/models.py b/netbox/extras/models.py index fb97cfd8f..a0f37075c 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -482,15 +482,11 @@ class Graph(models.Model): # Export templates # -def get_export_template_models(): - return model_names_to_filter_dict(EXPORTTEMPLATE_MODELS) - - class ExportTemplate(models.Model): content_type = models.ForeignKey( to=ContentType, on_delete=models.CASCADE, - limit_choices_to=get_export_template_models + limit_choices_to=EXPORTTEMPLATE_MODELS ) name = models.CharField( max_length=100 From 215b4d0b3fae8bd998fbd59b8704d65430173d62 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:18:47 -0500 Subject: [PATCH 6/8] #3892: Convert WEBHOOK_MODELS to a Q object --- netbox/extras/constants.py | 78 +++++++++++-------- netbox/extras/migrations/0022_custom_links.py | 2 +- ..._links_squashed_0034_configcontext_tags.py | 2 +- .../0036_contenttype_filters_to_q_objects.py | 7 +- netbox/extras/models.py | 6 +- netbox/extras/webhooks.py | 7 +- 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index b1e5ebf41..b12bc2f2c 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -139,36 +139,48 @@ LOG_LEVEL_CODES = { } # Models which support registered webhooks -WEBHOOK_MODELS = [ - 'circuits.circuit', - 'circuits.provider', - 'dcim.cable', - 'dcim.consoleport', - 'dcim.consoleserverport', - 'dcim.device', - 'dcim.devicebay', - 'dcim.devicetype', - 'dcim.interface', - 'dcim.inventoryitem', - 'dcim.frontport', - 'dcim.manufacturer', - 'dcim.poweroutlet', - 'dcim.powerpanel', - 'dcim.powerport', - 'dcim.powerfeed', - 'dcim.rack', - 'dcim.rearport', - 'dcim.region', - 'dcim.site', - 'dcim.virtualchassis', - 'ipam.aggregate', - 'ipam.ipaddress', - 'ipam.prefix', - 'ipam.service', - 'ipam.vlan', - 'ipam.vrf', - 'secrets.secret', - 'tenancy.tenant', - 'virtualization.cluster', - 'virtualization.virtualmachine', -] +WEBHOOK_MODELS = Q( + Q(app_label='circuits', model__in=[ + 'circuit', + 'provider', + ]) | + Q(app_label='dcim', model__in=[ + 'cable', + 'consoleport', + 'consoleserverport', + 'device', + 'devicebay', + 'devicetype', + 'frontport', + 'interface', + 'inventoryitem', + 'manufacturer', + 'poweroutlet', + 'powerpanel', + 'powerport', + 'powerfeed', + 'rack', + 'rearport', + 'region', + 'site', + 'virtualchassis', + ]) | + Q(app_label='ipam', model__in=[ + 'aggregate', + 'ipaddress', + 'prefix', + 'service', + 'vlan', + 'vrf', + ]) | + Q(app_label='secrets', model__in=[ + 'secret', + ]) | + Q(app_label='tenancy', model__in=[ + 'tenant', + ]) | + Q(app_label='virtualization', model__in=[ + 'cluster', + 'virtualmachine', + ]) +) diff --git a/netbox/extras/migrations/0022_custom_links.py b/netbox/extras/migrations/0022_custom_links.py index 366863018..54d841a4f 100644 --- a/netbox/extras/migrations/0022_custom_links.py +++ b/netbox/extras/migrations/0022_custom_links.py @@ -43,6 +43,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='webhook', name='obj_type', - field=models.ManyToManyField(limit_choices_to=extras.models.get_webhook_models, related_name='webhooks', to='contenttypes.ContentType'), + field=models.ManyToManyField(related_name='webhooks', to='contenttypes.ContentType'), ), ] diff --git a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py index bfa22b7db..b10841a6a 100644 --- a/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py +++ b/netbox/extras/migrations/0022_custom_links_squashed_0034_configcontext_tags.py @@ -125,7 +125,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='webhook', name='obj_type', - field=models.ManyToManyField(limit_choices_to=extras.models.get_webhook_models, related_name='webhooks', to='contenttypes.ContentType'), + field=models.ManyToManyField(related_name='webhooks', to='contenttypes.ContentType'), ), migrations.RunSQL( sql="SELECT setval('extras_tag_id_seq', (SELECT id FROM extras_tag ORDER BY id DESC LIMIT 1) + 1)", diff --git a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py index 49fcfb782..12e9bdc0b 100644 --- a/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py +++ b/netbox/extras/migrations/0036_contenttype_filters_to_q_objects.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.8 on 2020-01-15 21:11 +# Generated by Django 2.2.8 on 2020-01-15 21:18 from django.db import migrations, models import django.db.models.deletion @@ -31,4 +31,9 @@ class Migration(migrations.Migration): name='type', field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['device', 'interface', 'site'])), _connector='OR')), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), ), + migrations.AlterField( + model_name='webhook', + name='obj_type', + field=models.ManyToManyField(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ['circuit', 'provider'])), models.Q(('app_label', 'dcim'), ('model__in', ['cable', 'consoleport', 'consoleserverport', 'device', 'devicebay', 'devicetype', 'frontport', 'interface', 'inventoryitem', 'manufacturer', 'poweroutlet', 'powerpanel', 'powerport', 'powerfeed', 'rack', 'rearport', 'region', 'site', 'virtualchassis'])), models.Q(('app_label', 'ipam'), ('model__in', ['aggregate', 'ipaddress', 'prefix', 'service', 'vlan', 'vrf'])), models.Q(('app_label', 'secrets'), ('model__in', ['secret'])), models.Q(('app_label', 'tenancy'), ('model__in', ['tenant'])), models.Q(('app_label', 'virtualization'), ('model__in', ['cluster', 'virtualmachine'])), _connector='OR')), related_name='webhooks', to='contenttypes.ContentType'), + ), ] diff --git a/netbox/extras/models.py b/netbox/extras/models.py index a0f37075c..752892821 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -43,10 +43,6 @@ __all__ = ( # Webhooks # -def get_webhook_models(): - return model_names_to_filter_dict(WEBHOOK_MODELS) - - class Webhook(models.Model): """ A Webhook defines a request that will be sent to a remote application when an object is created, updated, and/or @@ -58,7 +54,7 @@ class Webhook(models.Model): to=ContentType, related_name='webhooks', verbose_name='Object types', - limit_choices_to=get_webhook_models, + limit_choices_to=WEBHOOK_MODELS, help_text="The object(s) to which this Webhook applies." ) name = models.CharField( diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py index 00c61cfb3..5017582cc 100644 --- a/netbox/extras/webhooks.py +++ b/netbox/extras/webhooks.py @@ -1,6 +1,5 @@ import datetime -from django.conf import settings from django.contrib.contenttypes.models import ContentType from extras.models import Webhook @@ -14,7 +13,10 @@ def enqueue_webhooks(instance, user, request_id, action): Find Webhook(s) assigned to this instance + action and enqueue them to be processed """ - if instance._meta.label.lower() not in WEBHOOK_MODELS: + obj_type = ContentType.objects.get_for_model(instance.__class__) + + webhook_models = ContentType.objects.filter(WEBHOOK_MODELS) + if obj_type not in webhook_models: return # Retrieve any applicable Webhooks @@ -23,7 +25,6 @@ def enqueue_webhooks(instance, user, request_id, action): ObjectChangeActionChoices.ACTION_UPDATE: 'type_update', ObjectChangeActionChoices.ACTION_DELETE: 'type_delete', }[action] - obj_type = ContentType.objects.get_for_model(instance.__class__) webhooks = Webhook.objects.filter(obj_type=obj_type, enabled=True, **{action_flag: True}) if webhooks.exists(): From c28684a8b37bfbd41fc73bfa1f8f0360bd037273 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:21:41 -0500 Subject: [PATCH 7/8] Remove obsolete utility function model_names_to_filter_dict() --- netbox/extras/api/serializers.py | 1 - netbox/extras/models.py | 2 +- netbox/extras/tests/test_api.py | 1 - netbox/utilities/utils.py | 11 ----------- 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 7f49f4204..0e27a8ee5 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -20,7 +20,6 @@ from utilities.api import ( ChoiceField, ContentTypeField, get_serializer_for_model, SerializerNotFound, SerializedPKRelatedField, ValidatedModelSerializer, ) -from utilities.utils import model_names_to_filter_dict from .nested_serializers import * diff --git a/netbox/extras/models.py b/netbox/extras/models.py index 752892821..1ef503297 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -13,7 +13,7 @@ from django.urls import reverse from taggit.models import TagBase, GenericTaggedItemBase from utilities.fields import ColorField -from utilities.utils import deepmerge, model_names_to_filter_dict, render_jinja2 +from utilities.utils import deepmerge, render_jinja2 from .choices import * from .constants import * from .querysets import ConfigContextQuerySet diff --git a/netbox/extras/tests/test_api.py b/netbox/extras/tests/test_api.py index 8957f24f9..40579f631 100644 --- a/netbox/extras/tests/test_api.py +++ b/netbox/extras/tests/test_api.py @@ -13,7 +13,6 @@ from extras.models import ConfigContext, Graph, ExportTemplate, Tag from extras.scripts import BooleanVar, IntegerVar, Script, StringVar from tenancy.models import Tenant, TenantGroup from utilities.testing import APITestCase, choices_to_dict -from utilities.utils import model_names_to_filter_dict class ChoicesTest(APITestCase): diff --git a/netbox/utilities/utils.py b/netbox/utilities/utils.py index de3dd8ce6..dc2185988 100644 --- a/netbox/utilities/utils.py +++ b/netbox/utilities/utils.py @@ -62,17 +62,6 @@ def dynamic_import(name): return mod -def model_names_to_filter_dict(names): - """ - Accept a list of content types in the format ['.', '.', ...] and return a dictionary - suitable for QuerySet filtering. - """ - # TODO: This should match on the app_label as well as the model name to avoid potential duplicate names - return { - 'model__in': [model.split('.')[1] for model in names], - } - - def get_subquery(model, field): """ Return a Subquery suitable for annotating a child object count. From bc696f2e11ced6ec63ccccb80538c8a02d7c6872 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 15 Jan 2020 16:25:26 -0500 Subject: [PATCH 8/8] Make filter test logic more obvious --- netbox/extras/tests/test_filters.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/netbox/extras/tests/test_filters.py b/netbox/extras/tests/test_filters.py index 55aa330a6..130f94298 100644 --- a/netbox/extras/tests/test_filters.py +++ b/netbox/extras/tests/test_filters.py @@ -3,6 +3,7 @@ from django.test import TestCase from dcim.models import DeviceRole, Platform, Region, Site from extras.choices import * +from extras.constants import GRAPH_MODELS from extras.filters import * from extras.models import ConfigContext, ExportTemplate, Graph from tenancy.models import Tenant, TenantGroup @@ -15,7 +16,8 @@ class GraphTestCase(TestCase): @classmethod def setUpTestData(cls): - content_types = ContentType.objects.filter(model__in=['site', 'device', 'interface']) + # Get the first three available types + content_types = ContentType.objects.filter(GRAPH_MODELS)[:3] graphs = ( Graph(name='Graph 1', type=content_types[0], template_language=TemplateLanguageChoices.LANGUAGE_DJANGO, source='http://example.com/1'), @@ -29,7 +31,8 @@ class GraphTestCase(TestCase): self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) def test_type(self): - params = {'type': ContentType.objects.get(model='site').pk} + content_type = ContentType.objects.filter(GRAPH_MODELS).first() + params = {'type': content_type.pk} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1) # TODO: Remove in v2.8