import datetime
import json

import pytz
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.http import Http404, HttpRequest
from django.test import Client, TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from freezegun import freeze_time

from wagtail.core.models import Page, PageManager, Site, get_page_models
from wagtail.tests.testapp.models import (
    AbstractPage, Advert, AlwaysShowInMenusPage, BlogCategory, BlogCategoryBlogPage, BusinessChild,
    BusinessIndex, BusinessNowherePage, BusinessSubIndex, CustomManager, CustomManagerPage,
    CustomPageQuerySet, EventIndex, EventPage, GenericSnippetPage, ManyToManyBlogPage, MTIBasePage,
    MTIChildPage, MyCustomPage, OneToOnePage, PageWithExcludedCopyField, SimplePage,
    SingleEventPage, SingletonPage, StandardIndex, TaggedPage)
from wagtail.tests.utils import WagtailTestUtils


def get_ct(model):
    return ContentType.objects.get_for_model(model)


class TestValidation(TestCase):
    fixtures = ['test.json']

    def test_can_create(self):
        """
        Check that basic page creation works
        """
        homepage = Page.objects.get(url_path='/home/')
        hello_page = SimplePage(title="Hello world", slug='hello-world', content="hello")
        homepage.add_child(instance=hello_page)

        # check that hello_page exists in the db
        retrieved_page = Page.objects.get(id=hello_page.id)
        self.assertEqual(retrieved_page.title, "Hello world")

    def test_title_is_required(self):
        homepage = Page.objects.get(url_path='/home/')

        hello_page = SimplePage(slug='hello-world', content="hello")
        with self.assertRaises(ValidationError):
            homepage.add_child(instance=hello_page)

        hello_page = SimplePage(title="", slug='hello-world', content="hello")
        with self.assertRaises(ValidationError):
            homepage.add_child(instance=hello_page)

    def test_slug_is_autogenerated(self):
        homepage = Page.objects.get(url_path='/home/')

        # slug should be auto-assigned to a slugified version of the title
        hello_page = SimplePage(title="Hello world", content="hello")
        homepage.add_child(instance=hello_page)
        retrieved_page = Page.objects.get(id=hello_page.id)
        self.assertEqual(retrieved_page.slug, 'hello-world')

        # auto-generated slug should receive a suffix to make it unique
        events_page = SimplePage(title="Events", content="hello")
        homepage.add_child(instance=events_page)
        retrieved_page = Page.objects.get(id=events_page.id)
        self.assertEqual(retrieved_page.slug, 'events-2')

    def test_slug_must_be_unique_within_parent(self):
        homepage = Page.objects.get(url_path='/home/')

        events_page = SimplePage(title="Events", slug='events', content="hello")
        with self.assertRaises(ValidationError):
            homepage.add_child(instance=events_page)

    def test_slug_can_duplicate_other_sections(self):
        homepage = Page.objects.get(url_path='/home/')

        # the Events section has a page with slug='christmas', but we still allow
        # it as a slug elsewhere
        christmas_page = SimplePage(title="Christmas", slug='christmas', content="hello")
        homepage.add_child(instance=christmas_page)
        self.assertTrue(Page.objects.filter(id=christmas_page.id).exists())

    def test_get_admin_display_title(self):
        homepage = Page.objects.get(url_path='/home/')
        self.assertEqual(homepage.draft_title, homepage.get_admin_display_title())

    def test_get_admin_display_title_with_blank_draft_title(self):
        # Display title should fall back on the live title if draft_title is blank;
        # this can happen if the page was created through fixtures or migrations that
        # didn't explicitly account for draft_title
        # (since draft_title doesn't get populated automatically on save in those cases)
        Page.objects.filter(url_path='/home/').update(title='live title', draft_title='')
        homepage = Page.objects.get(url_path='/home/')

        self.assertEqual(homepage.get_admin_display_title(), 'live title')

    def test_draft_title_is_autopopulated(self):
        homepage = Page.objects.get(url_path='/home/')

        hello_page = SimplePage(title="Hello world", content="hello")
        homepage.add_child(instance=hello_page)
        retrieved_page = Page.objects.get(id=hello_page.id)
        self.assertEqual(retrieved_page.draft_title, "Hello world")

        hello_page = SimplePage(title="Hello world", draft_title="Hello world edited", content="hello")
        homepage.add_child(instance=hello_page)
        retrieved_page = Page.objects.get(id=hello_page.id)
        self.assertEqual(retrieved_page.draft_title, "Hello world edited")


@override_settings(ALLOWED_HOSTS=['localhost', 'events.example.com', 'about.example.com', 'unknown.site.com'])
class TestSiteRouting(TestCase):
    fixtures = ['test.json']

    def setUp(self):
        self.default_site = Site.objects.get(is_default_site=True)
        events_page = Page.objects.get(url_path='/home/events/')
        about_page = Page.objects.get(url_path='/home/about-us/')
        self.events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)
        self.alternate_port_events_site = Site.objects.create(
            hostname='events.example.com',
            root_page=events_page,
            port='8765'
        )
        self.about_site = Site.objects.create(hostname='about.example.com', root_page=about_page)
        self.alternate_port_default_site = Site.objects.create(hostname=self.default_site.hostname, port='8765', root_page=self.default_site.root_page)
        self.unrecognised_port = '8000'
        self.unrecognised_hostname = 'unknown.site.com'

    def test_no_host_header_routes_to_default_site(self):
        # requests without a Host: header should be directed to the default site
        request = HttpRequest()
        request.path = '/'
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.default_site)

    def test_valid_headers_route_to_specific_site(self):
        # requests with a known Host: header should be directed to the specific site
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.events_site.hostname
        request.META['SERVER_PORT'] = self.events_site.port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.events_site)

    def test_ports_in_request_headers_are_respected(self):
        # ports in the Host: header should be respected
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.alternate_port_events_site.hostname
        request.META['SERVER_PORT'] = self.alternate_port_events_site.port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site)

    def test_unrecognised_host_header_routes_to_default_site(self):
        # requests with an unrecognised Host: header should be directed to the default site
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.unrecognised_hostname
        request.META['SERVER_PORT'] = '80'
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.default_site)

    def test_unrecognised_port_and_default_host_routes_to_default_site(self):
        # requests to the default host on an unrecognised port should be directed to the default site
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.default_site.hostname
        request.META['SERVER_PORT'] = self.unrecognised_port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.default_site)

    def test_unrecognised_port_and_unrecognised_host_routes_to_default_site(self):
        # requests with an unrecognised Host: header _and_ an unrecognised port
        # hould be directed to the default site
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.unrecognised_hostname
        request.META['SERVER_PORT'] = self.unrecognised_port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.default_site)

    def test_unrecognised_port_on_known_hostname_routes_there_if_no_ambiguity(self):
        # requests on an unrecognised port should be directed to the site with
        # matching hostname if there is no ambiguity
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.about_site.hostname
        request.META['SERVER_PORT'] = self.unrecognised_port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.about_site)

    def test_unrecognised_port_on_known_hostname_routes_to_default_site_if_ambiguity(self):
        # requests on an unrecognised port should be directed to the default
        # site, even if their hostname (but not port) matches more than one
        # other entry
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = self.events_site.hostname
        request.META['SERVER_PORT'] = self.unrecognised_port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.default_site)

    def test_port_in_http_host_header_is_ignored(self):
        # port in the HTTP_HOST header is ignored
        request = HttpRequest()
        request.path = '/'
        request.META['HTTP_HOST'] = "%s:%s" % (self.events_site.hostname, self.events_site.port)
        request.META['SERVER_PORT'] = self.alternate_port_events_site.port
        with self.assertNumQueries(1):
            self.assertEqual(Site.find_for_request(request), self.alternate_port_events_site)


