test_basic.py 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. from nose.tools import eq_, ok_
  2. from wtforms import fields, validators
  3. from flask_admin import form
  4. from flask_admin._compat import as_unicode
  5. from flask_admin.contrib.mongoengine import ModelView
  6. from . import setup
  7. from datetime import datetime
  8. class CustomModelView(ModelView):
  9. def __init__(self, model,
  10. name=None, category=None, endpoint=None, url=None,
  11. **kwargs):
  12. for k, v in kwargs.iteritems():
  13. setattr(self, k, v)
  14. super(CustomModelView, self).__init__(model,
  15. name, category,
  16. endpoint, url)
  17. def create_models(db):
  18. class Model1(db.Document):
  19. test1 = db.StringField(max_length=20)
  20. test2 = db.StringField(max_length=20)
  21. test3 = db.StringField()
  22. test4 = db.StringField()
  23. datetime_field = db.DateTimeField()
  24. def __str__(self):
  25. return self.test1
  26. class Model2(db.Document):
  27. string_field = db.StringField()
  28. int_field = db.IntField()
  29. float_field = db.FloatField()
  30. bool_field = db.BooleanField()
  31. model1 = db.ReferenceField(Model1)
  32. Model1.objects.delete()
  33. Model2.objects.delete()
  34. return Model1, Model2
  35. def fill_db(Model1, Model2):
  36. Model1('test1_val_1', 'test2_val_1').save()
  37. Model1('test1_val_2', 'test2_val_2').save()
  38. Model1('test1_val_3', 'test2_val_3').save()
  39. Model1('test1_val_4', 'test2_val_4').save()
  40. Model1(None, 'empty_obj').save()
  41. Model2('string_field_val_1', None, None, True).save()
  42. Model2('string_field_val_2', None, None, False).save()
  43. Model2('string_field_val_3', 5000, 25.9).save()
  44. Model2('string_field_val_4', 9000, 75.5).save()
  45. Model2('string_field_val_5', 6169453081680413441).save()
  46. Model1('datetime_obj1', datetime_field=datetime(2014, 4, 3, 1, 9, 0)).save()
  47. Model1('datetime_obj2', datetime_field=datetime(2013, 3, 2, 0, 8, 0)).save()
  48. def test_model():
  49. app, db, admin = setup()
  50. Model1, Model2 = create_models(db)
  51. view = CustomModelView(Model1)
  52. admin.add_view(view)
  53. eq_(view.model, Model1)
  54. eq_(view.name, 'Model1')
  55. eq_(view.endpoint, 'model1')
  56. eq_(view._primary_key, 'id')
  57. ok_('test1' in view._sortable_columns)
  58. ok_('test2' in view._sortable_columns)
  59. ok_('test3' in view._sortable_columns)
  60. ok_('test4' in view._sortable_columns)
  61. ok_(view._create_form_class is not None)
  62. ok_(view._edit_form_class is not None)
  63. eq_(view._search_supported, False)
  64. eq_(view._filters, None)
  65. eq_(view._create_form_class.test1.field_class, fields.StringField)
  66. eq_(view._create_form_class.test2.field_class, fields.StringField)
  67. eq_(view._create_form_class.test3.field_class, fields.TextAreaField)
  68. eq_(view._create_form_class.test4.field_class, fields.TextAreaField)
  69. # Make some test clients
  70. client = app.test_client()
  71. rv = client.get('/admin/model1/')
  72. eq_(rv.status_code, 200)
  73. rv = client.get('/admin/model1/new/')
  74. eq_(rv.status_code, 200)
  75. rv = client.post('/admin/model1/new/',
  76. data=dict(test1='test1large', test2='test2'))
  77. eq_(rv.status_code, 302)
  78. model = Model1.objects.first()
  79. eq_(model.test1, 'test1large')
  80. eq_(model.test2, 'test2')
  81. eq_(model.test3, '')
  82. eq_(model.test4, '')
  83. rv = client.get('/admin/model1/')
  84. eq_(rv.status_code, 200)
  85. ok_('test1large' in rv.data)
  86. url = '/admin/model1/edit/?id=%s' % model.id
  87. rv = client.get(url)
  88. eq_(rv.status_code, 200)
  89. rv = client.post(url,
  90. data=dict(test1='test1small', test2='test2large'))
  91. eq_(rv.status_code, 302)
  92. model = Model1.objects.first()
  93. eq_(model.test1, 'test1small')
  94. eq_(model.test2, 'test2large')
  95. eq_(model.test3, '')
  96. eq_(model.test4, '')
  97. url = '/admin/model1/delete/?id=%s' % model.id
  98. rv = client.post(url)
  99. eq_(rv.status_code, 302)
  100. eq_(Model1.objects.count(), 0)
  101. def test_column_editable_list():
  102. app, db, admin = setup()
  103. Model1, Model2 = create_models(db)
  104. view = CustomModelView(Model1,
  105. column_editable_list=['test1', 'datetime_field'])
  106. admin.add_view(view)
  107. fill_db(Model1, Model2)
  108. client = app.test_client()
  109. # Test in-line edit field rendering
  110. rv = client.get('/admin/model1/')
  111. data = rv.data.decode('utf-8')
  112. ok_('data-role="x-editable"' in data)
  113. # Form - Test basic in-line edit functionality
  114. obj1 = Model1.objects.get(test1='test1_val_3')
  115. rv = client.post('/admin/model1/ajax/update/', data={
  116. 'list_form_pk': str(obj1.id),
  117. 'test1': 'change-success-1',
  118. })
  119. data = rv.data.decode('utf-8')
  120. ok_('Record was successfully saved.' == data)
  121. # confirm the value has changed
  122. rv = client.get('/admin/model1/')
  123. data = rv.data.decode('utf-8')
  124. ok_('change-success-1' in data)
  125. # Test validation error
  126. obj2 = Model1.objects.get(test1='datetime_obj1')
  127. rv = client.post('/admin/model1/ajax/update/', data={
  128. 'list_form_pk': str(obj2.id),
  129. 'datetime_field': 'problematic-input',
  130. })
  131. eq_(rv.status_code, 500)
  132. # Test invalid primary key
  133. rv = client.post('/admin/model1/ajax/update/', data={
  134. 'list_form_pk': '1000',
  135. 'test1': 'problematic-input',
  136. })
  137. data = rv.data.decode('utf-8')
  138. eq_(rv.status_code, 500)
  139. # Test editing column not in column_editable_list
  140. rv = client.post('/admin/model1/ajax/update/', data={
  141. 'list_form_pk': '1',
  142. 'test2': 'problematic-input',
  143. })
  144. data = rv.data.decode('utf-8')
  145. ok_('problematic-input' not in data)
  146. # Test in-line editing for relations
  147. view = CustomModelView(Model2, column_editable_list=['model1'])
  148. admin.add_view(view)
  149. obj3 = Model2.objects.get(string_field='string_field_val_1')
  150. rv = client.post('/admin/model2/ajax/update/', data={
  151. 'list_form_pk': str(obj3.id),
  152. 'model1': str(obj1.id),
  153. })
  154. data = rv.data.decode('utf-8')
  155. ok_('Record was successfully saved.' == data)
  156. # confirm the value has changed
  157. rv = client.get('/admin/model2/')
  158. data = rv.data.decode('utf-8')
  159. ok_('test1_val_1' in data)
  160. def test_details_view():
  161. app, db, admin = setup()
  162. Model1, Model2 = create_models(db)
  163. view_no_details = CustomModelView(Model1)
  164. admin.add_view(view_no_details)
  165. # fields are scaffolded
  166. view_w_details = CustomModelView(Model2, can_view_details=True)
  167. admin.add_view(view_w_details)
  168. # show only specific fields in details w/ column_details_list
  169. string_field_view = CustomModelView(Model2, can_view_details=True,
  170. column_details_list=["string_field"],
  171. endpoint="sf_view")
  172. admin.add_view(string_field_view)
  173. fill_db(Model1, Model2)
  174. client = app.test_client()
  175. m1_id = Model1.objects.first().id
  176. m2_id = Model2.objects.first().id
  177. # ensure link to details is hidden when can_view_details is disabled
  178. rv = client.get('/admin/model1/')
  179. data = rv.data.decode('utf-8')
  180. ok_('/admin/model1/details/' not in data)
  181. # ensure link to details view appears
  182. rv = client.get('/admin/model2/')
  183. data = rv.data.decode('utf-8')
  184. ok_('/admin/model2/details/' in data)
  185. # test redirection when details are disabled
  186. url = '/admin/model1/details/?url=%2Fadmin%2Fmodel1%2F&id=' + str(m1_id)
  187. rv = client.get(url)
  188. eq_(rv.status_code, 302)
  189. # test if correct data appears in details view when enabled
  190. url = '/admin/model2/details/?url=%2Fadmin%2Fmodel2%2F&id=' + str(m2_id)
  191. rv = client.get(url)
  192. data = rv.data.decode('utf-8')
  193. ok_('String Field' in data)
  194. ok_('string_field_val_1' in data)
  195. ok_('Int Field' in data)
  196. # test column_details_list
  197. url = '/admin/sf_view/details/?url=%2Fadmin%2Fsf_view%2F&id=' + str(m2_id)
  198. rv = client.get(url)
  199. data = rv.data.decode('utf-8')
  200. ok_('String Field' in data)
  201. ok_('string_field_val_1' in data)
  202. ok_('Int Field' not in data)
  203. def test_column_filters():
  204. app, db, admin = setup()
  205. Model1, Model2 = create_models(db)
  206. # fill DB with values
  207. fill_db(Model1, Model2)
  208. # Test string filter
  209. view = CustomModelView(Model1, column_filters=['test1'])
  210. admin.add_view(view)
  211. eq_(len(view._filters), 7)
  212. eq_(
  213. [(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
  214. [
  215. (0, 'contains'),
  216. (1, 'not contains'),
  217. (2, 'equals'),
  218. (3, 'not equal'),
  219. (4, 'empty'),
  220. (5, 'in list'),
  221. (6, 'not in list'),
  222. ]
  223. )
  224. # Make some test clients
  225. client = app.test_client()
  226. # string - equals
  227. rv = client.get('/admin/model1/?flt0_0=test1_val_1')
  228. eq_(rv.status_code, 200)
  229. data = rv.data.decode('utf-8')
  230. ok_('test2_val_1' in data)
  231. ok_('test1_val_2' not in data)
  232. # string - not equal
  233. rv = client.get('/admin/model1/?flt0_1=test1_val_1')
  234. eq_(rv.status_code, 200)
  235. data = rv.data.decode('utf-8')
  236. ok_('test2_val_1' not in data)
  237. ok_('test1_val_2' in data)
  238. # string - contains
  239. rv = client.get('/admin/model1/?flt0_2=test1_val_1')
  240. eq_(rv.status_code, 200)
  241. data = rv.data.decode('utf-8')
  242. ok_('test2_val_1' in data)
  243. ok_('test1_val_2' not in data)
  244. # string - not contains
  245. rv = client.get('/admin/model1/?flt0_3=test1_val_1')
  246. eq_(rv.status_code, 200)
  247. data = rv.data.decode('utf-8')
  248. ok_('test2_val_1' not in data)
  249. ok_('test1_val_2' in data)
  250. # string - empty
  251. rv = client.get('/admin/model1/?flt0_4=1')
  252. eq_(rv.status_code, 200)
  253. data = rv.data.decode('utf-8')
  254. ok_('empty_obj' in data)
  255. ok_('test1_val_1' not in data)
  256. ok_('test1_val_2' not in data)
  257. # string - not empty
  258. rv = client.get('/admin/model1/?flt0_4=0')
  259. eq_(rv.status_code, 200)
  260. data = rv.data.decode('utf-8')
  261. ok_('empty_obj' not in data)
  262. ok_('test1_val_1' in data)
  263. ok_('test1_val_2' in data)
  264. # string - in list
  265. rv = client.get('/admin/model1/?flt0_5=test1_val_1%2Ctest1_val_2')
  266. eq_(rv.status_code, 200)
  267. data = rv.data.decode('utf-8')
  268. ok_('test2_val_1' in data)
  269. ok_('test2_val_2' in data)
  270. ok_('test1_val_3' not in data)
  271. ok_('test1_val_4' not in data)
  272. # string - not in list
  273. rv = client.get('/admin/model1/?flt0_6=test1_val_1%2Ctest1_val_2')
  274. eq_(rv.status_code, 200)
  275. data = rv.data.decode('utf-8')
  276. ok_('test2_val_1' not in data)
  277. ok_('test2_val_2' not in data)
  278. ok_('test1_val_3' in data)
  279. ok_('test1_val_4' in data)
  280. # Test numeric filter
  281. view = CustomModelView(Model2, column_filters=['int_field'])
  282. admin.add_view(view)
  283. eq_(
  284. [(f['index'], f['operation']) for f in view._filter_groups[u'Int Field']],
  285. [
  286. (0, 'equals'),
  287. (1, 'not equal'),
  288. (2, 'greater than'),
  289. (3, 'smaller than'),
  290. (4, 'empty'),
  291. (5, 'in list'),
  292. (6, 'not in list'),
  293. ]
  294. )
  295. # integer - equals
  296. rv = client.get('/admin/model2/?flt0_0=5000')
  297. eq_(rv.status_code, 200)
  298. data = rv.data.decode('utf-8')
  299. ok_('string_field_val_3' in data)
  300. ok_('string_field_val_4' not in data)
  301. # integer - equals (huge number)
  302. rv = client.get('/admin/model2/?flt0_0=6169453081680413441')
  303. eq_(rv.status_code, 200)
  304. data = rv.data.decode('utf-8')
  305. ok_('string_field_val_5' in data)
  306. ok_('string_field_val_4' not in data)
  307. # integer - equals - test validation
  308. rv = client.get('/admin/model2/?flt0_0=badval')
  309. eq_(rv.status_code, 200)
  310. data = rv.data.decode('utf-8')
  311. ok_('Invalid Filter Value' in data)
  312. # integer - not equal
  313. rv = client.get('/admin/model2/?flt0_1=5000')
  314. eq_(rv.status_code, 200)
  315. data = rv.data.decode('utf-8')
  316. ok_('string_field_val_3' not in data)
  317. ok_('string_field_val_4' in data)
  318. # integer - greater
  319. rv = client.get('/admin/model2/?flt0_2=6000')
  320. eq_(rv.status_code, 200)
  321. data = rv.data.decode('utf-8')
  322. ok_('string_field_val_3' not in data)
  323. ok_('string_field_val_4' in data)
  324. # integer - smaller
  325. rv = client.get('/admin/model2/?flt0_3=6000')
  326. eq_(rv.status_code, 200)
  327. data = rv.data.decode('utf-8')
  328. ok_('string_field_val_3' in data)
  329. ok_('string_field_val_4' not in data)
  330. # integer - empty
  331. rv = client.get('/admin/model2/?flt0_4=1')
  332. eq_(rv.status_code, 200)
  333. data = rv.data.decode('utf-8')
  334. ok_('string_field_val_1' in data)
  335. ok_('string_field_val_2' in data)
  336. ok_('string_field_val_3' not in data)
  337. ok_('string_field_val_4' not in data)
  338. # integer - not empty
  339. rv = client.get('/admin/model2/?flt0_4=0')
  340. eq_(rv.status_code, 200)
  341. data = rv.data.decode('utf-8')
  342. ok_('string_field_val_1' not in data)
  343. ok_('string_field_val_2' not in data)
  344. ok_('string_field_val_3' in data)
  345. ok_('string_field_val_4' in data)
  346. # integer - in list
  347. rv = client.get('/admin/model2/?flt0_5=5000%2C9000')
  348. eq_(rv.status_code, 200)
  349. data = rv.data.decode('utf-8')
  350. ok_('string_field_val_1' not in data)
  351. ok_('string_field_val_2' not in data)
  352. ok_('string_field_val_3' in data)
  353. ok_('string_field_val_4' in data)
  354. # integer - in list (huge number)
  355. rv = client.get('/admin/model2/?flt0_5=6169453081680413441')
  356. eq_(rv.status_code, 200)
  357. data = rv.data.decode('utf-8')
  358. ok_('string_field_val_1' not in data)
  359. ok_('string_field_val_5' in data)
  360. # integer - in list - test validation
  361. rv = client.get('/admin/model2/?flt0_5=5000%2Cbadval')
  362. eq_(rv.status_code, 200)
  363. data = rv.data.decode('utf-8')
  364. ok_('Invalid Filter Value' in data)
  365. # integer - not in list
  366. rv = client.get('/admin/model2/?flt0_6=5000%2C9000')
  367. eq_(rv.status_code, 200)
  368. data = rv.data.decode('utf-8')
  369. ok_('string_field_val_1' in data)
  370. ok_('string_field_val_2' in data)
  371. ok_('string_field_val_3' not in data)
  372. ok_('string_field_val_4' not in data)
  373. # Test boolean filter
  374. view = CustomModelView(Model2, column_filters=['bool_field'],
  375. endpoint="_bools")
  376. admin.add_view(view)
  377. eq_(
  378. [(f['index'], f['operation']) for f in view._filter_groups[u'Bool Field']],
  379. [
  380. (0, 'equals'),
  381. (1, 'not equal'),
  382. ]
  383. )
  384. # boolean - equals - Yes
  385. rv = client.get('/admin/_bools/?flt0_0=1')
  386. eq_(rv.status_code, 200)
  387. data = rv.data.decode('utf-8')
  388. ok_('string_field_val_1' in data)
  389. ok_('string_field_val_2' not in data)
  390. # boolean - equals - No
  391. rv = client.get('/admin/_bools/?flt0_0=0')
  392. eq_(rv.status_code, 200)
  393. data = rv.data.decode('utf-8')
  394. ok_('string_field_val_1' not in data)
  395. ok_('string_field_val_2' in data)
  396. # boolean - not equals - Yes
  397. rv = client.get('/admin/_bools/?flt0_1=1')
  398. eq_(rv.status_code, 200)
  399. data = rv.data.decode('utf-8')
  400. ok_('string_field_val_1' not in data)
  401. ok_('string_field_val_2' in data)
  402. # boolean - not equals - No
  403. rv = client.get('/admin/_bools/?flt0_1=0')
  404. eq_(rv.status_code, 200)
  405. data = rv.data.decode('utf-8')
  406. ok_('string_field_val_1' in data)
  407. ok_('string_field_val_2' not in data)
  408. # Test float filter
  409. view = CustomModelView(Model2, column_filters=['float_field'],
  410. endpoint="_float")
  411. admin.add_view(view)
  412. eq_(
  413. [(f['index'], f['operation']) for f in view._filter_groups[u'Float Field']],
  414. [
  415. (0, 'equals'),
  416. (1, 'not equal'),
  417. (2, 'greater than'),
  418. (3, 'smaller than'),
  419. (4, 'empty'),
  420. (5, 'in list'),
  421. (6, 'not in list'),
  422. ]
  423. )
  424. # float - equals
  425. rv = client.get('/admin/_float/?flt0_0=25.9')
  426. eq_(rv.status_code, 200)
  427. data = rv.data.decode('utf-8')
  428. ok_('string_field_val_3' in data)
  429. ok_('string_field_val_4' not in data)
  430. # float - equals - test validation
  431. rv = client.get('/admin/_float/?flt0_0=badval')
  432. eq_(rv.status_code, 200)
  433. data = rv.data.decode('utf-8')
  434. ok_('Invalid Filter Value' in data)
  435. # float - not equal
  436. rv = client.get('/admin/_float/?flt0_1=25.9')
  437. eq_(rv.status_code, 200)
  438. data = rv.data.decode('utf-8')
  439. ok_('string_field_val_3' not in data)
  440. ok_('string_field_val_4' in data)
  441. # float - greater
  442. rv = client.get('/admin/_float/?flt0_2=60.5')
  443. eq_(rv.status_code, 200)
  444. data = rv.data.decode('utf-8')
  445. ok_('string_field_val_3' not in data)
  446. ok_('string_field_val_4' in data)
  447. # float - smaller
  448. rv = client.get('/admin/_float/?flt0_3=60.5')
  449. eq_(rv.status_code, 200)
  450. data = rv.data.decode('utf-8')
  451. ok_('string_field_val_3' in data)
  452. ok_('string_field_val_4' not in data)
  453. # float - empty
  454. rv = client.get('/admin/_float/?flt0_4=1')
  455. eq_(rv.status_code, 200)
  456. data = rv.data.decode('utf-8')
  457. ok_('string_field_val_1' in data)
  458. ok_('string_field_val_2' in data)
  459. ok_('string_field_val_3' not in data)
  460. ok_('string_field_val_4' not in data)
  461. # float - not empty
  462. rv = client.get('/admin/_float/?flt0_4=0')
  463. eq_(rv.status_code, 200)
  464. data = rv.data.decode('utf-8')
  465. ok_('string_field_val_1' not in data)
  466. ok_('string_field_val_2' not in data)
  467. ok_('string_field_val_3' in data)
  468. ok_('string_field_val_4' in data)
  469. # float - in list
  470. rv = client.get('/admin/_float/?flt0_5=25.9%2C75.5')
  471. eq_(rv.status_code, 200)
  472. data = rv.data.decode('utf-8')
  473. ok_('string_field_val_1' not in data)
  474. ok_('string_field_val_2' not in data)
  475. ok_('string_field_val_3' in data)
  476. ok_('string_field_val_4' in data)
  477. # float - in list - test validation
  478. rv = client.get('/admin/_float/?flt0_5=25.9%2Cbadval')
  479. eq_(rv.status_code, 200)
  480. data = rv.data.decode('utf-8')
  481. ok_('Invalid Filter Value' in data)
  482. # float - not in list
  483. rv = client.get('/admin/_float/?flt0_6=25.9%2C75.5')
  484. eq_(rv.status_code, 200)
  485. data = rv.data.decode('utf-8')
  486. ok_('string_field_val_1' in data)
  487. ok_('string_field_val_2' in data)
  488. ok_('string_field_val_3' not in data)
  489. ok_('string_field_val_4' not in data)
  490. # Test datetime filter
  491. view = CustomModelView(Model1,
  492. column_filters=['datetime_field'],
  493. endpoint="_datetime")
  494. admin.add_view(view)
  495. eq_(
  496. [(f['index'], f['operation']) for f in view._filter_groups[u'Datetime Field']],
  497. [
  498. (0, 'equals'),
  499. (1, 'not equal'),
  500. (2, 'greater than'),
  501. (3, 'smaller than'),
  502. (4, 'between'),
  503. (5, 'not between'),
  504. (6, 'empty'),
  505. ]
  506. )
  507. # datetime - equals
  508. rv = client.get('/admin/_datetime/?flt0_0=2014-04-03+01%3A09%3A00')
  509. eq_(rv.status_code, 200)
  510. data = rv.data.decode('utf-8')
  511. ok_('datetime_obj1' in data)
  512. ok_('datetime_obj2' not in data)
  513. # datetime - not equal
  514. rv = client.get('/admin/_datetime/?flt0_1=2014-04-03+01%3A09%3A00')
  515. eq_(rv.status_code, 200)
  516. data = rv.data.decode('utf-8')
  517. ok_('datetime_obj1' not in data)
  518. ok_('datetime_obj2' in data)
  519. # datetime - greater
  520. rv = client.get('/admin/_datetime/?flt0_2=2014-04-03+01%3A08%3A00')
  521. eq_(rv.status_code, 200)
  522. data = rv.data.decode('utf-8')
  523. ok_('datetime_obj1' in data)
  524. ok_('datetime_obj2' not in data)
  525. # datetime - smaller
  526. rv = client.get('/admin/_datetime/?flt0_3=2014-04-03+01%3A08%3A00')
  527. eq_(rv.status_code, 200)
  528. data = rv.data.decode('utf-8')
  529. ok_('datetime_obj1' not in data)
  530. ok_('datetime_obj2' in data)
  531. # datetime - between
  532. rv = client.get('/admin/_datetime/?flt0_4=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
  533. eq_(rv.status_code, 200)
  534. data = rv.data.decode('utf-8')
  535. ok_('datetime_obj1' in data)
  536. ok_('datetime_obj2' not in data)
  537. # datetime - not between
  538. rv = client.get('/admin/_datetime/?flt0_5=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
  539. eq_(rv.status_code, 200)
  540. data = rv.data.decode('utf-8')
  541. ok_('datetime_obj1' not in data)
  542. ok_('datetime_obj2' in data)
  543. # datetime - empty
  544. rv = client.get('/admin/_datetime/?flt0_6=1')
  545. eq_(rv.status_code, 200)
  546. data = rv.data.decode('utf-8')
  547. ok_('test1_val_1' in data)
  548. ok_('datetime_obj1' not in data)
  549. ok_('datetime_obj2' not in data)
  550. # datetime - not empty
  551. rv = client.get('/admin/_datetime/?flt0_6=0')
  552. eq_(rv.status_code, 200)
  553. data = rv.data.decode('utf-8')
  554. ok_('test1_val_1' not in data)
  555. ok_('datetime_obj1' in data)
  556. ok_('datetime_obj2' in data)
  557. def test_default_sort():
  558. app, db, admin = setup()
  559. M1, _ = create_models(db)
  560. M1(test1='c').save()
  561. M1(test1='b').save()
  562. M1(test1='a').save()
  563. eq_(M1.objects.count(), 3)
  564. view = CustomModelView(M1, column_default_sort='test1')
  565. admin.add_view(view)
  566. _, data = view.get_list(0, None, None, None, None)
  567. eq_(data[0].test1, 'a')
  568. eq_(data[1].test1, 'b')
  569. eq_(data[2].test1, 'c')
  570. def test_extra_fields():
  571. app, db, admin = setup()
  572. Model1, _ = create_models(db)
  573. view = CustomModelView(
  574. Model1,
  575. form_extra_fields={
  576. 'extra_field': fields.StringField('Extra Field')
  577. }
  578. )
  579. admin.add_view(view)
  580. client = app.test_client()
  581. rv = client.get('/admin/model1/new/')
  582. eq_(rv.status_code, 200)
  583. # Check presence and order
  584. data = rv.data.decode('utf-8')
  585. ok_('Extra Field' in data)
  586. pos1 = data.find('Extra Field')
  587. pos2 = data.find('Test1')
  588. ok_(pos2 < pos1)
  589. def test_extra_field_order():
  590. app, db, admin = setup()
  591. Model1, _ = create_models(db)
  592. view = CustomModelView(
  593. Model1,
  594. form_extra_fields={
  595. 'extra_field': fields.StringField('Extra Field')
  596. }
  597. )
  598. admin.add_view(view)
  599. client = app.test_client()
  600. rv = client.get('/admin/model1/new/')
  601. eq_(rv.status_code, 200)
  602. # Check presence and order
  603. data = rv.data.decode('utf-8')
  604. ok_('Extra Field' in data)
  605. pos1 = data.find('Extra Field')
  606. pos2 = data.find('Test1')
  607. ok_(pos2 < pos1)
  608. def test_custom_form_base():
  609. app, db, admin = setup()
  610. class TestForm(form.BaseForm):
  611. pass
  612. Model1, _ = create_models(db)
  613. view = CustomModelView(
  614. Model1,
  615. form_base_class=TestForm
  616. )
  617. admin.add_view(view)
  618. ok_(hasattr(view._create_form_class, 'test1'))
  619. create_form = view.create_form()
  620. ok_(isinstance(create_form, TestForm))
  621. def test_subdocument_config():
  622. app, db, admin = setup()
  623. class Comment(db.EmbeddedDocument):
  624. name = db.StringField(max_length=20, required=True)
  625. value = db.StringField(max_length=20)
  626. class Model1(db.Document):
  627. test1 = db.StringField(max_length=20)
  628. subdoc = db.EmbeddedDocumentField(Comment)
  629. # Check only
  630. view1 = CustomModelView(
  631. Model1,
  632. form_subdocuments={
  633. 'subdoc': {
  634. 'form_columns': ('name',)
  635. }
  636. }
  637. )
  638. ok_(hasattr(view1._create_form_class, 'subdoc'))
  639. form = view1.create_form()
  640. ok_('name' in dir(form.subdoc.form))
  641. ok_('value' not in dir(form.subdoc.form))
  642. # Check exclude
  643. view2 = CustomModelView(
  644. Model1,
  645. form_subdocuments={
  646. 'subdoc': {
  647. 'form_excluded_columns': ('value',)
  648. }
  649. }
  650. )
  651. form = view2.create_form()
  652. ok_('name' in dir(form.subdoc.form))
  653. ok_('value' not in dir(form.subdoc.form))
  654. def test_subdocument_class_config():
  655. app, db, admin = setup()
  656. from flask_admin.contrib.mongoengine import EmbeddedForm
  657. class Comment(db.EmbeddedDocument):
  658. name = db.StringField(max_length=20, required=True)
  659. value = db.StringField(max_length=20)
  660. class Model1(db.Document):
  661. test1 = db.StringField(max_length=20)
  662. subdoc = db.EmbeddedDocumentField(Comment)
  663. class EmbeddedConfig(EmbeddedForm):
  664. form_columns = ('name',)
  665. # Check only
  666. view1 = CustomModelView(
  667. Model1,
  668. form_subdocuments={
  669. 'subdoc': EmbeddedConfig()
  670. }
  671. )
  672. form = view1.create_form()
  673. ok_('name' in dir(form.subdoc.form))
  674. ok_('value' not in dir(form.subdoc.form))
  675. def test_nested_subdocument_config():
  676. app, db, admin = setup()
  677. # Check recursive
  678. class Comment(db.EmbeddedDocument):
  679. name = db.StringField(max_length=20, required=True)
  680. value = db.StringField(max_length=20)
  681. class Nested(db.EmbeddedDocument):
  682. name = db.StringField(max_length=20, required=True)
  683. comment = db.EmbeddedDocumentField(Comment)
  684. class Model1(db.Document):
  685. test1 = db.StringField(max_length=20)
  686. nested = db.EmbeddedDocumentField(Nested)
  687. view1 = CustomModelView(
  688. Model1,
  689. form_subdocuments={
  690. 'nested': {
  691. 'form_subdocuments': {
  692. 'comment': {
  693. 'form_columns': ('name',)
  694. }
  695. }
  696. }
  697. }
  698. )
  699. form = view1.create_form()
  700. ok_('name' in dir(form.nested.form.comment.form))
  701. ok_('value' not in dir(form.nested.form.comment.form))
  702. def test_nested_list_subdocument():
  703. app, db, admin = setup()
  704. class Comment(db.EmbeddedDocument):
  705. name = db.StringField(max_length=20, required=True)
  706. value = db.StringField(max_length=20)
  707. class Model1(db.Document):
  708. test1 = db.StringField(max_length=20)
  709. subdoc = db.ListField(db.EmbeddedDocumentField(Comment))
  710. # Check only
  711. view1 = CustomModelView(
  712. Model1,
  713. form_subdocuments={
  714. 'subdoc': {
  715. 'form_subdocuments': {
  716. None: {
  717. 'form_columns': ('name',)
  718. }
  719. }
  720. }
  721. }
  722. )
  723. form = view1.create_form()
  724. inline_form = form.subdoc.unbound_field.args[2]
  725. ok_('name' in dir(inline_form))
  726. ok_('value' not in dir(inline_form))
  727. def test_nested_sortedlist_subdocument():
  728. app, db, admin = setup()
  729. class Comment(db.EmbeddedDocument):
  730. name = db.StringField(max_length=20, required=True)
  731. value = db.StringField(max_length=20)
  732. class Model1(db.Document):
  733. test1 = db.StringField(max_length=20)
  734. subdoc = db.SortedListField(db.EmbeddedDocumentField(Comment))
  735. # Check only
  736. view1 = CustomModelView(
  737. Model1,
  738. form_subdocuments={
  739. 'subdoc': {
  740. 'form_subdocuments': {
  741. None: {
  742. 'form_columns': ('name',)
  743. }
  744. }
  745. }
  746. }
  747. )
  748. form = view1.create_form()
  749. inline_form = form.subdoc.unbound_field.args[2]
  750. ok_('name' in dir(inline_form))
  751. ok_('value' not in dir(inline_form))
  752. def test_sortedlist_subdocument_validation():
  753. app, db, admin = setup()
  754. class Comment(db.EmbeddedDocument):
  755. name = db.StringField(max_length=20, required=True)
  756. value = db.StringField(max_length=20)
  757. class Model1(db.Document):
  758. test1 = db.StringField(max_length=20)
  759. subdoc = db.SortedListField(db.EmbeddedDocumentField(Comment))
  760. view = CustomModelView(Model1)
  761. admin.add_view(view)
  762. client = app.test_client()
  763. rv = client.post('/admin/model1/new/',
  764. data={'test1': 'test1large', 'subdoc-0-name': 'comment', 'subdoc-0-value': 'test'})
  765. eq_(rv.status_code, 302)
  766. rv = client.post('/admin/model1/new/',
  767. data={'test1': 'test1large', 'subdoc-0-name': '', 'subdoc-0-value': 'test'})
  768. eq_(rv.status_code, 200)
  769. ok_('This field is required' in rv.data)
  770. def test_list_subdocument_validation():
  771. app, db, admin = setup()
  772. class Comment(db.EmbeddedDocument):
  773. name = db.StringField(max_length=20, required=True)
  774. value = db.StringField(max_length=20)
  775. class Model1(db.Document):
  776. test1 = db.StringField(max_length=20)
  777. subdoc = db.ListField(db.EmbeddedDocumentField(Comment))
  778. view = CustomModelView(Model1)
  779. admin.add_view(view)
  780. client = app.test_client()
  781. rv = client.post('/admin/model1/new/',
  782. data={'test1': 'test1large', 'subdoc-0-name': 'comment', 'subdoc-0-value': 'test'})
  783. eq_(rv.status_code, 302)
  784. rv = client.post('/admin/model1/new/',
  785. data={'test1': 'test1large', 'subdoc-0-name': '', 'subdoc-0-value': 'test'})
  786. eq_(rv.status_code, 200)
  787. ok_('This field is required' in rv.data)
  788. def test_ajax_fk():
  789. app, db, admin = setup()
  790. Model1, Model2 = create_models(db)
  791. view = CustomModelView(
  792. Model2,
  793. url='view',
  794. form_ajax_refs={
  795. 'model1': {
  796. 'fields': ('test1', 'test2')
  797. }
  798. }
  799. )
  800. admin.add_view(view)
  801. ok_(u'model1' in view._form_ajax_refs)
  802. model = Model1(test1=u'first')
  803. model.save()
  804. model2 = Model1(test1=u'foo', test2=u'bar').save()
  805. # Check loader
  806. loader = view._form_ajax_refs[u'model1']
  807. mdl = loader.get_one(model.id)
  808. eq_(mdl.test1, model.test1)
  809. items = loader.get_list(u'fir')
  810. eq_(len(items), 1)
  811. eq_(items[0].id, model.id)
  812. items = loader.get_list(u'bar')
  813. eq_(len(items), 1)
  814. eq_(items[0].test1, u'foo')
  815. # Check form generation
  816. form = view.create_form()
  817. eq_(form.model1.__class__.__name__, u'AjaxSelectField')
  818. with app.test_request_context('/admin/view/'):
  819. ok_(u'value=""' not in form.model1())
  820. form.model1.data = model
  821. needle = u'data-json="[&quot;%s&quot;, &quot;first&quot;]"' % as_unicode(model.id)
  822. ok_(needle in form.model1())
  823. ok_(u'value="%s"' % as_unicode(model.id) in form.model1())
  824. # Check querying
  825. client = app.test_client()
  826. req = client.get(u'/admin/view/ajax/lookup/?name=model1&query=foo')
  827. eq_(req.data, u'[["%s", "foo"]]' % model2.id)
  828. # Check submitting
  829. client.post('/admin/view/new/', data={u'model1': as_unicode(model.id)})
  830. mdl = Model2.objects.first()
  831. ok_(mdl is not None)
  832. ok_(mdl.model1 is not None)
  833. eq_(mdl.model1.id, model.id)
  834. eq_(mdl.model1.test1, u'first')
  835. def test_nested_ajax_refs():
  836. app, db, admin = setup()
  837. # Check recursive
  838. class Comment(db.Document):
  839. name = db.StringField(max_length=20, required=True)
  840. value = db.StringField(max_length=20)
  841. class Nested(db.EmbeddedDocument):
  842. name = db.StringField(max_length=20, required=True)
  843. comment = db.ReferenceField(Comment)
  844. class Model1(db.Document):
  845. test1 = db.StringField(max_length=20)
  846. nested = db.EmbeddedDocumentField(Nested)
  847. view1 = CustomModelView(
  848. Model1,
  849. form_subdocuments={
  850. 'nested': {
  851. 'form_ajax_refs': {
  852. 'comment': {
  853. 'fields': ['name']
  854. }
  855. }
  856. }
  857. }
  858. )
  859. form = view1.create_form()
  860. eq_(type(form.nested.form.comment).__name__, 'AjaxSelectField')
  861. ok_('nested-comment' in view1._form_ajax_refs)
  862. def test_form_flat_choices():
  863. app, db, admin = setup()
  864. class Model(db.Document):
  865. name = db.StringField(max_length=20, choices=('a', 'b', 'c'))
  866. view = CustomModelView(Model)
  867. admin.add_view(view)
  868. form = view.create_form()
  869. eq_(form.name.choices, [('a', 'a'), ('b', 'b'), ('c', 'c')])
  870. def test_form_args():
  871. app, db, admin = setup()
  872. class Model(db.Document):
  873. test = db.StringField(required=True)
  874. shared_form_args = {'test': {'validators': [validators.Regexp('test')]}}
  875. view = CustomModelView(Model, form_args=shared_form_args)
  876. admin.add_view(view)
  877. # ensure shared field_args don't create duplicate validators
  878. create_form = view.create_form()
  879. eq_(len(create_form.test.validators), 2)
  880. edit_form = view.edit_form()
  881. eq_(len(edit_form.test.validators), 2)
  882. def test_form_args_embeddeddoc():
  883. app, db, admin = setup()
  884. class Info(db.EmbeddedDocument):
  885. name = db.StringField()
  886. age = db.StringField()
  887. class Model(db.Document):
  888. info = db.EmbeddedDocumentField('Info')
  889. timestamp = db.DateTimeField()
  890. view = CustomModelView(
  891. Model,
  892. form_args={
  893. 'info': {'label': 'Information'},
  894. 'timestamp': {'label': 'Last Updated Time'}
  895. }
  896. )
  897. admin.add_view(view)
  898. form = view.create_form()
  899. eq_(form.timestamp.label.text, 'Last Updated Time')
  900. # This is the failure
  901. eq_(form.info.label.text, 'Information')
  902. def test_simple_list_pager():
  903. app, db, admin = setup()
  904. Model1, _ = create_models(db)
  905. class TestModelView(CustomModelView):
  906. simple_list_pager = True
  907. def get_count_query(self):
  908. assert False
  909. view = TestModelView(Model1)
  910. admin.add_view(view)
  911. count, data = view.get_list(0, None, None, None, None)
  912. ok_(count is None)
  913. def test_export_csv():
  914. app, db, admin = setup()
  915. Model1, Model2 = create_models(db)
  916. view = CustomModelView(Model1, can_export=True,
  917. column_list=['test1', 'test2'], export_max_rows=2,
  918. endpoint='row_limit_2')
  919. admin.add_view(view)
  920. for x in range(5):
  921. fill_db(Model1, Model2)
  922. client = app.test_client()
  923. # test export_max_rows
  924. rv = client.get('/admin/row_limit_2/export/csv/')
  925. data = rv.data.decode('utf-8')
  926. eq_(rv.status_code, 200)
  927. ok_("Test1,Test2\r\n"
  928. "test1_val_1,test2_val_1\r\n"
  929. "test1_val_2,test2_val_2\r\n" == data)
  930. view = CustomModelView(Model1, can_export=True,
  931. column_list=['test1', 'test2'],
  932. endpoint='no_row_limit')
  933. admin.add_view(view)
  934. # test row limit without export_max_rows
  935. rv = client.get('/admin/no_row_limit/export/csv/')
  936. data = rv.data.decode('utf-8')
  937. eq_(rv.status_code, 200)
  938. ok_(len(data.splitlines()) > 21)