1import collections 2import datetime 3import json 4 5from django.contrib.auth import get_user_model 6from django.urls import reverse 7from django.utils import timezone 8 9from wagtail.api.v2.tests.test_pages import TestPageDetail, TestPageListing 10from wagtail.core import hooks 11from wagtail.core.models import Locale, Page 12from wagtail.tests.demosite import models 13from wagtail.tests.testapp.models import SimplePage, StreamPage 14from wagtail.users.models import UserProfile 15 16from .utils import AdminAPITestCase 17 18 19def get_total_page_count(): 20 # Need to take away 1 as the root page is invisible over the API by default 21 return Page.objects.count() - 1 22 23 24class TestAdminPageListing(AdminAPITestCase, TestPageListing): 25 fixtures = ['demosite.json'] 26 27 def get_response(self, **params): 28 return self.client.get(reverse('wagtailadmin_api:pages:listing'), params) 29 30 def get_page_id_list(self, content): 31 return [page['id'] for page in content['items']] 32 33 # BASIC TESTS 34 35 def test_basic(self): 36 response = self.get_response() 37 38 self.assertEqual(response.status_code, 200) 39 self.assertEqual(response['Content-type'], 'application/json') 40 41 # Will crash if the JSON is invalid 42 content = json.loads(response.content.decode('UTF-8')) 43 44 # Check that the meta section is there 45 self.assertIn('meta', content) 46 self.assertIsInstance(content['meta'], dict) 47 48 # Check that the total count is there and correct 49 self.assertIn('total_count', content['meta']) 50 self.assertIsInstance(content['meta']['total_count'], int) 51 self.assertEqual(content['meta']['total_count'], get_total_page_count()) 52 53 # Check that the items section is there 54 self.assertIn('items', content) 55 self.assertIsInstance(content['items'], list) 56 57 # Check that each page has a meta section with type, detail_url, html_url, status and children attributes 58 for page in content['items']: 59 self.assertIn('meta', page) 60 self.assertIsInstance(page['meta'], dict) 61 self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'html_url', 'status', 'children', 'slug', 'first_published_at', 'latest_revision_created_at'}) 62 63 # Check the type info 64 self.assertIsInstance(content['__types'], dict) 65 self.assertEqual(set(content['__types'].keys()), { 66 'demosite.EventPage', 67 'demosite.StandardIndexPage', 68 'demosite.PersonPage', 69 'demosite.HomePage', 70 'demosite.StandardPage', 71 'demosite.EventIndexPage', 72 'demosite.ContactPage', 73 'demosite.BlogEntryPage', 74 'demosite.BlogIndexPage', 75 }) 76 self.assertEqual(set(content['__types']['demosite.EventPage'].keys()), {'verbose_name', 'verbose_name_plural'}) 77 self.assertEqual(content['__types']['demosite.EventPage']['verbose_name'], 'event page') 78 self.assertEqual(content['__types']['demosite.EventPage']['verbose_name_plural'], 'event pages') 79 80 # Not applicable to the admin API 81 test_unpublished_pages_dont_appear_in_list = None 82 test_private_pages_dont_appear_in_list = None 83 84 def test_unpublished_pages_appear_in_list(self): 85 total_count = get_total_page_count() 86 87 page = models.BlogEntryPage.objects.get(id=16) 88 page.unpublish() 89 90 response = self.get_response() 91 content = json.loads(response.content.decode('UTF-8')) 92 self.assertEqual(content['meta']['total_count'], total_count) 93 94 def test_private_pages_appear_in_list(self): 95 total_count = get_total_page_count() 96 97 page = models.BlogIndexPage.objects.get(id=5) 98 page.view_restrictions.create(password='test') 99 100 new_total_count = get_total_page_count() 101 self.assertEqual(total_count, total_count) 102 103 response = self.get_response() 104 content = json.loads(response.content.decode('UTF-8')) 105 self.assertEqual(content['meta']['total_count'], new_total_count) 106 107 def test_get_in_non_content_language(self): 108 # set logged-in user's admin UI language to Swedish 109 user = get_user_model().objects.get(email='test@email.com') 110 UserProfile.objects.update_or_create(user=user, defaults={'preferred_language': 'se'}) 111 112 response = self.get_response() 113 114 self.assertEqual(response.status_code, 200) 115 self.assertEqual(response['Content-type'], 'application/json') 116 117 # Will crash if the JSON is invalid 118 content = json.loads(response.content.decode('UTF-8')) 119 self.assertIn('meta', content) 120 121 # FIELDS 122 123 # Not applicable to the admin API 124 test_parent_field_gives_error = None 125 126 def test_fields(self): 127 response = self.get_response(type='demosite.BlogEntryPage', fields='title,date,feed_image') 128 content = json.loads(response.content.decode('UTF-8')) 129 130 for page in content['items']: 131 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title', 'date', 'feed_image'}) 132 133 def test_fields_default(self): 134 response = self.get_response(type='demosite.BlogEntryPage') 135 content = json.loads(response.content.decode('UTF-8')) 136 137 for page in content['items']: 138 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title'}) 139 self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'html_url', 'children', 'status', 'slug', 'first_published_at', 'latest_revision_created_at'}) 140 141 def test_remove_meta_fields(self): 142 response = self.get_response(fields='-html_url') 143 content = json.loads(response.content.decode('UTF-8')) 144 145 for page in content['items']: 146 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title'}) 147 self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'slug', 'first_published_at', 'latest_revision_created_at', 'status', 'children'}) 148 149 def test_remove_all_meta_fields(self): 150 response = self.get_response(fields='-type,-detail_url,-slug,-first_published_at,-html_url,-latest_revision_created_at,-status,-children') 151 content = json.loads(response.content.decode('UTF-8')) 152 153 for page in content['items']: 154 self.assertEqual(set(page.keys()), {'id', 'title', 'admin_display_title'}) 155 156 def test_remove_fields(self): 157 response = self.get_response(fields='-title,-admin_display_title') 158 content = json.loads(response.content.decode('UTF-8')) 159 160 for page in content['items']: 161 self.assertEqual(set(page.keys()), {'id', 'meta'}) 162 163 def test_remove_id_field(self): 164 response = self.get_response(fields='-id') 165 content = json.loads(response.content.decode('UTF-8')) 166 167 for page in content['items']: 168 self.assertEqual(set(page.keys()), {'meta', 'title', 'admin_display_title'}) 169 170 def test_all_fields(self): 171 response = self.get_response(type='demosite.BlogEntryPage', fields='*') 172 content = json.loads(response.content.decode('UTF-8')) 173 174 for page in content['items']: 175 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title', 'date', 'related_links', 'tags', 'carousel_items', 'body', 'feed_image', 'feed_image_thumbnail'}) 176 self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'show_in_menus', 'first_published_at', 'seo_title', 'slug', 'parent', 'html_url', 'search_description', 'locale', 'children', 'descendants', 'ancestors', 'translations', 'status', 'latest_revision_created_at'}) 177 178 def test_all_fields_then_remove_something(self): 179 response = self.get_response(type='demosite.BlogEntryPage', fields='*,-title,-admin_display_title,-date,-seo_title,-status') 180 content = json.loads(response.content.decode('UTF-8')) 181 182 for page in content['items']: 183 self.assertEqual(set(page.keys()), {'id', 'meta', 'related_links', 'tags', 'carousel_items', 'body', 'feed_image', 'feed_image_thumbnail'}) 184 self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'show_in_menus', 'first_published_at', 'slug', 'parent', 'html_url', 'search_description', 'locale', 'children', 'descendants', 'ancestors', 'translations', 'latest_revision_created_at'}) 185 186 def test_all_nested_fields(self): 187 response = self.get_response(type='demosite.BlogEntryPage', fields='feed_image(*)') 188 content = json.loads(response.content.decode('UTF-8')) 189 190 for page in content['items']: 191 self.assertEqual(set(page['feed_image'].keys()), {'id', 'meta', 'title', 'width', 'height', 'thumbnail'}) 192 193 def test_fields_foreign_key(self): 194 # Only the base the detail_url is different here from the public API 195 response = self.get_response(type='demosite.BlogEntryPage', fields='title,date,feed_image') 196 content = json.loads(response.content.decode('UTF-8')) 197 198 for page in content['items']: 199 feed_image = page['feed_image'] 200 201 if feed_image is not None: 202 self.assertIsInstance(feed_image, dict) 203 self.assertEqual(set(feed_image.keys()), {'id', 'meta', 'title'}) 204 self.assertIsInstance(feed_image['id'], int) 205 self.assertIsInstance(feed_image['meta'], dict) 206 self.assertEqual(set(feed_image['meta'].keys()), {'type', 'detail_url', 'download_url'}) 207 self.assertEqual(feed_image['meta']['type'], 'wagtailimages.Image') 208 self.assertEqual(feed_image['meta']['detail_url'], 'http://localhost/admin/api/main/images/%d/' % feed_image['id']) 209 210 def test_fields_parent(self): 211 response = self.get_response(type='demosite.BlogEntryPage', fields='parent') 212 content = json.loads(response.content.decode('UTF-8')) 213 214 for page in content['items']: 215 parent = page['meta']['parent'] 216 217 # All blog entry pages have the same parent 218 self.assertDictEqual(parent, { 219 'id': 5, 220 'meta': { 221 'type': 'demosite.BlogIndexPage', 222 'detail_url': 'http://localhost/admin/api/main/pages/5/', 223 'html_url': 'http://localhost/blog-index/', 224 }, 225 'title': "Blog index" 226 }) 227 228 def test_fields_descendants(self): 229 response = self.get_response(fields='descendants') 230 content = json.loads(response.content.decode('UTF-8')) 231 232 for page in content['items']: 233 descendants = page['meta']['descendants'] 234 self.assertEqual(set(descendants.keys()), {'count', 'listing_url'}) 235 self.assertIsInstance(descendants['count'], int) 236 self.assertEqual(descendants['listing_url'], 'http://localhost/admin/api/main/pages/?descendant_of=%d' % page['id']) 237 238 def test_fields_child_relation(self): 239 response = self.get_response(type='demosite.BlogEntryPage', fields='title,related_links') 240 content = json.loads(response.content.decode('UTF-8')) 241 242 for page in content['items']: 243 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title', 'related_links'}) 244 self.assertIsInstance(page['related_links'], list) 245 246 def test_fields_ordering(self): 247 response = self.get_response(type='demosite.BlogEntryPage', fields='date,title,feed_image,related_links') 248 249 # Will crash if the JSON is invalid 250 content = json.loads(response.content.decode('UTF-8')) 251 252 # Test field order 253 content = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(response.content.decode('UTF-8')) 254 field_order = [ 255 'id', 256 'meta', 257 'title', 258 'admin_display_title', 259 'date', 260 'feed_image', 261 'related_links', 262 ] 263 self.assertEqual(list(content['items'][0].keys()), field_order) 264 265 def test_fields_tags(self): 266 response = self.get_response(type='demosite.BlogEntryPage', fields='tags') 267 content = json.loads(response.content.decode('UTF-8')) 268 269 for page in content['items']: 270 self.assertEqual(set(page.keys()), {'id', 'meta', 'tags', 'title', 'admin_display_title'}) 271 self.assertIsInstance(page['tags'], list) 272 273 def test_fields_translations(self): 274 # Add a translation of the homepage 275 french = Locale.objects.create(language_code='fr') 276 homepage = Page.objects.get(depth=2) 277 french_homepage = homepage.copy_for_translation(french) 278 279 response = self.get_response(fields='translations') 280 content = json.loads(response.content.decode('UTF-8')) 281 282 for page in content['items']: 283 translations = page['meta']['translations'] 284 285 if page['id'] == homepage.id: 286 self.assertEqual(len(translations), 1) 287 self.assertEqual(translations[0]['id'], french_homepage.id) 288 self.assertEqual(translations[0]['meta']['locale'], 'fr') 289 290 elif page['id'] == french_homepage.id: 291 self.assertEqual(len(translations), 1) 292 self.assertEqual(translations[0]['id'], homepage.id) 293 self.assertEqual(translations[0]['meta']['locale'], 'en') 294 295 else: 296 self.assertEqual(translations, []) 297 298 # CHILD OF FILTER 299 300 # Not applicable to the admin API 301 test_child_of_page_thats_not_in_same_site_gives_error = None 302 303 def test_child_of_root(self): 304 # Only return the homepage as that's the only child of the "root" node 305 # in the tree. This is different to the public API which pretends the 306 # homepage of the current site is the root page. 307 response = self.get_response(child_of='root') 308 content = json.loads(response.content.decode('UTF-8')) 309 310 page_id_list = self.get_page_id_list(content) 311 self.assertEqual(page_id_list, [2]) 312 313 def test_child_of_page_1(self): 314 # Public API doesn't allow this, as it's the root page 315 response = self.get_response(child_of=1) 316 json.loads(response.content.decode('UTF-8')) 317 318 self.assertEqual(response.status_code, 200) 319 320 # DESCENDANT OF FILTER 321 322 # Not applicable to the admin API 323 test_descendant_of_page_thats_not_in_same_site_gives_error = None 324 325 def test_descendant_of_root(self): 326 response = self.get_response(descendant_of='root') 327 content = json.loads(response.content.decode('UTF-8')) 328 329 page_id_list = self.get_page_id_list(content) 330 self.assertEqual(page_id_list, [2, 4, 8, 9, 5, 16, 18, 19, 6, 10, 15, 17, 21, 22, 23, 20, 13, 14, 12]) 331 332 def test_descendant_of_root_doesnt_give_error(self): 333 # Public API doesn't allow this 334 response = self.get_response(descendant_of=1) 335 json.loads(response.content.decode('UTF-8')) 336 337 self.assertEqual(response.status_code, 200) 338 339 # FOR EXPLORER FILTER 340 341 def make_simple_page(self, parent, title): 342 return parent.add_child(instance=SimplePage(title=title, content='Simple page')) 343 344 def test_for_explorer_filter(self): 345 movies = self.make_simple_page(Page.objects.get(pk=1), 'Movies') 346 visible_movies = [ 347 self.make_simple_page(movies, 'The Way of the Dragon'), 348 self.make_simple_page(movies, 'Enter the Dragon'), 349 self.make_simple_page(movies, 'Dragons Forever'), 350 ] 351 hidden_movies = [ 352 self.make_simple_page(movies, 'The Hidden Fortress'), 353 self.make_simple_page(movies, 'Crouching Tiger, Hidden Dragon'), 354 self.make_simple_page(movies, 'Crouching Tiger, Hidden Dragon: Sword of Destiny'), 355 ] 356 357 response = self.get_response(child_of=movies.pk, for_explorer=1) 358 content = json.loads(response.content.decode('UTF-8')) 359 page_id_list = self.get_page_id_list(content) 360 self.assertEqual(page_id_list, [page.pk for page in visible_movies]) 361 362 response = self.get_response(child_of=movies.pk) 363 content = json.loads(response.content.decode('UTF-8')) 364 page_id_list = self.get_page_id_list(content) 365 self.assertEqual(page_id_list, [page.pk for page in visible_movies + hidden_movies]) 366 367 def test_for_explorer_no_child_of(self): 368 response = self.get_response(for_explorer=1) 369 self.assertEqual(response.status_code, 400) 370 content = json.loads(response.content.decode('UTF-8')) 371 self.assertEqual(content, { 372 'message': 'filtering by for_explorer without child_of is not supported', 373 }) 374 375 def test_for_explorer_construct_explorer_page_queryset_ordering(self): 376 def set_custom_ordering(parent_page, pages, request): 377 return pages.order_by('-title') 378 379 with hooks.register_temporarily('construct_explorer_page_queryset', set_custom_ordering): 380 response = self.get_response(for_explorer=True, child_of=2) 381 382 content = json.loads(response.content.decode('UTF-8')) 383 page_id_list = self.get_page_id_list(content) 384 385 self.assertEqual(page_id_list, [6, 20, 4, 12, 5]) 386 387 # HAS CHILDREN FILTER 388 389 def test_has_children_filter(self): 390 response = self.get_response(has_children='true') 391 content = json.loads(response.content.decode('UTF-8')) 392 393 page_id_list = self.get_page_id_list(content) 394 self.assertEqual(page_id_list, [2, 4, 5, 6, 21, 20]) 395 396 def test_has_children_filter_off(self): 397 response = self.get_response(has_children='false') 398 content = json.loads(response.content.decode('UTF-8')) 399 400 page_id_list = self.get_page_id_list(content) 401 self.assertEqual(page_id_list, [8, 9, 16, 18, 19, 10, 15, 17, 22, 23, 13, 14, 12]) 402 403 def test_has_children_filter_int(self): 404 response = self.get_response(has_children=1) 405 content = json.loads(response.content.decode('UTF-8')) 406 407 page_id_list = self.get_page_id_list(content) 408 self.assertEqual(page_id_list, [2, 4, 5, 6, 21, 20]) 409 410 def test_has_children_filter_int_off(self): 411 response = self.get_response(has_children=0) 412 content = json.loads(response.content.decode('UTF-8')) 413 414 page_id_list = self.get_page_id_list(content) 415 self.assertEqual(page_id_list, [8, 9, 16, 18, 19, 10, 15, 17, 22, 23, 13, 14, 12]) 416 417 def test_has_children_filter_invalid_integer(self): 418 response = self.get_response(has_children=3) 419 content = json.loads(response.content.decode('UTF-8')) 420 421 self.assertEqual(response.status_code, 400) 422 self.assertEqual(content, {'message': "has_children must be 'true' or 'false'"}) 423 424 def test_has_children_filter_invalid_value(self): 425 response = self.get_response(has_children='yes') 426 content = json.loads(response.content.decode('UTF-8')) 427 428 self.assertEqual(response.status_code, 400) 429 self.assertEqual(content, {'message': "has_children must be 'true' or 'false'"}) 430 431 # TYPE FILTER 432 433 def test_type_filter_items_are_all_blog_entries(self): 434 response = self.get_response(type='demosite.BlogEntryPage') 435 content = json.loads(response.content.decode('UTF-8')) 436 437 for page in content['items']: 438 self.assertEqual(page['meta']['type'], 'demosite.BlogEntryPage') 439 440 # No specific fields available by default 441 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title'}) 442 443 def test_type_filter_multiple(self): 444 response = self.get_response(type='demosite.BlogEntryPage,demosite.EventPage') 445 content = json.loads(response.content.decode('UTF-8')) 446 447 blog_page_seen = False 448 event_page_seen = False 449 450 for page in content['items']: 451 self.assertIn(page['meta']['type'], ['demosite.BlogEntryPage', 'demosite.EventPage']) 452 453 if page['meta']['type'] == 'demosite.BlogEntryPage': 454 blog_page_seen = True 455 elif page['meta']['type'] == 'demosite.EventPage': 456 event_page_seen = True 457 458 # Only generic fields available 459 self.assertEqual(set(page.keys()), {'id', 'meta', 'title', 'admin_display_title'}) 460 461 self.assertTrue(blog_page_seen, "No blog pages were found in the items") 462 self.assertTrue(event_page_seen, "No event pages were found in the items") 463 464 465class TestAdminPageDetail(AdminAPITestCase, TestPageDetail): 466 fixtures = ['demosite.json'] 467 468 def get_response(self, page_id, **params): 469 return self.client.get(reverse('wagtailadmin_api:pages:detail', args=(page_id, )), params) 470 471 def test_basic(self): 472 response = self.get_response(16) 473 474 self.assertEqual(response.status_code, 200) 475 self.assertEqual(response['Content-type'], 'application/json') 476 477 # Will crash if the JSON is invalid 478 content = json.loads(response.content.decode('UTF-8')) 479 480 # Check the id field 481 self.assertIn('id', content) 482 self.assertEqual(content['id'], 16) 483 484 # Check that the meta section is there 485 self.assertIn('meta', content) 486 self.assertIsInstance(content['meta'], dict) 487 488 # Check the meta type 489 self.assertIn('type', content['meta']) 490 self.assertEqual(content['meta']['type'], 'demosite.BlogEntryPage') 491 492 # Check the meta detail_url 493 self.assertIn('detail_url', content['meta']) 494 self.assertEqual(content['meta']['detail_url'], 'http://localhost/admin/api/main/pages/16/') 495 496 # Check the meta html_url 497 self.assertIn('html_url', content['meta']) 498 self.assertEqual(content['meta']['html_url'], 'http://localhost/blog-index/blog-post/') 499 500 # Check the meta status 501 502 self.assertIn('status', content['meta']) 503 self.assertEqual(content['meta']['status'], { 504 'status': 'live', 505 'live': True, 506 'has_unpublished_changes': False 507 }) 508 509 # Check the meta children 510 511 self.assertIn('children', content['meta']) 512 self.assertEqual(content['meta']['children'], { 513 'count': 0, 514 'listing_url': 'http://localhost/admin/api/main/pages/?child_of=16' 515 }) 516 517 # Check the parent field 518 self.assertIn('parent', content['meta']) 519 self.assertIsInstance(content['meta']['parent'], dict) 520 self.assertEqual(set(content['meta']['parent'].keys()), {'id', 'meta', 'title'}) 521 self.assertEqual(content['meta']['parent']['id'], 5) 522 self.assertIsInstance(content['meta']['parent']['meta'], dict) 523 self.assertEqual(set(content['meta']['parent']['meta'].keys()), {'type', 'detail_url', 'html_url'}) 524 self.assertEqual(content['meta']['parent']['meta']['type'], 'demosite.BlogIndexPage') 525 self.assertEqual(content['meta']['parent']['meta']['detail_url'], 'http://localhost/admin/api/main/pages/5/') 526 self.assertEqual(content['meta']['parent']['meta']['html_url'], 'http://localhost/blog-index/') 527 528 # Check that the custom fields are included 529 self.assertIn('date', content) 530 self.assertIn('body', content) 531 self.assertIn('tags', content) 532 self.assertIn('feed_image', content) 533 self.assertIn('related_links', content) 534 self.assertIn('carousel_items', content) 535 536 # Check that the date was serialised properly 537 self.assertEqual(content['date'], '2013-12-02') 538 539 # Check that the tags were serialised properly 540 self.assertEqual(content['tags'], ['bird', 'wagtail']) 541 542 # Check that the feed image was serialised properly 543 self.assertIsInstance(content['feed_image'], dict) 544 self.assertEqual(set(content['feed_image'].keys()), {'id', 'meta', 'title'}) 545 self.assertEqual(content['feed_image']['id'], 7) 546 self.assertIsInstance(content['feed_image']['meta'], dict) 547 self.assertEqual(set(content['feed_image']['meta'].keys()), {'type', 'detail_url', 'download_url'}) 548 self.assertEqual(content['feed_image']['meta']['type'], 'wagtailimages.Image') 549 self.assertEqual(content['feed_image']['meta']['detail_url'], 'http://localhost/admin/api/main/images/7/') 550 551 # Check that the child relations were serialised properly 552 self.assertEqual(content['related_links'], []) 553 for carousel_item in content['carousel_items']: 554 self.assertEqual(set(carousel_item.keys()), {'id', 'meta', 'embed_url', 'link', 'caption', 'image'}) 555 self.assertEqual(set(carousel_item['meta'].keys()), {'type'}) 556 557 # Check the type info 558 self.assertIsInstance(content['__types'], dict) 559 self.assertEqual(set(content['__types'].keys()), { 560 'wagtailcore.Page', 561 'demosite.HomePage', 562 'demosite.BlogIndexPage', 563 'demosite.BlogEntryPageCarouselItem', 564 'demosite.BlogEntryPage', 565 'wagtailimages.Image' 566 }) 567 self.assertEqual(set(content['__types']['demosite.BlogIndexPage'].keys()), {'verbose_name', 'verbose_name_plural'}) 568 self.assertEqual(content['__types']['demosite.BlogIndexPage']['verbose_name'], 'blog index page') 569 self.assertEqual(content['__types']['demosite.BlogIndexPage']['verbose_name_plural'], 'blog index pages') 570 571 # Overriden from public API tests 572 def test_meta_parent_id_doesnt_show_root_page(self): 573 # Root page is visible in the admin API 574 response = self.get_response(2) 575 content = json.loads(response.content.decode('UTF-8')) 576 577 self.assertIsNotNone(content['meta']['parent']) 578 579 def test_field_ordering(self): 580 # Need to override this as the admin API has a __types field 581 582 response = self.get_response(16) 583 584 # Will crash if the JSON is invalid 585 content = json.loads(response.content.decode('UTF-8')) 586 587 # Test field order 588 content = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(response.content.decode('UTF-8')) 589 field_order = [ 590 'id', 591 'meta', 592 'title', 593 'admin_display_title', 594 'body', 595 'tags', 596 'date', 597 'feed_image', 598 'feed_image_thumbnail', 599 'carousel_items', 600 'related_links', 601 '__types', 602 ] 603 self.assertEqual(list(content.keys()), field_order) 604 605 def test_meta_status_draft(self): 606 # Unpublish the page 607 Page.objects.get(id=16).unpublish() 608 609 response = self.get_response(16) 610 content = json.loads(response.content.decode('UTF-8')) 611 612 self.assertIn('status', content['meta']) 613 self.assertEqual(content['meta']['status'], { 614 'status': 'draft', 615 'live': False, 616 'has_unpublished_changes': True 617 }) 618 619 def test_meta_status_live_draft(self): 620 # Save revision without republish 621 Page.objects.get(id=16).save_revision() 622 623 response = self.get_response(16) 624 content = json.loads(response.content.decode('UTF-8')) 625 626 self.assertIn('status', content['meta']) 627 self.assertEqual(content['meta']['status'], { 628 'status': 'live + draft', 629 'live': True, 630 'has_unpublished_changes': True 631 }) 632 633 def test_meta_status_scheduled(self): 634 # Unpublish and save revision with go live date in the future 635 Page.objects.get(id=16).unpublish() 636 tomorrow = timezone.now() + datetime.timedelta(days=1) 637 Page.objects.get(id=16).save_revision(approved_go_live_at=tomorrow) 638 639 response = self.get_response(16) 640 content = json.loads(response.content.decode('UTF-8')) 641 642 self.assertIn('status', content['meta']) 643 self.assertEqual(content['meta']['status'], { 644 'status': 'scheduled', 645 'live': False, 646 'has_unpublished_changes': True 647 }) 648 649 def test_meta_status_expired(self): 650 # Unpublish and set expired flag 651 Page.objects.get(id=16).unpublish() 652 Page.objects.filter(id=16).update(expired=True) 653 654 response = self.get_response(16) 655 content = json.loads(response.content.decode('UTF-8')) 656 657 self.assertIn('status', content['meta']) 658 self.assertEqual(content['meta']['status'], { 659 'status': 'expired', 660 'live': False, 661 'has_unpublished_changes': True 662 }) 663 664 def test_meta_children_for_parent(self): 665 # Homepage should have children 666 response = self.get_response(2) 667 content = json.loads(response.content.decode('UTF-8')) 668 669 self.assertIn('children', content['meta']) 670 self.assertEqual(content['meta']['children'], { 671 'count': 5, 672 'listing_url': 'http://localhost/admin/api/main/pages/?child_of=2' 673 }) 674 675 def test_meta_descendants(self): 676 # Homepage should have children 677 response = self.get_response(2) 678 content = json.loads(response.content.decode('UTF-8')) 679 680 self.assertIn('descendants', content['meta']) 681 self.assertEqual(content['meta']['descendants'], { 682 'count': 18, 683 'listing_url': 'http://localhost/admin/api/main/pages/?descendant_of=2' 684 }) 685 686 def test_meta_ancestors(self): 687 # Homepage should have children 688 response = self.get_response(16) 689 content = json.loads(response.content.decode('UTF-8')) 690 691 self.assertIn('ancestors', content['meta']) 692 self.assertIsInstance(content['meta']['ancestors'], list) 693 self.assertEqual(len(content['meta']['ancestors']), 3) 694 self.assertEqual(content['meta']['ancestors'][0].keys(), {'id', 'meta', 'title', 'admin_display_title'}) 695 self.assertEqual(content['meta']['ancestors'][0]['title'], 'Root') 696 self.assertEqual(content['meta']['ancestors'][1]['title'], 'Home page') 697 self.assertEqual(content['meta']['ancestors'][2]['title'], 'Blog index') 698 699 # FIELDS 700 701 def test_remove_all_meta_fields(self): 702 response = self.get_response(16, fields='-type,-detail_url,-slug,-first_published_at,-html_url,-descendants,-latest_revision_created_at,-children,-ancestors,-show_in_menus,-seo_title,-parent,-status,-search_description') 703 content = json.loads(response.content.decode('UTF-8')) 704 705 self.assertNotIn('meta', set(content.keys())) 706 self.assertIn('id', set(content.keys())) 707 708 def test_remove_all_fields(self): 709 response = self.get_response(16, fields='_,id,type') 710 content = json.loads(response.content.decode('UTF-8')) 711 712 self.assertEqual(set(content.keys()), {'id', 'meta', '__types'}) 713 self.assertEqual(set(content['meta'].keys()), {'type'}) 714 715 def test_all_nested_fields(self): 716 response = self.get_response(16, fields='feed_image(*)') 717 content = json.loads(response.content.decode('UTF-8')) 718 719 self.assertEqual(set(content['feed_image'].keys()), {'id', 'meta', 'title', 'width', 'height', 'thumbnail'}) 720 721 def test_fields_foreign_key(self): 722 response = self.get_response(16) 723 content = json.loads(response.content.decode('UTF-8')) 724 725 feed_image = content['feed_image'] 726 727 self.assertIsInstance(feed_image, dict) 728 self.assertEqual(set(feed_image.keys()), {'id', 'meta', 'title'}) 729 self.assertIsInstance(feed_image['id'], int) 730 self.assertIsInstance(feed_image['meta'], dict) 731 self.assertEqual(set(feed_image['meta'].keys()), {'type', 'detail_url', 'download_url'}) 732 self.assertEqual(feed_image['meta']['type'], 'wagtailimages.Image') 733 self.assertEqual(feed_image['meta']['detail_url'], 'http://localhost/admin/api/main/images/%d/' % feed_image['id']) 734 735 736class TestAdminPageDetailWithStreamField(AdminAPITestCase): 737 fixtures = ['test.json'] 738 739 def setUp(self): 740 super().setUp() 741 742 self.homepage = Page.objects.get(url_path='/home/') 743 744 def make_stream_page(self, body): 745 stream_page = StreamPage( 746 title='stream page', 747 slug='stream-page', 748 body=body 749 ) 750 return self.homepage.add_child(instance=stream_page) 751 752 def test_can_fetch_streamfield_content(self): 753 stream_page = self.make_stream_page('[{"type": "text", "value": "foo"}]') 754 755 response_url = reverse('wagtailadmin_api:pages:detail', args=(stream_page.id, )) 756 response = self.client.get(response_url) 757 758 self.assertEqual(response.status_code, 200) 759 self.assertEqual(response['content-type'], 'application/json') 760 761 content = json.loads(response.content.decode('utf-8')) 762 763 self.assertIn('id', content) 764 self.assertEqual(content['id'], stream_page.id) 765 self.assertIn('body', content) 766 self.assertEqual(len(content['body']), 1) 767 self.assertEqual(content['body'][0]['type'], 'text') 768 self.assertEqual(content['body'][0]['value'], 'foo') 769 self.assertTrue(content['body'][0]['id']) 770 771 def test_image_block(self): 772 stream_page = self.make_stream_page('[{"type": "image", "value": 1}]') 773 774 response_url = reverse('wagtailadmin_api:pages:detail', args=(stream_page.id, )) 775 response = self.client.get(response_url) 776 content = json.loads(response.content.decode('utf-8')) 777 778 # ForeignKeys in a StreamField shouldn't be translated into dictionary representation 779 self.assertEqual(content['body'][0]['type'], 'image') 780 self.assertEqual(content['body'][0]['value'], 1) 781 782 783class TestCustomAdminDisplayTitle(AdminAPITestCase): 784 fixtures = ['test.json'] 785 786 def setUp(self): 787 super().setUp() 788 789 self.event_page = Page.objects.get(url_path='/home/events/saint-patrick/') 790 791 def test_custom_admin_display_title_shown_on_detail_page(self): 792 api_url = reverse('wagtailadmin_api:pages:detail', args=(self.event_page.id, )) 793 response = self.client.get(api_url) 794 content = json.loads(response.content.decode('utf-8')) 795 796 self.assertEqual(content['title'], "Saint Patrick") 797 self.assertEqual(content['admin_display_title'], "Saint Patrick (single event)") 798 799 def test_custom_admin_display_title_shown_on_listing(self): 800 api_url = reverse('wagtailadmin_api:pages:listing') 801 response = self.client.get(api_url) 802 content = json.loads(response.content.decode('utf-8')) 803 804 matching_items = [item for item in content['items'] if item['id'] == self.event_page.id] 805 self.assertEqual(1, len(matching_items)) 806 self.assertEqual(matching_items[0]['title'], "Saint Patrick") 807 self.assertEqual(matching_items[0]['admin_display_title'], "Saint Patrick (single event)") 808 809 810# Overwrite imported test cases do Django doesn't run them 811TestPageDetail = None 812TestPageListing = None 813