class TestRouting(TestCase):
    fixtures = ['test.json']

    # need to clear urlresolver caches before/after tests, because we override ROOT_URLCONF
    # in some tests here
    def setUp(self):
        from django.urls import clear_url_caches
        clear_url_caches()

    def tearDown(self):
        from django.urls import clear_url_caches
        clear_url_caches()

    def test_urls(self):
        default_site = Site.objects.get(is_default_site=True)
        homepage = Page.objects.get(url_path='/home/')
        christmas_page = Page.objects.get(url_path='/home/events/christmas/')

        # Basic installation only has one site configured, so page.url will return local URLs
        self.assertEqual(
            homepage.get_url_parts(),
            (default_site.id, 'http://localhost', '/')
        )
        self.assertEqual(homepage.full_url, 'http://localhost/')
        self.assertEqual(homepage.url, '/')
        self.assertEqual(homepage.relative_url(default_site), '/')
        self.assertEqual(homepage.get_site(), default_site)

        self.assertEqual(
            christmas_page.get_url_parts(),
            (default_site.id, 'http://localhost', '/events/christmas/')
        )
        self.assertEqual(christmas_page.full_url, 'http://localhost/events/christmas/')
        self.assertEqual(christmas_page.url, '/events/christmas/')
        self.assertEqual(christmas_page.relative_url(default_site), '/events/christmas/')
        self.assertEqual(christmas_page.get_site(), default_site)

    def test_page_with_no_url(self):
        root = Page.objects.get(url_path='/')
        default_site = Site.objects.get(is_default_site=True)

        self.assertEqual(root.get_url_parts(), None)
        self.assertEqual(root.full_url, None)
        self.assertEqual(root.url, None)
        self.assertEqual(root.relative_url(default_site), None)
        self.assertEqual(root.get_site(), None)

    def test_urls_with_multiple_sites(self):
        events_page = Page.objects.get(url_path='/home/events/')
        events_site = Site.objects.create(hostname='events.example.com', root_page=events_page)

        second_events_site = Site.objects.create(
            hostname='second_events.example.com', root_page=events_page)

        default_site = Site.objects.get(is_default_site=True)
        homepage = Page.objects.get(url_path='/home/')
        christmas_page = Page.objects.get(url_path='/home/events/christmas/')

        # with multiple sites, page.url will return full URLs to ensure that
        # they work across sites
        self.assertEqual(
            homepage.get_url_parts(),
            (default_site.id, 'http://localhost', '/')
        )
        self.assertEqual(homepage.full_url, 'http://localhost/')
        self.assertEqual(homepage.url, 'http://localhost/')
        self.assertEqual(homepage.relative_url(default_site), '/')
        self.assertEqual(homepage.relative_url(events_site), 'http://localhost/')
        self.assertEqual(homepage.get_site(), default_site)

        self.assertEqual(
            christmas_page.get_url_parts(),
            (events_site.id, 'http://events.example.com', '/christmas/')
        )
        self.assertEqual(christmas_page.full_url, 'http://events.example.com/christmas/')
        self.assertEqual(christmas_page.url, 'http://events.example.com/christmas/')
        self.assertEqual(christmas_page.relative_url(default_site), 'http://events.example.com/christmas/')
        self.assertEqual(christmas_page.relative_url(events_site), '/christmas/')
        self.assertEqual(christmas_page.get_site(), events_site)

        request = HttpRequest()

        request.site = events_site
        self.assertEqual(
            christmas_page.get_url_parts(request=request),
            (events_site.id, 'http://events.example.com', '/christmas/')
        )

        request.site = second_events_site
        self.assertEqual(
            christmas_page.get_url_parts(request=request),
            (second_events_site.id, 'http://second_events.example.com', '/christmas/')
        )

    @override_settings(ROOT_URLCONF='wagtail.tests.non_root_urls')
    def test_urls_with_non_root_urlconf(self):
        default_site = Site.objects.get(is_default_site=True)
        homepage = Page.objects.get(url_path='/home/')
        christmas_page = Page.objects.get(url_path='/home/events/christmas/')

        # Basic installation only has one site configured, so page.url will return local URLs
        self.assertEqual(
            homepage.get_url_parts(),
            (default_site.id, 'http://localhost', '/site/')
        )
        self.assertEqual(homepage.full_url, 'http://localhost/site/')
        self.assertEqual(homepage.url, '/site/')
        self.assertEqual(homepage.relative_url(default_site), '/site/')
        self.assertEqual(homepage.get_site(), default_site)

        self.assertEqual(
            christmas_page.get_url_parts(),
            (default_site.id, 'http://localhost', '/site/events/christmas/')
        )
        self.assertEqual(christmas_page.full_url, 'http://localhost/site/events/christmas/')
        self.assertEqual(christmas_page.url, '/site/events/christmas/')
        self.assertEqual(christmas_page.relative_url(default_site), '/site/events/christmas/')
        self.assertEqual(christmas_page.get_site(), default_site)

    def test_request_routing(self):
        homepage = Page.objects.get(url_path='/home/')
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')

        request = HttpRequest()
        request.path = '/events/christmas/'
        (found_page, args, kwargs) = homepage.route(request, ['events', 'christmas'])
        self.assertEqual(found_page, christmas_page)

    def test_request_serving(self):
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')

        request = HttpRequest()
        request.user = AnonymousUser()
        request.site = Site.objects.first()

        response = christmas_page.serve(request)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context_data['self'], christmas_page)
        # confirm that the event_page.html template was used
        self.assertContains(response, '<h2>Event</h2>')

    def test_route_to_unknown_page_returns_404(self):
        homepage = Page.objects.get(url_path='/home/')

        request = HttpRequest()
        request.path = '/events/quinquagesima/'
        with self.assertRaises(Http404):
            homepage.route(request, ['events', 'quinquagesima'])

    def test_route_to_unpublished_page_returns_404(self):
        homepage = Page.objects.get(url_path='/home/')

        request = HttpRequest()
        request.path = '/events/tentative-unpublished-event/'
        with self.assertRaises(Http404):
            homepage.route(request, ['events', 'tentative-unpublished-event'])

    # Override CACHES so we don't generate any cache-related SQL queries (tests use DatabaseCache
    # otherwise) and so cache.get will always return None.
    @override_settings(CACHES={'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}})
    def test_request_scope_site_root_paths_cache(self):
        homepage = Page.objects.get(url_path='/home/')
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')

        # without a request, get_url should only issue 1 SQL query
        with self.assertNumQueries(1):
            self.assertEqual(homepage.get_url(), '/')
        # subsequent calls with the same page should generate no SQL queries
        with self.assertNumQueries(0):
            self.assertEqual(homepage.get_url(), '/')
        # subsequent calls with a different page will still generate 1 SQL query
        with self.assertNumQueries(1):
            self.assertEqual(christmas_page.get_url(), '/events/christmas/')

        # with a request, the first call to get_url should issue 1 SQL query
        request = HttpRequest()
        with self.assertNumQueries(1):
            self.assertEqual(homepage.get_url(request=request), '/')
        # subsequent calls should issue no SQL queries
        with self.assertNumQueries(0):
            self.assertEqual(homepage.get_url(request=request), '/')
        # even if called on a different page
        with self.assertNumQueries(0):
            self.assertEqual(christmas_page.get_url(request=request), '/events/christmas/')


class TestServeView(TestCase):
    fixtures = ['test.json']

    def setUp(self):
        # Explicitly clear the cache of site root paths. Normally this would be kept
        # in sync by the Site.save logic, but this is bypassed when the database is
        # rolled back between tests using transactions.
        from django.core.cache import cache
        cache.delete('wagtail_site_root_paths')

        # also need to clear urlresolver caches before/after tests, because we override
        # ROOT_URLCONF in some tests here
        from django.urls import clear_url_caches
        clear_url_caches()

    def tearDown(self):
        from django.urls import clear_url_caches
        clear_url_caches()

    def test_serve(self):
        response = self.client.get('/events/christmas/')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.templates[0].name, 'tests/event_page.html')
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
        self.assertEqual(response.context['self'], christmas_page)

        self.assertContains(response, '<h1>Christmas</h1>')
        self.assertContains(response, '<h2>Event</h2>')

    @override_settings(ROOT_URLCONF='wagtail.tests.non_root_urls')
    def test_serve_with_non_root_urls(self):
        response = self.client.get('/site/events/christmas/')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.templates[0].name, 'tests/event_page.html')
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
        self.assertEqual(response.context['self'], christmas_page)

        self.assertContains(response, '<h1>Christmas</h1>')
        self.assertContains(response, '<h2>Event</h2>')

    def test_serve_unknown_page_returns_404(self):
        response = self.client.get('/events/quinquagesima/')
        self.assertEqual(response.status_code, 404)

    def test_serve_unpublished_page_returns_404(self):
        response = self.client.get('/events/tentative-unpublished-event/')
        self.assertEqual(response.status_code, 404)

    @override_settings(ALLOWED_HOSTS=['localhost', 'events.example.com'])
    def test_serve_with_multiple_sites(self):
        events_page = Page.objects.get(url_path='/home/events/')
        Site.objects.create(hostname='events.example.com', root_page=events_page)

        response = self.client.get('/christmas/', HTTP_HOST='events.example.com')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.templates[0].name, 'tests/event_page.html')
        christmas_page = EventPage.objects.get(url_path='/home/events/christmas/')
        self.assertEqual(response.context['self'], christmas_page)

        self.assertContains(response, '<h1>Christmas</h1>')
        self.assertContains(response, '<h2>Event</h2>')

        # same request to the default host should return a 404
        c = Client()
        response = c.get('/christmas/', HTTP_HOST='localhost')
        self.assertEqual(response.status_code, 404)

    def test_serve_with_custom_context(self):
        response = self.client.get('/events/')
        self.assertEqual(response.status_code, 200)

        # should render the whole page
        self.assertContains(response, '<h1>Events</h1>')

        # response should contain data from the custom 'events' context variable
        self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')

    def test_ajax_response(self):
        response = self.client.get('/events/', HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(response.status_code, 200)

        # should only render the content of includes/event_listing.html, not the whole page
        self.assertNotContains(response, '<h1>Events</h1>')
        self.assertContains(response, '<a href="/events/christmas/">Christmas</a>')

    def test_before_serve_hook(self):
        response = self.client.get('/events/', HTTP_USER_AGENT='GoogleBot')
        self.assertContains(response, 'bad googlebot no cookie')


class TestStaticSitePaths(TestCase):
    def setUp(self):
        self.root_page = Page.objects.get(id=1)

        # For simple tests
        self.home_page = self.root_page.add_child(
            instance=SimplePage(title="Homepage", slug="home2", content="hello")
        )
        self.about_page = self.home_page.add_child(
            instance=SimplePage(title="About us", slug="about", content="hello")
        )
        self.contact_page = self.home_page.add_child(
            instance=SimplePage(title="Contact", slug="contact", content="hello")
        )

        # For custom tests
        self.event_index = self.root_page.add_child(instance=EventIndex(title="Events", slug="events"))
        for i in range(20):
            self.event_index.add_child(instance=EventPage(
                title="Event " + str(i), slug="event" + str(i),
                location='the moon', audience='public',
                cost='free', date_from='2001-01-01',
            ))

    def test_local_static_site_paths(self):
        paths = list(self.about_page.get_static_site_paths())

        self.assertEqual(paths, ['/'])

    def test_child_static_site_paths(self):
        paths = list(self.home_page.get_static_site_paths())

        self.assertEqual(paths, ['/', '/about/', '/contact/'])

    def test_custom_static_site_paths(self):
        paths = list(self.event_index.get_static_site_paths())

        # Event index path
        expected_paths = ['/']

        # One path for each page of results
        expected_paths.extend(['/' + str(i + 1) + '/' for i in range(5)])

        # One path for each event page
        expected_paths.extend(['/event' + str(i) + '/' for i in range(20)])

        paths.sort()
        expected_paths.sort()
        self.assertEqual(paths, expected_paths)


class TestMovePage(TestCase):
    fixtures = ['test.json']

    def test_move_page(self):
        about_us_page = SimplePage.objects.get(url_path='/home/about-us/')
        events_index = EventIndex.objects.get(url_path='/home/events/')

        events_index.move(about_us_page, pos='last-child')

        # re-fetch events index to confirm that db fields have been updated
        events_index = EventIndex.objects.get(id=events_index.id)
        self.assertEqual(events_index.url_path, '/home/about-us/events/')
        self.assertEqual(events_index.depth, 4)
        self.assertEqual(events_index.get_parent().id, about_us_page.id)

        # children of events_index should also have been updated
        christmas = events_index.get_children().get(slug='christmas')
        self.assertEqual(christmas.depth, 5)
        self.assertEqual(christmas.url_path, '/home/about-us/events/christmas/')


class TestPrevNextSiblings(TestCase):
    fixtures = ['test.json']

    def test_get_next_siblings(self):
        christmas_event = Page.objects.get(url_path='/home/events/christmas/')
        self.assertTrue(christmas_event.get_next_siblings().filter(url_path='/home/events/final-event/').exists())

    def test_get_next_siblings_inclusive(self):
        christmas_event = Page.objects.get(url_path='/home/events/christmas/')

        # First element must always be the current page
        self.assertEqual(christmas_event.get_next_siblings(inclusive=True).first(), christmas_event)

    def test_get_prev_siblings(self):
        final_event = Page.objects.get(url_path='/home/events/final-event/')
        self.assertTrue(final_event.get_prev_siblings().filter(url_path='/home/events/christmas/').exists())

        # First element must always be the current page
        self.assertEqual(final_event.get_prev_siblings(inclusive=True).first(), final_event)


class TestLiveRevision(TestCase):
    fixtures = ['test.json']

    @freeze_time("2017-01-01 12:00:00")
    def test_publish_method_will_set_live_revision(self):
        page = Page.objects.get(id=2)

        revision = page.save_revision()
        revision.publish()

        page.refresh_from_db()
        self.assertEqual(page.live_revision, revision)
        self.assertEqual(page.last_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=pytz.utc))
        # first_published_at should not change
        self.assertEqual(page.first_published_at, datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=pytz.utc))

    @freeze_time("2017-01-01 12:00:00")
    def test_unpublish_method_will_clean_live_revision(self):
        page = Page.objects.get(id=2)

        revision = page.save_revision()
        revision.publish()

        page.refresh_from_db()

        page.unpublish()

        page.refresh_from_db()
        self.assertIsNone(page.live_revision)
        # first_published_at / last_published_at should remain unchanged on unpublish
        self.assertEqual(page.first_published_at, datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=pytz.utc))
        self.assertEqual(page.last_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=pytz.utc))

    @freeze_time("2017-01-01 12:00:00")
    def test_copy_method_with_keep_live_will_update_live_revision(self):
        about_us = SimplePage.objects.get(url_path='/home/about-us/')
        revision = about_us.save_revision()
        revision.publish()

        new_about_us = about_us.copy(keep_live=True, update_attrs={'title': "New about us", 'slug': 'new-about-us'})
        self.assertIsNotNone(new_about_us.live_revision)
        self.assertNotEqual(about_us.live_revision, new_about_us.live_revision)

        # first_published_at / last_published_at should reflect the current time,
        # not the source page's publish dates, since the copied page is being published
        # for the first time
        self.assertEqual(new_about_us.first_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=pytz.utc))
        self.assertEqual(new_about_us.last_published_at, datetime.datetime(2017, 1, 1, 12, 0, 0, tzinfo=pytz.utc))

    def test_copy_method_without_keep_live_will_not_update_live_revision(self):
        about_us = SimplePage.objects.get(url_path='/home/about-us/')
        revision = about_us.save_revision()
        revision.publish()
        about_us.refresh_from_db()
        self.assertIsNotNone(about_us.live_revision)

        new_about_us = about_us.copy(keep_live=False, update_attrs={'title': "New about us", 'slug': 'new-about-us'})
        self.assertIsNone(new_about_us.live_revision)
        # first_published_at / last_published_at should be blank, because the copied article
        # has not been published
        self.assertIsNone(new_about_us.first_published_at)
        self.assertIsNone(new_about_us.last_published_at)

    @freeze_time("2017-01-01 12:00:00")
    def test_publish_with_future_go_live_does_not_set_live_revision(self):
        about_us = SimplePage.objects.get(url_path='/home/about-us/')
        about_us.go_live_at = datetime.datetime(2018, 1, 1, 12, 0, 0, tzinfo=pytz.utc)
        revision = about_us.save_revision()
        revision.publish()
        about_us.refresh_from_db()

        self.assertFalse(about_us.live)
        self.assertIsNone(about_us.live_revision)

        # first_published_at / last_published_at should remain unchanged
        self.assertEqual(about_us.first_published_at, datetime.datetime(2014, 1, 1, 12, 0, 0, tzinfo=pytz.utc))
        self.assertEqual(about_us.last_published_at, datetime.datetime(2014, 2, 1, 12, 0, 0, tzinfo=pytz.utc))


