test_customfields.py 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545
  1. import datetime
  2. from decimal import Decimal
  3. from django.core.exceptions import ValidationError
  4. from django.urls import reverse
  5. from rest_framework import status
  6. from core.models import ObjectType
  7. from dcim.filtersets import SiteFilterSet
  8. from dcim.forms import SiteImportForm
  9. from dcim.models import Manufacturer, Rack, Site
  10. from extras.choices import *
  11. from extras.models import CustomField, CustomFieldChoiceSet
  12. from ipam.models import VLAN
  13. from utilities.choices import CSVDelimiterChoices, ImportFormatChoices
  14. from utilities.testing import APITestCase, TestCase
  15. from virtualization.models import VirtualMachine
  16. class CustomFieldTest(TestCase):
  17. @classmethod
  18. def setUpTestData(cls):
  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. cls.object_type = ObjectType.objects.get_for_model(Site)
  25. def test_invalid_name(self):
  26. """
  27. Try creating a CustomField with an invalid name.
  28. """
  29. with self.assertRaises(ValidationError):
  30. # Invalid character
  31. CustomField(name='?', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean()
  32. with self.assertRaises(ValidationError):
  33. # Double underscores not permitted
  34. CustomField(name='foo__bar', type=CustomFieldTypeChoices.TYPE_TEXT).full_clean()
  35. def test_text_field(self):
  36. value = 'Foobar!'
  37. # Create a custom field & check that initial value is null
  38. cf = CustomField.objects.create(
  39. name='text_field',
  40. type=CustomFieldTypeChoices.TYPE_TEXT,
  41. required=False
  42. )
  43. cf.object_types.set([self.object_type])
  44. instance = Site.objects.first()
  45. self.assertIsNone(instance.custom_field_data[cf.name])
  46. # Assign a value and check that it is saved
  47. instance.custom_field_data[cf.name] = value
  48. instance.save()
  49. instance.refresh_from_db()
  50. self.assertEqual(instance.custom_field_data[cf.name], value)
  51. # Delete the stored value and check that it is now null
  52. instance.custom_field_data.pop(cf.name)
  53. instance.save()
  54. instance.refresh_from_db()
  55. self.assertIsNone(instance.custom_field_data.get(cf.name))
  56. def test_longtext_field(self):
  57. value = 'A' * 256
  58. # Create a custom field & check that initial value is null
  59. cf = CustomField.objects.create(
  60. name='longtext_field',
  61. type=CustomFieldTypeChoices.TYPE_LONGTEXT,
  62. required=False
  63. )
  64. cf.object_types.set([self.object_type])
  65. instance = Site.objects.first()
  66. self.assertIsNone(instance.custom_field_data[cf.name])
  67. # Assign a value and check that it is saved
  68. instance.custom_field_data[cf.name] = value
  69. instance.save()
  70. instance.refresh_from_db()
  71. self.assertEqual(instance.custom_field_data[cf.name], value)
  72. # Delete the stored value and check that it is now null
  73. instance.custom_field_data.pop(cf.name)
  74. instance.save()
  75. instance.refresh_from_db()
  76. self.assertIsNone(instance.custom_field_data.get(cf.name))
  77. def test_integer_field(self):
  78. # Create a custom field & check that initial value is null
  79. cf = CustomField.objects.create(
  80. name='integer_field',
  81. type=CustomFieldTypeChoices.TYPE_INTEGER,
  82. required=False
  83. )
  84. cf.object_types.set([self.object_type])
  85. instance = Site.objects.first()
  86. self.assertIsNone(instance.custom_field_data[cf.name])
  87. for value in (123456, 0, -123456):
  88. # Assign a value and check that it is saved
  89. instance.custom_field_data[cf.name] = value
  90. instance.save()
  91. instance.refresh_from_db()
  92. self.assertEqual(instance.custom_field_data[cf.name], value)
  93. # Delete the stored value and check that it is now null
  94. instance.custom_field_data.pop(cf.name)
  95. instance.save()
  96. instance.refresh_from_db()
  97. self.assertIsNone(instance.custom_field_data.get(cf.name))
  98. def test_decimal_field(self):
  99. # Create a custom field & check that initial value is null
  100. cf = CustomField.objects.create(
  101. name='decimal_field',
  102. type=CustomFieldTypeChoices.TYPE_DECIMAL,
  103. required=False
  104. )
  105. cf.object_types.set([self.object_type])
  106. instance = Site.objects.first()
  107. self.assertIsNone(instance.custom_field_data[cf.name])
  108. for value in (123456.54, 0, -123456.78):
  109. # Assign a value and check that it is saved
  110. instance.custom_field_data[cf.name] = value
  111. instance.save()
  112. instance.refresh_from_db()
  113. self.assertEqual(instance.custom_field_data[cf.name], value)
  114. # Delete the stored value and check that it is now null
  115. instance.custom_field_data.pop(cf.name)
  116. instance.save()
  117. instance.refresh_from_db()
  118. self.assertIsNone(instance.custom_field_data.get(cf.name))
  119. def test_boolean_field(self):
  120. # Create a custom field & check that initial value is null
  121. cf = CustomField.objects.create(
  122. name='boolean_field',
  123. type=CustomFieldTypeChoices.TYPE_INTEGER,
  124. required=False
  125. )
  126. cf.object_types.set([self.object_type])
  127. instance = Site.objects.first()
  128. self.assertIsNone(instance.custom_field_data[cf.name])
  129. for value in (True, False):
  130. # Assign a value and check that it is saved
  131. instance.custom_field_data[cf.name] = value
  132. instance.save()
  133. instance.refresh_from_db()
  134. self.assertEqual(instance.custom_field_data[cf.name], value)
  135. # Delete the stored value and check that it is now null
  136. instance.custom_field_data.pop(cf.name)
  137. instance.save()
  138. instance.refresh_from_db()
  139. self.assertIsNone(instance.custom_field_data.get(cf.name))
  140. def test_date_field(self):
  141. value = datetime.date(2016, 6, 23)
  142. # Create a custom field & check that initial value is null
  143. cf = CustomField.objects.create(
  144. name='date_field',
  145. type=CustomFieldTypeChoices.TYPE_DATE,
  146. required=False
  147. )
  148. cf.object_types.set([self.object_type])
  149. instance = Site.objects.first()
  150. self.assertIsNone(instance.custom_field_data[cf.name])
  151. # Assign a value and check that it is saved
  152. instance.custom_field_data[cf.name] = cf.serialize(value)
  153. instance.save()
  154. instance.refresh_from_db()
  155. self.assertEqual(instance.cf[cf.name], value)
  156. # Delete the stored value and check that it is now null
  157. instance.custom_field_data.pop(cf.name)
  158. instance.save()
  159. instance.refresh_from_db()
  160. self.assertIsNone(instance.custom_field_data.get(cf.name))
  161. def test_datetime_field(self):
  162. value = datetime.datetime(2016, 6, 23, 9, 45, 0)
  163. # Create a custom field & check that initial value is null
  164. cf = CustomField.objects.create(
  165. name='date_field',
  166. type=CustomFieldTypeChoices.TYPE_DATETIME,
  167. required=False
  168. )
  169. cf.object_types.set([self.object_type])
  170. instance = Site.objects.first()
  171. self.assertIsNone(instance.custom_field_data[cf.name])
  172. # Assign a value and check that it is saved
  173. instance.custom_field_data[cf.name] = cf.serialize(value)
  174. instance.save()
  175. instance.refresh_from_db()
  176. self.assertEqual(instance.cf[cf.name], value)
  177. # Delete the stored value and check that it is now null
  178. instance.custom_field_data.pop(cf.name)
  179. instance.save()
  180. instance.refresh_from_db()
  181. self.assertIsNone(instance.custom_field_data.get(cf.name))
  182. def test_url_field(self):
  183. value = 'http://example.com/'
  184. # Create a custom field & check that initial value is null
  185. cf = CustomField.objects.create(
  186. name='url_field',
  187. type=CustomFieldTypeChoices.TYPE_URL,
  188. required=False
  189. )
  190. cf.object_types.set([self.object_type])
  191. instance = Site.objects.first()
  192. self.assertIsNone(instance.custom_field_data[cf.name])
  193. # Assign a value and check that it is saved
  194. instance.custom_field_data[cf.name] = value
  195. instance.save()
  196. instance.refresh_from_db()
  197. self.assertEqual(instance.custom_field_data[cf.name], value)
  198. # Delete the stored value and check that it is now null
  199. instance.custom_field_data.pop(cf.name)
  200. instance.save()
  201. instance.refresh_from_db()
  202. self.assertIsNone(instance.custom_field_data.get(cf.name))
  203. def test_json_field(self):
  204. value = '{"foo": 1, "bar": 2}'
  205. # Create a custom field & check that initial value is null
  206. cf = CustomField.objects.create(
  207. name='json_field',
  208. type=CustomFieldTypeChoices.TYPE_JSON,
  209. required=False
  210. )
  211. cf.object_types.set([self.object_type])
  212. instance = Site.objects.first()
  213. self.assertIsNone(instance.custom_field_data[cf.name])
  214. # Assign a value and check that it is saved
  215. instance.custom_field_data[cf.name] = value
  216. instance.save()
  217. instance.refresh_from_db()
  218. self.assertEqual(instance.custom_field_data[cf.name], value)
  219. # Delete the stored value and check that it is now null
  220. instance.custom_field_data.pop(cf.name)
  221. instance.save()
  222. instance.refresh_from_db()
  223. self.assertIsNone(instance.custom_field_data.get(cf.name))
  224. def test_select_field(self):
  225. CHOICES = (
  226. ('a', 'Option A'),
  227. ('b', 'Option B'),
  228. ('c', 'Option C'),
  229. )
  230. value = 'a'
  231. # Create a set of custom field choices
  232. choice_set = CustomFieldChoiceSet.objects.create(
  233. name='Custom Field Choice Set 1',
  234. extra_choices=CHOICES
  235. )
  236. # Create a custom field & check that initial value is null
  237. cf = CustomField.objects.create(
  238. name='select_field',
  239. type=CustomFieldTypeChoices.TYPE_SELECT,
  240. required=False,
  241. choice_set=choice_set
  242. )
  243. cf.object_types.set([self.object_type])
  244. instance = Site.objects.first()
  245. self.assertIsNone(instance.custom_field_data[cf.name])
  246. # Assign a value and check that it is saved
  247. instance.custom_field_data[cf.name] = value
  248. instance.save()
  249. instance.refresh_from_db()
  250. self.assertEqual(instance.custom_field_data[cf.name], value)
  251. # Delete the stored value and check that it is now null
  252. instance.custom_field_data.pop(cf.name)
  253. instance.save()
  254. instance.refresh_from_db()
  255. self.assertIsNone(instance.custom_field_data.get(cf.name))
  256. def test_multiselect_field(self):
  257. CHOICES = (
  258. ('a', 'Option A'),
  259. ('b', 'Option B'),
  260. ('c', 'Option C'),
  261. )
  262. value = ['a', 'b']
  263. # Create a set of custom field choices
  264. choice_set = CustomFieldChoiceSet.objects.create(
  265. name='Custom Field Choice Set 1',
  266. extra_choices=CHOICES
  267. )
  268. # Create a custom field & check that initial value is null
  269. cf = CustomField.objects.create(
  270. name='multiselect_field',
  271. type=CustomFieldTypeChoices.TYPE_MULTISELECT,
  272. required=False,
  273. choice_set=choice_set
  274. )
  275. cf.object_types.set([self.object_type])
  276. instance = Site.objects.first()
  277. self.assertIsNone(instance.custom_field_data[cf.name])
  278. # Assign a value and check that it is saved
  279. instance.custom_field_data[cf.name] = value
  280. instance.save()
  281. instance.refresh_from_db()
  282. self.assertEqual(instance.custom_field_data[cf.name], value)
  283. # Delete the stored value and check that it is now null
  284. instance.custom_field_data.pop(cf.name)
  285. instance.save()
  286. instance.refresh_from_db()
  287. self.assertIsNone(instance.custom_field_data.get(cf.name))
  288. def test_object_field(self):
  289. value = VLAN.objects.create(name='VLAN 1', vid=1).pk
  290. # Create a custom field & check that initial value is null
  291. cf = CustomField.objects.create(
  292. name='object_field',
  293. type=CustomFieldTypeChoices.TYPE_OBJECT,
  294. object_type=ObjectType.objects.get_for_model(VLAN),
  295. required=False
  296. )
  297. cf.object_types.set([self.object_type])
  298. instance = Site.objects.first()
  299. self.assertIsNone(instance.custom_field_data[cf.name])
  300. # Assign a value and check that it is saved
  301. instance.custom_field_data[cf.name] = value
  302. instance.save()
  303. instance.refresh_from_db()
  304. self.assertEqual(instance.custom_field_data[cf.name], value)
  305. # Delete the stored value and check that it is now null
  306. instance.custom_field_data.pop(cf.name)
  307. instance.save()
  308. instance.refresh_from_db()
  309. self.assertIsNone(instance.custom_field_data.get(cf.name))
  310. def test_multiobject_field(self):
  311. vlans = (
  312. VLAN(name='VLAN 1', vid=1),
  313. VLAN(name='VLAN 2', vid=2),
  314. VLAN(name='VLAN 3', vid=3),
  315. )
  316. VLAN.objects.bulk_create(vlans)
  317. value = [vlan.pk for vlan in vlans]
  318. # Create a custom field & check that initial value is null
  319. cf = CustomField.objects.create(
  320. name='object_field',
  321. type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
  322. object_type=ObjectType.objects.get_for_model(VLAN),
  323. required=False
  324. )
  325. cf.object_types.set([self.object_type])
  326. instance = Site.objects.first()
  327. self.assertIsNone(instance.custom_field_data[cf.name])
  328. # Assign a value and check that it is saved
  329. instance.custom_field_data[cf.name] = value
  330. instance.save()
  331. instance.refresh_from_db()
  332. self.assertEqual(instance.custom_field_data[cf.name], value)
  333. # Delete the stored value and check that it is now null
  334. instance.custom_field_data.pop(cf.name)
  335. instance.save()
  336. instance.refresh_from_db()
  337. self.assertIsNone(instance.custom_field_data.get(cf.name))
  338. def test_rename_customfield(self):
  339. obj_type = ObjectType.objects.get_for_model(Site)
  340. FIELD_DATA = 'abc'
  341. # Create a custom field
  342. cf = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='field1')
  343. cf.save()
  344. cf.object_types.set([obj_type])
  345. # Assign custom field data to an object
  346. site = Site.objects.create(
  347. name='Site 1',
  348. slug='site-1',
  349. custom_field_data={'field1': FIELD_DATA}
  350. )
  351. site.refresh_from_db()
  352. self.assertEqual(site.custom_field_data['field1'], FIELD_DATA)
  353. # Rename the custom field
  354. cf.name = 'field2'
  355. cf.save()
  356. # Check that custom field data on the object has been updated
  357. site.refresh_from_db()
  358. self.assertNotIn('field1', site.custom_field_data)
  359. self.assertEqual(site.custom_field_data['field2'], FIELD_DATA)
  360. def test_default_value_validation(self):
  361. choiceset = CustomFieldChoiceSet.objects.create(
  362. name="Test Choice Set",
  363. extra_choices=(
  364. ('choice1', 'Choice 1'),
  365. ('choice2', 'Choice 2'),
  366. )
  367. )
  368. site = Site.objects.create(name='Site 1', slug='site-1')
  369. object_type = ObjectType.objects.get_for_model(Site)
  370. # Text
  371. CustomField(name='test', type='text', required=True, default="Default text").full_clean()
  372. # Integer
  373. CustomField(name='test', type='integer', required=True, default=1).full_clean()
  374. with self.assertRaises(ValidationError):
  375. CustomField(name='test', type='integer', required=True, default='xxx').full_clean()
  376. # Boolean
  377. CustomField(name='test', type='boolean', required=True, default=True).full_clean()
  378. with self.assertRaises(ValidationError):
  379. CustomField(name='test', type='boolean', required=True, default='xxx').full_clean()
  380. # Date
  381. CustomField(name='test', type='date', required=True, default="2023-02-25").full_clean()
  382. with self.assertRaises(ValidationError):
  383. CustomField(name='test', type='date', required=True, default='xxx').full_clean()
  384. # Datetime
  385. CustomField(name='test', type='datetime', required=True, default="2023-02-25 02:02:02").full_clean()
  386. with self.assertRaises(ValidationError):
  387. CustomField(name='test', type='datetime', required=True, default='xxx').full_clean()
  388. # URL
  389. CustomField(name='test', type='url', required=True, default="https://www.netbox.dev").full_clean()
  390. # JSON
  391. CustomField(name='test', type='json', required=True, default='{"test": "object"}').full_clean()
  392. # Selection
  393. CustomField(name='test', type='select', required=True, choice_set=choiceset, default='choice1').full_clean()
  394. with self.assertRaises(ValidationError):
  395. CustomField(name='test', type='select', required=True, choice_set=choiceset, default='xxx').full_clean()
  396. # Multi-select
  397. CustomField(
  398. name='test',
  399. type='multiselect',
  400. required=True,
  401. choice_set=choiceset,
  402. default=['choice1'] # Single default choice
  403. ).full_clean()
  404. CustomField(
  405. name='test',
  406. type='multiselect',
  407. required=True,
  408. choice_set=choiceset,
  409. default=['choice1', 'choice2'] # Multiple default choices
  410. ).full_clean()
  411. with self.assertRaises(ValidationError):
  412. CustomField(
  413. name='test',
  414. type='multiselect',
  415. required=True,
  416. choice_set=choiceset,
  417. default=['xxx']
  418. ).full_clean()
  419. # Object
  420. CustomField(name='test', type='object', required=True, object_type=object_type, default=site.pk).full_clean()
  421. with self.assertRaises(ValidationError):
  422. CustomField(name='test', type='object', required=True, object_type=object_type, default="xxx").full_clean()
  423. # Multi-object
  424. CustomField(
  425. name='test',
  426. type='multiobject',
  427. required=True,
  428. object_type=object_type,
  429. default=[site.pk]
  430. ).full_clean()
  431. with self.assertRaises(ValidationError):
  432. CustomField(
  433. name='test',
  434. type='multiobject',
  435. required=True,
  436. object_type=object_type,
  437. default=["xxx"]
  438. ).full_clean()
  439. class CustomFieldManagerTest(TestCase):
  440. @classmethod
  441. def setUpTestData(cls):
  442. object_type = ObjectType.objects.get_for_model(Site)
  443. custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
  444. custom_field.save()
  445. custom_field.object_types.set([object_type])
  446. def test_get_for_model(self):
  447. self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
  448. self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0)
  449. class CustomFieldAPITest(APITestCase):
  450. @classmethod
  451. def setUpTestData(cls):
  452. object_type = ObjectType.objects.get_for_model(Site)
  453. # Create some VLANs
  454. vlans = (
  455. VLAN(name='VLAN 1', vid=1),
  456. VLAN(name='VLAN 2', vid=2),
  457. VLAN(name='VLAN 3', vid=3),
  458. VLAN(name='VLAN 4', vid=4),
  459. VLAN(name='VLAN 5', vid=5),
  460. )
  461. VLAN.objects.bulk_create(vlans)
  462. # Create a set of custom field choices
  463. choice_set = CustomFieldChoiceSet.objects.create(
  464. name='Custom Field Choice Set 1',
  465. extra_choices=(('foo', 'Foo'), ('bar', 'Bar'), ('baz', 'Baz'))
  466. )
  467. custom_fields = (
  468. CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo'),
  469. CustomField(type=CustomFieldTypeChoices.TYPE_LONGTEXT, name='longtext_field', default='ABC'),
  470. CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='integer_field', default=123),
  471. CustomField(type=CustomFieldTypeChoices.TYPE_DECIMAL, name='decimal_field', default=123.45),
  472. CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='boolean_field', default=False),
  473. CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='date_field', default='2020-01-01'),
  474. CustomField(type=CustomFieldTypeChoices.TYPE_DATETIME, name='datetime_field', default='2020-01-01T01:23:45'),
  475. CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='url_field', default='http://example.com/1'),
  476. CustomField(type=CustomFieldTypeChoices.TYPE_JSON, name='json_field', default='{"x": "y"}'),
  477. CustomField(
  478. type=CustomFieldTypeChoices.TYPE_SELECT,
  479. name='select_field',
  480. default='foo',
  481. choice_set=choice_set
  482. ),
  483. CustomField(
  484. type=CustomFieldTypeChoices.TYPE_MULTISELECT,
  485. name='multiselect_field',
  486. default=['foo'],
  487. choice_set=choice_set
  488. ),
  489. CustomField(
  490. type=CustomFieldTypeChoices.TYPE_OBJECT,
  491. name='object_field',
  492. object_type=ObjectType.objects.get_for_model(VLAN),
  493. default=vlans[0].pk,
  494. ),
  495. CustomField(
  496. type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
  497. name='multiobject_field',
  498. object_type=ObjectType.objects.get_for_model(VLAN),
  499. default=[vlans[0].pk, vlans[1].pk],
  500. ),
  501. )
  502. for cf in custom_fields:
  503. cf.save()
  504. cf.object_types.set([object_type])
  505. # Create some sites *after* creating the custom fields. This ensures that
  506. # default values are not set for the assigned objects.
  507. sites = (
  508. Site(name='Site 1', slug='site-1'),
  509. Site(name='Site 2', slug='site-2'),
  510. )
  511. Site.objects.bulk_create(sites)
  512. # Assign custom field values for site 2
  513. sites[1].custom_field_data = {
  514. custom_fields[0].name: 'bar',
  515. custom_fields[1].name: 'DEF',
  516. custom_fields[2].name: 456,
  517. custom_fields[3].name: Decimal('456.78'),
  518. custom_fields[4].name: True,
  519. custom_fields[5].name: '2020-01-02',
  520. custom_fields[6].name: '2020-01-02 12:00:00',
  521. custom_fields[7].name: 'http://example.com/2',
  522. custom_fields[8].name: '{"foo": 1, "bar": 2}',
  523. custom_fields[9].name: 'bar',
  524. custom_fields[10].name: ['bar', 'baz'],
  525. custom_fields[11].name: vlans[1].pk,
  526. custom_fields[12].name: [vlans[2].pk, vlans[3].pk],
  527. }
  528. sites[1].save()
  529. def test_get_custom_fields(self):
  530. TYPES = {
  531. CustomFieldTypeChoices.TYPE_TEXT: 'string',
  532. CustomFieldTypeChoices.TYPE_LONGTEXT: 'string',
  533. CustomFieldTypeChoices.TYPE_INTEGER: 'integer',
  534. CustomFieldTypeChoices.TYPE_DECIMAL: 'decimal',
  535. CustomFieldTypeChoices.TYPE_BOOLEAN: 'boolean',
  536. CustomFieldTypeChoices.TYPE_DATE: 'string',
  537. CustomFieldTypeChoices.TYPE_DATETIME: 'string',
  538. CustomFieldTypeChoices.TYPE_URL: 'string',
  539. CustomFieldTypeChoices.TYPE_JSON: 'object',
  540. CustomFieldTypeChoices.TYPE_SELECT: 'string',
  541. CustomFieldTypeChoices.TYPE_MULTISELECT: 'array',
  542. CustomFieldTypeChoices.TYPE_OBJECT: 'object',
  543. CustomFieldTypeChoices.TYPE_MULTIOBJECT: 'array',
  544. }
  545. self.add_permissions('extras.view_customfield')
  546. url = reverse('extras-api:customfield-list')
  547. response = self.client.get(url, **self.header)
  548. self.assertEqual(response.data['count'], len(TYPES))
  549. # Validate data types
  550. for customfield in response.data['results']:
  551. cf_type = customfield['type']['value']
  552. self.assertEqual(customfield['data_type'], TYPES[cf_type])
  553. def test_get_single_object_without_custom_field_data(self):
  554. """
  555. Validate that custom fields are present on an object even if it has no values defined.
  556. """
  557. site1 = Site.objects.get(name='Site 1')
  558. url = reverse('dcim-api:site-detail', kwargs={'pk': site1.pk})
  559. self.add_permissions('dcim.view_site')
  560. response = self.client.get(url, **self.header)
  561. self.assertEqual(response.data['name'], site1.name)
  562. self.assertEqual(response.data['custom_fields'], {
  563. 'text_field': None,
  564. 'longtext_field': None,
  565. 'integer_field': None,
  566. 'decimal_field': None,
  567. 'boolean_field': None,
  568. 'date_field': None,
  569. 'datetime_field': None,
  570. 'url_field': None,
  571. 'json_field': None,
  572. 'select_field': None,
  573. 'multiselect_field': None,
  574. 'object_field': None,
  575. 'multiobject_field': None,
  576. })
  577. def test_get_single_object_with_custom_field_data(self):
  578. """
  579. Validate that custom fields are present and correctly set for an object with values defined.
  580. """
  581. site2 = Site.objects.get(name='Site 2')
  582. site2_cfvs = site2.cf
  583. url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})
  584. self.add_permissions('dcim.view_site')
  585. response = self.client.get(url, **self.header)
  586. self.assertEqual(response.data['name'], site2.name)
  587. self.assertEqual(response.data['custom_fields']['text_field'], site2_cfvs['text_field'])
  588. self.assertEqual(response.data['custom_fields']['longtext_field'], site2_cfvs['longtext_field'])
  589. self.assertEqual(response.data['custom_fields']['integer_field'], site2_cfvs['integer_field'])
  590. self.assertEqual(response.data['custom_fields']['decimal_field'], site2_cfvs['decimal_field'])
  591. self.assertEqual(response.data['custom_fields']['boolean_field'], site2_cfvs['boolean_field'])
  592. self.assertEqual(response.data['custom_fields']['date_field'], site2_cfvs['date_field'])
  593. self.assertEqual(response.data['custom_fields']['datetime_field'], site2_cfvs['datetime_field'])
  594. self.assertEqual(response.data['custom_fields']['url_field'], site2_cfvs['url_field'])
  595. self.assertEqual(response.data['custom_fields']['json_field'], site2_cfvs['json_field'])
  596. self.assertEqual(response.data['custom_fields']['select_field'], site2_cfvs['select_field'])
  597. self.assertEqual(response.data['custom_fields']['multiselect_field'], site2_cfvs['multiselect_field'])
  598. self.assertEqual(response.data['custom_fields']['object_field']['id'], site2_cfvs['object_field'].pk)
  599. self.assertEqual(
  600. [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
  601. [obj.pk for obj in site2_cfvs['multiobject_field']]
  602. )
  603. def test_create_single_object_with_defaults(self):
  604. """
  605. Create a new site with no specified custom field values and check that it received the default values.
  606. """
  607. cf_defaults = {
  608. cf.name: cf.default for cf in CustomField.objects.all()
  609. }
  610. data = {
  611. 'name': 'Site 3',
  612. 'slug': 'site-3',
  613. }
  614. url = reverse('dcim-api:site-list')
  615. self.add_permissions('dcim.add_site')
  616. response = self.client.post(url, data, format='json', **self.header)
  617. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  618. # Validate response data
  619. response_cf = response.data['custom_fields']
  620. self.assertEqual(response_cf['text_field'], cf_defaults['text_field'])
  621. self.assertEqual(response_cf['longtext_field'], cf_defaults['longtext_field'])
  622. self.assertEqual(response_cf['integer_field'], cf_defaults['integer_field'])
  623. self.assertEqual(response_cf['decimal_field'], cf_defaults['decimal_field'])
  624. self.assertEqual(response_cf['boolean_field'], cf_defaults['boolean_field'])
  625. self.assertEqual(response_cf['date_field'].isoformat(), cf_defaults['date_field'])
  626. self.assertEqual(response_cf['datetime_field'].isoformat(), cf_defaults['datetime_field'])
  627. self.assertEqual(response_cf['url_field'], cf_defaults['url_field'])
  628. self.assertEqual(response_cf['json_field'], cf_defaults['json_field'])
  629. self.assertEqual(response_cf['select_field'], cf_defaults['select_field'])
  630. self.assertEqual(response_cf['multiselect_field'], cf_defaults['multiselect_field'])
  631. self.assertEqual(response_cf['object_field']['id'], cf_defaults['object_field'])
  632. self.assertEqual(
  633. [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
  634. cf_defaults['multiobject_field']
  635. )
  636. # Validate database data
  637. site = Site.objects.get(pk=response.data['id'])
  638. self.assertEqual(site.custom_field_data['text_field'], cf_defaults['text_field'])
  639. self.assertEqual(site.custom_field_data['longtext_field'], cf_defaults['longtext_field'])
  640. self.assertEqual(site.custom_field_data['integer_field'], cf_defaults['integer_field'])
  641. self.assertEqual(site.custom_field_data['decimal_field'], cf_defaults['decimal_field'])
  642. self.assertEqual(site.custom_field_data['boolean_field'], cf_defaults['boolean_field'])
  643. self.assertEqual(site.custom_field_data['date_field'], cf_defaults['date_field'])
  644. self.assertEqual(site.custom_field_data['datetime_field'], cf_defaults['datetime_field'])
  645. self.assertEqual(site.custom_field_data['url_field'], cf_defaults['url_field'])
  646. self.assertEqual(site.custom_field_data['json_field'], cf_defaults['json_field'])
  647. self.assertEqual(site.custom_field_data['select_field'], cf_defaults['select_field'])
  648. self.assertEqual(site.custom_field_data['multiselect_field'], cf_defaults['multiselect_field'])
  649. self.assertEqual(site.custom_field_data['object_field'], cf_defaults['object_field'])
  650. self.assertEqual(site.custom_field_data['multiobject_field'], cf_defaults['multiobject_field'])
  651. def test_create_single_object_with_values(self):
  652. """
  653. Create a single new site with a value for each type of custom field.
  654. """
  655. data = {
  656. 'name': 'Site 3',
  657. 'slug': 'site-3',
  658. 'custom_fields': {
  659. 'text_field': 'bar',
  660. 'longtext_field': 'blah blah blah',
  661. 'integer_field': 456,
  662. 'decimal_field': 456.78,
  663. 'boolean_field': True,
  664. 'date_field': datetime.date(2020, 1, 2),
  665. 'datetime_field': datetime.datetime(2020, 1, 2, 12, 0, 0),
  666. 'url_field': 'http://example.com/2',
  667. 'json_field': '{"foo": 1, "bar": 2}',
  668. 'select_field': 'bar',
  669. 'multiselect_field': ['bar', 'baz'],
  670. 'object_field': VLAN.objects.get(vid=2).pk,
  671. 'multiobject_field': list(VLAN.objects.filter(vid__in=[3, 4]).values_list('pk', flat=True)),
  672. },
  673. }
  674. url = reverse('dcim-api:site-list')
  675. self.add_permissions('dcim.add_site')
  676. response = self.client.post(url, data, format='json', **self.header)
  677. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  678. # Validate response data
  679. response_cf = response.data['custom_fields']
  680. data_cf = data['custom_fields']
  681. self.assertEqual(response_cf['text_field'], data_cf['text_field'])
  682. self.assertEqual(response_cf['longtext_field'], data_cf['longtext_field'])
  683. self.assertEqual(response_cf['integer_field'], data_cf['integer_field'])
  684. self.assertEqual(response_cf['decimal_field'], data_cf['decimal_field'])
  685. self.assertEqual(response_cf['boolean_field'], data_cf['boolean_field'])
  686. self.assertEqual(response_cf['date_field'], data_cf['date_field'])
  687. self.assertEqual(response_cf['datetime_field'], data_cf['datetime_field'])
  688. self.assertEqual(response_cf['url_field'], data_cf['url_field'])
  689. self.assertEqual(response_cf['json_field'], data_cf['json_field'])
  690. self.assertEqual(response_cf['select_field'], data_cf['select_field'])
  691. self.assertEqual(response_cf['multiselect_field'], data_cf['multiselect_field'])
  692. self.assertEqual(response_cf['object_field']['id'], data_cf['object_field'])
  693. self.assertEqual(
  694. [obj['id'] for obj in response_cf['multiobject_field']],
  695. data_cf['multiobject_field']
  696. )
  697. # Validate database data
  698. site = Site.objects.get(pk=response.data['id'])
  699. self.assertEqual(site.custom_field_data['text_field'], data_cf['text_field'])
  700. self.assertEqual(site.custom_field_data['longtext_field'], data_cf['longtext_field'])
  701. self.assertEqual(site.custom_field_data['integer_field'], data_cf['integer_field'])
  702. self.assertEqual(site.custom_field_data['decimal_field'], data_cf['decimal_field'])
  703. self.assertEqual(site.custom_field_data['boolean_field'], data_cf['boolean_field'])
  704. self.assertEqual(site.cf['date_field'], data_cf['date_field'])
  705. self.assertEqual(site.cf['datetime_field'], data_cf['datetime_field'])
  706. self.assertEqual(site.custom_field_data['url_field'], data_cf['url_field'])
  707. self.assertEqual(site.custom_field_data['json_field'], data_cf['json_field'])
  708. self.assertEqual(site.custom_field_data['select_field'], data_cf['select_field'])
  709. self.assertEqual(site.custom_field_data['multiselect_field'], data_cf['multiselect_field'])
  710. self.assertEqual(site.custom_field_data['object_field'], data_cf['object_field'])
  711. self.assertEqual(site.custom_field_data['multiobject_field'], data_cf['multiobject_field'])
  712. def test_create_multiple_objects_with_defaults(self):
  713. """
  714. Create three new sites with no specified custom field values and check that each received
  715. the default custom field values.
  716. """
  717. cf_defaults = {
  718. cf.name: cf.default for cf in CustomField.objects.all()
  719. }
  720. data = (
  721. {
  722. 'name': 'Site 3',
  723. 'slug': 'site-3',
  724. },
  725. {
  726. 'name': 'Site 4',
  727. 'slug': 'site-4',
  728. },
  729. {
  730. 'name': 'Site 5',
  731. 'slug': 'site-5',
  732. },
  733. )
  734. url = reverse('dcim-api:site-list')
  735. self.add_permissions('dcim.add_site')
  736. response = self.client.post(url, data, format='json', **self.header)
  737. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  738. self.assertEqual(len(response.data), len(data))
  739. for i, obj in enumerate(data):
  740. # Validate response data
  741. response_cf = response.data[i]['custom_fields']
  742. self.assertEqual(response_cf['text_field'], cf_defaults['text_field'])
  743. self.assertEqual(response_cf['longtext_field'], cf_defaults['longtext_field'])
  744. self.assertEqual(response_cf['integer_field'], cf_defaults['integer_field'])
  745. self.assertEqual(response_cf['decimal_field'], cf_defaults['decimal_field'])
  746. self.assertEqual(response_cf['boolean_field'], cf_defaults['boolean_field'])
  747. self.assertEqual(response_cf['date_field'].isoformat(), cf_defaults['date_field'])
  748. self.assertEqual(response_cf['datetime_field'].isoformat(), cf_defaults['datetime_field'])
  749. self.assertEqual(response_cf['url_field'], cf_defaults['url_field'])
  750. self.assertEqual(response_cf['json_field'], cf_defaults['json_field'])
  751. self.assertEqual(response_cf['select_field'], cf_defaults['select_field'])
  752. self.assertEqual(response_cf['multiselect_field'], cf_defaults['multiselect_field'])
  753. self.assertEqual(response_cf['object_field']['id'], cf_defaults['object_field'])
  754. self.assertEqual(
  755. [obj['id'] for obj in response_cf['multiobject_field']],
  756. cf_defaults['multiobject_field']
  757. )
  758. # Validate database data
  759. site = Site.objects.get(pk=response.data[i]['id'])
  760. self.assertEqual(site.custom_field_data['text_field'], cf_defaults['text_field'])
  761. self.assertEqual(site.custom_field_data['longtext_field'], cf_defaults['longtext_field'])
  762. self.assertEqual(site.custom_field_data['integer_field'], cf_defaults['integer_field'])
  763. self.assertEqual(site.custom_field_data['decimal_field'], cf_defaults['decimal_field'])
  764. self.assertEqual(site.custom_field_data['boolean_field'], cf_defaults['boolean_field'])
  765. self.assertEqual(site.custom_field_data['date_field'], cf_defaults['date_field'])
  766. self.assertEqual(site.custom_field_data['datetime_field'], cf_defaults['datetime_field'])
  767. self.assertEqual(site.custom_field_data['url_field'], cf_defaults['url_field'])
  768. self.assertEqual(site.custom_field_data['json_field'], cf_defaults['json_field'])
  769. self.assertEqual(site.custom_field_data['select_field'], cf_defaults['select_field'])
  770. self.assertEqual(site.custom_field_data['multiselect_field'], cf_defaults['multiselect_field'])
  771. self.assertEqual(site.custom_field_data['object_field'], cf_defaults['object_field'])
  772. self.assertEqual(site.custom_field_data['multiobject_field'], cf_defaults['multiobject_field'])
  773. def test_create_multiple_objects_with_values(self):
  774. """
  775. Create a three new sites, each with custom fields defined.
  776. """
  777. custom_field_data = {
  778. 'text_field': 'bar',
  779. 'longtext_field': 'abcdefghij',
  780. 'integer_field': 456,
  781. 'decimal_field': 456.78,
  782. 'boolean_field': True,
  783. 'date_field': datetime.date(2020, 1, 2),
  784. 'datetime_field': datetime.datetime(2020, 1, 2, 12, 0, 0),
  785. 'url_field': 'http://example.com/2',
  786. 'json_field': '{"foo": 1, "bar": 2}',
  787. 'select_field': 'bar',
  788. 'multiselect_field': ['bar', 'baz'],
  789. 'object_field': VLAN.objects.get(vid=2).pk,
  790. 'multiobject_field': list(VLAN.objects.filter(vid__in=[3, 4]).values_list('pk', flat=True)),
  791. }
  792. data = (
  793. {
  794. 'name': 'Site 3',
  795. 'slug': 'site-3',
  796. 'custom_fields': custom_field_data,
  797. },
  798. {
  799. 'name': 'Site 4',
  800. 'slug': 'site-4',
  801. 'custom_fields': custom_field_data,
  802. },
  803. {
  804. 'name': 'Site 5',
  805. 'slug': 'site-5',
  806. 'custom_fields': custom_field_data,
  807. },
  808. )
  809. url = reverse('dcim-api:site-list')
  810. self.add_permissions('dcim.add_site')
  811. response = self.client.post(url, data, format='json', **self.header)
  812. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  813. self.assertEqual(len(response.data), len(data))
  814. for i, obj in enumerate(data):
  815. # Validate response data
  816. response_cf = response.data[i]['custom_fields']
  817. self.assertEqual(response_cf['text_field'], custom_field_data['text_field'])
  818. self.assertEqual(response_cf['longtext_field'], custom_field_data['longtext_field'])
  819. self.assertEqual(response_cf['integer_field'], custom_field_data['integer_field'])
  820. self.assertEqual(response_cf['decimal_field'], custom_field_data['decimal_field'])
  821. self.assertEqual(response_cf['boolean_field'], custom_field_data['boolean_field'])
  822. self.assertEqual(response_cf['date_field'], custom_field_data['date_field'])
  823. self.assertEqual(response_cf['datetime_field'], custom_field_data['datetime_field'])
  824. self.assertEqual(response_cf['url_field'], custom_field_data['url_field'])
  825. self.assertEqual(response_cf['json_field'], custom_field_data['json_field'])
  826. self.assertEqual(response_cf['select_field'], custom_field_data['select_field'])
  827. self.assertEqual(response_cf['multiselect_field'], custom_field_data['multiselect_field'])
  828. self.assertEqual(response_cf['object_field']['id'], custom_field_data['object_field'])
  829. self.assertEqual(
  830. [obj['id'] for obj in response_cf['multiobject_field']],
  831. custom_field_data['multiobject_field']
  832. )
  833. # Validate database data
  834. site = Site.objects.get(pk=response.data[i]['id'])
  835. self.assertEqual(site.custom_field_data['text_field'], custom_field_data['text_field'])
  836. self.assertEqual(site.custom_field_data['longtext_field'], custom_field_data['longtext_field'])
  837. self.assertEqual(site.custom_field_data['integer_field'], custom_field_data['integer_field'])
  838. self.assertEqual(site.custom_field_data['decimal_field'], custom_field_data['decimal_field'])
  839. self.assertEqual(site.custom_field_data['boolean_field'], custom_field_data['boolean_field'])
  840. self.assertEqual(site.cf['date_field'], custom_field_data['date_field'])
  841. self.assertEqual(site.cf['datetime_field'], custom_field_data['datetime_field'])
  842. self.assertEqual(site.custom_field_data['url_field'], custom_field_data['url_field'])
  843. self.assertEqual(site.custom_field_data['json_field'], custom_field_data['json_field'])
  844. self.assertEqual(site.custom_field_data['select_field'], custom_field_data['select_field'])
  845. self.assertEqual(site.custom_field_data['multiselect_field'], custom_field_data['multiselect_field'])
  846. self.assertEqual(site.custom_field_data['object_field'], custom_field_data['object_field'])
  847. self.assertEqual(site.custom_field_data['multiobject_field'], custom_field_data['multiobject_field'])
  848. def test_update_single_object_with_values(self):
  849. """
  850. Update an object with existing custom field values. Ensure that only the updated custom field values are
  851. modified.
  852. """
  853. site2 = Site.objects.get(name='Site 2')
  854. original_cfvs = {**site2.cf}
  855. data = {
  856. 'custom_fields': {
  857. 'text_field': 'ABCD',
  858. 'integer_field': 1234,
  859. },
  860. }
  861. url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})
  862. self.add_permissions('dcim.change_site')
  863. response = self.client.patch(url, data, format='json', **self.header)
  864. self.assertHttpStatus(response, status.HTTP_200_OK)
  865. # Validate response data
  866. response_cf = response.data['custom_fields']
  867. self.assertEqual(response_cf['text_field'], data['custom_fields']['text_field'])
  868. self.assertEqual(response_cf['longtext_field'], original_cfvs['longtext_field'])
  869. self.assertEqual(response_cf['integer_field'], data['custom_fields']['integer_field'])
  870. self.assertEqual(response_cf['decimal_field'], original_cfvs['decimal_field'])
  871. self.assertEqual(response_cf['boolean_field'], original_cfvs['boolean_field'])
  872. self.assertEqual(response_cf['date_field'], original_cfvs['date_field'])
  873. self.assertEqual(response_cf['datetime_field'], original_cfvs['datetime_field'])
  874. self.assertEqual(response_cf['url_field'], original_cfvs['url_field'])
  875. self.assertEqual(response_cf['json_field'], original_cfvs['json_field'])
  876. self.assertEqual(response_cf['select_field'], original_cfvs['select_field'])
  877. self.assertEqual(response_cf['multiselect_field'], original_cfvs['multiselect_field'])
  878. self.assertEqual(response_cf['object_field']['id'], original_cfvs['object_field'].pk)
  879. self.assertListEqual(
  880. [obj['id'] for obj in response_cf['multiobject_field']],
  881. [obj.pk for obj in original_cfvs['multiobject_field']]
  882. )
  883. # Validate database data
  884. site2 = Site.objects.get(pk=site2.pk)
  885. self.assertEqual(site2.cf['text_field'], data['custom_fields']['text_field'])
  886. self.assertEqual(site2.cf['longtext_field'], original_cfvs['longtext_field'])
  887. self.assertEqual(site2.cf['integer_field'], data['custom_fields']['integer_field'])
  888. self.assertEqual(site2.cf['decimal_field'], original_cfvs['decimal_field'])
  889. self.assertEqual(site2.cf['boolean_field'], original_cfvs['boolean_field'])
  890. self.assertEqual(site2.cf['date_field'], original_cfvs['date_field'])
  891. self.assertEqual(site2.cf['datetime_field'], original_cfvs['datetime_field'])
  892. self.assertEqual(site2.cf['url_field'], original_cfvs['url_field'])
  893. self.assertEqual(site2.cf['json_field'], original_cfvs['json_field'])
  894. self.assertEqual(site2.cf['select_field'], original_cfvs['select_field'])
  895. self.assertEqual(site2.cf['multiselect_field'], original_cfvs['multiselect_field'])
  896. self.assertEqual(site2.cf['object_field'], original_cfvs['object_field'])
  897. self.assertListEqual(
  898. list(site2.cf['multiobject_field']),
  899. list(original_cfvs['multiobject_field'])
  900. )
  901. def test_specify_related_object_by_attr(self):
  902. site1 = Site.objects.get(name='Site 1')
  903. vlans = VLAN.objects.all()[:3]
  904. url = reverse('dcim-api:site-detail', kwargs={'pk': site1.pk})
  905. self.add_permissions('dcim.change_site')
  906. # Set related objects by PK
  907. data = {
  908. 'custom_fields': {
  909. 'object_field': vlans[0].pk,
  910. 'multiobject_field': [vlans[1].pk, vlans[2].pk],
  911. },
  912. }
  913. response = self.client.patch(url, data, format='json', **self.header)
  914. self.assertHttpStatus(response, status.HTTP_200_OK)
  915. self.assertEqual(
  916. response.data['custom_fields']['object_field']['id'],
  917. vlans[0].pk
  918. )
  919. self.assertListEqual(
  920. [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
  921. [vlans[1].pk, vlans[2].pk]
  922. )
  923. # Set related objects by name
  924. data = {
  925. 'custom_fields': {
  926. 'object_field': {
  927. 'name': vlans[0].name,
  928. },
  929. 'multiobject_field': [
  930. {
  931. 'name': vlans[1].name
  932. },
  933. {
  934. 'name': vlans[2].name
  935. },
  936. ],
  937. },
  938. }
  939. response = self.client.patch(url, data, format='json', **self.header)
  940. self.assertHttpStatus(response, status.HTTP_200_OK)
  941. self.assertEqual(
  942. response.data['custom_fields']['object_field']['id'],
  943. vlans[0].pk
  944. )
  945. self.assertListEqual(
  946. [obj['id'] for obj in response.data['custom_fields']['multiobject_field']],
  947. [vlans[1].pk, vlans[2].pk]
  948. )
  949. # Clear related objects
  950. data = {
  951. 'custom_fields': {
  952. 'object_field': None,
  953. 'multiobject_field': [],
  954. },
  955. }
  956. response = self.client.patch(url, data, format='json', **self.header)
  957. self.assertHttpStatus(response, status.HTTP_200_OK)
  958. self.assertIsNone(response.data['custom_fields']['object_field'])
  959. self.assertListEqual(response.data['custom_fields']['multiobject_field'], [])
  960. def test_minimum_maximum_values_validation(self):
  961. site2 = Site.objects.get(name='Site 2')
  962. url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})
  963. self.add_permissions('dcim.change_site')
  964. cf_integer = CustomField.objects.get(name='integer_field')
  965. cf_integer.validation_minimum = 10
  966. cf_integer.validation_maximum = 20
  967. cf_integer.save()
  968. data = {'custom_fields': {'integer_field': 9}}
  969. response = self.client.patch(url, data, format='json', **self.header)
  970. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  971. data = {'custom_fields': {'integer_field': 21}}
  972. response = self.client.patch(url, data, format='json', **self.header)
  973. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  974. data = {'custom_fields': {'integer_field': 15}}
  975. response = self.client.patch(url, data, format='json', **self.header)
  976. self.assertHttpStatus(response, status.HTTP_200_OK)
  977. def test_regex_validation(self):
  978. site2 = Site.objects.get(name='Site 2')
  979. url = reverse('dcim-api:site-detail', kwargs={'pk': site2.pk})
  980. self.add_permissions('dcim.change_site')
  981. cf_text = CustomField.objects.get(name='text_field')
  982. cf_text.validation_regex = r'^[A-Z]{3}$' # Three uppercase letters
  983. cf_text.save()
  984. data = {'custom_fields': {'text_field': 'ABC123'}}
  985. response = self.client.patch(url, data, format='json', **self.header)
  986. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  987. data = {'custom_fields': {'text_field': 'abc'}}
  988. response = self.client.patch(url, data, format='json', **self.header)
  989. self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
  990. data = {'custom_fields': {'text_field': 'ABC'}}
  991. response = self.client.patch(url, data, format='json', **self.header)
  992. self.assertHttpStatus(response, status.HTTP_200_OK)
  993. class CustomFieldImportTest(TestCase):
  994. user_permissions = (
  995. 'dcim.view_site',
  996. 'dcim.add_site',
  997. )
  998. @classmethod
  999. def setUpTestData(cls):
  1000. # Create a set of custom field choices
  1001. choice_set = CustomFieldChoiceSet.objects.create(
  1002. name='Custom Field Choice Set 1',
  1003. extra_choices=(
  1004. ('a', 'Option A'),
  1005. ('b', 'Option B'),
  1006. ('c', 'Option C'),
  1007. )
  1008. )
  1009. custom_fields = (
  1010. CustomField(name='text', type=CustomFieldTypeChoices.TYPE_TEXT),
  1011. CustomField(name='longtext', type=CustomFieldTypeChoices.TYPE_LONGTEXT),
  1012. CustomField(name='integer', type=CustomFieldTypeChoices.TYPE_INTEGER),
  1013. CustomField(name='decimal', type=CustomFieldTypeChoices.TYPE_DECIMAL),
  1014. CustomField(name='boolean', type=CustomFieldTypeChoices.TYPE_BOOLEAN),
  1015. CustomField(name='date', type=CustomFieldTypeChoices.TYPE_DATE),
  1016. CustomField(name='datetime', type=CustomFieldTypeChoices.TYPE_DATETIME),
  1017. CustomField(name='url', type=CustomFieldTypeChoices.TYPE_URL),
  1018. CustomField(name='json', type=CustomFieldTypeChoices.TYPE_JSON),
  1019. CustomField(name='select', type=CustomFieldTypeChoices.TYPE_SELECT, choice_set=choice_set),
  1020. CustomField(name='multiselect', type=CustomFieldTypeChoices.TYPE_MULTISELECT, choice_set=choice_set),
  1021. )
  1022. for cf in custom_fields:
  1023. cf.save()
  1024. cf.object_types.set([ObjectType.objects.get_for_model(Site)])
  1025. def test_import(self):
  1026. """
  1027. Import a Site in CSV format, including a value for each CustomField.
  1028. """
  1029. data = (
  1030. ('name', 'slug', 'status', 'cf_text', 'cf_longtext', 'cf_integer', 'cf_decimal', 'cf_boolean', 'cf_date', 'cf_datetime', 'cf_url', 'cf_json', 'cf_select', 'cf_multiselect'),
  1031. ('Site 1', 'site-1', 'active', 'ABC', 'Foo', '123', '123.45', 'True', '2020-01-01', '2020-01-01 12:00:00', 'http://example.com/1', '{"foo": 123}', 'a', '"a,b"'),
  1032. ('Site 2', 'site-2', 'active', 'DEF', 'Bar', '456', '456.78', 'False', '2020-01-02', '2020-01-02 12:00:00', 'http://example.com/2', '{"bar": 456}', 'b', '"b,c"'),
  1033. ('Site 3', 'site-3', 'active', '', '', '', '', '', '', '', '', '', '', ''),
  1034. )
  1035. csv_data = '\n'.join(','.join(row) for row in data)
  1036. response = self.client.post(reverse('dcim:site_import'), {
  1037. 'data': csv_data,
  1038. 'format': ImportFormatChoices.CSV,
  1039. 'csv_delimiter': CSVDelimiterChoices.AUTO,
  1040. })
  1041. self.assertEqual(response.status_code, 302)
  1042. self.assertEqual(Site.objects.count(), 3)
  1043. # Validate data for site 1
  1044. site1 = Site.objects.get(name='Site 1')
  1045. self.assertEqual(len(site1.custom_field_data), 11)
  1046. self.assertEqual(site1.custom_field_data['text'], 'ABC')
  1047. self.assertEqual(site1.custom_field_data['longtext'], 'Foo')
  1048. self.assertEqual(site1.custom_field_data['integer'], 123)
  1049. self.assertEqual(site1.custom_field_data['decimal'], 123.45)
  1050. self.assertEqual(site1.custom_field_data['boolean'], True)
  1051. self.assertEqual(site1.cf['date'].isoformat(), '2020-01-01')
  1052. self.assertEqual(site1.cf['datetime'].isoformat(), '2020-01-01T12:00:00+00:00')
  1053. self.assertEqual(site1.custom_field_data['url'], 'http://example.com/1')
  1054. self.assertEqual(site1.custom_field_data['json'], {"foo": 123})
  1055. self.assertEqual(site1.custom_field_data['select'], 'a')
  1056. self.assertEqual(site1.custom_field_data['multiselect'], ['a', 'b'])
  1057. # Validate data for site 2
  1058. site2 = Site.objects.get(name='Site 2')
  1059. self.assertEqual(len(site2.custom_field_data), 11)
  1060. self.assertEqual(site2.custom_field_data['text'], 'DEF')
  1061. self.assertEqual(site2.custom_field_data['longtext'], 'Bar')
  1062. self.assertEqual(site2.custom_field_data['integer'], 456)
  1063. self.assertEqual(site2.custom_field_data['decimal'], 456.78)
  1064. self.assertEqual(site2.custom_field_data['boolean'], False)
  1065. self.assertEqual(site2.cf['date'].isoformat(), '2020-01-02')
  1066. self.assertEqual(site2.cf['datetime'].isoformat(), '2020-01-02T12:00:00+00:00')
  1067. self.assertEqual(site2.custom_field_data['url'], 'http://example.com/2')
  1068. self.assertEqual(site2.custom_field_data['json'], {"bar": 456})
  1069. self.assertEqual(site2.custom_field_data['select'], 'b')
  1070. self.assertEqual(site2.custom_field_data['multiselect'], ['b', 'c'])
  1071. # No custom field data should be set for site 3
  1072. site3 = Site.objects.get(name='Site 3')
  1073. self.assertFalse(any(site3.custom_field_data.values()))
  1074. def test_import_missing_required(self):
  1075. """
  1076. Attempt to import an object missing a required custom field.
  1077. """
  1078. # Set one of our CustomFields to required
  1079. CustomField.objects.filter(name='text').update(required=True)
  1080. form_data = {
  1081. 'name': 'Site 1',
  1082. 'slug': 'site-1',
  1083. }
  1084. form = SiteImportForm(data=form_data)
  1085. self.assertFalse(form.is_valid())
  1086. self.assertIn('cf_text', form.errors)
  1087. def test_import_invalid_choice(self):
  1088. """
  1089. Attempt to import an object with an invalid choice selection.
  1090. """
  1091. form_data = {
  1092. 'name': 'Site 1',
  1093. 'slug': 'site-1',
  1094. 'cf_select': 'Choice X'
  1095. }
  1096. form = SiteImportForm(data=form_data)
  1097. self.assertFalse(form.is_valid())
  1098. self.assertIn('cf_select', form.errors)
  1099. class CustomFieldModelTest(TestCase):
  1100. @classmethod
  1101. def setUpTestData(cls):
  1102. cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='foo')
  1103. cf1.save()
  1104. cf1.object_types.set([ObjectType.objects.get_for_model(Site)])
  1105. cf2 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='bar')
  1106. cf2.save()
  1107. cf2.object_types.set([ObjectType.objects.get_for_model(Rack)])
  1108. def test_cf_data(self):
  1109. """
  1110. Check that custom field data is present on the instance immediately after being set and after being fetched
  1111. from the database.
  1112. """
  1113. site = Site(name='Test Site', slug='test-site')
  1114. # Check custom field data on new instance
  1115. site.custom_field_data['foo'] = 'abc'
  1116. self.assertEqual(site.cf['foo'], 'abc')
  1117. # Check custom field data from database
  1118. site.save()
  1119. site = Site.objects.get(name='Test Site')
  1120. self.assertEqual(site.cf['foo'], 'abc')
  1121. def test_invalid_data(self):
  1122. """
  1123. Setting custom field data for a non-applicable (or non-existent) CustomField should raise a ValidationError.
  1124. """
  1125. site = Site(name='Test Site', slug='test-site')
  1126. # Set custom field data
  1127. site.custom_field_data['foo'] = 'abc'
  1128. site.custom_field_data['bar'] = 'def'
  1129. with self.assertRaises(ValidationError):
  1130. site.clean()
  1131. del site.custom_field_data['bar']
  1132. site.clean()
  1133. def test_missing_required_field(self):
  1134. """
  1135. Check that a ValidationError is raised if any required custom fields are not present.
  1136. """
  1137. cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='baz', required=True)
  1138. cf3.save()
  1139. cf3.object_types.set([ObjectType.objects.get_for_model(Site)])
  1140. site = Site(name='Test Site', slug='test-site')
  1141. # Set custom field data with a required field omitted
  1142. site.custom_field_data['foo'] = 'abc'
  1143. with self.assertRaises(ValidationError):
  1144. site.clean()
  1145. site.custom_field_data['baz'] = 'def'
  1146. site.clean()
  1147. class CustomFieldModelFilterTest(TestCase):
  1148. queryset = Site.objects.all()
  1149. filterset = SiteFilterSet
  1150. @classmethod
  1151. def setUpTestData(cls):
  1152. object_type = ObjectType.objects.get_for_model(Site)
  1153. manufacturers = Manufacturer.objects.bulk_create((
  1154. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  1155. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  1156. Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
  1157. Manufacturer(name='Manufacturer 4', slug='manufacturer-4'),
  1158. ))
  1159. choice_set = CustomFieldChoiceSet.objects.create(
  1160. name='Custom Field Choice Set 1',
  1161. extra_choices=(('a', 'A'), ('b', 'B'), ('c', 'C'))
  1162. )
  1163. # Integer filtering
  1164. cf = CustomField(name='cf1', type=CustomFieldTypeChoices.TYPE_INTEGER)
  1165. cf.save()
  1166. cf.object_types.set([object_type])
  1167. # Decimal filtering
  1168. cf = CustomField(name='cf2', type=CustomFieldTypeChoices.TYPE_DECIMAL)
  1169. cf.save()
  1170. cf.object_types.set([object_type])
  1171. # Boolean filtering
  1172. cf = CustomField(name='cf3', type=CustomFieldTypeChoices.TYPE_BOOLEAN)
  1173. cf.save()
  1174. cf.object_types.set([object_type])
  1175. # Exact text filtering
  1176. cf = CustomField(
  1177. name='cf4',
  1178. type=CustomFieldTypeChoices.TYPE_TEXT,
  1179. filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
  1180. )
  1181. cf.save()
  1182. cf.object_types.set([object_type])
  1183. # Loose text filtering
  1184. cf = CustomField(
  1185. name='cf5',
  1186. type=CustomFieldTypeChoices.TYPE_TEXT,
  1187. filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
  1188. )
  1189. cf.save()
  1190. cf.object_types.set([object_type])
  1191. # Date filtering
  1192. cf = CustomField(name='cf6', type=CustomFieldTypeChoices.TYPE_DATE)
  1193. cf.save()
  1194. cf.object_types.set([object_type])
  1195. # Exact URL filtering
  1196. cf = CustomField(
  1197. name='cf7',
  1198. type=CustomFieldTypeChoices.TYPE_URL,
  1199. filter_logic=CustomFieldFilterLogicChoices.FILTER_EXACT
  1200. )
  1201. cf.save()
  1202. cf.object_types.set([object_type])
  1203. # Loose URL filtering
  1204. cf = CustomField(
  1205. name='cf8',
  1206. type=CustomFieldTypeChoices.TYPE_URL,
  1207. filter_logic=CustomFieldFilterLogicChoices.FILTER_LOOSE
  1208. )
  1209. cf.save()
  1210. cf.object_types.set([object_type])
  1211. # Selection filtering
  1212. cf = CustomField(
  1213. name='cf9',
  1214. type=CustomFieldTypeChoices.TYPE_SELECT,
  1215. choice_set=choice_set
  1216. )
  1217. cf.save()
  1218. cf.object_types.set([object_type])
  1219. # Multiselect filtering
  1220. cf = CustomField(
  1221. name='cf10',
  1222. type=CustomFieldTypeChoices.TYPE_MULTISELECT,
  1223. choice_set=choice_set
  1224. )
  1225. cf.save()
  1226. cf.object_types.set([object_type])
  1227. # Object filtering
  1228. cf = CustomField(
  1229. name='cf11',
  1230. type=CustomFieldTypeChoices.TYPE_OBJECT,
  1231. object_type=ObjectType.objects.get_for_model(Manufacturer)
  1232. )
  1233. cf.save()
  1234. cf.object_types.set([object_type])
  1235. # Multi-object filtering
  1236. cf = CustomField(
  1237. name='cf12',
  1238. type=CustomFieldTypeChoices.TYPE_MULTIOBJECT,
  1239. object_type=ObjectType.objects.get_for_model(Manufacturer)
  1240. )
  1241. cf.save()
  1242. cf.object_types.set([object_type])
  1243. Site.objects.bulk_create([
  1244. Site(name='Site 1', slug='site-1', custom_field_data={
  1245. 'cf1': 100,
  1246. 'cf2': 100.1,
  1247. 'cf3': True,
  1248. 'cf4': 'foo',
  1249. 'cf5': 'foo',
  1250. 'cf6': '2016-06-26',
  1251. 'cf7': 'http://a.example.com',
  1252. 'cf8': 'http://a.example.com',
  1253. 'cf9': 'A',
  1254. 'cf10': ['A', 'B'],
  1255. 'cf11': manufacturers[0].pk,
  1256. 'cf12': [manufacturers[0].pk, manufacturers[3].pk],
  1257. }),
  1258. Site(name='Site 2', slug='site-2', custom_field_data={
  1259. 'cf1': 200,
  1260. 'cf2': 200.2,
  1261. 'cf3': True,
  1262. 'cf4': 'foobar',
  1263. 'cf5': 'foobar',
  1264. 'cf6': '2016-06-27',
  1265. 'cf7': 'http://b.example.com',
  1266. 'cf8': 'http://b.example.com',
  1267. 'cf9': 'B',
  1268. 'cf10': ['B', 'C'],
  1269. 'cf11': manufacturers[1].pk,
  1270. 'cf12': [manufacturers[1].pk, manufacturers[3].pk],
  1271. }),
  1272. Site(name='Site 3', slug='site-3', custom_field_data={
  1273. 'cf1': 300,
  1274. 'cf2': 300.3,
  1275. 'cf3': False,
  1276. 'cf4': 'bar',
  1277. 'cf5': 'bar',
  1278. 'cf6': '2016-06-28',
  1279. 'cf7': 'http://c.example.com',
  1280. 'cf8': 'http://c.example.com',
  1281. 'cf9': 'C',
  1282. 'cf10': None,
  1283. 'cf11': manufacturers[2].pk,
  1284. 'cf12': [manufacturers[2].pk, manufacturers[3].pk],
  1285. }),
  1286. ])
  1287. def test_filter_integer(self):
  1288. self.assertEqual(self.filterset({'cf_cf1': [100, 200]}, self.queryset).qs.count(), 2)
  1289. self.assertEqual(self.filterset({'cf_cf1__n': [200]}, self.queryset).qs.count(), 2)
  1290. self.assertEqual(self.filterset({'cf_cf1__gt': [200]}, self.queryset).qs.count(), 1)
  1291. self.assertEqual(self.filterset({'cf_cf1__gte': [200]}, self.queryset).qs.count(), 2)
  1292. self.assertEqual(self.filterset({'cf_cf1__lt': [200]}, self.queryset).qs.count(), 1)
  1293. self.assertEqual(self.filterset({'cf_cf1__lte': [200]}, self.queryset).qs.count(), 2)
  1294. def test_filter_decimal(self):
  1295. self.assertEqual(self.filterset({'cf_cf2': [100.1, 200.2]}, self.queryset).qs.count(), 2)
  1296. self.assertEqual(self.filterset({'cf_cf2__n': [200.2]}, self.queryset).qs.count(), 2)
  1297. self.assertEqual(self.filterset({'cf_cf2__gt': [200.2]}, self.queryset).qs.count(), 1)
  1298. self.assertEqual(self.filterset({'cf_cf2__gte': [200.2]}, self.queryset).qs.count(), 2)
  1299. self.assertEqual(self.filterset({'cf_cf2__lt': [200.2]}, self.queryset).qs.count(), 1)
  1300. self.assertEqual(self.filterset({'cf_cf2__lte': [200.2]}, self.queryset).qs.count(), 2)
  1301. def test_filter_boolean(self):
  1302. self.assertEqual(self.filterset({'cf_cf3': True}, self.queryset).qs.count(), 2)
  1303. self.assertEqual(self.filterset({'cf_cf3': False}, self.queryset).qs.count(), 1)
  1304. def test_filter_text_strict(self):
  1305. self.assertEqual(self.filterset({'cf_cf4': ['foo']}, self.queryset).qs.count(), 1)
  1306. self.assertEqual(self.filterset({'cf_cf4__n': ['foo']}, self.queryset).qs.count(), 2)
  1307. self.assertEqual(self.filterset({'cf_cf4__ic': ['foo']}, self.queryset).qs.count(), 2)
  1308. self.assertEqual(self.filterset({'cf_cf4__nic': ['foo']}, self.queryset).qs.count(), 1)
  1309. self.assertEqual(self.filterset({'cf_cf4__isw': ['foo']}, self.queryset).qs.count(), 2)
  1310. self.assertEqual(self.filterset({'cf_cf4__nisw': ['foo']}, self.queryset).qs.count(), 1)
  1311. self.assertEqual(self.filterset({'cf_cf4__iew': ['bar']}, self.queryset).qs.count(), 2)
  1312. self.assertEqual(self.filterset({'cf_cf4__niew': ['bar']}, self.queryset).qs.count(), 1)
  1313. self.assertEqual(self.filterset({'cf_cf4__ie': ['FOO']}, self.queryset).qs.count(), 1)
  1314. self.assertEqual(self.filterset({'cf_cf4__nie': ['FOO']}, self.queryset).qs.count(), 2)
  1315. def test_filter_text_loose(self):
  1316. self.assertEqual(self.filterset({'cf_cf5': ['foo']}, self.queryset).qs.count(), 2)
  1317. def test_filter_date(self):
  1318. self.assertEqual(self.filterset({'cf_cf6': ['2016-06-26', '2016-06-27']}, self.queryset).qs.count(), 2)
  1319. self.assertEqual(self.filterset({'cf_cf6__n': ['2016-06-27']}, self.queryset).qs.count(), 2)
  1320. self.assertEqual(self.filterset({'cf_cf6__gt': ['2016-06-27']}, self.queryset).qs.count(), 1)
  1321. self.assertEqual(self.filterset({'cf_cf6__gte': ['2016-06-27']}, self.queryset).qs.count(), 2)
  1322. self.assertEqual(self.filterset({'cf_cf6__lt': ['2016-06-27']}, self.queryset).qs.count(), 1)
  1323. self.assertEqual(self.filterset({'cf_cf6__lte': ['2016-06-27']}, self.queryset).qs.count(), 2)
  1324. def test_filter_url_strict(self):
  1325. self.assertEqual(self.filterset({'cf_cf7': ['http://a.example.com', 'http://b.example.com']}, self.queryset).qs.count(), 2)
  1326. self.assertEqual(self.filterset({'cf_cf7__n': ['http://b.example.com']}, self.queryset).qs.count(), 2)
  1327. self.assertEqual(self.filterset({'cf_cf7__ic': ['b']}, self.queryset).qs.count(), 1)
  1328. self.assertEqual(self.filterset({'cf_cf7__nic': ['b']}, self.queryset).qs.count(), 2)
  1329. self.assertEqual(self.filterset({'cf_cf7__isw': ['http://']}, self.queryset).qs.count(), 3)
  1330. self.assertEqual(self.filterset({'cf_cf7__nisw': ['http://']}, self.queryset).qs.count(), 0)
  1331. self.assertEqual(self.filterset({'cf_cf7__iew': ['.com']}, self.queryset).qs.count(), 3)
  1332. self.assertEqual(self.filterset({'cf_cf7__niew': ['.com']}, self.queryset).qs.count(), 0)
  1333. self.assertEqual(self.filterset({'cf_cf7__ie': ['HTTP://A.EXAMPLE.COM']}, self.queryset).qs.count(), 1)
  1334. self.assertEqual(self.filterset({'cf_cf7__nie': ['HTTP://A.EXAMPLE.COM']}, self.queryset).qs.count(), 2)
  1335. def test_filter_url_loose(self):
  1336. self.assertEqual(self.filterset({'cf_cf8': ['example.com']}, self.queryset).qs.count(), 3)
  1337. def test_filter_select(self):
  1338. self.assertEqual(self.filterset({'cf_cf9': ['A', 'B']}, self.queryset).qs.count(), 2)
  1339. def test_filter_multiselect(self):
  1340. self.assertEqual(self.filterset({'cf_cf10': ['A']}, self.queryset).qs.count(), 1)
  1341. self.assertEqual(self.filterset({'cf_cf10': ['A', 'C']}, self.queryset).qs.count(), 2)
  1342. self.assertEqual(self.filterset({'cf_cf10': ['null']}, self.queryset).qs.count(), 1)
  1343. def test_filter_object(self):
  1344. manufacturer_ids = Manufacturer.objects.values_list('id', flat=True)
  1345. self.assertEqual(self.filterset({'cf_cf11': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(), 2)
  1346. def test_filter_multiobject(self):
  1347. manufacturer_ids = Manufacturer.objects.values_list('id', flat=True)
  1348. self.assertEqual(self.filterset({'cf_cf12': [manufacturer_ids[0], manufacturer_ids[1]]}, self.queryset).qs.count(), 2)
  1349. self.assertEqual(self.filterset({'cf_cf12': [manufacturer_ids[3]]}, self.queryset).qs.count(), 3)