test_pages.py 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848
  1. import collections
  2. import datetime
  3. import json
  4. from django.contrib.auth import get_user_model
  5. from django.contrib.auth.models import Group, Permission
  6. from django.urls import reverse
  7. from django.utils import timezone
  8. from wagtail import hooks
  9. from wagtail.api.v2.tests.test_pages import TestPageDetail, TestPageListing
  10. from wagtail.models import GroupPagePermission, Locale, Page, PageLogEntry
  11. from wagtail.test.demosite import models
  12. from wagtail.test.testapp.models import EventIndex, EventPage, SimplePage, StreamPage
  13. from wagtail.users.models import UserProfile
  14. from .utils import AdminAPITestCase
  15. def get_total_page_count():
  16. # Need to take away 1 as the root page is invisible over the API by default
  17. return Page.objects.count() - 1
  18. class TestAdminPageListing(AdminAPITestCase, TestPageListing):
  19. fixtures = ["demosite.json"]
  20. def get_response(self, **params):
  21. return self.client.get(reverse("wagtailadmin_api:pages:listing"), params)
  22. def get_page_id_list(self, content):
  23. return [page["id"] for page in content["items"]]
  24. # BASIC TESTS
  25. def test_basic(self):
  26. response = self.get_response()
  27. self.assertEqual(response.status_code, 200)
  28. self.assertEqual(response["Content-type"], "application/json")
  29. # Will crash if the JSON is invalid
  30. content = json.loads(response.content.decode("UTF-8"))
  31. # Check that the meta section is there
  32. self.assertIn("meta", content)
  33. self.assertIsInstance(content["meta"], dict)
  34. # Check that the total count is there and correct
  35. self.assertIn("total_count", content["meta"])
  36. self.assertIsInstance(content["meta"]["total_count"], int)
  37. self.assertEqual(content["meta"]["total_count"], get_total_page_count())
  38. # Check that the items section is there
  39. self.assertIn("items", content)
  40. self.assertIsInstance(content["items"], list)
  41. # Check that each page has a meta section with type, detail_url, html_url, status and children attributes
  42. for page in content["items"]:
  43. self.assertIn("meta", page)
  44. self.assertIsInstance(page["meta"], dict)
  45. self.assertEqual(
  46. set(page["meta"].keys()),
  47. {
  48. "type",
  49. "detail_url",
  50. "html_url",
  51. "status",
  52. "children",
  53. "slug",
  54. "first_published_at",
  55. "latest_revision_created_at",
  56. },
  57. )
  58. # Check the type info
  59. self.assertIsInstance(content["__types"], dict)
  60. self.assertEqual(
  61. set(content["__types"].keys()),
  62. {
  63. "demosite.EventPage",
  64. "demosite.StandardIndexPage",
  65. "demosite.PersonPage",
  66. "demosite.HomePage",
  67. "demosite.StandardPage",
  68. "demosite.EventIndexPage",
  69. "demosite.ContactPage",
  70. "demosite.BlogEntryPage",
  71. "demosite.BlogIndexPage",
  72. },
  73. )
  74. self.assertEqual(
  75. set(content["__types"]["demosite.EventPage"].keys()),
  76. {"verbose_name", "verbose_name_plural"},
  77. )
  78. self.assertEqual(
  79. content["__types"]["demosite.EventPage"]["verbose_name"], "event page"
  80. )
  81. self.assertEqual(
  82. content["__types"]["demosite.EventPage"]["verbose_name_plural"],
  83. "event pages",
  84. )
  85. # Not applicable to the admin API
  86. test_unpublished_pages_dont_appear_in_list = None
  87. test_private_pages_dont_appear_in_list = None
  88. def test_unpublished_pages_appear_in_list(self):
  89. total_count = get_total_page_count()
  90. page = models.BlogEntryPage.objects.get(id=16)
  91. page.unpublish()
  92. response = self.get_response()
  93. content = json.loads(response.content.decode("UTF-8"))
  94. self.assertEqual(content["meta"]["total_count"], total_count)
  95. def test_private_pages_appear_in_list(self):
  96. total_count = get_total_page_count()
  97. page = models.BlogIndexPage.objects.get(id=5)
  98. page.view_restrictions.create(password="test")
  99. new_total_count = get_total_page_count()
  100. self.assertEqual(total_count, total_count)
  101. response = self.get_response()
  102. content = json.loads(response.content.decode("UTF-8"))
  103. self.assertEqual(content["meta"]["total_count"], new_total_count)
  104. def test_get_in_non_content_language(self):
  105. # set logged-in user's admin UI language to Swedish
  106. user = get_user_model().objects.get(email="test@email.com")
  107. UserProfile.objects.update_or_create(
  108. user=user, defaults={"preferred_language": "se"}
  109. )
  110. response = self.get_response()
  111. self.assertEqual(response.status_code, 200)
  112. self.assertEqual(response["Content-type"], "application/json")
  113. # Will crash if the JSON is invalid
  114. content = json.loads(response.content.decode("UTF-8"))
  115. self.assertIn("meta", content)
  116. # FIELDS
  117. # Not applicable to the admin API
  118. test_parent_field_gives_error = None
  119. def test_fields(self):
  120. response = self.get_response(
  121. type="demosite.BlogEntryPage", fields="title,date,feed_image"
  122. )
  123. content = json.loads(response.content.decode("UTF-8"))
  124. for page in content["items"]:
  125. self.assertEqual(
  126. set(page.keys()),
  127. {"id", "meta", "title", "admin_display_title", "date", "feed_image"},
  128. )
  129. def test_fields_default(self):
  130. response = self.get_response(type="demosite.BlogEntryPage")
  131. content = json.loads(response.content.decode("UTF-8"))
  132. for page in content["items"]:
  133. self.assertEqual(
  134. set(page.keys()), {"id", "meta", "title", "admin_display_title"}
  135. )
  136. self.assertEqual(
  137. set(page["meta"].keys()),
  138. {
  139. "type",
  140. "detail_url",
  141. "html_url",
  142. "children",
  143. "status",
  144. "slug",
  145. "first_published_at",
  146. "latest_revision_created_at",
  147. },
  148. )
  149. def test_remove_meta_fields(self):
  150. response = self.get_response(fields="-html_url")
  151. content = json.loads(response.content.decode("UTF-8"))
  152. for page in content["items"]:
  153. self.assertEqual(
  154. set(page.keys()), {"id", "meta", "title", "admin_display_title"}
  155. )
  156. self.assertEqual(
  157. set(page["meta"].keys()),
  158. {
  159. "type",
  160. "detail_url",
  161. "slug",
  162. "first_published_at",
  163. "latest_revision_created_at",
  164. "status",
  165. "children",
  166. },
  167. )
  168. def test_remove_all_meta_fields(self):
  169. response = self.get_response(
  170. fields="-type,-detail_url,-slug,-first_published_at,-html_url,-latest_revision_created_at,-status,-children"
  171. )
  172. content = json.loads(response.content.decode("UTF-8"))
  173. for page in content["items"]:
  174. self.assertEqual(set(page.keys()), {"id", "title", "admin_display_title"})
  175. def test_remove_fields(self):
  176. response = self.get_response(fields="-title,-admin_display_title")
  177. content = json.loads(response.content.decode("UTF-8"))
  178. for page in content["items"]:
  179. self.assertEqual(set(page.keys()), {"id", "meta"})
  180. def test_remove_id_field(self):
  181. response = self.get_response(fields="-id")
  182. content = json.loads(response.content.decode("UTF-8"))
  183. for page in content["items"]:
  184. self.assertEqual(set(page.keys()), {"meta", "title", "admin_display_title"})
  185. def test_all_fields(self):
  186. response = self.get_response(type="demosite.BlogEntryPage", fields="*")
  187. content = json.loads(response.content.decode("UTF-8"))
  188. for page in content["items"]:
  189. self.assertEqual(
  190. set(page.keys()),
  191. {
  192. "id",
  193. "meta",
  194. "title",
  195. "admin_display_title",
  196. "date",
  197. "related_links",
  198. "tags",
  199. "carousel_items",
  200. "body",
  201. "feed_image",
  202. "feed_image_thumbnail",
  203. },
  204. )
  205. self.assertEqual(
  206. set(page["meta"].keys()),
  207. {
  208. "type",
  209. "detail_url",
  210. "show_in_menus",
  211. "first_published_at",
  212. "seo_title",
  213. "slug",
  214. "parent",
  215. "html_url",
  216. "search_description",
  217. "locale",
  218. "alias_of",
  219. "children",
  220. "descendants",
  221. "ancestors",
  222. "translations",
  223. "status",
  224. "latest_revision_created_at",
  225. },
  226. )
  227. def test_all_fields_then_remove_something(self):
  228. response = self.get_response(
  229. type="demosite.BlogEntryPage",
  230. fields="*,-title,-admin_display_title,-date,-seo_title,-status",
  231. )
  232. content = json.loads(response.content.decode("UTF-8"))
  233. for page in content["items"]:
  234. self.assertEqual(
  235. set(page.keys()),
  236. {
  237. "id",
  238. "meta",
  239. "related_links",
  240. "tags",
  241. "carousel_items",
  242. "body",
  243. "feed_image",
  244. "feed_image_thumbnail",
  245. },
  246. )
  247. self.assertEqual(
  248. set(page["meta"].keys()),
  249. {
  250. "type",
  251. "detail_url",
  252. "show_in_menus",
  253. "first_published_at",
  254. "slug",
  255. "parent",
  256. "html_url",
  257. "search_description",
  258. "locale",
  259. "alias_of",
  260. "children",
  261. "descendants",
  262. "ancestors",
  263. "translations",
  264. "latest_revision_created_at",
  265. },
  266. )
  267. def test_all_nested_fields(self):
  268. response = self.get_response(
  269. type="demosite.BlogEntryPage", fields="feed_image(*)"
  270. )
  271. content = json.loads(response.content.decode("UTF-8"))
  272. for page in content["items"]:
  273. self.assertEqual(
  274. set(page["feed_image"].keys()),
  275. {"id", "meta", "title", "width", "height", "thumbnail"},
  276. )
  277. def test_fields_foreign_key(self):
  278. # Only the base the detail_url is different here from the public API
  279. response = self.get_response(
  280. type="demosite.BlogEntryPage", fields="title,date,feed_image"
  281. )
  282. content = json.loads(response.content.decode("UTF-8"))
  283. for page in content["items"]:
  284. feed_image = page["feed_image"]
  285. if feed_image is not None:
  286. self.assertIsInstance(feed_image, dict)
  287. self.assertEqual(set(feed_image.keys()), {"id", "meta", "title"})
  288. self.assertIsInstance(feed_image["id"], int)
  289. self.assertIsInstance(feed_image["meta"], dict)
  290. self.assertEqual(
  291. set(feed_image["meta"].keys()),
  292. {"type", "detail_url", "download_url"},
  293. )
  294. self.assertEqual(feed_image["meta"]["type"], "wagtailimages.Image")
  295. self.assertEqual(
  296. feed_image["meta"]["detail_url"],
  297. "http://localhost/admin/api/main/images/%d/" % feed_image["id"],
  298. )
  299. def test_fields_parent(self):
  300. response = self.get_response(type="demosite.BlogEntryPage", fields="parent")
  301. content = json.loads(response.content.decode("UTF-8"))
  302. for page in content["items"]:
  303. parent = page["meta"]["parent"]
  304. # All blog entry pages have the same parent
  305. self.assertDictEqual(
  306. parent,
  307. {
  308. "id": 5,
  309. "meta": {
  310. "type": "demosite.BlogIndexPage",
  311. "detail_url": "http://localhost/admin/api/main/pages/5/",
  312. "html_url": "http://localhost/blog-index/",
  313. },
  314. "title": "Blog index",
  315. },
  316. )
  317. def test_fields_descendants(self):
  318. response = self.get_response(fields="descendants")
  319. content = json.loads(response.content.decode("UTF-8"))
  320. for page in content["items"]:
  321. descendants = page["meta"]["descendants"]
  322. self.assertEqual(set(descendants.keys()), {"count", "listing_url"})
  323. self.assertIsInstance(descendants["count"], int)
  324. self.assertEqual(
  325. descendants["listing_url"],
  326. "http://localhost/admin/api/main/pages/?descendant_of=%d" % page["id"],
  327. )
  328. def test_fields_child_relation(self):
  329. response = self.get_response(
  330. type="demosite.BlogEntryPage", fields="title,related_links"
  331. )
  332. content = json.loads(response.content.decode("UTF-8"))
  333. for page in content["items"]:
  334. self.assertEqual(
  335. set(page.keys()),
  336. {"id", "meta", "title", "admin_display_title", "related_links"},
  337. )
  338. self.assertIsInstance(page["related_links"], list)
  339. def test_fields_ordering(self):
  340. response = self.get_response(
  341. type="demosite.BlogEntryPage", fields="date,title,feed_image,related_links"
  342. )
  343. # Will crash if the JSON is invalid
  344. content = json.loads(response.content.decode("UTF-8"))
  345. # Test field order
  346. content = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(
  347. response.content.decode("UTF-8")
  348. )
  349. field_order = [
  350. "id",
  351. "meta",
  352. "title",
  353. "admin_display_title",
  354. "date",
  355. "feed_image",
  356. "related_links",
  357. ]
  358. self.assertEqual(list(content["items"][0].keys()), field_order)
  359. def test_fields_tags(self):
  360. response = self.get_response(type="demosite.BlogEntryPage", fields="tags")
  361. content = json.loads(response.content.decode("UTF-8"))
  362. for page in content["items"]:
  363. self.assertEqual(
  364. set(page.keys()), {"id", "meta", "tags", "title", "admin_display_title"}
  365. )
  366. self.assertIsInstance(page["tags"], list)
  367. def test_fields_translations(self):
  368. # Add a translation of the homepage
  369. french = Locale.objects.create(language_code="fr")
  370. homepage = Page.objects.get(depth=2)
  371. french_homepage = homepage.copy_for_translation(french)
  372. response = self.get_response(fields="translations")
  373. content = json.loads(response.content.decode("UTF-8"))
  374. for page in content["items"]:
  375. translations = page["meta"]["translations"]
  376. if page["id"] == homepage.id:
  377. self.assertEqual(len(translations), 1)
  378. self.assertEqual(translations[0]["id"], french_homepage.id)
  379. self.assertEqual(translations[0]["meta"]["locale"], "fr")
  380. elif page["id"] == french_homepage.id:
  381. self.assertEqual(len(translations), 1)
  382. self.assertEqual(translations[0]["id"], homepage.id)
  383. self.assertEqual(translations[0]["meta"]["locale"], "en")
  384. else:
  385. self.assertEqual(translations, [])
  386. # CHILD OF FILTER
  387. # Not applicable to the admin API
  388. test_child_of_page_thats_not_in_same_site_gives_error = None
  389. def test_child_of_root(self):
  390. # Only return the homepage as that's the only child of the "root" node
  391. # in the tree. This is different to the public API which pretends the
  392. # homepage of the current site is the root page.
  393. response = self.get_response(child_of="root")
  394. content = json.loads(response.content.decode("UTF-8"))
  395. page_id_list = self.get_page_id_list(content)
  396. self.assertEqual(page_id_list, [2])
  397. def test_child_of_page_1(self):
  398. # Public API doesn't allow this, as it's the root page
  399. response = self.get_response(child_of=1)
  400. json.loads(response.content.decode("UTF-8"))
  401. self.assertEqual(response.status_code, 200)
  402. # DESCENDANT OF FILTER
  403. # Not applicable to the admin API
  404. test_descendant_of_page_thats_not_in_same_site_gives_error = None
  405. def test_descendant_of_root(self):
  406. response = self.get_response(descendant_of="root")
  407. content = json.loads(response.content.decode("UTF-8"))
  408. page_id_list = self.get_page_id_list(content)
  409. self.assertEqual(
  410. page_id_list,
  411. [2, 4, 8, 9, 5, 16, 18, 19, 6, 10, 15, 17, 21, 22, 23, 20, 13, 14, 12],
  412. )
  413. def test_descendant_of_root_doesnt_give_error(self):
  414. # Public API doesn't allow this
  415. response = self.get_response(descendant_of=1)
  416. json.loads(response.content.decode("UTF-8"))
  417. self.assertEqual(response.status_code, 200)
  418. # FOR EXPLORER FILTER
  419. def make_simple_page(self, parent, title):
  420. return parent.add_child(instance=SimplePage(title=title, content="Simple page"))
  421. def test_for_explorer_filter(self):
  422. movies = self.make_simple_page(Page.objects.get(pk=1), "Movies")
  423. visible_movies = [
  424. self.make_simple_page(movies, "The Way of the Dragon"),
  425. self.make_simple_page(movies, "Enter the Dragon"),
  426. self.make_simple_page(movies, "Dragons Forever"),
  427. ]
  428. hidden_movies = [
  429. self.make_simple_page(movies, "The Hidden Fortress"),
  430. self.make_simple_page(movies, "Crouching Tiger, Hidden Dragon"),
  431. self.make_simple_page(
  432. movies, "Crouching Tiger, Hidden Dragon: Sword of Destiny"
  433. ),
  434. ]
  435. response = self.get_response(child_of=movies.pk, for_explorer=1)
  436. content = json.loads(response.content.decode("UTF-8"))
  437. page_id_list = self.get_page_id_list(content)
  438. self.assertEqual(page_id_list, [page.pk for page in visible_movies])
  439. response = self.get_response(child_of=movies.pk)
  440. content = json.loads(response.content.decode("UTF-8"))
  441. page_id_list = self.get_page_id_list(content)
  442. self.assertEqual(
  443. page_id_list, [page.pk for page in visible_movies + hidden_movies]
  444. )
  445. def test_for_explorer_no_child_of(self):
  446. response = self.get_response(for_explorer=1)
  447. self.assertEqual(response.status_code, 400)
  448. content = json.loads(response.content.decode("UTF-8"))
  449. self.assertEqual(
  450. content,
  451. {
  452. "message": "filtering by for_explorer without child_of is not supported",
  453. },
  454. )
  455. def test_for_explorer_construct_explorer_page_queryset_ordering(self):
  456. def set_custom_ordering(parent_page, pages, request):
  457. return pages.order_by("-title")
  458. with hooks.register_temporarily(
  459. "construct_explorer_page_queryset", set_custom_ordering
  460. ):
  461. response = self.get_response(for_explorer=True, child_of=2)
  462. content = json.loads(response.content.decode("UTF-8"))
  463. page_id_list = self.get_page_id_list(content)
  464. self.assertEqual(page_id_list, [6, 20, 4, 12, 5])
  465. # HAS CHILDREN FILTER
  466. def test_has_children_filter(self):
  467. response = self.get_response(has_children="true")
  468. content = json.loads(response.content.decode("UTF-8"))
  469. page_id_list = self.get_page_id_list(content)
  470. self.assertEqual(page_id_list, [2, 4, 5, 6, 21, 20])
  471. def test_has_children_filter_off(self):
  472. response = self.get_response(has_children="false")
  473. content = json.loads(response.content.decode("UTF-8"))
  474. page_id_list = self.get_page_id_list(content)
  475. self.assertEqual(
  476. page_id_list, [8, 9, 16, 18, 19, 10, 15, 17, 22, 23, 13, 14, 12]
  477. )
  478. def test_has_children_filter_int(self):
  479. response = self.get_response(has_children=1)
  480. content = json.loads(response.content.decode("UTF-8"))
  481. page_id_list = self.get_page_id_list(content)
  482. self.assertEqual(page_id_list, [2, 4, 5, 6, 21, 20])
  483. def test_has_children_filter_int_off(self):
  484. response = self.get_response(has_children=0)
  485. content = json.loads(response.content.decode("UTF-8"))
  486. page_id_list = self.get_page_id_list(content)
  487. self.assertEqual(
  488. page_id_list, [8, 9, 16, 18, 19, 10, 15, 17, 22, 23, 13, 14, 12]
  489. )
  490. def test_has_children_filter_invalid_integer(self):
  491. response = self.get_response(has_children=3)
  492. content = json.loads(response.content.decode("UTF-8"))
  493. self.assertEqual(response.status_code, 400)
  494. self.assertEqual(content, {"message": "has_children must be 'true' or 'false'"})
  495. def test_has_children_filter_invalid_value(self):
  496. response = self.get_response(has_children="yes")
  497. content = json.loads(response.content.decode("UTF-8"))
  498. self.assertEqual(response.status_code, 400)
  499. self.assertEqual(content, {"message": "has_children must be 'true' or 'false'"})
  500. # TYPE FILTER
  501. def test_type_filter_items_are_all_blog_entries(self):
  502. response = self.get_response(type="demosite.BlogEntryPage")
  503. content = json.loads(response.content.decode("UTF-8"))
  504. for page in content["items"]:
  505. self.assertEqual(page["meta"]["type"], "demosite.BlogEntryPage")
  506. # No specific fields available by default
  507. self.assertEqual(
  508. set(page.keys()), {"id", "meta", "title", "admin_display_title"}
  509. )
  510. def test_type_filter_multiple(self):
  511. response = self.get_response(type="demosite.BlogEntryPage,demosite.EventPage")
  512. content = json.loads(response.content.decode("UTF-8"))
  513. blog_page_seen = False
  514. event_page_seen = False
  515. for page in content["items"]:
  516. self.assertIn(
  517. page["meta"]["type"], ["demosite.BlogEntryPage", "demosite.EventPage"]
  518. )
  519. if page["meta"]["type"] == "demosite.BlogEntryPage":
  520. blog_page_seen = True
  521. elif page["meta"]["type"] == "demosite.EventPage":
  522. event_page_seen = True
  523. # Only generic fields available
  524. self.assertEqual(
  525. set(page.keys()), {"id", "meta", "title", "admin_display_title"}
  526. )
  527. self.assertTrue(blog_page_seen, "No blog pages were found in the items")
  528. self.assertTrue(event_page_seen, "No event pages were found in the items")
  529. class TestAdminPageDetail(AdminAPITestCase, TestPageDetail):
  530. fixtures = ["demosite.json"]
  531. def get_response(self, page_id, **params):
  532. return self.client.get(
  533. reverse("wagtailadmin_api:pages:detail", args=(page_id,)), params
  534. )
  535. def test_basic(self):
  536. response = self.get_response(16)
  537. self.assertEqual(response.status_code, 200)
  538. self.assertEqual(response["Content-type"], "application/json")
  539. # Will crash if the JSON is invalid
  540. content = json.loads(response.content.decode("UTF-8"))
  541. # Check the id field
  542. self.assertIn("id", content)
  543. self.assertEqual(content["id"], 16)
  544. # Check that the meta section is there
  545. self.assertIn("meta", content)
  546. self.assertIsInstance(content["meta"], dict)
  547. # Check the meta type
  548. self.assertIn("type", content["meta"])
  549. self.assertEqual(content["meta"]["type"], "demosite.BlogEntryPage")
  550. # Check the meta detail_url
  551. self.assertIn("detail_url", content["meta"])
  552. self.assertEqual(
  553. content["meta"]["detail_url"], "http://localhost/admin/api/main/pages/16/"
  554. )
  555. # Check the meta html_url
  556. self.assertIn("html_url", content["meta"])
  557. self.assertEqual(
  558. content["meta"]["html_url"], "http://localhost/blog-index/blog-post/"
  559. )
  560. # Check the meta status
  561. self.assertIn("status", content["meta"])
  562. self.assertEqual(
  563. content["meta"]["status"],
  564. {"status": "live", "live": True, "has_unpublished_changes": False},
  565. )
  566. # Check the meta children
  567. self.assertIn("children", content["meta"])
  568. self.assertEqual(
  569. content["meta"]["children"],
  570. {
  571. "count": 0,
  572. "listing_url": "http://localhost/admin/api/main/pages/?child_of=16",
  573. },
  574. )
  575. # Check the parent field
  576. self.assertIn("parent", content["meta"])
  577. self.assertIsInstance(content["meta"]["parent"], dict)
  578. self.assertEqual(set(content["meta"]["parent"].keys()), {"id", "meta", "title"})
  579. self.assertEqual(content["meta"]["parent"]["id"], 5)
  580. self.assertIsInstance(content["meta"]["parent"]["meta"], dict)
  581. self.assertEqual(
  582. set(content["meta"]["parent"]["meta"].keys()),
  583. {"type", "detail_url", "html_url"},
  584. )
  585. self.assertEqual(
  586. content["meta"]["parent"]["meta"]["type"], "demosite.BlogIndexPage"
  587. )
  588. self.assertEqual(
  589. content["meta"]["parent"]["meta"]["detail_url"],
  590. "http://localhost/admin/api/main/pages/5/",
  591. )
  592. self.assertEqual(
  593. content["meta"]["parent"]["meta"]["html_url"],
  594. "http://localhost/blog-index/",
  595. )
  596. # Check the alias_of field
  597. # See test_alias_page for a test on an alias page
  598. self.assertIn("alias_of", content["meta"])
  599. self.assertIsNone(content["meta"]["alias_of"])
  600. # Check that the custom fields are included
  601. self.assertIn("date", content)
  602. self.assertIn("body", content)
  603. self.assertIn("tags", content)
  604. self.assertIn("feed_image", content)
  605. self.assertIn("related_links", content)
  606. self.assertIn("carousel_items", content)
  607. # Check that the date was serialised properly
  608. self.assertEqual(content["date"], "2013-12-02")
  609. # Check that the tags were serialised properly
  610. self.assertEqual(content["tags"], ["bird", "wagtail"])
  611. # Check that the feed image was serialised properly
  612. self.assertIsInstance(content["feed_image"], dict)
  613. self.assertEqual(set(content["feed_image"].keys()), {"id", "meta", "title"})
  614. self.assertEqual(content["feed_image"]["id"], 7)
  615. self.assertIsInstance(content["feed_image"]["meta"], dict)
  616. self.assertEqual(
  617. set(content["feed_image"]["meta"].keys()),
  618. {"type", "detail_url", "download_url"},
  619. )
  620. self.assertEqual(content["feed_image"]["meta"]["type"], "wagtailimages.Image")
  621. self.assertEqual(
  622. content["feed_image"]["meta"]["detail_url"],
  623. "http://localhost/admin/api/main/images/7/",
  624. )
  625. # Check that the child relations were serialised properly
  626. self.assertEqual(content["related_links"], [])
  627. for carousel_item in content["carousel_items"]:
  628. self.assertEqual(
  629. set(carousel_item.keys()),
  630. {"id", "meta", "embed_url", "link", "caption", "image"},
  631. )
  632. self.assertEqual(set(carousel_item["meta"].keys()), {"type"})
  633. # Check the type info
  634. self.assertIsInstance(content["__types"], dict)
  635. self.assertEqual(
  636. set(content["__types"].keys()),
  637. {
  638. "wagtailcore.Page",
  639. "demosite.HomePage",
  640. "demosite.BlogIndexPage",
  641. "demosite.BlogEntryPageCarouselItem",
  642. "demosite.BlogEntryPage",
  643. "wagtailimages.Image",
  644. },
  645. )
  646. self.assertEqual(
  647. set(content["__types"]["demosite.BlogIndexPage"].keys()),
  648. {"verbose_name", "verbose_name_plural"},
  649. )
  650. self.assertEqual(
  651. content["__types"]["demosite.BlogIndexPage"]["verbose_name"],
  652. "blog index page",
  653. )
  654. self.assertEqual(
  655. content["__types"]["demosite.BlogIndexPage"]["verbose_name_plural"],
  656. "blog index pages",
  657. )
  658. # overridden from public API tests
  659. def test_meta_parent_id_doesnt_show_root_page(self):
  660. # Root page is visible in the admin API
  661. response = self.get_response(2)
  662. content = json.loads(response.content.decode("UTF-8"))
  663. self.assertIsNotNone(content["meta"]["parent"])
  664. def test_field_ordering(self):
  665. # Need to override this as the admin API has a __types field
  666. response = self.get_response(16)
  667. # Will crash if the JSON is invalid
  668. content = json.loads(response.content.decode("UTF-8"))
  669. # Test field order
  670. content = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(
  671. response.content.decode("UTF-8")
  672. )
  673. field_order = [
  674. "id",
  675. "meta",
  676. "title",
  677. "admin_display_title",
  678. "body",
  679. "tags",
  680. "date",
  681. "feed_image",
  682. "feed_image_thumbnail",
  683. "carousel_items",
  684. "related_links",
  685. "__types",
  686. ]
  687. self.assertEqual(list(content.keys()), field_order)
  688. def test_meta_status_draft(self):
  689. # Unpublish the page
  690. Page.objects.get(id=16).unpublish()
  691. response = self.get_response(16)
  692. content = json.loads(response.content.decode("UTF-8"))
  693. self.assertIn("status", content["meta"])
  694. self.assertEqual(
  695. content["meta"]["status"],
  696. {"status": "draft", "live": False, "has_unpublished_changes": True},
  697. )
  698. def test_meta_status_live_draft(self):
  699. # Save revision without republish
  700. Page.objects.get(id=16).save_revision()
  701. response = self.get_response(16)
  702. content = json.loads(response.content.decode("UTF-8"))
  703. self.assertIn("status", content["meta"])
  704. self.assertEqual(
  705. content["meta"]["status"],
  706. {"status": "live + draft", "live": True, "has_unpublished_changes": True},
  707. )
  708. def test_meta_status_scheduled(self):
  709. # Unpublish and save revision with go live date in the future
  710. Page.objects.get(id=16).unpublish()
  711. tomorrow = timezone.now() + datetime.timedelta(days=1)
  712. Page.objects.get(id=16).save_revision(approved_go_live_at=tomorrow)
  713. response = self.get_response(16)
  714. content = json.loads(response.content.decode("UTF-8"))
  715. self.assertIn("status", content["meta"])
  716. self.assertEqual(
  717. content["meta"]["status"],
  718. {"status": "scheduled", "live": False, "has_unpublished_changes": True},
  719. )
  720. def test_meta_status_expired(self):
  721. # Unpublish and set expired flag
  722. Page.objects.get(id=16).unpublish()
  723. Page.objects.filter(id=16).update(expired=True)
  724. response = self.get_response(16)
  725. content = json.loads(response.content.decode("UTF-8"))
  726. self.assertIn("status", content["meta"])
  727. self.assertEqual(
  728. content["meta"]["status"],
  729. {"status": "expired", "live": False, "has_unpublished_changes": True},
  730. )
  731. def test_meta_children_for_parent(self):
  732. # Homepage should have children
  733. response = self.get_response(2)
  734. content = json.loads(response.content.decode("UTF-8"))
  735. self.assertIn("children", content["meta"])
  736. self.assertEqual(
  737. content["meta"]["children"],
  738. {
  739. "count": 5,
  740. "listing_url": "http://localhost/admin/api/main/pages/?child_of=2",
  741. },
  742. )
  743. def test_meta_descendants(self):
  744. # Homepage should have children
  745. response = self.get_response(2)
  746. content = json.loads(response.content.decode("UTF-8"))
  747. self.assertIn("descendants", content["meta"])
  748. self.assertEqual(
  749. content["meta"]["descendants"],
  750. {
  751. "count": 18,
  752. "listing_url": "http://localhost/admin/api/main/pages/?descendant_of=2",
  753. },
  754. )
  755. def test_meta_ancestors(self):
  756. # Homepage should have children
  757. response = self.get_response(16)
  758. content = json.loads(response.content.decode("UTF-8"))
  759. self.assertIn("ancestors", content["meta"])
  760. self.assertIsInstance(content["meta"]["ancestors"], list)
  761. self.assertEqual(len(content["meta"]["ancestors"]), 3)
  762. self.assertEqual(
  763. content["meta"]["ancestors"][0].keys(),
  764. {"id", "meta", "title", "admin_display_title"},
  765. )
  766. self.assertEqual(content["meta"]["ancestors"][0]["title"], "Root")
  767. self.assertEqual(content["meta"]["ancestors"][1]["title"], "Home page")
  768. self.assertEqual(content["meta"]["ancestors"][2]["title"], "Blog index")
  769. def test_alias_page(self):
  770. original = Page.objects.get(id=16).specific
  771. alias = original.create_alias(update_slug="new-slug")
  772. response = self.get_response(alias.id)
  773. self.assertEqual(response.status_code, 200)
  774. self.assertEqual(response["Content-type"], "application/json")
  775. # Will crash if the JSON is invalid
  776. content = json.loads(response.content.decode("UTF-8"))
  777. self.assertEqual(content["meta"]["type"], "demosite.BlogEntryPage")
  778. self.assertEqual(
  779. content["meta"]["html_url"], "http://localhost/blog-index/new-slug/"
  780. )
  781. # Check alias_of field
  782. self.assertIn("alias_of", content["meta"])
  783. self.assertIsInstance(content["meta"]["alias_of"], dict)
  784. self.assertEqual(
  785. set(content["meta"]["alias_of"].keys()), {"id", "meta", "title"}
  786. )
  787. self.assertEqual(content["meta"]["alias_of"]["id"], 16)
  788. self.assertIsInstance(content["meta"]["alias_of"]["meta"], dict)
  789. self.assertEqual(
  790. set(content["meta"]["alias_of"]["meta"].keys()),
  791. {"type", "detail_url", "html_url"},
  792. )
  793. self.assertEqual(
  794. content["meta"]["alias_of"]["meta"]["type"], "demosite.BlogEntryPage"
  795. )
  796. self.assertEqual(
  797. content["meta"]["alias_of"]["meta"]["detail_url"],
  798. "http://localhost/admin/api/main/pages/16/",
  799. )
  800. self.assertEqual(
  801. content["meta"]["alias_of"]["meta"]["html_url"],
  802. "http://localhost/blog-index/blog-post/",
  803. )
  804. # FIELDS
  805. def test_remove_all_meta_fields(self):
  806. response = self.get_response(
  807. 16,
  808. fields="-type,-detail_url,-slug,-first_published_at,-html_url,-descendants,-latest_revision_created_at,-alias_of,-children,-ancestors,-show_in_menus,-seo_title,-parent,-status,-search_description",
  809. )
  810. content = json.loads(response.content.decode("UTF-8"))
  811. self.assertNotIn("meta", set(content.keys()))
  812. self.assertIn("id", set(content.keys()))
  813. def test_remove_all_fields(self):
  814. response = self.get_response(16, fields="_,id,type")
  815. content = json.loads(response.content.decode("UTF-8"))
  816. self.assertEqual(set(content.keys()), {"id", "meta", "__types"})
  817. self.assertEqual(set(content["meta"].keys()), {"type"})
  818. def test_all_nested_fields(self):
  819. response = self.get_response(16, fields="feed_image(*)")
  820. content = json.loads(response.content.decode("UTF-8"))
  821. self.assertEqual(
  822. set(content["feed_image"].keys()),
  823. {"id", "meta", "title", "width", "height", "thumbnail"},
  824. )
  825. def test_fields_foreign_key(self):
  826. response = self.get_response(16)
  827. content = json.loads(response.content.decode("UTF-8"))
  828. feed_image = content["feed_image"]
  829. self.assertIsInstance(feed_image, dict)
  830. self.assertEqual(set(feed_image.keys()), {"id", "meta", "title"})
  831. self.assertIsInstance(feed_image["id"], int)
  832. self.assertIsInstance(feed_image["meta"], dict)
  833. self.assertEqual(
  834. set(feed_image["meta"].keys()), {"type", "detail_url", "download_url"}
  835. )
  836. self.assertEqual(feed_image["meta"]["type"], "wagtailimages.Image")
  837. self.assertEqual(
  838. feed_image["meta"]["detail_url"],
  839. "http://localhost/admin/api/main/images/%d/" % feed_image["id"],
  840. )
  841. class TestAdminPageDetailWithStreamField(AdminAPITestCase):
  842. fixtures = ["test.json"]
  843. def setUp(self):
  844. super().setUp()
  845. self.homepage = Page.objects.get(url_path="/home/")
  846. def make_stream_page(self, body):
  847. stream_page = StreamPage(title="stream page", slug="stream-page", body=body)
  848. return self.homepage.add_child(instance=stream_page)
  849. def test_can_fetch_streamfield_content(self):
  850. stream_page = self.make_stream_page('[{"type": "text", "value": "foo"}]')
  851. response_url = reverse("wagtailadmin_api:pages:detail", args=(stream_page.id,))
  852. response = self.client.get(response_url)
  853. self.assertEqual(response.status_code, 200)
  854. self.assertEqual(response["content-type"], "application/json")
  855. content = json.loads(response.content.decode("utf-8"))
  856. self.assertIn("id", content)
  857. self.assertEqual(content["id"], stream_page.id)
  858. self.assertIn("body", content)
  859. self.assertEqual(len(content["body"]), 1)
  860. self.assertEqual(content["body"][0]["type"], "text")
  861. self.assertEqual(content["body"][0]["value"], "foo")
  862. self.assertTrue(content["body"][0]["id"])
  863. def test_image_block(self):
  864. stream_page = self.make_stream_page('[{"type": "image", "value": 1}]')
  865. response_url = reverse("wagtailadmin_api:pages:detail", args=(stream_page.id,))
  866. response = self.client.get(response_url)
  867. content = json.loads(response.content.decode("utf-8"))
  868. # ForeignKeys in a StreamField shouldn't be translated into dictionary representation
  869. self.assertEqual(content["body"][0]["type"], "image")
  870. self.assertEqual(content["body"][0]["value"], 1)
  871. class TestCustomAdminDisplayTitle(AdminAPITestCase):
  872. fixtures = ["test.json"]
  873. def setUp(self):
  874. super().setUp()
  875. self.event_page = Page.objects.get(url_path="/home/events/saint-patrick/")
  876. def test_custom_admin_display_title_shown_on_detail_page(self):
  877. api_url = reverse("wagtailadmin_api:pages:detail", args=(self.event_page.id,))
  878. response = self.client.get(api_url)
  879. content = json.loads(response.content.decode("utf-8"))
  880. self.assertEqual(content["title"], "Saint Patrick")
  881. self.assertEqual(content["admin_display_title"], "Saint Patrick (single event)")
  882. def test_custom_admin_display_title_shown_on_listing(self):
  883. api_url = reverse("wagtailadmin_api:pages:listing")
  884. response = self.client.get(api_url)
  885. content = json.loads(response.content.decode("utf-8"))
  886. matching_items = [
  887. item for item in content["items"] if item["id"] == self.event_page.id
  888. ]
  889. self.assertEqual(1, len(matching_items))
  890. self.assertEqual(matching_items[0]["title"], "Saint Patrick")
  891. self.assertEqual(
  892. matching_items[0]["admin_display_title"], "Saint Patrick (single event)"
  893. )
  894. class TestCopyPageAction(AdminAPITestCase):
  895. fixtures = ["test.json"]
  896. def get_response(self, page_id, data):
  897. return self.client.post(
  898. reverse("wagtailadmin_api:pages:action", args=[page_id, "copy"]), data
  899. )
  900. def test_copy_page(self):
  901. response = self.get_response(3, {})
  902. self.assertEqual(response.status_code, 201)
  903. content = json.loads(response.content.decode("utf-8"))
  904. new_page = Page.objects.get(id=content["id"])
  905. self.assertEqual(new_page.title, "Events")
  906. self.assertEqual(new_page.slug, "events-1")
  907. self.assertTrue(new_page.live)
  908. self.assertFalse(new_page.get_children().exists())
  909. def test_copy_page_change_title(self):
  910. response = self.get_response(3, {"title": "New title"})
  911. self.assertEqual(response.status_code, 201)
  912. content = json.loads(response.content.decode("utf-8"))
  913. new_page = Page.objects.get(id=content["id"])
  914. self.assertEqual(new_page.title, "New title")
  915. self.assertEqual(new_page.slug, "events-1")
  916. def test_copy_page_change_slug(self):
  917. response = self.get_response(3, {"slug": "new-slug"})
  918. self.assertEqual(response.status_code, 201)
  919. content = json.loads(response.content.decode("utf-8"))
  920. new_page = Page.objects.get(id=content["id"])
  921. self.assertEqual(new_page.slug, "new-slug")
  922. def test_copy_page_destination(self):
  923. response = self.get_response(3, {"destination_page_id": 3})
  924. self.assertEqual(response.status_code, 201)
  925. content = json.loads(response.content.decode("utf-8"))
  926. new_page = Page.objects.get(id=content["id"])
  927. self.assertEqual(new_page.title, "Events")
  928. self.assertTrue(new_page.live)
  929. self.assertFalse(new_page.get_children().exists())
  930. def test_copy_page_recursive(self):
  931. response = self.get_response(
  932. 3,
  933. {
  934. "recursive": True,
  935. },
  936. )
  937. self.assertEqual(response.status_code, 201)
  938. content = json.loads(response.content.decode("utf-8"))
  939. new_page = Page.objects.get(id=content["id"])
  940. self.assertEqual(new_page.title, "Events")
  941. self.assertTrue(new_page.get_children().exists())
  942. def test_copy_page_in_draft(self):
  943. response = self.get_response(
  944. 3,
  945. {
  946. "keep_live": False,
  947. },
  948. )
  949. self.assertEqual(response.status_code, 201)
  950. content = json.loads(response.content.decode("utf-8"))
  951. new_page = Page.objects.get(id=content["id"])
  952. self.assertEqual(new_page.title, "Events")
  953. self.assertFalse(new_page.live)
  954. # Check errors
  955. def test_without_publish_permissions_at_destination_with_keep_live_false(self):
  956. self.user.is_superuser = False
  957. self.user.user_permissions.add(
  958. Permission.objects.get(
  959. content_type__app_label="wagtailadmin", codename="access_admin"
  960. )
  961. )
  962. self.user.groups.add(Group.objects.get(name="Editors"))
  963. self.user.save()
  964. response = self.get_response(
  965. 3,
  966. {
  967. "destination_page_id": 1,
  968. "keep_live": False,
  969. },
  970. )
  971. self.assertEqual(response.status_code, 403)
  972. content = json.loads(response.content.decode("utf-8"))
  973. self.assertEqual(
  974. content, {"detail": "You do not have permission to perform this action."}
  975. )
  976. def test_recursively_copy_into_self(self):
  977. response = self.get_response(
  978. 3,
  979. {
  980. "destination_page_id": 3,
  981. "recursive": True,
  982. },
  983. )
  984. self.assertEqual(response.status_code, 400)
  985. content = json.loads(response.content.decode("utf-8"))
  986. self.assertEqual(
  987. content,
  988. {"message": "You cannot copy a tree branch recursively into itself"},
  989. )
  990. def test_without_create_permissions_at_destination(self):
  991. self.user.is_superuser = False
  992. self.user.user_permissions.add(
  993. Permission.objects.get(
  994. content_type__app_label="wagtailadmin", codename="access_admin"
  995. )
  996. )
  997. self.user.save()
  998. response = self.get_response(
  999. 3,
  1000. {
  1001. "destination_page_id": 2,
  1002. },
  1003. )
  1004. self.assertEqual(response.status_code, 403)
  1005. content = json.loads(response.content.decode("utf-8"))
  1006. self.assertEqual(
  1007. content, {"detail": "You do not have permission to perform this action."}
  1008. )
  1009. def test_without_publish_permissions_at_destination_with_keep_live(self):
  1010. self.user.is_superuser = False
  1011. self.user.user_permissions.add(
  1012. Permission.objects.get(
  1013. content_type__app_label="wagtailadmin", codename="access_admin"
  1014. )
  1015. )
  1016. self.user.groups.add(Group.objects.get(name="Editors"))
  1017. self.user.save()
  1018. GroupPagePermission.objects.create(
  1019. group=Group.objects.get(name="Editors"), page_id=2, permission_type="add"
  1020. )
  1021. response = self.get_response(
  1022. 3,
  1023. {
  1024. "destination_page_id": 2,
  1025. },
  1026. )
  1027. self.assertEqual(response.status_code, 403)
  1028. content = json.loads(response.content.decode("utf-8"))
  1029. self.assertEqual(
  1030. content, {"detail": "You do not have permission to perform this action."}
  1031. )
  1032. def test_respects_page_creation_rules(self):
  1033. # Only one homepage may exist
  1034. response = self.get_response(2, {})
  1035. self.assertEqual(response.status_code, 403)
  1036. content = json.loads(response.content.decode("utf-8"))
  1037. self.assertEqual(
  1038. content, {"detail": "You do not have permission to perform this action."}
  1039. )
  1040. def test_copy_page_slug_in_use(self):
  1041. response = self.get_response(
  1042. 3,
  1043. {
  1044. "slug": "events",
  1045. },
  1046. )
  1047. self.assertEqual(response.status_code, 400)
  1048. content = json.loads(response.content.decode("utf-8"))
  1049. self.assertEqual(content, {"slug": ["This slug is already in use"]})
  1050. class TestConvertAliasPageAction(AdminAPITestCase):
  1051. fixtures = ["test.json"]
  1052. def setUp(self):
  1053. super().setUp()
  1054. # Find root page
  1055. self.root_page = Page.objects.get(id=2)
  1056. # Add child page
  1057. self.child_page = SimplePage(
  1058. title="Hello world!", slug="hello-world", content="hello"
  1059. )
  1060. self.root_page.add_child(instance=self.child_page)
  1061. # Add alias page
  1062. self.alias_page = self.child_page.create_alias(update_slug="alias-page")
  1063. def get_response(self, page_id):
  1064. return self.client.post(
  1065. reverse("wagtailadmin_api:pages:action", args=[page_id, "convert_alias"])
  1066. )
  1067. def test_convert_alias(self):
  1068. response = self.get_response(self.alias_page.id)
  1069. self.assertEqual(response.status_code, 200)
  1070. # Check the page was converted
  1071. self.alias_page.refresh_from_db()
  1072. self.assertIsNone(self.alias_page.alias_of)
  1073. # Check that a revision was created
  1074. revision = self.alias_page.revisions.get()
  1075. self.assertEqual(revision.user, self.user)
  1076. self.assertEqual(self.alias_page.live_revision, revision)
  1077. # Check audit log
  1078. log = PageLogEntry.objects.get(action="wagtail.convert_alias")
  1079. self.assertFalse(log.content_changed)
  1080. self.assertEqual(
  1081. log.data,
  1082. {
  1083. "page": {
  1084. "id": self.alias_page.id,
  1085. "title": self.alias_page.get_admin_display_title(),
  1086. }
  1087. },
  1088. )
  1089. self.assertEqual(log.page, self.alias_page.page_ptr)
  1090. self.assertEqual(log.revision, revision)
  1091. self.assertEqual(log.user, self.user)
  1092. def test_convert_alias_not_alias(self):
  1093. response = self.get_response(self.child_page.id)
  1094. self.assertEqual(response.status_code, 400)
  1095. content = json.loads(response.content.decode("utf-8"))
  1096. self.assertEqual(content, {"message": "Page must be an alias to be converted."})
  1097. def test_convert_alias_bad_permission(self):
  1098. # Remove privileges from user
  1099. self.user.is_superuser = False
  1100. self.user.user_permissions.add(
  1101. Permission.objects.get(
  1102. content_type__app_label="wagtailadmin", codename="access_admin"
  1103. )
  1104. )
  1105. self.user.save()
  1106. response = self.get_response(self.alias_page.id)
  1107. self.assertEqual(response.status_code, 403)
  1108. class TestDeletePageAction(AdminAPITestCase):
  1109. fixtures = ["test.json"]
  1110. def get_response(self, page_id):
  1111. return self.client.post(
  1112. reverse("wagtailadmin_api:pages:action", args=[page_id, "delete"])
  1113. )
  1114. def test_delete_page(self):
  1115. response = self.get_response(4)
  1116. # Page is deleted
  1117. self.assertEqual(response.status_code, 204)
  1118. self.assertFalse(Page.objects.filter(id=4).exists())
  1119. def test_delete_page_bad_permissions(self):
  1120. # Remove privileges from user
  1121. self.user.is_superuser = False
  1122. self.user.user_permissions.add(
  1123. Permission.objects.get(
  1124. content_type__app_label="wagtailadmin", codename="access_admin"
  1125. )
  1126. )
  1127. self.user.save()
  1128. # delete
  1129. response = self.get_response(4)
  1130. self.assertEqual(response.status_code, 403)
  1131. content = json.loads(response.content.decode("utf-8"))
  1132. self.assertEqual(
  1133. content, {"detail": "You do not have permission to perform this action."}
  1134. )
  1135. # Page is still there
  1136. self.assertTrue(Page.objects.filter(id=4).exists())
  1137. class TestPublishPageAction(AdminAPITestCase):
  1138. fixtures = ["test.json"]
  1139. def get_response(self, page_id):
  1140. return self.client.post(
  1141. reverse("wagtailadmin_api:pages:action", args=[page_id, "publish"])
  1142. )
  1143. def test_publish_page(self):
  1144. unpublished_page = Page.objects.get(slug="tentative-unpublished-event")
  1145. self.assertIsNone(unpublished_page.first_published_at)
  1146. self.assertEqual(
  1147. unpublished_page.first_published_at, unpublished_page.last_published_at
  1148. )
  1149. self.assertIs(unpublished_page.live, False)
  1150. response = self.get_response(unpublished_page.id)
  1151. self.assertEqual(response.status_code, 200)
  1152. unpublished_page.refresh_from_db()
  1153. self.assertIsNotNone(unpublished_page.first_published_at)
  1154. self.assertEqual(
  1155. unpublished_page.first_published_at, unpublished_page.last_published_at
  1156. )
  1157. self.assertIs(unpublished_page.live, True)
  1158. def test_publish_insufficient_permissions(self):
  1159. self.user.is_superuser = False
  1160. self.user.user_permissions.add(
  1161. Permission.objects.get(
  1162. content_type__app_label="wagtailadmin", codename="access_admin"
  1163. )
  1164. )
  1165. self.user.groups.add(Group.objects.get(name="Editors"))
  1166. self.user.save()
  1167. response = self.get_response(4)
  1168. self.assertEqual(response.status_code, 403)
  1169. content = json.loads(response.content.decode("utf-8"))
  1170. self.assertEqual(
  1171. content, {"detail": "You do not have permission to perform this action."}
  1172. )
  1173. def test_publish_alias_page(self):
  1174. home = Page.objects.get(slug="home")
  1175. alias_page = home.create_alias(update_slug="new-home-page")
  1176. response = self.get_response(alias_page.id)
  1177. self.assertEqual(response.status_code, 400)
  1178. content = json.loads(response.content.decode("utf-8"))
  1179. self.assertEqual(
  1180. content,
  1181. {
  1182. "message": (
  1183. "save_revision() was called on an alias page. "
  1184. "Revisions are not required for alias pages as they are an exact copy of another page."
  1185. )
  1186. },
  1187. )
  1188. class TestUnpublishPageAction(AdminAPITestCase):
  1189. fixtures = ["test.json"]
  1190. def get_response(self, page_id, data):
  1191. return self.client.post(
  1192. reverse("wagtailadmin_api:pages:action", args=[page_id, "unpublish"]), data
  1193. )
  1194. def test_unpublish_page(self):
  1195. self.assertTrue(Page.objects.get(id=3).live)
  1196. response = self.get_response(3, {})
  1197. self.assertEqual(response.status_code, 200)
  1198. # Check that the page was unpublished
  1199. self.assertFalse(Page.objects.get(id=3).live)
  1200. def test_unpublish_page_include_descendants(self):
  1201. page = Page.objects.get(slug="home")
  1202. # Check that the page has live descendants that aren't locked.
  1203. self.assertTrue(page.get_descendants().live().filter(locked=False).exists())
  1204. response = self.get_response(page.id, {"recursive": True})
  1205. self.assertEqual(response.status_code, 200)
  1206. # Check that the page is unpublished
  1207. page.refresh_from_db()
  1208. self.assertFalse(page.live)
  1209. # Check that the descendant pages that weren't locked are unpublished as well
  1210. descendant_pages = page.get_descendants().filter(locked=False)
  1211. self.assertTrue(descendant_pages.exists())
  1212. for descendant_page in descendant_pages:
  1213. self.assertFalse(descendant_page.live)
  1214. def test_unpublish_page_without_including_descendants(self):
  1215. page = Page.objects.get(slug="secret-plans")
  1216. # Check that the page has live descendants that aren't locked.
  1217. self.assertTrue(page.get_descendants().live().filter(locked=False).exists())
  1218. response = self.get_response(page.id, {"recursive": False})
  1219. self.assertEqual(response.status_code, 200)
  1220. # Check that the page is unpublished
  1221. page.refresh_from_db()
  1222. self.assertFalse(page.live)
  1223. # Check that the descendant pages that weren't locked aren't unpublished.
  1224. self.assertTrue(page.get_descendants().live().filter(locked=False).exists())
  1225. def test_unpublish_invalid_page_id(self):
  1226. response = self.get_response(12345, {})
  1227. self.assertEqual(response.status_code, 404)
  1228. def test_unpublish_page_insufficient_permission(self):
  1229. self.user.is_superuser = False
  1230. self.user.user_permissions.add(
  1231. Permission.objects.get(
  1232. content_type__app_label="wagtailadmin", codename="access_admin"
  1233. )
  1234. )
  1235. self.user.save()
  1236. response = self.get_response(3, {})
  1237. self.assertEqual(response.status_code, 403)
  1238. content = json.loads(response.content.decode("utf-8"))
  1239. self.assertEqual(
  1240. content, {"detail": "You do not have permission to perform this action."}
  1241. )
  1242. class TestMovePageAction(AdminAPITestCase):
  1243. fixtures = ["test.json"]
  1244. def get_response(self, page_id, data):
  1245. return self.client.post(
  1246. reverse("wagtailadmin_api:pages:action", args=[page_id, "move"]), data
  1247. )
  1248. def test_move_page(self):
  1249. response = self.get_response(4, {"destination_page_id": 3})
  1250. self.assertEqual(response.status_code, 200)
  1251. def test_move_page_bad_permissions(self):
  1252. # Remove privileges from user
  1253. self.user.is_superuser = False
  1254. self.user.user_permissions.add(
  1255. Permission.objects.get(
  1256. content_type__app_label="wagtailadmin", codename="access_admin"
  1257. )
  1258. )
  1259. self.user.save()
  1260. # Move
  1261. response = self.get_response(4, {"destination_page_id": 3})
  1262. self.assertEqual(response.status_code, 403)
  1263. content = json.loads(response.content.decode("utf-8"))
  1264. self.assertEqual(
  1265. content, {"detail": "You do not have permission to perform this action."}
  1266. )
  1267. def test_move_page_without_destination_page_id(self):
  1268. response = self.get_response(4, {})
  1269. self.assertEqual(response.status_code, 400)
  1270. content = json.loads(response.content.decode("utf-8"))
  1271. self.assertEqual(content, {"destination_page_id": ["This field is required."]})
  1272. class TestCopyForTranslationAction(AdminAPITestCase):
  1273. fixtures = ["test.json"]
  1274. def get_response(self, page_id, data):
  1275. return self.client.post(
  1276. reverse(
  1277. "wagtailadmin_api:pages:action", args=[page_id, "copy_for_translation"]
  1278. ),
  1279. data,
  1280. )
  1281. def setUp(self):
  1282. super().setUp()
  1283. self.en_homepage = Page.objects.get(url_path="/home/").specific
  1284. self.en_eventindex = EventIndex.objects.get(url_path="/home/events/")
  1285. self.en_eventpage = EventPage.objects.get(url_path="/home/events/christmas/")
  1286. self.root_page = self.en_homepage.get_parent()
  1287. self.fr_locale = Locale.objects.create(language_code="fr")
  1288. def test_copy_homepage_for_translation(self):
  1289. response = self.get_response(self.en_homepage.id, {"locale": "fr"})
  1290. self.assertEqual(response.status_code, 201)
  1291. content = json.loads(response.content.decode("utf-8"))
  1292. fr_homepage = Page.objects.get(id=content["id"])
  1293. self.assertNotEqual(self.en_homepage.id, fr_homepage.id)
  1294. self.assertEqual(fr_homepage.locale, self.fr_locale)
  1295. self.assertEqual(fr_homepage.translation_key, self.en_homepage.translation_key)
  1296. # At the top level, the language code should be appended to the slug
  1297. self.assertEqual(fr_homepage.slug, "home-fr")
  1298. # Translation must be in draft
  1299. self.assertFalse(fr_homepage.live)
  1300. self.assertTrue(fr_homepage.has_unpublished_changes)
  1301. def test_copy_childpage_without_parent(self):
  1302. response = self.get_response(self.en_eventindex.id, {"locale": "fr"})
  1303. self.assertEqual(response.status_code, 400)
  1304. content = json.loads(response.content.decode("utf-8"))
  1305. self.assertEqual(content, {"message": "Parent page is not translated."})
  1306. def test_copy_childpage_with_copy_parents(self):
  1307. response = self.get_response(
  1308. self.en_eventindex.id, {"locale": "fr", "copy_parents": True}
  1309. )
  1310. self.assertEqual(response.status_code, 201)
  1311. content = json.loads(response.content.decode("utf-8"))
  1312. fr_eventindex = Page.objects.get(id=content["id"])
  1313. self.assertNotEqual(self.en_eventindex.id, fr_eventindex.id)
  1314. self.assertEqual(fr_eventindex.locale, self.fr_locale)
  1315. self.assertEqual(
  1316. fr_eventindex.translation_key, self.en_eventindex.translation_key
  1317. )
  1318. self.assertEqual(self.en_eventindex.slug, fr_eventindex.slug)
  1319. # This should create the homepage as well
  1320. fr_homepage = fr_eventindex.get_parent()
  1321. self.assertNotEqual(self.en_homepage.id, fr_homepage.id)
  1322. self.assertEqual(fr_homepage.locale, self.fr_locale)
  1323. self.assertEqual(fr_homepage.translation_key, self.en_homepage.translation_key)
  1324. self.assertEqual(fr_homepage.slug, "home-fr")
  1325. def test_copy_for_translation_no_locale(self):
  1326. response = self.get_response(self.en_homepage.id, {})
  1327. self.assertEqual(response.status_code, 400)
  1328. content = json.loads(response.content.decode("utf-8"))
  1329. self.assertEqual(content, {"locale": ["This field is required."]})
  1330. def test_copy_for_translation_unknown_locale(self):
  1331. response = self.get_response(self.en_homepage.id, {"locale": "de"})
  1332. self.assertEqual(response.status_code, 404)
  1333. content = json.loads(response.content.decode("utf-8"))
  1334. self.assertEqual(content, {"message": "No Locale matches the given query."})
  1335. class TestCreatePageAliasAction(AdminAPITestCase):
  1336. fixtures = ["test.json"]
  1337. def setUp(self):
  1338. super().setUp()
  1339. self.events_index = EventIndex.objects.get(url_path="/home/events/")
  1340. self.about_us = SimplePage.objects.get(url_path="/home/about-us/")
  1341. def get_response(self, page_id, data):
  1342. return self.client.post(
  1343. reverse("wagtailadmin_api:pages:action", args=[page_id, "create_alias"]),
  1344. data,
  1345. )
  1346. def test_create_alias(self):
  1347. # Set a different draft title, aliases are not supposed to
  1348. # have a different draft_title because they don't have revisions.
  1349. # This should be corrected when copying
  1350. self.about_us.draft_title = "Draft title"
  1351. self.about_us.save(update_fields=["draft_title"])
  1352. response = self.get_response(
  1353. self.about_us.id, data={"update_slug": "new-about-us"}
  1354. )
  1355. self.assertEqual(response.status_code, 201)
  1356. content = json.loads(response.content.decode("utf-8"))
  1357. new_about_us = Page.objects.get(id=content["id"])
  1358. # Check that new_about_us is correct
  1359. self.assertIsInstance(new_about_us.specific, SimplePage)
  1360. self.assertEqual(new_about_us.slug, "new-about-us")
  1361. # Draft title should be changed to match the live title
  1362. self.assertEqual(new_about_us.draft_title, "About us")
  1363. # Check that new_about_us is a different page
  1364. self.assertNotEqual(self.about_us.id, new_about_us.id)
  1365. # Check that the url path was updated
  1366. self.assertEqual(new_about_us.url_path, "/home/new-about-us/")
  1367. # Check that the alias_of field was filled in
  1368. self.assertEqual(new_about_us.alias_of.specific, self.about_us)
  1369. def test_create_alias_recursive(self):
  1370. response = self.get_response(
  1371. self.events_index.id,
  1372. data={"recursive": True, "update_slug": "new-events-index"},
  1373. )
  1374. self.assertEqual(response.status_code, 201)
  1375. content = json.loads(response.content.decode("utf-8"))
  1376. new_events_index = Page.objects.get(id=content["id"])
  1377. # Get christmas event
  1378. old_christmas_event = (
  1379. self.events_index.get_children().filter(slug="christmas").first()
  1380. )
  1381. new_christmas_event = (
  1382. new_events_index.get_children().filter(slug="christmas").first()
  1383. )
  1384. # Check that the event exists in both places
  1385. self.assertIsNotNone(new_christmas_event, "Child pages weren't copied")
  1386. self.assertIsNotNone(
  1387. old_christmas_event, "Child pages were removed from original page"
  1388. )
  1389. # Check that the url path was updated
  1390. self.assertEqual(
  1391. new_christmas_event.url_path, "/home/new-events-index/christmas/"
  1392. )
  1393. # Check that the children were also created as aliases
  1394. self.assertEqual(new_christmas_event.alias_of, old_christmas_event)
  1395. def test_create_alias_doesnt_copy_recursively_to_the_same_tree(self):
  1396. response = self.get_response(
  1397. self.events_index.id,
  1398. data={"recursive": True, "destination_page_id": self.events_index.id},
  1399. )
  1400. self.assertEqual(response.status_code, 400)
  1401. content = json.loads(response.content.decode("utf-8"))
  1402. self.assertEqual(
  1403. content,
  1404. {"message": "You cannot copy a tree branch recursively into itself"},
  1405. )
  1406. def test_create_alias_without_publish_permissions(self):
  1407. self.user.is_superuser = False
  1408. self.user.user_permissions.add(
  1409. Permission.objects.get(
  1410. content_type__app_label="wagtailadmin", codename="access_admin"
  1411. )
  1412. )
  1413. self.user.save()
  1414. response = self.get_response(
  1415. self.events_index.id,
  1416. data={"recursive": True, "update_slug": "new-events-index"},
  1417. )
  1418. self.assertEqual(response.status_code, 403)
  1419. content = json.loads(response.content.decode("utf-8"))
  1420. self.assertEqual(
  1421. content, {"detail": "You do not have permission to perform this action."}
  1422. )
  1423. class TestRevertToPageRevisionAction(AdminAPITestCase):
  1424. fixtures = ["test.json"]
  1425. def setUp(self):
  1426. super().setUp()
  1427. self.events_page = Page.objects.get(id=3)
  1428. # Create revision to revert back to
  1429. self.first_revision = self.events_page.save_revision()
  1430. # Change page title
  1431. self.events_page.title = "Evenements"
  1432. self.events_page.save_revision().publish()
  1433. def get_response(self, page_id, data):
  1434. return self.client.post(
  1435. reverse(
  1436. "wagtailadmin_api:pages:action",
  1437. args=[page_id, "revert_to_page_revision"],
  1438. ),
  1439. data,
  1440. )
  1441. def test_revert_to_page_revision(self):
  1442. self.assertEqual(self.events_page.title, "Evenements")
  1443. response = self.get_response(
  1444. self.events_page.id, {"revision_id": self.first_revision.id}
  1445. )
  1446. self.assertEqual(response.status_code, 200)
  1447. self.events_page.get_latest_revision().publish()
  1448. self.events_page.refresh_from_db()
  1449. self.assertEqual(self.events_page.title, "Events")
  1450. def test_revert_to_page_revision_bad_permissions(self):
  1451. # Remove privileges from user
  1452. self.user.is_superuser = False
  1453. self.user.user_permissions.add(
  1454. Permission.objects.get(
  1455. content_type__app_label="wagtailadmin", codename="access_admin"
  1456. )
  1457. )
  1458. self.user.save()
  1459. response = self.get_response(
  1460. self.events_page.id, {"revision_id": self.first_revision.id}
  1461. )
  1462. self.assertEqual(response.status_code, 403)
  1463. content = json.loads(response.content.decode("utf-8"))
  1464. self.assertEqual(
  1465. content, {"detail": "You do not have permission to perform this action."}
  1466. )
  1467. def test_revert_to_page_revision_without_revision_id(self):
  1468. response = self.get_response(self.events_page.id, {})
  1469. self.assertEqual(response.status_code, 400)
  1470. content = json.loads(response.content.decode("utf-8"))
  1471. self.assertEqual(content, {"revision_id": ["This field is required."]})
  1472. def test_revert_to_page_revision_bad_revision_id(self):
  1473. self.assertEqual(self.events_page.title, "Evenements")
  1474. response = self.get_response(self.events_page.id, {"revision_id": 999})
  1475. self.assertEqual(response.status_code, 404)
  1476. content = json.loads(response.content.decode("utf-8"))
  1477. self.assertEqual(
  1478. content, {"message": "No PageRevision matches the given query."}
  1479. )
  1480. # Overwrite imported test cases do Django doesn't run them
  1481. TestPageDetail = None
  1482. TestPageListing = None