class TestCopyPage(TestCase):
    fixtures = ['test.json']

    def test_copy_page_copies(self):
        about_us = SimplePage.objects.get(url_path='/home/about-us/')

        # Copy it
        new_about_us = about_us.copy(update_attrs={'title': "New about us", 'slug': 'new-about-us'})

        # Check that new_about_us is correct
        self.assertIsInstance(new_about_us, SimplePage)
        self.assertEqual(new_about_us.title, "New about us")
        self.assertEqual(new_about_us.slug, 'new-about-us')

        # Check that new_about_us is a different page
        self.assertNotEqual(about_us.id, new_about_us.id)

        # Check that the url path was updated
        self.assertEqual(new_about_us.url_path, '/home/new-about-us/')

    def test_copy_page_copies_child_objects(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the speakers were copied
        self.assertEqual(new_christmas_event.speakers.count(), 1, "Child objects weren't copied")

        # Check that the speakers weren't removed from old page
        self.assertEqual(christmas_event.speakers.count(), 1, "Child objects were removed from the original page")

        # Check that advert placements were also copied (there's a gotcha here, since the advert_placements
        # relation is defined on Page, not EventPage)
        self.assertEqual(
            new_christmas_event.advert_placements.count(), 1, "Child objects defined on the superclass weren't copied"
        )
        self.assertEqual(
            christmas_event.advert_placements.count(),
            1,
            "Child objects defined on the superclass were removed from the original page"
        )

    def test_copy_page_copies_revisions(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision()

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the revisions were copied
        # Copying creates a new revision so we're expecting the new page to have two revisions
        self.assertEqual(new_christmas_event.revisions.count(), 2)

        # Check that the revisions weren't removed from old page
        self.assertEqual(christmas_event.revisions.count(), 1, "Revisions were removed from the original page")

        # Check that the attributes were updated in the latest revision
        latest_revision = new_christmas_event.get_latest_revision_as_page()
        self.assertEqual(latest_revision.title, "New christmas event")
        self.assertEqual(latest_revision.slug, 'new-christmas-event')

        # get_latest_revision_as_page might bypass the revisions table if it determines
        # that there are no draft edits since publish - so retrieve it explicitly from the
        # revision data, to ensure it's been updated there too
        latest_revision = new_christmas_event.get_latest_revision().as_page_object()
        self.assertEqual(latest_revision.title, "New christmas event")
        self.assertEqual(latest_revision.slug, 'new-christmas-event')

        # Check that the ids within the revision were updated correctly
        new_revision = new_christmas_event.revisions.first()
        new_revision_content = json.loads(new_revision.content_json)
        self.assertEqual(new_revision_content['pk'], new_christmas_event.id)
        self.assertEqual(new_revision_content['speakers'][0]['page'], new_christmas_event.id)

        # Also, check that the child objects in the new revision are given new IDs
        old_speakers_ids = set(christmas_event.speakers.values_list('id', flat=True))
        new_speakers_ids = set(speaker['pk'] for speaker in new_revision_content['speakers'])
        self.assertFalse(
            old_speakers_ids.intersection(new_speakers_ids),
            "Child objects in revisions were not given a new primary key"
        )

    def test_copy_page_copies_revisions_and_doesnt_submit_for_moderation(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision(submitted_for_moderation=True)

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the old revision is still submitted for moderation
        self.assertTrue(christmas_event.revisions.order_by('created_at').first().submitted_for_moderation)

        # Check that the new revision is not submitted for moderation
        self.assertFalse(new_christmas_event.revisions.order_by('created_at').first().submitted_for_moderation)

    def test_copy_page_copies_revisions_and_doesnt_change_created_at(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision(submitted_for_moderation=True)

        # Set the created_at of the revision to a time in the past
        revision = christmas_event.get_latest_revision()
        revision.created_at = datetime.datetime(2014, 1, 1, 0, 0, 0, tzinfo=pytz.utc)
        revision.save()

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the created_at time is the same
        christmas_event_created_at = christmas_event.revisions.order_by('created_at').first().created_at
        new_christmas_event_created_at = new_christmas_event.revisions.order_by('created_at').first().created_at
        self.assertEqual(christmas_event_created_at, new_christmas_event_created_at)

    def test_copy_page_copies_revisions_and_doesnt_schedule(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision(approved_go_live_at=datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc))

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the old revision is still scheduled
        self.assertEqual(
            christmas_event.revisions.order_by('created_at').first().approved_go_live_at,
            datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc)
        )

        # Check that the new revision is not scheduled
        self.assertEqual(new_christmas_event.revisions.order_by('created_at').first().approved_go_live_at, None)

    def test_copy_page_doesnt_copy_revisions_if_told_not_to_do_so(self):
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision()

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}, copy_revisions=False
        )

        # Check that the revisions weren't copied
        # Copying creates a new revision so we're expecting the new page to have one revision
        self.assertEqual(new_christmas_event.revisions.count(), 1)

        # Check that the revisions weren't removed from old page
        self.assertEqual(christmas_event.revisions.count(), 1, "Revisions were removed from the original page")

    def test_copy_page_copies_child_objects_with_nonspecific_class(self):
        # Get chrismas page as Page instead of EventPage
        christmas_event = Page.objects.get(url_path='/home/events/christmas/')

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}
        )

        # Check that the type of the new page is correct
        self.assertIsInstance(new_christmas_event, EventPage)

        # Check that the speakers were copied
        self.assertEqual(new_christmas_event.speakers.count(), 1, "Child objects weren't copied")

    def test_copy_page_copies_recursively(self):
        events_index = EventIndex.objects.get(url_path='/home/events/')

        # Copy it
        new_events_index = events_index.copy(
            recursive=True, update_attrs={'title': "New events index", 'slug': 'new-events-index'}
        )

        # Get christmas event
        old_christmas_event = events_index.get_children().filter(slug='christmas').first()
        new_christmas_event = new_events_index.get_children().filter(slug='christmas').first()

        # Check that the event exists in both places
        self.assertNotEqual(new_christmas_event, None, "Child pages weren't copied")
        self.assertNotEqual(old_christmas_event, None, "Child pages were removed from original page")

        # Check that the url path was updated
        self.assertEqual(new_christmas_event.url_path, '/home/new-events-index/christmas/')

    def test_copy_page_copies_recursively_with_child_objects(self):
        events_index = EventIndex.objects.get(url_path='/home/events/')

        # Copy it
        new_events_index = events_index.copy(
            recursive=True, update_attrs={'title': "New events index", 'slug': 'new-events-index'}
        )

        # Get christmas event
        old_christmas_event = events_index.get_children().filter(slug='christmas').first()
        new_christmas_event = new_events_index.get_children().filter(slug='christmas').first()

        # Check that the speakers were copied
        self.assertEqual(new_christmas_event.specific.speakers.count(), 1, "Child objects weren't copied")

        # Check that the speakers weren't removed from old page
        self.assertEqual(
            old_christmas_event.specific.speakers.count(), 1, "Child objects were removed from the original page"
        )

    def test_copy_page_copies_recursively_with_revisions(self):
        events_index = EventIndex.objects.get(url_path='/home/events/')
        old_christmas_event = events_index.get_children().filter(slug='christmas').first().specific
        old_christmas_event.save_revision()

        # Copy it
        new_events_index = events_index.copy(
            recursive=True, update_attrs={'title': "New events index", 'slug': 'new-events-index'}
        )

        # Get christmas event
        new_christmas_event = new_events_index.get_children().filter(slug='christmas').first()

        # Check that the revisions were copied
        # Copying creates a new revision so we're expecting the new page to have two revisions
        self.assertEqual(new_christmas_event.specific.revisions.count(), 2)

        # Check that the revisions weren't removed from old page
        self.assertEqual(
            old_christmas_event.specific.revisions.count(), 1, "Revisions were removed from the original page"
        )

    def test_copy_page_copies_recursively_but_doesnt_copy_revisions_if_told_not_to_do_so(self):
        events_index = EventIndex.objects.get(url_path='/home/events/')
        old_christmas_event = events_index.get_children().filter(slug='christmas').first()
        old_christmas_event.save_revision()

        # Copy it
        new_events_index = events_index.copy(
            recursive=True,
            update_attrs={'title': "New events index", 'slug': 'new-events-index'},
            copy_revisions=False
        )

        # Get christmas event
        new_christmas_event = new_events_index.get_children().filter(slug='christmas').first()

        # Check that the revisions weren't copied
        # Copying creates a new revision so we're expecting the new page to have one revision
        self.assertEqual(new_christmas_event.specific.revisions.count(), 1)

        # Check that the revisions weren't removed from old page
        self.assertEqual(
            old_christmas_event.specific.revisions.count(), 1, "Revisions were removed from the original page"
        )

    def test_copy_page_copies_recursively_to_the_same_tree(self):
        events_index = EventIndex.objects.get(url_path='/home/events/')
        old_christmas_event = events_index.get_children().filter(slug='christmas').first().specific
        old_christmas_event.save_revision()

        with self.assertRaises(Exception) as exception:
            events_index.copy(
                recursive=True, update_attrs={'title': "New events index", 'slug': 'new-events-index'}, to=events_index
            )
        self.assertEqual(str(exception.exception), "You cannot copy a tree branch recursively into itself")

    def test_copy_page_updates_user(self):
        event_moderator = get_user_model().objects.get(username='eventmoderator')
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
        christmas_event.save_revision()

        # Copy it
        new_christmas_event = christmas_event.copy(
            update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'},
            user=event_moderator,
        )

        # Check that the owner has been updated
        self.assertEqual(new_christmas_event.owner, event_moderator)

        # Check that the user on the last revision is correct
        self.assertEqual(new_christmas_event.get_latest_revision().user, event_moderator)

    def test_copy_multi_table_inheritance(self):
        saint_patrick_event = SingleEventPage.objects.get(url_path='/home/events/saint-patrick/')

        # Copy it
        new_saint_patrick_event = saint_patrick_event.copy(update_attrs={'slug': 'new-saint-patrick'})

        # Check that new_saint_patrick_event is correct
        self.assertIsInstance(new_saint_patrick_event, SingleEventPage)
        self.assertEqual(new_saint_patrick_event.excerpt, saint_patrick_event.excerpt)

        # Check that new_saint_patrick_event is a different page, including parents from both EventPage and Page
        self.assertNotEqual(saint_patrick_event.id, new_saint_patrick_event.id)
        self.assertNotEqual(saint_patrick_event.eventpage_ptr.id, new_saint_patrick_event.eventpage_ptr.id)
        self.assertNotEqual(
            saint_patrick_event.eventpage_ptr.page_ptr.id,
            new_saint_patrick_event.eventpage_ptr.page_ptr.id
        )

        # Check that the url path was updated
        self.assertEqual(new_saint_patrick_event.url_path, '/home/events/new-saint-patrick/')

        # Check that both parent instance exists
        self.assertIsInstance(EventPage.objects.get(id=new_saint_patrick_event.id), EventPage)
        self.assertIsInstance(Page.objects.get(id=new_saint_patrick_event.id), Page)

    def test_copy_page_copies_tags(self):
        # create and publish a TaggedPage under Events
        event_index = Page.objects.get(url_path='/home/events/')
        tagged_page = TaggedPage(title='My tagged page', slug='my-tagged-page')
        tagged_page.tags.add('wagtail', 'bird')
        event_index.add_child(instance=tagged_page)
        tagged_page.save_revision().publish()

        old_tagged_item_ids = [item.id for item in tagged_page.tagged_items.all()]
        # there should be two items here, with defined (truthy) IDs
        self.assertEqual(len(old_tagged_item_ids), 2)
        self.assertTrue(all(old_tagged_item_ids))

        # copy to underneath homepage
        homepage = Page.objects.get(url_path='/home/')
        new_tagged_page = tagged_page.copy(to=homepage)

        self.assertNotEqual(tagged_page.id, new_tagged_page.id)

        # new page should also have two tags
        new_tagged_item_ids = [item.id for item in new_tagged_page.tagged_items.all()]
        self.assertEqual(len(new_tagged_item_ids), 2)
        self.assertTrue(all(new_tagged_item_ids))

        # new tagged_item IDs should differ from old ones
        self.assertTrue(all([
            item_id not in old_tagged_item_ids
            for item_id in new_tagged_item_ids
        ]))

    def test_copy_page_with_m2m_relations(self):
        # create and publish a ManyToManyBlogPage under Events
        event_index = Page.objects.get(url_path='/home/events/')
        category = BlogCategory.objects.create(name='Birds')
        advert = Advert.objects.create(url='http://www.heinz.com/', text="beanz meanz heinz")

        blog_page = ManyToManyBlogPage(title='My blog page', slug='my-blog-page')
        event_index.add_child(instance=blog_page)

        blog_page.adverts.add(advert)
        BlogCategoryBlogPage.objects.create(category=category, page=blog_page)
        blog_page.save_revision().publish()

        # copy to underneath homepage
        homepage = Page.objects.get(url_path='/home/')
        new_blog_page = blog_page.copy(to=homepage)

        # M2M relations are not formally supported, so for now we're only interested in
        # the copy operation as a whole succeeding, rather than the child objects being copied
        self.assertNotEqual(blog_page.id, new_blog_page.id)

    def test_copy_page_with_generic_foreign_key(self):
        # create and publish a GenericSnippetPage under Events
        event_index = Page.objects.get(url_path='/home/events/')
        advert = Advert.objects.create(url='http://www.heinz.com/', text="beanz meanz heinz")

        page = GenericSnippetPage(title='My snippet page', slug='my-snippet-page')
        page.snippet_content_object = advert
        event_index.add_child(instance=page)

        page.save_revision().publish()

        # copy to underneath homepage
        homepage = Page.objects.get(url_path='/home/')
        new_page = page.copy(to=homepage)

        self.assertNotEqual(page.id, new_page.id)
        self.assertEqual(new_page.snippet_content_object, advert)

    def test_copy_page_with_o2o_relation(self):
        event_index = Page.objects.get(url_path='/home/events/')

        page = OneToOnePage(title='My page', slug='my-page')

        event_index.add_child(instance=page)

        homepage = Page.objects.get(url_path='/home/')
        new_page = page.copy(to=homepage)

        self.assertNotEqual(page.id, new_page.id)

    def test_copy_page_with_additional_excluded_fields(self):

        homepage = Page.objects.get(url_path='/home/')
        page = PageWithExcludedCopyField(
            title='Discovery',
            slug='disco',
            content='NCC-1031',
            special_field='Context is for Kings')
        new_page = page.copy(to=homepage)

        self.assertEqual(page.title, new_page.title)
        self.assertNotEqual(page.id, new_page.id)
        self.assertNotEqual(page.path, new_page.path)
        # special_field is in the list to be excluded
        self.assertNotEqual(page.special_field, new_page.special_field)


