1from django.contrib.contenttypes.models import ContentType 2from django.core.exceptions import ValidationError 3from django.urls import reverse 4from rest_framework import status 5 6from dcim.filtersets import SiteFilterSet 7from dcim.forms import SiteCSVForm 8from dcim.models import Site, Rack 9from extras.choices import * 10from extras.models import CustomField 11from utilities.testing import APITestCase, TestCase 12from virtualization.models import VirtualMachine 13 14 15class CustomFieldTest(TestCase): 16 17 def setUp(self): 18 19 Site.objects.bulk_create([ 20 Site(name='Site A', slug='site-a'), 21 Site(name='Site B', slug='site-b'), 22 Site(name='Site C', slug='site-c'), 23 ]) 24 25 def test_simple_fields(self): 26 DATA = ( 27 {'field_type': CustomFieldTypeChoices.TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''}, 28 {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 0, 'empty_value': None}, 29 {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 42, 'empty_value': None}, 30 {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': True, 'empty_value': None}, 31 {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': False, 'empty_value': None}, 32 {'field_type': CustomFieldTypeChoices.TYPE_DATE, 'field_value': '2016-06-23', 'empty_value': None}, 33 {'field_type': CustomFieldTypeChoices.TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''}, 34 ) 35 36 obj_type = ContentType.objects.get_for_model(Site) 37 38 for data in DATA: 39 40 # Create a custom field 41 cf = CustomField(type=data['field_type'], name='my_field', required=False) 42 cf.save() 43 cf.content_types.set([obj_type]) 44 45 # Check that the field has a null initial value 46 site = Site.objects.first() 47 self.assertIsNone(site.custom_field_data[cf.name]) 48 49 # Assign a value to the first Site 50 site.custom_field_data[cf.name] = data['field_value'] 51 site.save() 52 53 # Retrieve the stored value 54 site.refresh_from_db() 55 self.assertEqual(site.custom_field_data[cf.name], data['field_value']) 56 57 # Delete the stored value 58 site.custom_field_data.pop(cf.name) 59 site.save() 60 site.refresh_from_db() 61 self.assertIsNone(site.custom_field_data.get(cf.name)) 62 63 # Delete the custom field 64 cf.delete() 65 66 def test_select_field(self): 67 obj_type = ContentType.objects.get_for_model(Site) 68 69 # Create a custom field 70 cf = CustomField( 71 type=CustomFieldTypeChoices.TYPE_SELECT, 72 name='my_field', 73 required=False, 74 choices=['Option A', 'Option B', 'Option C'] 75 ) 76 cf.save() 77 cf.content_types.set([obj_type]) 78 79 # Check that the field has a null initial value 80 site = Site.objects.first() 81 self.assertIsNone(site.custom_field_data[cf.name]) 82 83 # Assign a value to the first Site 84 site.custom_field_data[cf.name] = 'Option A' 85 site.save() 86 87 # Retrieve the stored value 88 site.refresh_from_db() 89 self.assertEqual(site.custom_field_data[cf.name], 'Option A') 90 91 # Delete the stored value 92 site.custom_field_data.pop(cf.name) 93 site.save() 94 site.refresh_from_db() 95 self.assertIsNone(site.custom_field_data.get(cf.name)) 96 97 # Delete the custom field 98 cf.delete() 99 100 def test_rename_customfield(self): 101 obj_type = ContentType.objects.get_for_model(Site) 102 FIELD_DATA = 'abc' 103 104 # Create a custom field 105 cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1') 106 cf.save() 107 cf.content_types.set([obj_type]) 108 109 # Assign custom field data to an object 110 site = Site.objects.create( 111 name='Site 1', 112 slug='site-1', 113 custom_field_data={'field1': FIELD_DATA} 114 ) 115 site.refresh_from_db() 116 self.assertEqual(site.custom_field_data['field1'], FIELD_DATA) 117 118 # Rename the custom field 119 cf.name = 'field2' 120 cf.save() 121 122 # Check that custom field data on the object has been updated 123 site.refresh_from_db() 124 self.assertNotIn('field1', site.custom_field_data) 125 self.assertEqual(site.custom_field_data['field2'], FIELD_DATA) 126 127 128class CustomFieldManagerTest(TestCase): 129 130 def setUp(self): 131 content_type = ContentType.objects.get_for_model(Site) 132 custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo') 133 custom_field.save() 134 custom_field.content_types.set([content_type]) 135 136 def test_get_for_model(self): 137 self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1) 138 self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0) 139 140 141class CustomFieldAPITest(APITestCase): 142 143 @classmethod 144 def setUpTestData(cls): 145 content_type = ContentType.objects.get_for_model(Site) 146 147 # Text custom field 148 cls.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo') 149 cls.cf_text.save() 150 cls.cf_text.content_types.set([content_type]) 151 152 # Integer custom field 153 cls.cf_integer = CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='number_field', default=123) 154 cls.cf_integer.save() 155 cls.cf_integer.content_types.set([content_type]) 156 157 # Boolean custom field 158 cls.cf_boolean = CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='boolean_field', default=False) 159 cls.cf_boolean.save() 160 cls.cf_boolean.content_types.set([content_type]) 161 162 # Date custom field 163 cls.cf_date = CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='date_field', default='2020-01-01') 164 cls.cf_date.save() 165 cls.cf_date.content_types.set([content_type]) 166 167 # URL custom field 168 cls.cf_url = CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='url_field', default='http://example.com/1') 169 cls.cf_url.save() 170 cls.cf_url.content_types.set([content_type]) 171 172 # Select custom field 173 cls.cf_select = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='choice_field', choices=['Foo', 'Bar', 'Baz']) 174 cls.cf_select.default = 'Foo' 175 cls.cf_select.save() 176 cls.cf_select.content_types.set([content_type]) 177 178 # Create some sites 179 cls.sites = ( 180 Site(name='Site 1', slug='site-1'), 181 Site(name='Site 2', slug='site-2'), 182 ) 183 Site.objects.bulk_create(cls.sites) 184 185 # Assign custom field values for site 2 186 cls.sites[1].custom_field_data = { 187 cls.cf_text.name: 'bar', 188 cls.cf_integer.name: 456, 189 cls.cf_boolean.name: True, 190 cls.cf_date.name: '2020-01-02', 191 cls.cf_url.name: 'http://example.com/2', 192 cls.cf_select.name: 'Bar', 193 } 194 cls.sites[1].save() 195 196 def test_get_single_object_without_custom_field_data(self): 197 """ 198 Validate that custom fields are present on an object even if it has no values defined. 199 """ 200 url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[0].pk}) 201 self.add_permissions('dcim.view_site') 202 203 response = self.client.get(url, **self.header) 204 self.assertEqual(response.data['name'], self.sites[0].name) 205 self.assertEqual(response.data['custom_fields'], { 206 'text_field': None, 207 'number_field': None, 208 'boolean_field': None, 209 'date_field': None, 210 'url_field': None, 211 'choice_field': None, 212 }) 213 214 def test_get_single_object_with_custom_field_data(self): 215 """ 216 Validate that custom fields are present and correctly set for an object with values defined. 217 """ 218 site2_cfvs = self.sites[1].custom_field_data 219 url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk}) 220 self.add_permissions('dcim.view_site') 221 222 response = self.client.get(url, **self.header) 223 self.assertEqual(response.data['name'], self.sites[1].name) 224 self.assertEqual(response.data['custom_fields']['text_field'], site2_cfvs['text_field']) 225 self.assertEqual(response.data['custom_fields']['number_field'], site2_cfvs['number_field']) 226 self.assertEqual(response.data['custom_fields']['boolean_field'], site2_cfvs['boolean_field']) 227 self.assertEqual(response.data['custom_fields']['date_field'], site2_cfvs['date_field']) 228 self.assertEqual(response.data['custom_fields']['url_field'], site2_cfvs['url_field']) 229 self.assertEqual(response.data['custom_fields']['choice_field'], site2_cfvs['choice_field']) 230 231 def test_create_single_object_with_defaults(self): 232 """ 233 Create a new site with no specified custom field values and check that it received the default values. 234 """ 235 data = { 236 'name': 'Site 3', 237 'slug': 'site-3', 238 } 239 url = reverse('dcim-api:site-list') 240 self.add_permissions('dcim.add_site') 241 242 response = self.client.post(url, data, format='json', **self.header) 243 self.assertHttpStatus(response, status.HTTP_201_CREATED) 244 245 # Validate response data 246 response_cf = response.data['custom_fields'] 247 self.assertEqual(response_cf['text_field'], self.cf_text.default) 248 self.assertEqual(response_cf['number_field'], self.cf_integer.default) 249 self.assertEqual(response_cf['boolean_field'], self.cf_boolean.default) 250 self.assertEqual(response_cf['date_field'], self.cf_date.default) 251 self.assertEqual(response_cf['url_field'], self.cf_url.default) 252 self.assertEqual(response_cf['choice_field'], self.cf_select.default) 253 254 # Validate database data 255 site = Site.objects.get(pk=response.data['id']) 256 self.assertEqual(site.custom_field_data['text_field'], self.cf_text.default) 257 self.assertEqual(site.custom_field_data['number_field'], self.cf_integer.default) 258 self.assertEqual(site.custom_field_data['boolean_field'], self.cf_boolean.default) 259 self.assertEqual(str(site.custom_field_data['date_field']), self.cf_date.default) 260 self.assertEqual(site.custom_field_data['url_field'], self.cf_url.default) 261 self.assertEqual(site.custom_field_data['choice_field'], self.cf_select.default) 262 263 def test_create_single_object_with_values(self): 264 """ 265 Create a single new site with a value for each type of custom field. 266 """ 267 data = { 268 'name': 'Site 3', 269 'slug': 'site-3', 270 'custom_fields': { 271 'text_field': 'bar', 272 'number_field': 456, 273 'boolean_field': True, 274 'date_field': '2020-01-02', 275 'url_field': 'http://example.com/2', 276 'choice_field': 'Bar', 277 }, 278 } 279 url = reverse('dcim-api:site-list') 280 self.add_permissions('dcim.add_site') 281 282 response = self.client.post(url, data, format='json', **self.header) 283 self.assertHttpStatus(response, status.HTTP_201_CREATED) 284 285 # Validate response data 286 response_cf = response.data['custom_fields'] 287 data_cf = data['custom_fields'] 288 self.assertEqual(response_cf['text_field'], data_cf['text_field']) 289 self.assertEqual(response_cf['number_field'], data_cf['number_field']) 290 self.assertEqual(response_cf['boolean_field'], data_cf['boolean_field']) 291 self.assertEqual(response_cf['date_field'], data_cf['date_field']) 292 self.assertEqual(response_cf['url_field'], data_cf['url_field']) 293 self.assertEqual(response_cf['choice_field'], data_cf['choice_field']) 294 295 # Validate database data 296 site = Site.objects.get(pk=response.data['id']) 297 self.assertEqual(site.custom_field_data['text_field'], data_cf['text_field']) 298 self.assertEqual(site.custom_field_data['number_field'], data_cf['number_field']) 299 self.assertEqual(site.custom_field_data['boolean_field'], data_cf['boolean_field']) 300 self.assertEqual(str(site.custom_field_data['date_field']), data_cf['date_field']) 301 self.assertEqual(site.custom_field_data['url_field'], data_cf['url_field']) 302 self.assertEqual(site.custom_field_data['choice_field'], data_cf['choice_field']) 303 304 def test_create_multiple_objects_with_defaults(self): 305 """ 306 Create three news sites with no specified custom field values and check that each received 307 the default custom field values. 308 """ 309 data = ( 310 { 311 'name': 'Site 3', 312 'slug': 'site-3', 313 }, 314 { 315 'name': 'Site 4', 316 'slug': 'site-4', 317 }, 318 { 319 'name': 'Site 5', 320 'slug': 'site-5', 321 }, 322 ) 323 url = reverse('dcim-api:site-list') 324 self.add_permissions('dcim.add_site') 325 326 response = self.client.post(url, data, format='json', **self.header) 327 self.assertHttpStatus(response, status.HTTP_201_CREATED) 328 self.assertEqual(len(response.data), len(data)) 329 330 for i, obj in enumerate(data): 331 332 # Validate response data 333 response_cf = response.data[i]['custom_fields'] 334 self.assertEqual(response_cf['text_field'], self.cf_text.default) 335 self.assertEqual(response_cf['number_field'], self.cf_integer.default) 336 self.assertEqual(response_cf['boolean_field'], self.cf_boolean.default) 337 self.assertEqual(response_cf['date_field'], self.cf_date.default) 338 self.assertEqual(response_cf['url_field'], self.cf_url.default) 339 self.assertEqual(response_cf['choice_field'], self.cf_select.default) 340 341 # Validate database data 342 site = Site.objects.get(pk=response.data[i]['id']) 343 self.assertEqual(site.custom_field_data['text_field'], self.cf_text.default) 344 self.assertEqual(site.custom_field_data['number_field'], self.cf_integer.default) 345 self.assertEqual(site.custom_field_data['boolean_field'], self.cf_boolean.default) 346 self.assertEqual(str(site.custom_field_data['date_field']), self.cf_date.default) 347 self.assertEqual(site.custom_field_data['url_field'], self.cf_url.default) 348 self.assertEqual(site.custom_field_data['choice_field'], self.cf_select.default) 349 350 def test_create_multiple_objects_with_values(self): 351 """ 352 Create a three new sites, each with custom fields defined. 353 """ 354 custom_field_data = { 355 'text_field': 'bar', 356 'number_field': 456, 357 'boolean_field': True, 358 'date_field': '2020-01-02', 359 'url_field': 'http://example.com/2', 360 'choice_field': 'Bar', 361 } 362 data = ( 363 { 364 'name': 'Site 3', 365 'slug': 'site-3', 366 'custom_fields': custom_field_data, 367 }, 368 { 369 'name': 'Site 4', 370 'slug': 'site-4', 371 'custom_fields': custom_field_data, 372 }, 373 { 374 'name': 'Site 5', 375 'slug': 'site-5', 376 'custom_fields': custom_field_data, 377 }, 378 ) 379 url = reverse('dcim-api:site-list') 380 self.add_permissions('dcim.add_site') 381 382 response = self.client.post(url, data, format='json', **self.header) 383 self.assertHttpStatus(response, status.HTTP_201_CREATED) 384 self.assertEqual(len(response.data), len(data)) 385 386 for i, obj in enumerate(data): 387 388 # Validate response data 389 response_cf = response.data[i]['custom_fields'] 390 self.assertEqual(response_cf['text_field'], custom_field_data['text_field']) 391 self.assertEqual(response_cf['number_field'], custom_field_data['number_field']) 392 self.assertEqual(response_cf['boolean_field'], custom_field_data['boolean_field']) 393 self.assertEqual(response_cf['date_field'], custom_field_data['date_field']) 394 self.assertEqual(response_cf['url_field'], custom_field_data['url_field']) 395 self.assertEqual(response_cf['choice_field'], custom_field_data['choice_field']) 396 397 # Validate database data 398 site = Site.objects.get(pk=response.data[i]['id']) 399 self.assertEqual(site.custom_field_data['text_field'], custom_field_data['text_field']) 400 self.assertEqual(site.custom_field_data['number_field'], custom_field_data['number_field']) 401 self.assertEqual(site.custom_field_data['boolean_field'], custom_field_data['boolean_field']) 402 self.assertEqual(str(site.custom_field_data['date_field']), custom_field_data['date_field']) 403 self.assertEqual(site.custom_field_data['url_field'], custom_field_data['url_field']) 404 self.assertEqual(site.custom_field_data['choice_field'], custom_field_data['choice_field']) 405 406 def test_update_single_object_with_values(self): 407 """ 408 Update an object with existing custom field values. Ensure that only the updated custom field values are 409 modified. 410 """ 411 site = self.sites[1] 412 original_cfvs = {**site.custom_field_data} 413 data = { 414 'custom_fields': { 415 'text_field': 'ABCD', 416 'number_field': 1234, 417 }, 418 } 419 url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk}) 420 self.add_permissions('dcim.change_site') 421 422 response = self.client.patch(url, data, format='json', **self.header) 423 self.assertHttpStatus(response, status.HTTP_200_OK) 424 425 # Validate response data 426 response_cf = response.data['custom_fields'] 427 self.assertEqual(response_cf['text_field'], data['custom_fields']['text_field']) 428 self.assertEqual(response_cf['number_field'], data['custom_fields']['number_field']) 429 self.assertEqual(response_cf['boolean_field'], original_cfvs['boolean_field']) 430 self.assertEqual(response_cf['date_field'], original_cfvs['date_field']) 431 self.assertEqual(response_cf['url_field'], original_cfvs['url_field']) 432 self.assertEqual(response_cf['choice_field'], original_cfvs['choice_field']) 433 434 # Validate database data 435 site.refresh_from_db() 436 self.assertEqual(site.custom_field_data['text_field'], data['custom_fields']['text_field']) 437 self.assertEqual(site.custom_field_data['number_field'], data['custom_fields']['number_field']) 438 self.assertEqual(site.custom_field_data['boolean_field'], original_cfvs['boolean_field']) 439 self.assertEqual(site.custom_field_data['date_field'], original_cfvs['date_field']) 440 self.assertEqual(site.custom_field_data['url_field'], original_cfvs['url_field']) 441 self.assertEqual(site.custom_field_data['choice_field'], original_cfvs['choice_field']) 442 443 def test_minimum_maximum_values_validation(self): 444 url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk}) 445 self.add_permissions('dcim.change_site') 446 447 self.cf_integer.validation_minimum = 10 448 self.cf_integer.validation_maximum = 20 449 self.cf_integer.save() 450 451 data = {'custom_fields': {'number_field': 9}} 452 response = self.client.patch(url, data, format='json', **self.header) 453 self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) 454 455 data = {'custom_fields': {'number_field': 21}} 456 response = self.client.patch(url, data, format='json', **self.header) 457 self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) 458 459 data = {'custom_fields': {'number_field': 15}} 460 response = self.client.patch(url, data, format='json', **self.header) 461 self.assertHttpStatus(response, status.HTTP_200_OK) 462 463 def test_regex_validation(self): 464 url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk}) 465 self.add_permissions('dcim.change_site') 466 467 self.cf_text.validation_regex = r'^[A-Z]{3}$' # Three uppercase letters 468 self.cf_text.save() 469 470 data = {'custom_fields': {'text_field': 'ABC123'}} 471 response = self.client.patch(url, data, format='json', **self.header) 472 self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) 473 474 data = {'custom_fields': {'text_field': 'abc'}} 475 response = self.client.patch(url, data, format='json', **self.header) 476 self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST) 477 478 data = {'custom_fields': {'text_field': 'ABC'}} 479 response = self.client.patch(url, data, format='json', **self.header) 480 self.assertHttpStatus(response, status.HTTP_200_OK) 481 482 483class CustomFieldImportTest(TestCase): 484 user_permissions = ( 485 'dcim.view_site', 486 'dcim.add_site', 487 ) 488 489 @classmethod 490 def setUpTestData(cls): 491 492 custom_fields = ( 493 CustomField(name='text', type=CustomFieldTypeChoices.TYPE_TEXT), 494 CustomField(name='integer', type=CustomFieldTypeChoices.TYPE_INTEGER), 495 CustomField(name='boolean', type=CustomFieldTypeChoices.TYPE_BOOLEAN), 496 CustomField(name='date', type=CustomFieldTypeChoices.TYPE_DATE), 497 CustomField(name='url', type=CustomFieldTypeChoices.TYPE_URL), 498 CustomField(name='select', type=CustomFieldTypeChoices.TYPE_SELECT, choices=['Choice A', 'Choice B', 'Choice C']), 499 ) 500 for cf in custom_fields: 501 cf.save() 502 cf.content_types.set([ContentType.objects.get_for_model(Site)]) 503 504 def test_import(self): 505 """ 506 Import a Site in CSV format, including a value for each CustomField. 507 """ 508 data = ( 509 ('name', 'slug', 'status', 'cf_text', 'cf_integer', 'cf_boolean', 'cf_date', 'cf_url', 'cf_select'), 510 ('Site 1', 'site-1', 'active', 'ABC', '123', 'True', '2020-01-01', 'http://example.com/1', 'Choice A'), 511 ('Site 2', 'site-2', 'active', 'DEF', '456', 'False', '2020-01-02', 'http://example.com/2', 'Choice B'), 512 ('Site 3', 'site-3', 'active', '', '', '', '', '', ''), 513 ) 514 csv_data = '\n'.join(','.join(row) for row in data) 515 516 response = self.client.post(reverse('dcim:site_import'), {'csv': csv_data}) 517 self.assertEqual(response.status_code, 200) 518 519 # Validate data for site 1 520 site1 = Site.objects.get(name='Site 1') 521 self.assertEqual(len(site1.custom_field_data), 6) 522 self.assertEqual(site1.custom_field_data['text'], 'ABC') 523 self.assertEqual(site1.custom_field_data['integer'], 123) 524 self.assertEqual(site1.custom_field_data['boolean'], True) 525 self.assertEqual(site1.custom_field_data['date'], '2020-01-01') 526 self.assertEqual(site1.custom_field_data['url'], 'http://example.com/1') 527 self.assertEqual(site1.custom_field_data['select'], 'Choice A') 528 529 # Validate data for site 2 530 site2 = Site.objects.get(name='Site 2') 531 self.assertEqual(len(site2.custom_field_data), 6) 532 self.assertEqual(site2.custom_field_data['text'], 'DEF') 533 self.assertEqual(site2.custom_field_data['integer'], 456) 534 self.assertEqual(site2.custom_field_data['boolean'], False) 535 self.assertEqual(site2.custom_field_data['date'], '2020-01-02') 536 self.assertEqual(site2.custom_field_data['url'], 'http://example.com/2') 537 self.assertEqual(site2.custom_field_data['select'], 'Choice B') 538 539 # No custom field data should be set for site 3 540 site3 = Site.objects.get(name='Site 3') 541 self.assertFalse(any(site3.custom_field_data.values())) 542 543 def test_import_missing_required(self): 544 """ 545 Attempt to import an object missing a required custom field. 546 """ 547 # Set one of our CustomFields to required 548 CustomField.objects.filter(name='text').update(required=True) 549 550 form_data = { 551 'name': 'Site 1', 552 'slug': 'site-1', 553 } 554 555 form = SiteCSVForm(data=form_data) 556 self.assertFalse(form.is_valid()) 557 self.assertIn('cf_text', form.errors) 558 559 def test_import_invalid_choice(self): 560 """ 561 Attempt to import an object with an invalid choice selection. 562 """ 563 form_data = { 564 'name': 'Site 1', 565 'slug': 'site-1', 566 'cf_select': 'Choice X' 567 } 568 569 form = SiteCSVForm(data=form_data) 570 self.assertFalse(form.is_valid()) 571 self.assertIn('cf_select', form.errors) 572 573 574class CustomFieldModelTest(TestCase): 575 576 @classmethod 577 def setUpTestData(cls): 578 cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo') 579 cf1.save() 580 cf1.content_types.set([ContentType.objects.get_for_model(Site)]) 581 582 cf2 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='bar') 583 cf2.save() 584 cf2.content_types.set([ContentType.objects.get_for_model(Rack)]) 585 586 def test_cf_data(self): 587 """ 588 Check that custom field data is present on the instance immediately after being set and after being fetched 589 from the database. 590 """ 591 site = Site(name='Test Site', slug='test-site') 592 593 # Check custom field data on new instance 594 site.cf['foo'] = 'abc' 595 self.assertEqual(site.cf['foo'], 'abc') 596 597 # Check custom field data from database 598 site.save() 599 site = Site.objects.get(name='Test Site') 600 self.assertEqual(site.cf['foo'], 'abc') 601 602 def test_invalid_data(self): 603 """ 604 Setting custom field data for a non-applicable (or non-existent) CustomField should raise a ValidationError. 605 """ 606 site = Site(name='Test Site', slug='test-site') 607 608 # Set custom field data 609 site.cf['foo'] = 'abc' 610 site.cf['bar'] = 'def' 611 with self.assertRaises(ValidationError): 612 site.clean() 613 614 del(site.cf['bar']) 615 site.clean() 616 617 def test_missing_required_field(self): 618 """ 619 Check that a ValidationError is raised if any required custom fields are not present. 620 """ 621 cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='baz', required=True) 622 cf3.save() 623 cf3.content_types.set([ContentType.objects.get_for_model(Site)]) 624 625 site = Site(name='Test Site', slug='test-site') 626 627 # Set custom field data with a required field omitted 628 site.cf['foo'] = 'abc' 629 with self.assertRaises(ValidationError): 630 site.clean() 631 632 site.cf['baz'] = 'def' 633 site.clean() 634 635 636class CustomFieldFilterTest(TestCase): 637 queryset = Site.objects.all() 638 filterset = SiteFilterSet 639 640 @classmethod 641 def setUpTestData(cls): 642 obj_type = ContentType.objects.get_for_model(Site) 643 644 # Integer filtering 645 cf = CustomField(name='cf1', type=CustomFieldTypeChoices.TYPE_INTEGER) 646 cf.save() 647 cf.content_types.set([obj_type]) 648 649 # Boolean filtering 650 cf = CustomField(name='cf2', type=CustomFieldTypeChoices.TYPE_BOOLEAN) 651 cf.save() 652 cf.content_types.set([obj_type]) 653 654 # Exact text filtering 655 cf = CustomField(name='cf3', type=CustomFieldTypeChoices.TYPE_TEXT, 656 filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT) 657 cf.save() 658 cf.content_types.set([obj_type]) 659 660 # Loose text filtering 661 cf = CustomField(name='cf4', type=CustomFieldTypeChoices.TYPE_TEXT, 662 filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE) 663 cf.save() 664 cf.content_types.set([obj_type]) 665 666 # Date filtering 667 cf = CustomField(name='cf5', type=CustomFieldTypeChoices.TYPE_DATE) 668 cf.save() 669 cf.content_types.set([obj_type]) 670 671 # Exact URL filtering 672 cf = CustomField(name='cf6', type=CustomFieldTypeChoices.TYPE_URL, 673 filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT) 674 cf.save() 675 cf.content_types.set([obj_type]) 676 677 # Loose URL filtering 678 cf = CustomField(name='cf7', type=CustomFieldTypeChoices.TYPE_URL, 679 filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE) 680 cf.save() 681 cf.content_types.set([obj_type]) 682 683 # Selection filtering 684 cf = CustomField(name='cf8', type=CustomFieldTypeChoices.TYPE_SELECT, choices=['Foo', 'Bar', 'Baz']) 685 cf.save() 686 cf.content_types.set([obj_type]) 687 688 # Multiselect filtering 689 cf = CustomField(name='cf9', type=CustomFieldTypeChoices.TYPE_MULTISELECT, choices=['A', 'AA', 'B', 'C']) 690 cf.save() 691 cf.content_types.set([obj_type]) 692 693 Site.objects.bulk_create([ 694 Site(name='Site 1', slug='site-1', custom_field_data={ 695 'cf1': 100, 696 'cf2': True, 697 'cf3': 'foo', 698 'cf4': 'foo', 699 'cf5': '2016-06-26', 700 'cf6': 'http://foo.example.com/', 701 'cf7': 'http://foo.example.com/', 702 'cf8': 'Foo', 703 'cf9': ['A', 'B'], 704 }), 705 Site(name='Site 2', slug='site-2', custom_field_data={ 706 'cf1': 200, 707 'cf2': False, 708 'cf3': 'foobar', 709 'cf4': 'foobar', 710 'cf5': '2016-06-27', 711 'cf6': 'http://bar.example.com/', 712 'cf7': 'http://bar.example.com/', 713 'cf8': 'Bar', 714 'cf9': ['AA', 'B'], 715 }), 716 Site(name='Site 3', slug='site-3'), 717 ]) 718 719 def test_filter_integer(self): 720 self.assertEqual(self.filterset({'cf_cf1': 100}, self.queryset).qs.count(), 1) 721 722 def test_filter_boolean(self): 723 self.assertEqual(self.filterset({'cf_cf2': True}, self.queryset).qs.count(), 1) 724 self.assertEqual(self.filterset({'cf_cf2': False}, self.queryset).qs.count(), 1) 725 726 def test_filter_text(self): 727 self.assertEqual(self.filterset({'cf_cf3': 'foo'}, self.queryset).qs.count(), 1) 728 self.assertEqual(self.filterset({'cf_cf4': 'foo'}, self.queryset).qs.count(), 2) 729 730 def test_filter_date(self): 731 self.assertEqual(self.filterset({'cf_cf5': '2016-06-26'}, self.queryset).qs.count(), 1) 732 733 def test_filter_url(self): 734 self.assertEqual(self.filterset({'cf_cf6': 'http://foo.example.com/'}, self.queryset).qs.count(), 1) 735 self.assertEqual(self.filterset({'cf_cf7': 'example.com'}, self.queryset).qs.count(), 2) 736 737 def test_filter_select(self): 738 self.assertEqual(self.filterset({'cf_cf8': 'Foo'}, self.queryset).qs.count(), 1) 739 self.assertEqual(self.filterset({'cf_cf8': 'Bar'}, self.queryset).qs.count(), 1) 740 self.assertEqual(self.filterset({'cf_cf8': 'Baz'}, self.queryset).qs.count(), 0) 741 742 def test_filter_multiselect(self): 743 self.assertEqual(self.filterset({'cf_cf9': 'A'}, self.queryset).qs.count(), 1) 744 self.assertEqual(self.filterset({'cf_cf9': 'B'}, self.queryset).qs.count(), 2) 745 self.assertEqual(self.filterset({'cf_cf9': 'C'}, self.queryset).qs.count(), 0) 746