diff --git a/.github/workflows/lint-tests.yaml b/.github/workflows/lint-tests.yaml index 387b3ae..1e47346 100644 --- a/.github/workflows/lint-tests.yaml +++ b/.github/workflows/lint-tests.yaml @@ -69,6 +69,7 @@ jobs: with: repository: "netbox-community/netbox" path: netbox + ref: feature - name: Install netbox-custom-objects working-directory: netbox-custom-objects run: | diff --git a/netbox_custom_objects/__init__.py b/netbox_custom_objects/__init__.py index 2c5eb1f..57b0ab2 100644 --- a/netbox_custom_objects/__init__.py +++ b/netbox_custom_objects/__init__.py @@ -1,4 +1,3 @@ -import warnings import sys from django.core.exceptions import AppRegistryNotReady @@ -60,37 +59,37 @@ def get_model(self, model_name, require_ready=True): return obj.get_model() - def get_models(self, include_auto_created=False, include_swapped=False): - """Return all models for this plugin, including custom object type models.""" - # Get the regular Django models first - for model in super().get_models(include_auto_created, include_swapped): - yield model - - # Skip custom object type model loading if running during migration - if is_running_migration(): - return - - # Suppress warnings about database calls during model loading - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", category=RuntimeWarning, message=".*database.*" - ) - warnings.filterwarnings( - "ignore", category=UserWarning, message=".*database.*" - ) - - # Add custom object type models - from .models import CustomObjectType - - # Only load models that are already cached to avoid creating all models at startup - # This prevents the "two TaggableManagers with same through model" error - custom_object_types = CustomObjectType.objects.all() - for custom_type in custom_object_types: - # Only yield already cached models during discovery - if CustomObjectType.is_model_cached(custom_type.id): - model = CustomObjectType.get_cached_model(custom_type.id) - if model: - yield model + # def get_models(self, include_auto_created=False, include_swapped=False): + # """Return all models for this plugin, including custom object type models.""" + # # Get the regular Django models first + # for model in super().get_models(include_auto_created, include_swapped): + # yield model + # + # # Skip custom object type model loading if running during migration + # if is_running_migration(): + # return + # + # # Suppress warnings about database calls during model loading + # with warnings.catch_warnings(): + # warnings.filterwarnings( + # "ignore", category=RuntimeWarning, message=".*database.*" + # ) + # warnings.filterwarnings( + # "ignore", category=UserWarning, message=".*database.*" + # ) + # + # # Add custom object type models + # from .models import CustomObjectType + # + # # Only load models that are already cached to avoid creating all models at startup + # # This prevents the "two TaggableManagers with same through model" error + # custom_object_types = CustomObjectType.objects.all() + # for custom_type in custom_object_types: + # # Only yield already cached models during discovery + # if CustomObjectType.is_model_cached(custom_type.id): + # model = CustomObjectType.get_cached_model(custom_type.id) + # if model: + # yield model config = CustomObjectsPluginConfig diff --git a/netbox_custom_objects/tests/base.py b/netbox_custom_objects/tests/base.py index fab06df..4e24b61 100644 --- a/netbox_custom_objects/tests/base.py +++ b/netbox_custom_objects/tests/base.py @@ -1,6 +1,6 @@ # Test utilities for netbox_custom_objects plugin -from django.contrib.contenttypes.models import ContentType from django.test import Client +from core.models import ObjectType from extras.models import CustomFieldChoiceSet from utilities.testing import create_test_user @@ -61,14 +61,14 @@ def create_choice_set(cls, **kwargs): return CustomFieldChoiceSet.objects.create(**defaults) @classmethod - def get_device_content_type(cls): + def get_device_object_type(cls): """Get the device content type for object field testing.""" - return ContentType.objects.get(app_label='dcim', model='device') + return ObjectType.objects.get(app_label='dcim', model='device') @classmethod - def get_site_content_type(cls): + def get_site_object_type(cls): """Get the site content type for object field testing.""" - return ContentType.objects.get(app_label='dcim', model='site') + return ObjectType.objects.get(app_label='dcim', model='site') def create_simple_custom_object_type(self, **kwargs): """Create a simple custom object type with basic fields.""" @@ -99,7 +99,7 @@ def create_complex_custom_object_type(self, **kwargs): """Create a complex custom object type with various field types.""" custom_object_type = CustomObjectsTestCase.create_custom_object_type(**kwargs) choice_set = CustomObjectsTestCase.create_choice_set() - device_content_type = CustomObjectsTestCase.get_device_content_type() + device_object_type = CustomObjectsTestCase.get_device_object_type() # Primary text field CustomObjectsTestCase.create_custom_object_type_field( @@ -145,7 +145,7 @@ def create_complex_custom_object_type(self, **kwargs): name="device", label="Device", type="object", - related_object_type=device_content_type + related_object_type=device_object_type ) return custom_object_type @@ -169,8 +169,8 @@ def create_self_referential_custom_object_type(self, **kwargs): def create_multi_object_custom_object_type(self, **kwargs): """Create a custom object type with multi-object fields.""" custom_object_type = CustomObjectsTestCase.create_custom_object_type(**kwargs) - device_content_type = CustomObjectsTestCase.get_device_content_type() - site_content_type = CustomObjectsTestCase.get_site_content_type() + device_object_type = CustomObjectsTestCase.get_device_object_type() + site_object_type = CustomObjectsTestCase.get_site_object_type() # Primary text field CustomObjectsTestCase.create_custom_object_type_field( @@ -188,7 +188,7 @@ def create_multi_object_custom_object_type(self, **kwargs): name="devices", label="Devices", type="multiobject", - related_object_type=device_content_type + related_object_type=device_object_type ) # Multi-object field (sites) @@ -197,7 +197,7 @@ def create_multi_object_custom_object_type(self, **kwargs): name="sites", label="Sites", type="multiobject", - related_object_type=site_content_type + related_object_type=site_object_type ) return custom_object_type diff --git a/netbox_custom_objects/tests/test_field_types.py b/netbox_custom_objects/tests/test_field_types.py index 0dcdecd..5bd841d 100644 --- a/netbox_custom_objects/tests/test_field_types.py +++ b/netbox_custom_objects/tests/test_field_types.py @@ -602,7 +602,7 @@ class ObjectFieldTypeTestCase(FieldTypeTestCase): def setUp(self): """Set up test data.""" super().setUp() - self.device_content_type = self.get_device_content_type() + self.device_object_type = self.get_device_object_type() def test_object_field_creation(self): """Test creating an object field.""" @@ -611,11 +611,11 @@ def test_object_field_creation(self): name="device", label="Device", type="object", - related_object_type=self.device_content_type + related_object_type=self.device_object_type ) self.assertEqual(field.type, "object") - self.assertEqual(field.related_object_type, self.device_content_type) + self.assertEqual(field.related_object_type, self.device_object_type) def test_object_field_model_generation(self): """Test object field model generation.""" @@ -624,7 +624,7 @@ def test_object_field_model_generation(self): name="device", label="Device", type="object", - related_object_type=self.device_content_type + related_object_type=self.device_object_type ) field # To silence ruff error @@ -661,7 +661,7 @@ class MultiObjectFieldTypeTestCase(FieldTypeTestCase): def setUp(self): """Set up test data.""" super().setUp() - self.device_content_type = self.get_device_content_type() + self.device_object_type = self.get_device_object_type() def test_multiobject_field_creation(self): """Test creating a multiobject field.""" @@ -670,11 +670,11 @@ def test_multiobject_field_creation(self): name="devices", label="Devices", type="multiobject", - related_object_type=self.device_content_type + related_object_type=self.device_object_type ) self.assertEqual(field.type, "multiobject") - self.assertEqual(field.related_object_type, self.device_content_type) + self.assertEqual(field.related_object_type, self.device_object_type) def test_multiobject_field_model_generation(self): """Test multiobject field model generation.""" @@ -683,7 +683,7 @@ def test_multiobject_field_model_generation(self): name="devices", label="Devices", type="multiobject", - related_object_type=self.device_content_type + related_object_type=self.device_object_type ) field # To silence ruff error diff --git a/netbox_custom_objects/tests/test_models.py b/netbox_custom_objects/tests/test_models.py index 567daea..5076b70 100644 --- a/netbox_custom_objects/tests/test_models.py +++ b/netbox_custom_objects/tests/test_models.py @@ -303,7 +303,7 @@ def test_custom_object_type_field_object_type_validation(self): field.full_clean() # Should not allow related_object_type for non-object field - device_ct = self.get_device_content_type() + device_ct = self.get_device_object_type() with self.assertRaises(ValidationError): field = CustomObjectTypeField( custom_object_type=self.custom_object_type, @@ -463,7 +463,7 @@ def setUpTestData(cls): choice_set=choice_set, ) - site_ct = cls.get_site_content_type() + site_ct = cls.get_site_object_type() cls.create_custom_object_type_field( cls.custom_object_type, name="site", @@ -472,7 +472,7 @@ def setUpTestData(cls): related_object_type=site_ct, ) - site_ct = cls.get_site_content_type() + site_ct = cls.get_site_object_type() cls.create_custom_object_type_field( cls.custom_object_type, name="sites", @@ -492,7 +492,7 @@ def setUp(self): def test_custom_object_creation(self): """Test creating a custom object instance.""" now = timezone.now() - site_ct = self.get_site_content_type() + site_ct = self.get_site_object_type() site = site_ct.model_class().objects.create() instance = self.model.objects.create( diff --git a/netbox_custom_objects/tests/test_views.py b/netbox_custom_objects/tests/test_views.py new file mode 100644 index 0000000..7a9673b --- /dev/null +++ b/netbox_custom_objects/tests/test_views.py @@ -0,0 +1,729 @@ +from django.urls import reverse +from extras.models import CustomFieldChoiceSet +from utilities.testing import ViewTestCases + +from netbox_custom_objects.models import CustomObjectType, CustomObjectTypeField +from .base import CustomObjectsTestCase +from core.models.object_types import ObjectType + + +class CustomObjectTypeViewTestCase(CustomObjectsTestCase, ViewTestCases.PrimaryObjectViewTestCase): + """Test cases for CustomObjectType views.""" + + model = CustomObjectType + + @classmethod + def setUpTestData(cls): + """Set up test data.""" + + # Create test custom object types + cls.custom_object_type1 = CustomObjectType.objects.create( + name="TestObject1", + description="First test custom object type", + verbose_name_plural="Test Objects 1" + ) + + cls.custom_object_type2 = CustomObjectType.objects.create( + name="TestObject2", + description="Second test custom object type", + verbose_name_plural="Test Objects 2" + ) + + def setUp(self): + """Set up test data.""" + super().setUp() + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return 'plugins:{}:{}_{{}}'.format( + self.model._meta.app_label, + self.model._meta.model_name + ) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action and optionally a specific instance + """ + url_format = self._get_base_url() + + # If no instance was provided, assume we don't need a unique identifier + if instance is None: + return reverse(url_format.format(action)) + + return reverse(url_format.format(action), kwargs={'pk': instance.pk}) + + def test_create_object_with_permission(self): + ... + + def test_create_object_with_constrained_permission(self): + ... + + def test_edit_object_with_permission(self): + ... + + def test_edit_object_with_constrained_permission(self): + ... + + def test_bulk_edit_objects_with_permission(self): + ... + + def test_bulk_edit_objects_with_constrained_permission(self): + ... + + def test_bulk_update_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_constrained_permission(self): + ... + + def test_delete_object_with_permission(self): + ... + + def test_delete_object_with_constrained_permission(self): + ... + + def test_bulk_delete_objects_with_permission(self): + ... + + def test_bulk_delete_objects_with_constrained_permission(self): + ... + + +class CustomObjectTypeFieldViewTestCase(CustomObjectsTestCase, ViewTestCases.PrimaryObjectViewTestCase): + """Test cases for CustomObjectTypeField views.""" + + model = CustomObjectTypeField + + @classmethod + def setUpTestData(cls): + """Set up test data.""" + + # Create a custom object type + cls.custom_object_type = CustomObjectType.objects.create( + name="TestObject", + description="Test custom object type", + verbose_name_plural="Test Objects" + ) + + # Create test fields + cls.field1 = CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="field1", + label="Field 1", + type="text", + description="First test field" + ) + + cls.field2 = CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="field2", + label="Field 2", + type="integer", + description="Second test field" + ) + + def setUp(self): + """Set up test data.""" + super().setUp() + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return 'plugins:{}:{}_{{}}'.format( + self.model._meta.app_label, + self.model._meta.model_name + ) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action and optionally a specific instance + """ + url_format = self._get_base_url() + + # If no instance was provided, assume we don't need a unique identifier + if instance is None: + return reverse(url_format.format(action)) + + return reverse(url_format.format(action), kwargs={'pk': instance.pk}) + + def test_list_objects_anonymous(self): + ... + + def test_list_objects_with_permission(self): + ... + + def test_list_objects_without_permission(self): + ... + + def test_list_objects_with_constrained_permission(self): + ... + + def test_get_object_with_permission(self): + ... + + def test_get_object_with_constrained_permission(self): + ... + + def test_get_object_changelog(self): + ... + + def test_export_objects(self): + ... + + def test_create_object_with_permission(self): + ... + + def test_create_object_with_constrained_permission(self): + ... + + def test_edit_object_with_permission(self): + ... + + def test_edit_object_with_constrained_permission(self): + ... + + def test_bulk_edit_objects_with_permission(self): + ... + + def test_bulk_edit_objects_without_permission(self): + ... + + def test_bulk_edit_objects_with_constrained_permission(self): + ... + + def test_bulk_update_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_permission(self): + ... + + def test_bulk_import_objects_without_permission(self): + ... + + def test_bulk_import_objects_with_constrained_permission(self): + ... + + def test_delete_object_with_permission(self): + ... + + def test_bulk_delete_objects_without_permission(self): + ... + + def test_delete_object_with_constrained_permission(self): + ... + + def test_bulk_delete_objects_with_permission(self): + ... + + def test_bulk_delete_objects_with_constrained_permission(self): + ... + + +class CustomObjectViewTestCase(CustomObjectsTestCase, ViewTestCases.PrimaryObjectViewTestCase): + """Test cases for dynamic CustomObject views.""" + + @classmethod + def setUpTestData(cls): + """Set up test data.""" + + # Create a custom object type with fields + cls.custom_object_type = CustomObjectType.objects.create( + name="TestObject", + description="Test custom object type", + verbose_name_plural="Test Objects" + ) + + # Add a primary field + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="name", + label="Name", + type="text", + primary=True, + required=True + ) + + # Add additional fields + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="description", + label="Description", + type="text", + required=False + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="count", + label="Count", + type="integer", + validation_minimum=0, + validation_maximum=100 + ) + + # Get the dynamic model + cls.model = cls.custom_object_type.get_model() + + # Create test instances + cls.instance1 = cls.model.objects.create( + name="Test Instance 1", + description="First test instance", + count=10 + ) + + cls.instance2 = cls.model.objects.create( + name="Test Instance 2", + description="Second test instance", + count=20 + ) + + def setUp(self): + """Set up test data.""" + super().setUp() + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return 'plugins:{}:customobject_{{}}'.format(self.model._meta.app_label) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action and optionally a specific instance + """ + url_format = self._get_base_url() + + custom_object_type = self.model.custom_object_type.name.lower() + + # If no instance was provided, assume we don't need a unique identifier + if instance is None: + return reverse(url_format.format(action), kwargs={'custom_object_type': custom_object_type}) + + return reverse(url_format.format(action), kwargs={'pk': instance.pk, 'custom_object_type': custom_object_type}) + + def test_get_object_with_constrained_permission(self): + ... + + def test_get_object_changelog(self): + ... + + def test_create_object_with_permission(self): + ... + + def test_create_object_with_constrained_permission(self): + ... + + def test_edit_object_with_permission(self): + ... + + def test_edit_object_with_constrained_permission(self): + ... + + def test_bulk_edit_objects_with_permission(self): + ... + + def test_bulk_edit_objects_with_constrained_permission(self): + ... + + def test_bulk_update_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_permission(self): + ... + + def test_bulk_import_objects_without_permission(self): + ... + + def test_bulk_import_objects_with_constrained_permission(self): + ... + + def test_delete_object_with_permission(self): + ... + + def test_delete_object_with_constrained_permission(self): + ... + + def test_bulk_delete_objects_with_permission(self): + ... + + def test_bulk_delete_objects_with_constrained_permission(self): + ... + + +class ComplexCustomObjectViewTestCase(CustomObjectsTestCase, ViewTestCases.PrimaryObjectViewTestCase): + """Test cases for complex custom objects with various field types.""" + + @classmethod + def setUpTestData(cls): + """Set up test data.""" + + # Create choice set + cls.choice_set = CustomFieldChoiceSet.objects.create( + name="Test Choices", + extra_choices=[ + ["choice1", "Choice 1"], + ["choice2", "Choice 2"], + ["choice3", "Choice 3"], + ] + ) + + # Create custom object type with complex fields + cls.custom_object_type = CustomObjectType.objects.create( + name="ComplexObject", + description="Complex test custom object type", + verbose_name_plural="Complex Objects" + ) + + # Add primary field + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="name", + label="Name", + type="text", + primary=True, + required=True + ) + + # Add various field types + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="description", + label="Description", + type="longtext", + required=False + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="count", + label="Count", + type="integer", + validation_minimum=0, + validation_maximum=100, + default=10 + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="price", + label="Price", + type="decimal", + validation_minimum=0, + validation_maximum=1000, + default=50.00 + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="active", + label="Active", + type="boolean", + default=True + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="status", + label="Status", + type="select", + choice_set=cls.choice_set, + default="choice1" + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="multi_tags", + label="Tags", + type="multiselect", + choice_set=cls.choice_set, + default=["choice1", "choice2"] + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="website", + label="Website", + type="url", + validation_regex="^https://.*" + ) + + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="metadata", + label="Metadata", + type="json", + default={"key": "value"} + ) + + # Get the dynamic model + cls.model = cls.custom_object_type.get_model() + + # Create test instances + cls.instance_1 = cls.model.objects.create( + name="Complex Test Instance 1", + description="A complex test instance with various field types", + count=25, + price=75.50, + active=False, + status="choice2", + multi_tags=["choice2", "choice3"], + website="https://example.com", + metadata={"complex": "data", "number": 42} + ) + + cls.instance_2 = cls.model.objects.create( + name="Complex Test Instance 2", + description="A complex test instance with various field types", + count=20, + price=25.50, + active=False, + status="choice3", + multi_tags=["choice1", "choice3"], + website="https://example.com", + metadata={"complex": "data", "number": 42} + ) + + def setUp(self): + """Set up test data.""" + super().setUp() + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return 'plugins:{}:customobject_{{}}'.format(self.model._meta.app_label) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action and optionally a specific instance + """ + url_format = self._get_base_url() + + custom_object_type = self.model.custom_object_type.name.lower() + + # If no instance was provided, assume we don't need a unique identifier + if instance is None: + return reverse(url_format.format(action), kwargs={'custom_object_type': custom_object_type}) + + return reverse(url_format.format(action), kwargs={'pk': instance.pk, 'custom_object_type': custom_object_type}) + + def test_get_object_with_constrained_permission(self): + ... + + def test_get_object_changelog(self): + ... + + def test_create_object_with_permission(self): + ... + + def test_create_object_with_constrained_permission(self): + ... + + def test_edit_object_with_permission(self): + ... + + def test_edit_object_with_constrained_permission(self): + ... + + def test_bulk_edit_objects_with_permission(self): + ... + + def test_bulk_edit_objects_with_constrained_permission(self): + ... + + def test_bulk_update_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_permission(self): + ... + + def test_bulk_import_objects_without_permission(self): + ... + + def test_bulk_import_objects_with_constrained_permission(self): + ... + + def test_delete_object_with_permission(self): + ... + + def test_delete_object_with_constrained_permission(self): + ... + + def test_bulk_delete_objects_with_permission(self): + ... + + def test_bulk_delete_objects_with_constrained_permission(self): + ... + + +class ObjectFieldViewTestCase(CustomObjectsTestCase, ViewTestCases.PrimaryObjectViewTestCase): + """Test cases for custom objects with object and multi-object fields.""" + + @classmethod + def setUpTestData(cls): + """Set up test data.""" + + # Get content types for object fields + cls.device_content_type = ObjectType.objects.get(app_label='dcim', model='device') + cls.site_content_type = ObjectType.objects.get(app_label='dcim', model='site') + + # Create custom object type with object fields + cls.custom_object_type = CustomObjectType.objects.create( + name="ObjectTestObject", + description="Test custom object type with object fields", + verbose_name_plural="Object Test Objects" + ) + + # Add primary field + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="name", + label="Name", + type="text", + primary=True, + required=True + ) + + # Add object field + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="device", + label="Device", + type="object", + related_object_type=cls.device_content_type + ) + + # Add multi-object field + CustomObjectTypeField.objects.create( + custom_object_type=cls.custom_object_type, + name="sites", + label="Sites", + type="multiobject", + related_object_type=cls.site_content_type + ) + + # Get the dynamic model + cls.model = cls.custom_object_type.get_model() + + # Create test instances (if DCIM models are available) + try: + from dcim.models import Device, Site, DeviceRole, DeviceType, Manufacturer + + # Create test site + cls.site = Site.objects.create(name="Test Site", slug="test-site") + + # Create test device + manufacturer = Manufacturer.objects.create(name="Test Manufacturer", slug="test-manufacturer") + device_type = DeviceType.objects.create( + manufacturer=manufacturer, + model="Test Model", + slug="test-model" + ) + device_role = DeviceRole.objects.create(name="Test Role", slug="test-role") + cls.device = Device.objects.create( + name="Test Device", + site=cls.site, + device_type=device_type, + role=device_role + ) + + # Create custom object instances + cls.instance_1 = cls.model.objects.create( + name="Object Test Instance 1", + device=cls.device + ) + cls.instance_1.sites.add(cls.site) + + cls.instance_2 = cls.model.objects.create( + name="Object Test Instance 2", + device=cls.device + ) + + except ImportError: + # Skip if DCIM models are not available + cls.site = None + cls.device = None + cls.instance = None + + def setUp(self): + """Set up test data.""" + super().setUp() + + def _get_base_url(self): + """ + Return the base format for a URL for the test's model. Override this to test for a model which belongs + to a different app (e.g. testing Interfaces within the virtualization app). + """ + return 'plugins:{}:customobject_{{}}'.format(self.model._meta.app_label) + + def _get_url(self, action, instance=None): + """ + Return the URL name for a specific action and optionally a specific instance + """ + url_format = self._get_base_url() + + custom_object_type = self.model.custom_object_type.name.lower() + + # If no instance was provided, assume we don't need a unique identifier + if instance is None: + return reverse(url_format.format(action), kwargs={'custom_object_type': custom_object_type}) + + return reverse(url_format.format(action), kwargs={'pk': instance.pk, 'custom_object_type': custom_object_type}) + + def test_get_object_with_constrained_permission(self): + ... + + def test_get_object_changelog(self): + ... + + def test_create_object_with_permission(self): + ... + + def test_create_object_with_constrained_permission(self): + ... + + def test_edit_object_with_permission(self): + ... + + def test_edit_object_with_constrained_permission(self): + ... + + def test_bulk_edit_objects_with_permission(self): + ... + + def test_bulk_edit_objects_with_constrained_permission(self): + ... + + def test_bulk_update_objects_with_permission(self): + ... + + def test_bulk_import_objects_with_permission(self): + ... + + def test_bulk_import_objects_without_permission(self): + ... + + def test_bulk_import_objects_with_constrained_permission(self): + ... + + def test_delete_object_with_permission(self): + ... + + def test_delete_object_with_constrained_permission(self): + ... + + def test_bulk_delete_objects_with_permission(self): + ... + + def test_bulk_delete_objects_with_constrained_permission(self): + ...