class TestSubpageTypeBusinessRules(TestCase, WagtailTestUtils):
    def test_allowed_subpage_models(self):
        # SimplePage does not define any restrictions on subpage types
        # SimplePage is a valid subpage of SimplePage
        self.assertIn(SimplePage, SimplePage.allowed_subpage_models())
        # BusinessIndex is a valid subpage of SimplePage
        self.assertIn(BusinessIndex, SimplePage.allowed_subpage_models())
        # BusinessSubIndex is not valid, because it explicitly omits SimplePage from parent_page_types
        self.assertNotIn(BusinessSubIndex, SimplePage.allowed_subpage_models())

        # BusinessChild has an empty subpage_types list, so does not allow anything
        self.assertNotIn(SimplePage, BusinessChild.allowed_subpage_models())
        self.assertNotIn(BusinessIndex, BusinessChild.allowed_subpage_models())
        self.assertNotIn(BusinessSubIndex, BusinessChild.allowed_subpage_models())

        # BusinessSubIndex only allows BusinessChild as subpage type
        self.assertNotIn(SimplePage, BusinessSubIndex.allowed_subpage_models())
        self.assertIn(BusinessChild, BusinessSubIndex.allowed_subpage_models())

    def test_allowed_parent_page_models(self):
        # SimplePage does not define any restrictions on parent page types
        # SimplePage is a valid parent page of SimplePage
        self.assertIn(SimplePage, SimplePage.allowed_parent_page_models())
        # BusinessChild cannot be a parent of anything
        self.assertNotIn(BusinessChild, SimplePage.allowed_parent_page_models())

        # BusinessNowherePage does not allow anything as a parent
        self.assertNotIn(SimplePage, BusinessNowherePage.allowed_parent_page_models())
        self.assertNotIn(StandardIndex, BusinessNowherePage.allowed_parent_page_models())

        # BusinessSubIndex only allows BusinessIndex as a parent
        self.assertNotIn(SimplePage, BusinessSubIndex.allowed_parent_page_models())
        self.assertIn(BusinessIndex, BusinessSubIndex.allowed_parent_page_models())

    def test_can_exist_under(self):
        self.assertTrue(SimplePage.can_exist_under(SimplePage()))

        # StandardIndex should only be allowed under a Page
        self.assertTrue(StandardIndex.can_exist_under(Page()))
        self.assertFalse(StandardIndex.can_exist_under(SimplePage()))

        # The Business pages are quite restrictive in their structure
        self.assertTrue(BusinessSubIndex.can_exist_under(BusinessIndex()))
        self.assertTrue(BusinessChild.can_exist_under(BusinessIndex()))
        self.assertTrue(BusinessChild.can_exist_under(BusinessSubIndex()))

        self.assertFalse(BusinessSubIndex.can_exist_under(SimplePage()))
        self.assertFalse(BusinessSubIndex.can_exist_under(BusinessSubIndex()))
        self.assertFalse(BusinessChild.can_exist_under(SimplePage()))

    def test_can_create_at(self):
        # Pages are not `is_creatable`, and should not be creatable
        self.assertFalse(Page.can_create_at(Page()))

        # SimplePage can be created under a simple page
        self.assertTrue(SimplePage.can_create_at(SimplePage()))

        # StandardIndex can be created under a Page, but not a SimplePage
        self.assertTrue(StandardIndex.can_create_at(Page()))
        self.assertFalse(StandardIndex.can_create_at(SimplePage()))

        # The Business pages are quite restrictive in their structure
        self.assertTrue(BusinessSubIndex.can_create_at(BusinessIndex()))
        self.assertTrue(BusinessChild.can_create_at(BusinessIndex()))
        self.assertTrue(BusinessChild.can_create_at(BusinessSubIndex()))

        self.assertFalse(BusinessChild.can_create_at(SimplePage()))
        self.assertFalse(BusinessSubIndex.can_create_at(SimplePage()))

    def test_can_move_to(self):
        self.assertTrue(SimplePage().can_move_to(SimplePage()))

        # StandardIndex should only be allowed under a Page
        self.assertTrue(StandardIndex().can_move_to(Page()))
        self.assertFalse(StandardIndex().can_move_to(SimplePage()))

        # The Business pages are quite restrictive in their structure
        self.assertTrue(BusinessSubIndex().can_move_to(BusinessIndex()))
        self.assertTrue(BusinessChild().can_move_to(BusinessIndex()))
        self.assertTrue(BusinessChild().can_move_to(BusinessSubIndex()))

        self.assertFalse(BusinessChild().can_move_to(SimplePage()))
        self.assertFalse(BusinessSubIndex().can_move_to(SimplePage()))

    def test_singleton_page_creation(self):
        root_page = Page.objects.get(url_path='/home/')

        # A single singleton page should be creatable
        self.assertTrue(SingletonPage.can_create_at(root_page))

        # Create a singleton page
        root_page.add_child(instance=SingletonPage(
            title='singleton', slug='singleton'))

        # A second singleton page should not be creatable
        self.assertFalse(SingletonPage.can_create_at(root_page))


class TestIssue735(TestCase):
    """
    Issue 735 reports that URL paths of child pages are not
    updated correctly when slugs of parent pages are updated
    """
    fixtures = ['test.json']

    def test_child_urls_updated_on_parent_publish(self):
        event_index = Page.objects.get(url_path='/home/events/')
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')

        # Change the event index slug and publish it
        event_index.slug = 'old-events'
        event_index.save_revision().publish()

        # Check that the christmas events url path updated correctly
        new_christmas_event = EventPage.objects.get(id=christmas_event.id)
        self.assertEqual(new_christmas_event.url_path, '/home/old-events/christmas/')


class TestIssue756(TestCase):
    """
    Issue 756 reports that the latest_revision_created_at
    field was getting clobbered whenever a revision was published
    """
    def test_publish_revision_doesnt_remove_latest_revision_created_at(self):
        # Create a revision
        revision = Page.objects.get(id=1).save_revision()

        # Check that latest_revision_created_at is set
        self.assertIsNotNone(Page.objects.get(id=1).latest_revision_created_at)

        # Publish the revision
        revision.publish()

        # Check that latest_revision_created_at is still set
        self.assertIsNotNone(Page.objects.get(id=1).latest_revision_created_at)


class TestIssue1216(TestCase):
    """
    Test that url paths greater than 255 characters are supported
    """
    fixtures = ['test.json']

    def test_url_path_can_exceed_255_characters(self):
        event_index = Page.objects.get(url_path='/home/events/')
        christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')

        # Change the christmas_event slug first - this way, we test that the process for
        # updating child url paths also handles >255 character paths correctly
        new_christmas_slug = "christmas-%s-christmas" % ("0123456789" * 20)
        christmas_event.slug = new_christmas_slug
        christmas_event.save_revision().publish()

        # Change the event index slug and publish it
        new_event_index_slug = "events-%s-events" % ("0123456789" * 20)
        event_index.slug = new_event_index_slug
        event_index.save_revision().publish()

        # Check that the url path updated correctly
        new_christmas_event = EventPage.objects.get(id=christmas_event.id)
        expected_url_path = "/home/%s/%s/" % (new_event_index_slug, new_christmas_slug)
        self.assertEqual(new_christmas_event.url_path, expected_url_path)


class TestIsCreatable(TestCase):
    def test_is_creatable_default(self):
        """By default, pages should be creatable"""
        self.assertTrue(SimplePage.is_creatable)
        self.assertIn(SimplePage, get_page_models())

    def test_is_creatable_false(self):
        """Page types should be able to disable their creation"""
        self.assertFalse(MTIBasePage.is_creatable)
        # non-creatable pages should still appear in the get_page_models list
        self.assertIn(MTIBasePage, get_page_models())

    def test_is_creatable_not_inherited(self):
        """
        is_creatable should not be inherited in the normal manner, and should
        default to True unless set otherwise
        """
        self.assertTrue(MTIChildPage.is_creatable)
        self.assertIn(MTIChildPage, get_page_models())

    def test_abstract_pages(self):
        """
        Abstract models should not be creatable
        """
        self.assertFalse(AbstractPage.is_creatable)
        self.assertNotIn(AbstractPage, get_page_models())


class TestDeferredPageClasses(TestCase):
    def test_deferred_page_classes_are_not_registered(self):
        """
        In Django <1.10, a call to `defer` such as `SimplePage.objects.defer('content')`
        will dynamically create a subclass of SimplePage. Ensure that these subclasses
        are not registered in the get_page_models() list
        """
        list(SimplePage.objects.defer('content'))
        simplepage_subclasses = [cls for cls in get_page_models() if issubclass(cls, SimplePage)]
        self.assertEqual(simplepage_subclasses, [SimplePage])


class TestPageManager(TestCase):
    def test_page_manager(self):
        """
        Assert that the Page class uses PageManager
        """
        self.assertIs(type(Page.objects), PageManager)

    def test_page_subclass_manager(self):
        """
        Assert that Page subclasses get a PageManager without having to do
        anything special. MTI subclasses do *not* inherit their parents Manager
        by default.
        """
        self.assertIs(type(SimplePage.objects), PageManager)

    def test_custom_page_manager(self):
        """
        Subclasses should be able to override their default Manager, and
        Wagtail should respect this. It is up to the developer to ensure their
        custom Manager inherits from PageManager.
        """
        self.assertIs(type(CustomManagerPage.objects), CustomManager)

    def test_custom_page_queryset(self):
        """
        Managers that are constructed from a custom PageQuerySet
        (via PageManager.from_queryset(CustomPageQuerySet)) should return
        querysets of that type
        """
        self.assertIs(type(CustomManagerPage.objects.all()), CustomPageQuerySet)
        self.assertIs(type(CustomManagerPage.objects.about_spam()), CustomPageQuerySet)
        self.assertIs(type(CustomManagerPage.objects.all().about_spam()), CustomPageQuerySet)
        self.assertIs(type(CustomManagerPage.objects.about_spam().all()), CustomPageQuerySet)

    def test_abstract_base_page_manager(self):
        """
        Abstract base classes should be able to override their default Manager,
        and Wagtail should respect this. It is up to the developer to ensure
        their custom Manager inherits from PageManager.
        """
        self.assertIs(type(MyCustomPage.objects), CustomManager)


class TestIssue2024(TestCase):
    """
    This tests that deleting a content type can't delete any Page objects.
    """
    fixtures = ['test.json']

    def test_delete_content_type(self):
        event_index = Page.objects.get(url_path='/home/events/')

        # Delete the content type
        event_index_content_type = event_index.content_type
        event_index_content_type.delete()

        # Fetch the page again, it should still exist
        event_index = Page.objects.get(url_path='/home/events/')

        # Check that the content_type changed to Page
        self.assertEqual(event_index.content_type, ContentType.objects.get_for_model(Page))


@override_settings(ALLOWED_HOSTS=['localhost'])
class TestDummyRequest(TestCase):
    fixtures = ['test.json']

    def test_dummy_request_for_accessible_page(self):
        event_index = Page.objects.get(url_path='/home/events/')
        request = event_index.dummy_request()

        # request should have the correct path and hostname for this page
        self.assertEqual(request.path, '/events/')
        self.assertEqual(request.META['HTTP_HOST'], 'localhost')

        # check other env vars required by the WSGI spec
        self.assertEqual(request.META['REQUEST_METHOD'], 'GET')
        self.assertEqual(request.META['SCRIPT_NAME'], '')
        self.assertEqual(request.META['PATH_INFO'], '/events/')
        self.assertEqual(request.META['SERVER_NAME'], 'localhost')
        self.assertEqual(request.META['SERVER_PORT'], 80)
        self.assertEqual(request.META['SERVER_PROTOCOL'], 'HTTP/1.1')
        self.assertEqual(request.META['wsgi.version'], (1, 0))
        self.assertEqual(request.META['wsgi.url_scheme'], 'http')
        self.assertIn('wsgi.input', request.META)
        self.assertIn('wsgi.errors', request.META)
        self.assertIn('wsgi.multithread', request.META)
        self.assertIn('wsgi.multiprocess', request.META)
        self.assertIn('wsgi.run_once', request.META)

    def test_dummy_request_for_accessible_page_with_original_request(self):
        event_index = Page.objects.get(url_path='/home/events/')
        original_headers = {
            'REMOTE_ADDR': '192.168.0.1',
            'HTTP_X_FORWARDED_FOR': '192.168.0.2,192.168.0.3',
            'HTTP_COOKIE': "test=1;blah=2",
            'HTTP_USER_AGENT': "Test Agent",
            'HTTP_AUTHORIZATION': "Basic V2FndGFpbDpXYWd0YWlsCg==",
        }
        factory = RequestFactory(**original_headers)
        original_request = factory.get('/home/events/')
        request = event_index.dummy_request(original_request)

        # request should have the all the special headers we set in original_request
        self.assertEqual(request.META['REMOTE_ADDR'], original_request.META['REMOTE_ADDR'])
        self.assertEqual(request.META['HTTP_X_FORWARDED_FOR'], original_request.META['HTTP_X_FORWARDED_FOR'])
        self.assertEqual(request.META['HTTP_COOKIE'], original_request.META['HTTP_COOKIE'])
        self.assertEqual(request.META['HTTP_USER_AGENT'], original_request.META['HTTP_USER_AGENT'])
        self.assertEqual(request.META['HTTP_AUTHORIZATION'], original_request.META['HTTP_AUTHORIZATION'])

        # check other env vars required by the WSGI spec
        self.assertEqual(request.META['REQUEST_METHOD'], 'GET')
        self.assertEqual(request.META['SCRIPT_NAME'], '')
        self.assertEqual(request.META['PATH_INFO'], '/events/')
        self.assertEqual(request.META['SERVER_NAME'], 'localhost')
        self.assertEqual(request.META['SERVER_PORT'], 80)
        self.assertEqual(request.META['SERVER_PROTOCOL'], 'HTTP/1.1')
        self.assertEqual(request.META['wsgi.version'], (1, 0))
        self.assertEqual(request.META['wsgi.url_scheme'], 'http')
        self.assertIn('wsgi.input', request.META)
        self.assertIn('wsgi.errors', request.META)
        self.assertIn('wsgi.multithread', request.META)
        self.assertIn('wsgi.multiprocess', request.META)
        self.assertIn('wsgi.run_once', request.META)

    @override_settings(ALLOWED_HOSTS=['production.example.com'])
    def test_dummy_request_for_inaccessible_page_should_use_valid_host(self):
        root_page = Page.objects.get(url_path='/')
        request = root_page.dummy_request()

        # in the absence of an actual Site record where we can access this page,
        # dummy_request should still provide a hostname that Django's host header
        # validation won't reject
        self.assertEqual(request.META['HTTP_HOST'], 'production.example.com')

    @override_settings(ALLOWED_HOSTS=['*'])
    def test_dummy_request_for_inaccessible_page_with_wildcard_allowed_hosts(self):
        root_page = Page.objects.get(url_path='/')
        request = root_page.dummy_request()

        # '*' is not a valid hostname, so ensure that we replace it with something sensible
        self.assertNotEqual(request.META['HTTP_HOST'], '*')


class TestShowInMenusDefaultOption(TestCase):
    """
    This tests that a page model can define the default for 'show_in_menus'
    """
    fixtures = ['test.json']

    def test_show_in_menus_default(self):
        # Create a page that does not have the default init
        page = Page(
            title='My Awesome Page', slug='my-awesome-page')

        # Check that the page instance creates with show_in_menu as False
        self.assertFalse(page.show_in_menus)

    def test_show_in_menus_default_override(self):
        # Create a page that does have the default init
        page = AlwaysShowInMenusPage(
            title='My Awesome Page', slug='my-awesome-page')

        # Check that the page instance creates with show_in_menu as True
        self.assertTrue(page.show_in_menus)
