text:
A Presidential Address to the Oxford Diocesan Synod, November 2020
Proposition 22 became law last week in California. Voters who went to the polls to elect the President of the United States also passed into state law a proposition which is of key importance for the future of work globally. Under Prop 22, workers at gig companies such as Uber will continue to be classified as contractors without access to employee rights such as minimum wage, unemployment benefits, health insurance and collective bargaining. Uber and Lyft, the two companies who authored Prop 22 spent over $200 million in their campaign to ensure that it was passed, a record for this kind of ballot.
The debate around Prop 22 may seem a small issue in a different land. But the debate takes us to the heart of what is happening in the world of employment and the future of work.
It is tempting to keep all of our focus at present on COVID-19 and piloting the Church forward in the midst of the pandemic. But there are wider and deeper issues following on from coronavirus.
The cartoonist, Graeme Mackay published his first version of this cartoon in March as the lockdown began and then a revised version in May.
A tsunami of COVID approaches a modern city. Behind that wave, we see all too clearly, is a second wave labelled recession. Behind that recession is an even larger third wave, added for the later versions, labelled climate change.
Three catastrophes which will be with us in the coming years, which we will need to address, which will set a context for our lives and our Christian discipleship.
There is a fourth wave that this version of his cartoon misses, a fourth wave that we might label, coincidentally, the fourth industrial revolution.
The fourth industrial revolution
The recession, the second wave, is now with us and deepening. Unemployment had risen to 4.8% by the end of this week and rising. So far, recession is hitting young people the hardest: one in seven 16 to 24-year-olds is now without work. The media has had stories of over 500 applicants for barista type jobs in London. I welcome the Chancellor's extension of the furlough scheme until March, but it seems inevitable that those sombre figures will continue to rise for much of the next year together with their sombre consequences in peoples lives.
But this is no ordinary recession. The world of work is changing and shifting. We are in the midst of what is being called the fourth industrial revolution: the change in the world of work caused by a cluster of technologies: automation, artificial intelligence, greater computing power, rapid manipulation of data, robotics, machine learning and algorithmic decision making. The COVID recession comes in the midst of change that is already happening and will accelerate some of those changes.
Some jobs and some wealth is being created through these new technologies. They will undoubtedly bring great benefits, especially in the areas of medical advances, elimination of routine tasks and consumer choice but they also carry significant risks.
Daniel Susskind is Fellow in Economics at Balliol College, Oxford. He published a major book this year, A world without work: technology, innovation and how we should respond. Susskind says:
"It is hard to escape the conclusion that we are heading towards a world with less work for people to do. The threat of technological employment is real. More troubling still, the traditional response of "more education" ls likely to be less and less effective as time rolls on. Yet as I started to imagine what such a response might look like. I found myself grappling with the more fundamental question… how should we share our society's economic prosperity".
It's vital to realise that this cluster of technologies will affect every area of life and every profession. We are not talking here about a single invention, like the steam engine. We are talking about a general-purpose technology which affects everything. The change has been likened to the invention of the wheel, the control of fire or the mastery of electricity.
In any Industrial Revolution, some jobs are created, such as software engineers. Others are augmented, like radiographers. Others are replaced, like lift operators. To use an ordinary, everyday example, all of us will have experienced the increased use of robot checkouts in every supermarket, together with the use of smart shopping systems. We are aware of the increased use of remote technology by doctors and in managing personal finances, in delivering lectures online. In Milton Keynes, groceries are now delivered by robot routinely. All of this will have an impact on the labour market. Driverless cars are being tested on the streets of Oxford.
In the words of a recent RSA report: "Automation anxiety is back". "People looking for a new job will find the most inhospitable labour market in modern memory".
All of this is combined with a change in many employment practices to favour the company, capital, over labour: the rise of the gig economy. An increasing number of jobs in the economy are based, in effect, on humans working for algorithms, working for computers, rather than working for people.
Some of the companies are household names: Uber, Deliveroo, Just Eat, Amazon and Lyft. The technology is ultra-modern and developing faster than it can be tracked in the public awareness. Some of labour practices, exemplified by Proposition 22, are straight out of the Victorian era and the pages of Dickens. In the words of Olivia Solon:
"It is cheaper and easier to get humans to behave like robots than it is to get machines to behave like humans".
How should the Church respond?
But the human consequences are immense. The most vivid depiction I have yet seen of the transition to the gig economy from white-collar work forms part of the BBC dystopian drama Years and Years, first broadcast in May 2019.
How should the Church respond to this fourth industrial revolution and these massive changes in the future of work? Fundamentally our response should be to welcome any technology that augments human dignity and worth, while staunchly resisting every application of technology that threatens or replaces human dignity.
Later in this Synod we will focus on the work of clergy and those in ministry and those who work in and for the Diocese as employees and as volunteers. We do that against the backdrop of the changing world of work and the deep Christian principles which underpin not only our own understanding of work but also much of the modern labour movement.
In Scripture and in our tradition there is a rich theology of work from the Garden of Eden to the new Jerusalem, from the gentle wisdom of the Book of Proverbs to the parables of Jesus. But that theology is undeveloped for the 21st Century and we need to give our own scriptures greater weight. The Book of Isaiah ends with a striking focus on what makes for a good human society, the hallmark of the kingdom of God and includes good work and labour:
"No more shall there be (in Jerusalem) an infant who lives but a few days, or an old person who does not live out a lifetime……They shall build houses and inhabit them; they shall plant vineyards and eat their fruit….my chosen shall enjoy the work of their hands".
Good, purposeful work is a key part of God's vision for human flourishing. This means as a church and as a society we need to raise this debate and pay careful attention not just to the quantity but to the quality of jobs which are created.
These deeply Christian principles are embedded in the United Nations Sustainable Development Goals, especially Goal 8: Promote sustained, inclusive and sustainable economic growth, full and productive employment and decent work for all. They are echoed also in the Taylor Review on the future of work, commissioned by the government and published in 2018 under the title Good Work. The government accepted the recommendations of the report, that we need to focus as a society on the quality as well as the quantity of work, but we are still waiting for a detailed implementation plan.
There are many things which claim the attention of this Synod and of our Diocese in the coming months but few of such significance and importance. Both Church and Society need to re-engage in fresh thinking about work and its value in the light of the pandemic, the recession, the need for a carbon zero economy, the fourth industrial revolution and the rise of the gig economy. As in so many other areas, this will be a key decade of change.
Draft Synod motion
I want to propose to this Synod today that we return to this key theme at our next meeting in February and especially that we debate together and I hope pass a Diocesan Synod motion which will then go forward for a debate at the General Synod on the future quality and quantity of work in our society.
I particularly want us to commend as a Diocese and, I hope as the General Synod, work being done within the University of Oxford in partnership with other Universities across the world, and particularly in South Africa, on the notion of Fair Work within the gig economy.
The Fairwork project is attempting to articulate common international standards for platform workers which now affects tens of millions of people around the world. Those standards are in complete accord with the principles of justice and fairness and dignity we find in the scriptures:
Fair Conditions
Fair Contracts
Fair Management
These principles are the very opposite of what we find now in the State of California. My hope is that by raising this debate here and in the General Synod we can raise the profile of these issues within Church and Society at this key turning point for the world.
Simon Cross, my research and Parliamentary assistant has been working with me on developing a draft motion for debate here, and this is our initial draft:
Mindful of the deep economic effects of the pandemic, particularly on young adults, the impacts of new technology and and the global rise of the gig economy, this Synod
(a) affirms the dignity and value of purposeful work as key component of human flourishing
(b) endorses the five principles used for evaluating fair and dignified platform work in the gig economy by Fairwork.org, and
(c) calls for the Faith and Order Commission (FAOC) to advise on what is essential to dignified and fair work in the context of the fourth industrial revolution now in progress
I would hope that we will be able to hear in the context of our debate in February different contemporary experiences of work and the challenge of the labour market across our Diocese. As we give time in this Synod rightly to the principles of good work and ministry across the Church, so I hope we will return in February to the principles of the quality and quantity of work across the whole world, for everyone.
I would be very happy to receive comments and questions and suggested improvements to the draft motion in the time we have this morning or in the coming weeks.
Disease, technology and sustainability are combining to raise sharp questions for the whole world in the coming decade, especially questions of purpose: what are our lives for? What is the place of work within them? What is the relationship between our labour and the outcomes of our labour? How do we ensure good quality, satisfying work for all?
As Christians we have vital insights to bring to this debate of justice and human worth and dignity. We need to engage in a way which is contemplative, compassionate and courageous and play our part in responding to the crises of our time.
Gig-economy workers are not computers – Simon Cross
A response to overseas aid cuts


text:
Deer Hunter VR Hits Gear VR for Galaxy Note 4
Earlier in the month VRFocus reported on the launch of Deer Hunter VR, a new entry in the popular mobile app series from Glu Mobile developed exclusively for the Gear VR smartphone-based virtual reality (VR) head-mounted display (HMD) from Oculus VR and Samsung. The title was made available for the consumer version of the device, supporting the Galaxy S6, S6 edge, S6 edge plus, and Galaxy Note 5 handsets, notably skipping the Galaxy Note 4, which supports the original Innovator Edition of the kit. However, Dear Hunter VR is now available for this version too.
This comes as a surprise as, shortly before the release on the consumer version of the device, the developer had told VRFocus that it had no plans to support the older phone. A number of developers have been struggling to integrate the Galaxy Note 4 since the release of later handsets due to its older hardware. Most notably, Ustwo Games' Land's End is yet to appear on the device, with the developer noting that it is having trouble delivering a smooth and consistent experience on the hardware.
Deer Hunter VR itself is a first-person shooter (FPS) in which players track down a number of different wild animals in a range of environments including the Pacific Northwest and Savannah of Central Africa. As they hunt players will need to watch out for dangerous predators that they themselves could be attacked by as they use a new control scheme designed specifically for VR. A number of different rifles can be utilised as well as customised too. The title does not require a Bluetooth gamepad in order to be played.
VRFocus will continue to follow any and all releases for the Gear VR, reporting back with the latest updates on them.
Deer Hunter VR
Glu Mobile
Sony Releases Avalanche Of Gran Turismo Sport Screenshots For E3
More screenshots than corners on a street circuit.
byKevin Eva
Scene75 Entertainment Centre Opening VR Arcade
Another arcade featuring the HTC Vive is opening in the US.
Chernobyl VR Project Available to Pre-Order
The Farm 51 opens pre-orders for their forthcoming educational experience, Chernobyl VR Project.
Quest 2's Resident Evil 4 Reportedly Getting 'Mercenaries' Mode in 2022 [Update]
The popular gameplay mode will be a free update next year.

redpajama_set_name: RedPajamaCommonCrawl


Thoughtful and entertaining, provocative and proactive, political and poignant: these are just a few of the words that describe the talent on this list. Compiled and collected with input and information made available by talent agencies across the U.S., the list below represents some of the most engaging speakers available for colleges during Fall of 2018.
With a heated political climate and mid-term elections closing in, Mike Muse, with his #1Issue campaign, is arguably the most relevant and important speaker on college campuses today.
World-class choreographer and creative director for superstars like Kendrick Lamar, Britney Spears, and Madonna, Charm's interactive lecture is unlike anything currently available in the college market.
Four Obama White House alums bring no BS conversation about current events and politics. The hosts provide an easily digestible breakdown of the news that matters and explain what you can do about it.
STEM advocate and host of Cosmos and Star Talk, NDT is an author and champion of science literacy.
Equal pay for equal work. Meet the woman who laid the foundation for economic empowerment.
The acclaimed star of Selma, David Oyelowo is a great fit for campuses everywhere. Don't miss out.
A star of Queer Eye, Van Ness is capable of both comedy and substance in a powerful on campus performance.
CNN anchor and news personality, Don Lemon brings candid perspective to the biggest headlines.
Host of The United Shades of America, W. Kamau Bell has a long history on campuses as a comic and political satirist.
Star on Grey's Anatomy and producer for Stay Woke: The Black Lives Matter Movement, Jesse is a campus favorite.
Nobel Laureate and creator of the famous "Hockey Stick" graph in climate science, Mann has truth to share.
Host of NOVA Wonders on PBS, Talithia Williams unpacks and explains complex numerical concepts.
Journalist and producer of Latino USA on NPR, Maria Hinojosa is a powerful voice on immigration reform.
The Science Guy isn't done teaching the youth of America. This Renaissance Man is a campus icon.
Author of Fun Home and creator of the Bechdel Test, Alison Bechdel is a talented artist and writer.
New York Emmy award winner, Kelly Choi, is a creator and host of "Eat Out NY" and a health lifestyle advocate.
Green jobs advisor to the Obama White House and CNN contributor, Van Jones is an activist and author.
How do you define American? This question defines Vargas's outstanding performance as he explores the country today.


text:
import datetime
import json

from django.contrib.admin.utils import quote
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission
from django.contrib.contenttypes.models import ContentType
from django.core import checks
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpRequest, HttpResponse
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.timezone import make_aware
from taggit.models import Tag

from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.edit_handlers import FieldPanel
from wagtail.admin.forms import WagtailAdminModelForm
from wagtail.core import hooks
from wagtail.core.blocks.field_block import FieldBlockAdapter
from wagtail.core.models import Locale, ModelLogEntry, Page
from wagtail.snippets.action_menu import ActionMenuItem, get_base_snippet_action_menu_items
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.snippets.models import SNIPPET_MODELS, register_snippet
from wagtail.snippets.views.snippets import get_snippet_edit_handler
from wagtail.snippets.widgets import (
    AdminSnippetChooser, SnippetChooserAdapter, SnippetListingButton)
from wagtail.tests.snippets.forms import FancySnippetForm
from wagtail.tests.snippets.models import (
    AlphaSnippet, FancySnippet, FileUploadSnippet, RegisterDecorator, RegisterFunction,
    SearchableSnippet, StandardSnippet, StandardSnippetWithCustomPrimaryKey, TranslatableSnippet,
    ZuluSnippet)
from wagtail.tests.testapp.models import (
    Advert, AdvertWithCustomPrimaryKey, AdvertWithCustomUUIDPrimaryKey, AdvertWithTabbedInterface,
    SnippetChooserModel, SnippetChooserModelWithCustomPrimaryKey)
from wagtail.tests.utils import WagtailTestUtils

class TestSnippetIndexView(TestCase, WagtailTestUtils):
    def setUp(self):
        self.user = self.login()

    def get(self, params={}):
        return self.client.get(reverse('wagtailsnippets:index'), params)

    def test_get_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.get()
        self.assertEqual(response.status_code, 302)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/index.html')

    def test_displays_snippet(self):
        self.assertContains(self.get(), "Adverts")

class TestSnippetListView(TestCase, WagtailTestUtils):
    def setUp(self):
        self.login()
        user_model = get_user_model()
        self.user = user_model.objects.get()

    def get(self, params={}):
        return self.client.get(reverse('wagtailsnippets:list',
                                       args=('tests', 'advert')),
                               params)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

    def get_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.get()
        self.assertEqual(response.status_code, 302)

    def test_ordering(self):
        """
        Listing should be ordered by PK if no ordering has been set on the model
        """
        for i in range(10, 0, -1):
            Advert.objects.create(pk=i, text="advert %d" % i)
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['items'][0].text, "advert 1")

    def test_simple_pagination(self):

        pages = ['0', '1', '-1', '9999', 'Not a page']
        for page in pages:
            response = self.get({'p': page})
            self.assertEqual(response.status_code, 200)
            self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

    def test_displays_add_button(self):
        self.assertContains(self.get(), "Add advert")

    def test_not_searchable(self):
        self.assertFalse(self.get().context['is_searchable'])

    def test_register_snippet_listing_buttons_hook(self):
        advert = Advert.objects.create(text="My Lovely advert")

        def page_listing_buttons(snippet, user, next_url=None):
            self.assertEqual(snippet, advert)
            self.assertEqual(user, self.user)
            self.assertEqual(next_url, reverse('wagtailsnippets:list', args=('tests', 'advert')))

            yield SnippetListingButton(
                'Another useless snippet listing button',
                '/custom-url',
                priority=10
            )

        with hooks.register_temporarily('register_snippet_listing_buttons', page_listing_buttons):
            response = self.get()

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/listing_buttons.html')

        self.assertContains(response, 'Another useless snippet listing button')

    def test_construct_snippet_listing_buttons_hook(self):
        Advert.objects.create(text="My Lovely advert")

        # testapp implements a construct_snippetlisting_buttons hook
        # that add's an dummy button with the label 'Dummy Button' which points
        # to '/dummy-button'
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/listing_buttons.html')
        self.assertContains(response, 'Dummy Button')
        self.assertContains(response, '/dummy-button')

@override_settings(WAGTAIL_I18N_ENABLED=True)
class TestLocaleSelectorOnList(TestCase, WagtailTestUtils):
    def setUp(self):
        self.fr_locale = Locale.objects.create(language_code='fr')
        self.user = self.login()

    def test_locale_selector(self):
        response = self.client.get(
            reverse('wagtailsnippets:list', args=['snippetstests', 'translatablesnippet'])
        )

        switch_to_french_url = reverse('wagtailsnippets:list', args=['snippetstests', 'translatablesnippet']) + '?locale=fr'
        self.assertContains(response, f'')

        # Check that the add URLs include the locale
        add_url = reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet']) + '?locale=en'
        self.assertContains(response, f'')
        self.assertContains(response, f'No translatable snippets have been created. Why not add one')

    @override_settings(WAGTAIL_I18N_ENABLED=False)
    def test_locale_selector_not_present_when_i18n_disabled(self):
        response = self.client.get(
            reverse('wagtailsnippets:list', args=['snippetstests', 'translatablesnippet'])
        )

        switch_to_french_url = reverse('wagtailsnippets:list', args=['snippetstests', 'translatablesnippet']) + '?locale=fr'
        self.assertNotContains(response, f'')

        # Check that the add URLs don't include the locale
        add_url = reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet'])
        self.assertContains(response, f'')
        self.assertContains(response, f'No translatable snippets have been created. Why not add one')

    def test_locale_selector_not_present_on_non_translatable_snippet(self):
        response = self.client.get(
            reverse('wagtailsnippets:list', args=['tests', 'advert'])
        )

        self.assertNotContains(response, 'aria-label="French" class="u-link is-live">')

        # Check that the add URLs don't include the locale
        add_url = reverse('wagtailsnippets:add', args=['tests', 'advert'])
        self.assertContains(response, f'')
        self.assertContains(response, f'No adverts have been created. Why not add one')

class TestModelOrdering(TestCase, WagtailTestUtils):
    def setUp(self):
        for i in range(1, 10):
            AdvertWithTabbedInterface.objects.create(text="advert %d" % i)
        AdvertWithTabbedInterface.objects.create(text="aaaadvert")
        self.login()

    def test_listing_respects_model_ordering(self):
        response = self.client.get(
            reverse('wagtailsnippets:list', args=('tests', 'advertwithtabbedinterface'))
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['items'][0].text, "aaaadvert")

    def test_chooser_respects_model_ordering(self):
        response = self.client.get(
            reverse('wagtailsnippets:choose', args=('tests', 'advertwithtabbedinterface'))
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['items'][0].text, "aaaadvert")

class TestSnippetListViewWithSearchableSnippet(TestCase, WagtailTestUtils):
    def setUp(self):
        self.login()

        # Create some instances of the searchable snippet for testing
        self.snippet_a = SearchableSnippet.objects.create(text="Hello")
        self.snippet_b = SearchableSnippet.objects.create(text="World")
        self.snippet_c = SearchableSnippet.objects.create(text="Hello World")

    def get(self, params={}):
        return self.client.get(reverse('wagtailsnippets:list',
                                       args=('snippetstests', 'searchablesnippet')),
                               params)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

        # All snippets should be in items
        items = list(response.context['items'].object_list)
        self.assertIn(self.snippet_a, items)
        self.assertIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

    def test_is_searchable(self):
        self.assertTrue(self.get().context['is_searchable'])

    def test_search_hello(self):
        response = self.get({'q': "Hello"})

        # Just snippets with "Hello" should be in items
        items = list(response.context['items'].object_list)
        self.assertIn(self.snippet_a, items)
        self.assertNotIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

    def test_search_world(self):
        response = self.get({'q': "World"})

        # Just snippets with "World" should be in items
        items = list(response.context['items'].object_list)
        self.assertNotIn(self.snippet_a, items)
        self.assertIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

class TestSnippetCreateView(TestCase, WagtailTestUtils):
    def setUp(self):
        self.user = self.login()

    def get(self, params={}, model=Advert):
        args = (model._meta.app_label, model._meta.model_name)
        return self.client.get(reverse('wagtailsnippets:add', args=args), params)

    def post(self, post_data={}, model=Advert):
        args = (model._meta.app_label, model._meta.model_name)
        return self.client.post(reverse('wagtailsnippets:add', args=args), post_data)

    def test_get_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.get()
        self.assertEqual(response.status_code, 302)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/create.html')
        self.assertNotContains(response, '
')
        self.assertNotContains(response, 'Advert', html=True)
        self.assertNotContains(response, 'Other', html=True)

    def test_snippet_with_tabbed_interface(self):
        response = self.client.get(reverse('wagtailsnippets:add',
                                           args=('tests', 'advertwithtabbedinterface')))

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/create.html')
        self.assertContains(response, '
')
        self.assertContains(response, 'Advert', html=True)
        self.assertContains(response, 'Other', html=True)

    def test_create_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.post(post_data={'text': 'test text',
                                        'url': 'http://www.example.com/'})
        self.assertEqual(response.status_code, 302)

    def test_create_invalid(self):
        response = self.post(post_data={'foo': 'bar'})
        self.assertContains(response, "The snippet could not be created due to errors.")
        self.assertContains(response, """
This field is required.
""",
                            count=1, html=True)
        self.assertContains(response, "This field is required", count=1)

    def test_create(self):
        response = self.post(post_data={'text': 'test_advert',
                                        'url': 'http://www.example.com/'})
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('tests', 'advert')))

        snippets = Advert.objects.filter(text='test_advert')
        self.assertEqual(snippets.count(), 1)
        self.assertEqual(snippets.first().url, 'http://www.example.com/')

    def test_create_with_tags(self):
        tags = ['hello', 'world']
        response = self.post(post_data={'text': 'test_advert',
                                        'url': 'http://example.com/',
                                        'tags': ', '.join(tags)})

        self.assertRedirects(response, reverse('wagtailsnippets:list',
                                               args=('tests', 'advert')))

        snippet = Advert.objects.get(text='test_advert')

        expected_tags = list(Tag.objects.order_by('name').filter(name__in=tags))
        self.assertEqual(len(expected_tags), 2)
        self.assertEqual(
            list(snippet.tags.order_by('name')),
            expected_tags)

    def test_create_file_upload_multipart(self):
        response = self.get(model=FileUploadSnippet)
        self.assertContains(response, 'enctype="multipart/form-data"')

        response = self.post(model=FileUploadSnippet, post_data={
            'file': SimpleUploadedFile('test.txt', b"Uploaded file")})
        self.assertRedirects(response, reverse('wagtailsnippets:list',
                                               args=('snippetstests', 'fileuploadsnippet')))
        snippet = FileUploadSnippet.objects.get()
        self.assertEqual(snippet.file.read(), b"Uploaded file")

    def test_before_create_snippet_hook_get(self):
        def hook_func(request, model):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(model, Advert)
            return HttpResponse("Overridden!")

        with self.register_hook('before_create_snippet', hook_func):
            response = self.get()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

    def test_before_create_snippet_hook_post(self):
        def hook_func(request, model):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(model, Advert)
            return HttpResponse("Overridden!")

        with self.register_hook('before_create_snippet', hook_func):
            post_data = {
                'text': 'Hook test',
                'url': 'http://www.example.com/'
            }
            response = self.post(post_data=post_data)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted before advert was created
        self.assertFalse(Advert.objects.exists())

    def test_after_create_snippet_hook(self):
        def hook_func(request, instance):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(instance.text, 'Hook test')
            self.assertEqual(instance.url, 'http://www.example.com/')
            return HttpResponse("Overridden!")

        with self.register_hook('after_create_snippet', hook_func):
            post_data = {
                'text': 'Hook test',
                'url': 'http://www.example.com/'
            }
            response = self.post(post_data=post_data)

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted after advert was created
        self.assertTrue(Advert.objects.exists())

    def test_register_snippet_action_menu_item(self):
        class TestSnippetActionMenuItem(ActionMenuItem):
            label = "Test"
            name = "test"
            icon_name = "undo"
            classname = 'action-secondary'

            def is_shown(self, context):
                return True

        def hook_func(model):
            return TestSnippetActionMenuItem(order=0)

        with self.register_hook('register_snippet_action_menu_item', hook_func):
            get_base_snippet_action_menu_items.cache_clear()

            response = self.get()

        get_base_snippet_action_menu_items.cache_clear()

        self.assertContains(response, '<button type="submit" name="test" value="Test" class="button action-secondary"><svg class="icon icon-undo icon" aria-hidden="true" focusable="false"><use href="#icon-undo"></use></svg>Test</button>', html=True)

    def test_register_snippet_action_menu_item_as_none(self):
        def hook_func(model):
            return None

        with self.register_hook('register_snippet_action_menu_item', hook_func):
            get_base_snippet_action_menu_items.cache_clear()

            response = self.get()

        get_base_snippet_action_menu_items.cache_clear()
        self.assertEqual(response.status_code, 200)

    def test_construct_snippet_action_menu(self):
        class TestSnippetActionMenuItem(ActionMenuItem):
            label = "Test"
            name = "test"
            icon_name = "undo"
            classname = 'action-secondary'

            def is_shown(self, context):
                return True

        def hook_func(menu_items, request, context):
            self.assertIsInstance(menu_items, list)
            self.assertIsInstance(request, WSGIRequest)
            self.assertEqual(context['view'], 'create')
            self.assertEqual(context['model'], Advert)

            # Replace save menu item
            menu_items[:] = [
                TestSnippetActionMenuItem(order=0)
            ]

        with self.register_hook('construct_snippet_action_menu', hook_func):
            response = self.get()

        self.assertContains(response, '<button type="submit" name="test" value="Test" class="button action-secondary"><svg class="icon icon-undo icon" aria-hidden="true" focusable="false"><use href="#icon-undo"></use></svg>Test</button>', html=True)
        self.assertNotContains(response, "'Save'")

@override_settings(WAGTAIL_I18N_ENABLED=True)
class TestLocaleSelectorOnCreate(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.fr_locale = Locale.objects.create(language_code='fr')
        self.user = self.login()

    def test_locale_selector(self):
        response = self.client.get(
            reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet'])
        )

        switch_to_french_url = reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet']) + '?locale=fr'
        self.assertContains(response, f'')

    @override_settings(WAGTAIL_I18N_ENABLED=False)
    def test_locale_selector_not_present_when_i18n_disabled(self):
        response = self.client.get(
            reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet'])
        )

        switch_to_french_url = reverse('wagtailsnippets:add', args=['snippetstests', 'translatablesnippet']) + '?locale=fr'
        self.assertNotContains(response, f'')

    def test_locale_selector_not_present_on_non_translatable_snippet(self):
        response = self.client.get(
            reverse('wagtailsnippets:add', args=['tests', 'advert'])
        )

        switch_to_french_url = reverse('wagtailsnippets:add', args=['tests', 'advert']) + '?locale=fr'
        self.assertNotContains(response, f'')

class BaseTestSnippetEditView(TestCase, WagtailTestUtils):

    def get(self, params={}):
        snippet = self.test_snippet
        args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk))
        return self.client.get(reverse('wagtailsnippets:edit', args=args), params)

    def post(self, post_data={}):
        snippet = self.test_snippet
        args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk))
        return self.client.post(reverse('wagtailsnippets:edit', args=args), post_data)

    def setUp(self):
        self.user = self.login()

class TestSnippetEditView(BaseTestSnippetEditView):
    fixtures = ['test.json']

    def setUp(self):
        super().setUp()
        self.test_snippet = Advert.objects.get(pk=1)
        ModelLogEntry.objects.create(
            content_type=ContentType.objects.get_for_model(Advert),
            label="Test Advert",
            action='wagtail.create',
            timestamp=make_aware(datetime.datetime(2021, 9, 30, 10, 1, 0)),
            object_id='1',
        )

    def test_get_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.get()
        self.assertEqual(response.status_code, 302)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')
        self.assertNotContains(response, '
')
        self.assertNotContains(response, 'Advert', html=True)
        self.assertNotContains(response, 'Other', html=True)

        # "Last updated" timestamp should be present
        self.assertContains(response, 'data-wagtail-tooltip="Sept. 30, 2021, 10:01 a.m."')
        # History link should be present
        self.assertContains(response, 'href="/admin/snippets/tests/advert/history/%d/"' % self.test_snippet.pk)

        url_finder = AdminURLFinder(self.user)
        expected_url = '/admin/snippets/tests/advert/edit/%d/' % self.test_snippet.pk
        self.assertEqual(url_finder.get_edit_url(self.test_snippet), expected_url)

    def test_non_existant_model(self):
        response = self.client.get(reverse('wagtailsnippets:edit', args=('tests', 'foo', quote(self.test_snippet.pk))))
        self.assertEqual(response.status_code, 404)

    def test_nonexistant_id(self):
        response = self.client.get(reverse('wagtailsnippets:edit', args=('tests', 'advert', 999999)))
        self.assertEqual(response.status_code, 404)

    def test_edit_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.post(post_data={'text': 'test text',
                                        'url': 'http://www.example.com/'})
        self.assertEqual(response.status_code, 302)

        url_finder = AdminURLFinder(self.user)
        self.assertEqual(url_finder.get_edit_url(self.test_snippet), None)

    def test_edit_invalid(self):
        response = self.post(post_data={'foo': 'bar'})
        self.assertContains(response, "The snippet could not be saved due to errors.")
        self.assertContains(response, """
This field is required.
""",
                            count=1, html=True)
        self.assertContains(response, "This field is required", count=1)

    def test_edit(self):
        response = self.post(post_data={'text': 'edited_test_advert',
                                        'url': 'http://www.example.com/edited'})
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('tests', 'advert')))

        snippets = Advert.objects.filter(text='edited_test_advert')
        self.assertEqual(snippets.count(), 1)
        self.assertEqual(snippets.first().url, 'http://www.example.com/edited')

    def test_edit_with_tags(self):
        tags = ['hello', 'world']
        response = self.post(post_data={'text': 'edited_test_advert',
                                        'url': 'http://www.example.com/edited',
                                        'tags': ', '.join(tags)})

        self.assertRedirects(response, reverse('wagtailsnippets:list',
                                               args=('tests', 'advert')))

        snippet = Advert.objects.get(text='edited_test_advert')

        expected_tags = list(Tag.objects.order_by('name').filter(name__in=tags))
        self.assertEqual(len(expected_tags), 2)
        self.assertEqual(
            list(snippet.tags.order_by('name')),
            expected_tags)

    def test_before_edit_snippet_hook_get(self):

        def hook_func(request, instance):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(instance.text, 'test_advert')
            self.assertEqual(instance.url, 'http://www.example.com')
            return HttpResponse("Overridden!")

        with self.register_hook('before_edit_snippet', hook_func):
            response = self.get()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

    def test_before_edit_snippet_hook_post(self):

        def hook_func(request, instance):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(instance.text, 'test_advert')
            self.assertEqual(instance.url, 'http://www.example.com')
            return HttpResponse("Overridden!")

        with self.register_hook('before_edit_snippet', hook_func):
            response = self.post(post_data={'text': 'Edited and runs hook',
                                            'url': 'http://www.example.com/hook-enabled-edited'})

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted before advert was updated
        self.assertEqual(Advert.objects.get().text, "test_advert")

    def test_after_edit_snippet_hook(self):

        def hook_func(request, instance):
            self.assertIsInstance(request, HttpRequest)
            self.assertEqual(instance.text, 'Edited and runs hook')
            self.assertEqual(instance.url, 'http://www.example.com/hook-enabled-edited')
            return HttpResponse("Overridden!")

        with self.register_hook('after_edit_snippet', hook_func):
            response = self.post(post_data={'text': 'Edited and runs hook',
                                            'url': 'http://www.example.com/hook-enabled-edited'})

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted after advert was updated
        self.assertEqual(Advert.objects.get().text, "Edited and runs hook")

    def test_register_snippet_action_menu_item(self):
        class TestSnippetActionMenuItem(ActionMenuItem):
            label = "Test"
            name = "test"
            icon_name = "undo"
            classname = 'action-secondary'

            def is_shown(self, context):
                return True

        def hook_func(model):
            return TestSnippetActionMenuItem(order=0)

        with self.register_hook('register_snippet_action_menu_item', hook_func):
            get_base_snippet_action_menu_items.cache_clear()

            response = self.get()

        get_base_snippet_action_menu_items.cache_clear()

        self.assertContains(response, '<button type="submit" name="test" value="Test" class="button action-secondary"><svg class="icon icon-undo icon" aria-hidden="true" focusable="false"><use href="#icon-undo"></use></svg>Test</button>', html=True)

    def test_construct_snippet_action_menu(self):
        def hook_func(menu_items, request, context):
            self.assertIsInstance(menu_items, list)
            self.assertIsInstance(request, WSGIRequest)
            self.assertEqual(context['view'], 'edit')
            self.assertEqual(context['instance'], self.test_snippet)
            self.assertEqual(context['model'], Advert)

            # Remove the save item
            del menu_items[0]

        with self.register_hook('construct_snippet_action_menu', hook_func):
            response = self.get()

        self.assertNotContains(response, 'Save')

class TestEditTabbedSnippet(BaseTestSnippetEditView):

    def setUp(self):
        super().setUp()
        self.test_snippet = AdvertWithTabbedInterface.objects.create(
            text="test_advert",
            url="http://www.example.com",
            something_else="Model with tabbed interface")

    def test_snippet_with_tabbed_interface(self):
        response = self.get()

        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')
        self.assertContains(response, '
')
        self.assertContains(response, 'Advert', html=True)
        self.assertContains(response, 'Other', html=True)

class TestEditFileUploadSnippet(BaseTestSnippetEditView):

    def setUp(self):
        super().setUp()
        self.test_snippet = FileUploadSnippet.objects.create(
            file=ContentFile(b"Simple text document", 'test.txt'))

    def test_edit_file_upload_multipart(self):
        response = self.get()
        self.assertContains(response, 'enctype="multipart/form-data"')

        response = self.post(post_data={
            'file': SimpleUploadedFile('replacement.txt', b"Replacement document")})
        self.assertRedirects(response, reverse('wagtailsnippets:list',
                                               args=('snippetstests', 'fileuploadsnippet')))
        snippet = FileUploadSnippet.objects.get()
        self.assertEqual(snippet.file.read(), b"Replacement document")

@override_settings(WAGTAIL_I18N_ENABLED=True)
class TestLocaleSelectorOnEdit(BaseTestSnippetEditView):
    fixtures = ['test.json']

    LOCALE_SELECTOR_HTML = ''
    LOCALE_INDICATOR_HTML = '<use href="#icon-site"></use></svg>\n    English'

    def setUp(self):
        super().setUp()
        self.test_snippet = TranslatableSnippet.objects.create(text="This is a test")
        self.fr_locale = Locale.objects.create(language_code='fr')
        self.test_snippet_fr = self.test_snippet.copy_for_translation(self.fr_locale)
        self.test_snippet_fr.save()

    def test_locale_selector(self):
        response = self.get()

        self.assertContains(response, self.LOCALE_SELECTOR_HTML)

        switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
        self.assertContains(response, f'')

    def test_locale_selector_without_translation(self):
        self.test_snippet_fr.delete()

        response = self.get()

        self.assertContains(response, self.LOCALE_INDICATOR_HTML)

        switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
        self.assertNotContains(response, f'')

    @override_settings(WAGTAIL_I18N_ENABLED=False)
    def test_locale_selector_not_present_when_i18n_disabled(self):
        response = self.get()

        self.assertNotContains(response, self.LOCALE_SELECTOR_HTML)

        switch_to_french_url = reverse('wagtailsnippets:edit', args=['snippetstests', 'translatablesnippet', quote(self.test_snippet_fr.pk)])
        self.assertNotContains(response, f'')

    def test_locale_selector_not_present_on_non_translatable_snippet(self):
        self.test_snippet = Advert.objects.get(pk=1)

        response = self.get()

        self.assertNotContains(response, self.LOCALE_SELECTOR_HTML)
        self.assertNotContains(response, 'aria-label="French" class="u-link is-live">')

class TestSnippetDelete(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.test_snippet = Advert.objects.get(pk=1)
        self.user = self.login()

    def test_delete_get_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        self.assertEqual(response.status_code, 302)

    def test_delete_get(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        self.assertEqual(response.status_code, 200)

    def test_delete_post_with_limited_permissions(self):
        self.user.is_superuser = False
        self.user.user_permissions.add(
            Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        )
        self.user.save()

        response = self.client.post(
            reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), ))
        )
        self.assertEqual(response.status_code, 302)

    def test_delete_post(self):
        response = self.client.post(
            reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), ))
        )

        # Should be redirected to explorer page
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('tests', 'advert')))

        # Check that the page is gone
        self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0)

    @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
    def test_usage_link(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/confirm_delete.html')
        self.assertContains(response, 'Used 2 times')
        self.assertContains(response, self.test_snippet.usage_url())

    def test_before_delete_snippet_hook_get(self):
        advert = Advert.objects.create(
            url='http://www.example.com/',
            text='Test hook',
        )

        def hook_func(request, instances):
            self.assertIsInstance(request, HttpRequest)
            self.assertQuerysetEqual(instances, ["<Advert: Test hook>"])
            return HttpResponse("Overridden!")

        with self.register_hook('before_delete_snippet', hook_func):
            response = self.client.get(reverse('wagtailsnippets:delete', args=['tests', 'advert', quote(advert.pk)]))

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

    def test_before_delete_snippet_hook_post(self):
        advert = Advert.objects.create(
            url='http://www.example.com/',
            text='Test hook',
        )

        def hook_func(request, instances):
            self.assertIsInstance(request, HttpRequest)
            self.assertQuerysetEqual(instances, ["<Advert: Test hook>"])
            return HttpResponse("Overridden!")

        with self.register_hook('before_delete_snippet', hook_func):
            response = self.client.post(
                reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(advert.pk), ))
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted before advert was deleted
        self.assertTrue(Advert.objects.filter(pk=advert.pk).exists())

    def test_after_delete_snippet_hook(self):
        advert = Advert.objects.create(
            url='http://www.example.com/',
            text='Test hook',
        )

        def hook_func(request, instances):
            self.assertIsInstance(request, HttpRequest)
            self.assertQuerysetEqual(instances, ["<Advert: Test hook>"])
            return HttpResponse("Overridden!")

        with self.register_hook('after_delete_snippet', hook_func):
            response = self.client.post(
                reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(advert.pk), ))
            )

        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.content, b"Overridden!")

        # Request intercepted after advert was deleted
        self.assertFalse(Advert.objects.filter(pk=advert.pk).exists())

class TestSnippetDeleteMultipleWithOne(TestCase, WagtailTestUtils):
    # test deletion of one snippet using the delete-multiple URL
    # behaviour should mimic the TestSnippetDelete but with different URl structure
    fixtures = ['test.json']

    def setUp(self):
        self.snippet = Advert.objects.get(id=1)
        self.login()

    def test_delete_get(self):
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % (self.snippet.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_delete_post(self):
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % (self.snippet.id)
        response = self.client.post(url)

        # Should be redirected to explorer page
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('tests', 'advert')))

        # Check that the page is gone
        self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0)

class TestSnippetDeleteMultipleWithThree(TestCase, WagtailTestUtils):
    # test deletion of three snippets using the delete-multiple URL
    fixtures = ['test.json']

    def setUp(self):
        # first advert is in the fixtures
        Advert.objects.create(text="Boreas").save()
        Advert.objects.create(text="Cloud 9").save()
        self.snippets = Advert.objects.all()
        self.login()

    def test_delete_get(self):
        # tests that the URL is available on get
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % ('&id='.join(['%s' % snippet.id for snippet in self.snippets]))
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)

    def test_delete_post(self):
        # tests that the URL is available on post and deletes snippets
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % ('&id='.join(['%s' % snippet.id for snippet in self.snippets]))
        response = self.client.post(url)

        # Should be redirected to explorer page
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('tests', 'advert')))

        # Check that the page is gone
        self.assertEqual(Advert.objects.filter(text='test_advert').count(), 0)

class TestSnippetChooserPanel(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.request = RequestFactory().get('/')
        user = AnonymousUser()  # technically, Anonymous users cannot access the admin
        self.request.user = user

        model = SnippetChooserModel
        self.advert_text = 'Test advert text'
        test_snippet = model.objects.create(
            advert=Advert.objects.create(text=self.advert_text))

        self.edit_handler = get_snippet_edit_handler(model)
        self.form_class = self.edit_handler.get_form_class()
        form = self.form_class(instance=test_snippet)
        edit_handler = self.edit_handler.bind_to(
            instance=test_snippet, form=form, request=self.request)

        self.snippet_chooser_panel = [
            panel for panel in edit_handler.children
            if getattr(panel, 'field_name', None) == 'advert'][0]

    def test_create_snippet_chooser_panel_class(self):
        self.assertIsInstance(self.snippet_chooser_panel, SnippetChooserPanel)

    def test_render_as_field(self):
        field_html = self.snippet_chooser_panel.render_as_field()
        self.assertIn(self.advert_text, field_html)
        self.assertIn("Choose advert", field_html)
        self.assertIn("Choose another advert", field_html)

    def test_render_as_empty_field(self):
        test_snippet = SnippetChooserModel()
        form = self.form_class(instance=test_snippet)
        edit_handler = self.edit_handler.bind_to(
            instance=test_snippet, form=form, request=self.request)

        snippet_chooser_panel = [
            panel for panel in edit_handler.children
            if getattr(panel, 'field_name', None) == 'advert'
        ][0]

        field_html = snippet_chooser_panel.render_as_field()
        self.assertIn("Choose advert", field_html)
        self.assertIn("Choose another advert", field_html)

    def test_render_js(self):
        self.assertIn('createSnippetChooser("id_advert");',
                      self.snippet_chooser_panel.render_as_field())

    def test_target_model_autodetected(self):
        result = SnippetChooserPanel(
            'advert'
        ).bind_to(model=SnippetChooserModel).target_model
        self.assertEqual(result, Advert)

class TestSnippetRegistering(TestCase):
    def test_register_function(self):
        self.assertIn(RegisterFunction, SNIPPET_MODELS)

    def test_register_decorator(self):
        # Misbehaving decorators often return None
        self.assertIsNotNone(RegisterDecorator)
        self.assertIn(RegisterDecorator, SNIPPET_MODELS)

class TestSnippetOrdering(TestCase):
    def setUp(self):
        register_snippet(ZuluSnippet)
        register_snippet(AlphaSnippet)

    def test_snippets_ordering(self):
        # Ensure AlphaSnippet is before ZuluSnippet
        # Cannot check first and last position as other snippets
        # may get registered elsewhere during test
        self.assertLess(SNIPPET_MODELS.index(AlphaSnippet),
                        SNIPPET_MODELS.index(ZuluSnippet))

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

    @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
    def test_snippet_usage_count(self):
        advert = Advert.objects.get(pk=1)
        self.assertEqual(advert.get_usage().count(), 2)

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

    @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
    def test_snippet_used_by(self):
        advert = Advert.objects.get(pk=1)
        self.assertEqual(type(advert.get_usage()[0]), Page)

class TestSnippetHistory(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def get(self, params={}):
        snippet = self.test_snippet
        args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk))
        return self.client.get(reverse('wagtailsnippets:history', args=args), params)

    def setUp(self):
        self.user = self.login()
        self.test_snippet = Advert.objects.get(pk=1)
        ModelLogEntry.objects.create(
            content_type=ContentType.objects.get_for_model(Advert),
            label="Test Advert",
            action='wagtail.create',
            timestamp=make_aware(datetime.datetime(2021, 9, 30, 10, 1, 0)),
            object_id='1',
        )

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Created	', html=True)
        self.assertContains(response, '
')

class TestSnippetChoose(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.login()
        self.url_args = ['tests', 'advert']

    def get(self, params=None):
        return self.client.get(reverse('wagtailsnippets:choose',
                                       args=self.url_args),
                               params or {})

    def test_simple(self):
        response = self.get()
        self.assertTemplateUsed(response, 'wagtailsnippets/chooser/choose.html')

        # Check locale filter doesn't exist normally
        self.assertNotIn('<select id="snippet-chooser-locale" name="lang">', response.json()['html'])

    def test_ordering(self):
        """
        Listing should be ordered by PK if no ordering has been set on the model
        """
        Advert.objects.all().delete()
        for i in range(10, 0, -1):
            Advert.objects.create(pk=i, text="advert %d" % i)
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['items'][0].text, "advert 1")

    def test_simple_pagination(self):

        pages = ['0', '1', '-1', '9999', 'Not a page']
        for page in pages:
            response = self.get({'p': page})
            self.assertEqual(response.status_code, 200)
            self.assertTemplateUsed(response, 'wagtailsnippets/chooser/choose.html')

    def test_not_searchable(self):
        self.assertFalse(self.get().context['is_searchable'])

    @override_settings(WAGTAIL_I18N_ENABLED=True)
    def test_filter_by_locale(self):
        self.url_args = ['snippetstests', 'translatablesnippet']
        fr_locale = Locale.objects.create(language_code="fr")

        TranslatableSnippet.objects.create(text="English snippet")
        TranslatableSnippet.objects.create(text="French snippet", locale=fr_locale)

        response = self.get()

        # Check the filter is added
        self.assertIn('<select id="snippet-chooser-locale" name="locale_filter">', response.json()['html'])

        # Check both snippets are shown
        self.assertEqual(len(response.context['items']), 2)
        self.assertEqual(response.context['items'][0].text, "English snippet")
        self.assertEqual(response.context['items'][1].text, "French snippet")

        # Now test with a locale selected
        response = self.get({'locale': 'en'})

        self.assertEqual(len(response.context['items']), 1)
        self.assertEqual(response.context['items'][0].text, "English snippet")

class TestSnippetChooseWithSearchableSnippet(TestCase, WagtailTestUtils):
    def setUp(self):
        self.login()

        # Create some instances of the searchable snippet for testing
        self.snippet_a = SearchableSnippet.objects.create(text="Hello")
        self.snippet_b = SearchableSnippet.objects.create(text="World")
        self.snippet_c = SearchableSnippet.objects.create(text="Hello World")

    def get(self, params=None):
        return self.client.get(reverse('wagtailsnippets:choose',
                                       args=('snippetstests', 'searchablesnippet')),
                               params or {})

    def test_simple(self):
        response = self.get()
        self.assertTemplateUsed(response, 'wagtailsnippets/chooser/choose.html')

        # All snippets should be in items
        items = list(response.context['items'].object_list)
        self.assertIn(self.snippet_a, items)
        self.assertIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

    def test_is_searchable(self):
        self.assertTrue(self.get().context['is_searchable'])

    def test_search_hello(self):
        response = self.get({'q': "Hello"})

        # Just snippets with "Hello" should be in items
        items = list(response.context['items'].object_list)
        self.assertIn(self.snippet_a, items)
        self.assertNotIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

    def test_search_world(self):
        response = self.get({'q': "World"})

        # Just snippets with "World" should be in items
        items = list(response.context['items'].object_list)
        self.assertNotIn(self.snippet_a, items)
        self.assertIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

class TestSnippetChosen(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.login()

    def get(self, pk, params=None):
        return self.client.get(reverse('wagtailsnippets:chosen',
                                       args=('tests', 'advert', pk)),
                               params or {})

    def test_choose_a_page(self):
        response = self.get(pk=Advert.objects.all()[0].pk)
        response_json = json.loads(response.content.decode())
        self.assertEqual(response_json['step'], 'chosen')

    def test_choose_a_non_existing_page(self):

        response = self.get(999999)
        self.assertEqual(response.status_code, 404)

class TestAddOnlyPermissions(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.test_snippet = Advert.objects.get(pk=1)

        # Create a user with add_advert permission but not change_advert
        user = self.create_user(
            username='addonly',
            email='addonly@example.com',
            password='password'
        )
        add_permission = Permission.objects.get(content_type__app_label='tests', codename='add_advert')
        admin_permission = Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        user.user_permissions.add(add_permission, admin_permission)
        self.login(username='addonly', password='password')

    def test_get_index(self):
        response = self.client.get(reverse('wagtailsnippets:list',
                                   args=('tests', 'advert')))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

        # user should get an "Add advert" button
        self.assertContains(response, "Add advert")

    def test_get_add(self):
        response = self.client.get(reverse('wagtailsnippets:add',
                                   args=('tests', 'advert')))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/create.html')

    def test_get_edit(self):
        response = self.client.get(reverse('wagtailsnippets:edit',
                                   args=('tests', 'advert', quote(self.test_snippet.pk))))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_delete(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_delete_mulitple(self):
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % self.test_snippet.id
        response = self.client.get(url)
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

class TestEditOnlyPermissions(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.test_snippet = Advert.objects.get(pk=1)

        # Create a user with change_advert permission but not add_advert
        user = self.create_user(
            username='changeonly',
            email='changeonly@example.com',
            password='password'
        )
        change_permission = Permission.objects.get(content_type__app_label='tests', codename='change_advert')
        admin_permission = Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        user.user_permissions.add(change_permission, admin_permission)
        self.login(username='changeonly', password='password')

    def test_get_index(self):
        response = self.client.get(reverse('wagtailsnippets:list',
                                   args=('tests', 'advert')))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

        # user should not get an "Add advert" button
        self.assertNotContains(response, "Add advert")

    def test_get_add(self):
        response = self.client.get(reverse('wagtailsnippets:add',
                                   args=('tests', 'advert')))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_edit(self):
        response = self.client.get(reverse('wagtailsnippets:edit',
                                   args=('tests', 'advert', quote(self.test_snippet.pk))))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')

    def test_get_delete(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_delete_mulitple(self):
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % self.test_snippet.id
        response = self.client.get(url)
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

class TestDeleteOnlyPermissions(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.test_snippet = Advert.objects.get(pk=1)

        # Create a user with delete_advert permission
        user = self.create_user(
            username='deleteonly',
            password='password'
        )
        change_permission = Permission.objects.get(content_type__app_label='tests', codename='delete_advert')
        admin_permission = Permission.objects.get(content_type__app_label='wagtailadmin', codename='access_admin')
        user.user_permissions.add(change_permission, admin_permission)
        self.login(username='deleteonly', password='password')

    def test_get_index(self):
        response = self.client.get(reverse('wagtailsnippets:list',
                                   args=('tests', 'advert')))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

        # user should not get an "Add advert" button
        self.assertNotContains(response, "Add advert")

    def test_get_add(self):
        response = self.client.get(reverse('wagtailsnippets:add',
                                   args=('tests', 'advert')))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_edit(self):
        response = self.client.get(reverse('wagtailsnippets:edit',
                                   args=('tests', 'advert', quote(self.test_snippet.pk))))
        # permission should be denied
        self.assertRedirects(response, reverse('wagtailadmin_home'))

    def test_get_delete(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('tests', 'advert', quote(self.test_snippet.pk), )))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/confirm_delete.html')

    def test_get_delete_mulitple(self):
        url = reverse('wagtailsnippets:delete-multiple', args=('tests', 'advert'))
        url += '?id=%s' % self.test_snippet.id
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/confirm_delete.html')

class TestSnippetEditHandlers(TestCase, WagtailTestUtils):
    def test_standard_edit_handler(self):
        edit_handler = get_snippet_edit_handler(StandardSnippet)
        form_class = edit_handler.get_form_class()
        self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
        self.assertFalse(issubclass(form_class, FancySnippetForm))

    def test_fancy_edit_handler(self):
        edit_handler = get_snippet_edit_handler(FancySnippet)
        form_class = edit_handler.get_form_class()
        self.assertTrue(issubclass(form_class, WagtailAdminModelForm))
        self.assertTrue(issubclass(form_class, FancySnippetForm))

class TestInlinePanelMedia(TestCase, WagtailTestUtils):
    """
    Test that form media required by InlinePanels is correctly pulled in to the edit page
    """
    def test_inline_panel_media(self):
        self.login()

        response = self.client.get(reverse('wagtailsnippets:add', args=('snippetstests', 'multisectionrichtextsnippet')))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'wagtailadmin/js/draftail.js')

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

    def test_serialize(self):
        """The value of a SnippetChooserBlock (a snippet instance) should serialize to an ID"""
        block = SnippetChooserBlock(Advert)
        test_advert = Advert.objects.get(text='test_advert')

        self.assertEqual(block.get_prep_value(test_advert), test_advert.id)

        # None should serialize to None
        self.assertEqual(block.get_prep_value(None), None)

    def test_deserialize(self):
        """The serialized value of a SnippetChooserBlock (an ID) should deserialize to a snippet instance"""
        block = SnippetChooserBlock(Advert)
        test_advert = Advert.objects.get(text='test_advert')

        self.assertEqual(block.to_python(test_advert.id), test_advert)

        # None should deserialize to None
        self.assertEqual(block.to_python(None), None)

    def test_reference_model_by_string(self):
        block = SnippetChooserBlock('tests.Advert')
        test_advert = Advert.objects.get(text='test_advert')
        self.assertEqual(block.to_python(test_advert.id), test_advert)

    def test_adapt(self):
        block = SnippetChooserBlock(Advert, help_text="pick an advert, any advert")

        block.set_name('test_snippetchooserblock')
        js_args = FieldBlockAdapter().js_args(block)

        self.assertEqual(js_args[0], 'test_snippetchooserblock')
        self.assertIsInstance(js_args[1], AdminSnippetChooser)
        self.assertEqual(js_args[1].target_model, Advert)
        self.assertEqual(js_args[2], {
            'label': 'Test snippetchooserblock',
            'required': True,
            'icon': 'snippet',
            'helpText': 'pick an advert, any advert',
            'classname': 'field model_choice_field widget-admin_snippet_chooser fieldname-test_snippetchooserblock',
            'showAddCommentButton': True,
            'strings': {'ADD_COMMENT': 'Add Comment'}
        })

    def test_form_response(self):
        block = SnippetChooserBlock(Advert)
        test_advert = Advert.objects.get(text='test_advert')

        value = block.value_from_datadict({'advert': str(test_advert.id)}, {}, 'advert')
        self.assertEqual(value, test_advert)

        empty_value = block.value_from_datadict({'advert': ''}, {}, 'advert')
        self.assertEqual(empty_value, None)

    def test_clean(self):
        required_block = SnippetChooserBlock(Advert)
        nonrequired_block = SnippetChooserBlock(Advert, required=False)
        test_advert = Advert.objects.get(text='test_advert')

        self.assertEqual(required_block.clean(test_advert), test_advert)
        with self.assertRaises(ValidationError):
            required_block.clean(None)

        self.assertEqual(nonrequired_block.clean(test_advert), test_advert)
        self.assertEqual(nonrequired_block.clean(None), None)

class TestAdminSnippetChooserWidget(TestCase, WagtailTestUtils):
    def test_adapt(self):
        widget = AdminSnippetChooser(Advert)

        js_args = SnippetChooserAdapter().js_args(widget)

        self.assertEqual(len(js_args), 2)
        self.assertInHTML('<input type="hidden" name="__NAME__" id="__ID__">', js_args[0])
        self.assertIn('>Choose advert<', js_args[0])
        self.assertEqual(js_args[1], '__ID__')

class TestSnippetListViewWithCustomPrimaryKey(TestCase, WagtailTestUtils):
    def setUp(self):
        self.login()

        # Create some instances of the searchable snippet for testing
        self.snippet_a = StandardSnippetWithCustomPrimaryKey.objects.create(snippet_id="snippet/01", text="Hello")
        self.snippet_b = StandardSnippetWithCustomPrimaryKey.objects.create(snippet_id="snippet/02", text="Hello")
        self.snippet_c = StandardSnippetWithCustomPrimaryKey.objects.create(snippet_id="snippet/03", text="Hello")

    def get(self, params={}):
        return self.client.get(reverse('wagtailsnippets:list',
                                       args=('snippetstests', 'standardsnippetwithcustomprimarykey')),
                               params)

    def test_simple(self):
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/type_index.html')

        # All snippets should be in items
        items = list(response.context['items'].object_list)
        self.assertIn(self.snippet_a, items)
        self.assertIn(self.snippet_b, items)
        self.assertIn(self.snippet_c, items)

class TestSnippetViewWithCustomPrimaryKey(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        super(TestSnippetViewWithCustomPrimaryKey, self).setUp()
        self.login()
        self.snippet_a = StandardSnippetWithCustomPrimaryKey.objects.create(snippet_id="snippet/01", text="Hello")

    def get(self, snippet, params={}):
        args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk))
        return self.client.get(reverse('wagtailsnippets:edit', args=args), params)

    def post(self, snippet, post_data={}):
        args = (snippet._meta.app_label, snippet._meta.model_name, quote(snippet.pk))
        return self.client.post(reverse('wagtailsnippets:edit', args=args), post_data)

    def create(self, snippet, post_data={}, model=Advert):
        args = (snippet._meta.app_label, snippet._meta.model_name)
        return self.client.post(reverse('wagtailsnippets:add', args=args), post_data)

    def test_show_edit_view(self):
        response = self.get(self.snippet_a)
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/edit.html')

    def test_edit_invalid(self):
        response = self.post(self.snippet_a, post_data={'foo': 'bar'})
        self.assertContains(response, "The snippet could not be saved due to errors.")
        self.assertContains(response, "This field is required.")

    def test_edit(self):
        response = self.post(self.snippet_a, post_data={'text': 'Edited snippet',
                             'snippet_id': 'snippet_id_edited'})
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('snippetstests', 'standardsnippetwithcustomprimarykey')))

        snippets = StandardSnippetWithCustomPrimaryKey.objects.all()
        self.assertEqual(snippets.count(), 2)
        self.assertEqual(snippets.last().snippet_id, 'snippet_id_edited')

    def test_create(self):
        response = self.create(self.snippet_a, post_data={'text': 'test snippet',
                               'snippet_id': 'snippet/02'})
        self.assertRedirects(response, reverse('wagtailsnippets:list', args=('snippetstests', 'standardsnippetwithcustomprimarykey')))

        snippets = StandardSnippetWithCustomPrimaryKey.objects.all()
        self.assertEqual(snippets.count(), 2)
        self.assertEqual(snippets.last().text, 'test snippet')

    def test_get_delete(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('snippetstests', 'standardsnippetwithcustomprimarykey', quote(self.snippet_a.pk), )))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/confirm_delete.html')

    @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
    def test_usage_link(self):
        response = self.client.get(reverse('wagtailsnippets:delete', args=('snippetstests', 'standardsnippetwithcustomprimarykey', quote(self.snippet_a.pk), )))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'wagtailsnippets/snippets/confirm_delete.html')
        self.assertContains(response, 'Used 0 times')
        self.assertContains(response, self.snippet_a.usage_url())

    def test_redirect_to_edit(self):
        response = self.client.get('/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/snippet_2F01/')
        self.assertRedirects(response, '/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/edit/snippet_2F01/', status_code=301)

    def test_redirect_to_delete(self):
        response = self.client.get('/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/snippet_2F01/delete/')
        self.assertRedirects(response, '/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/delete/snippet_2F01/', status_code=301)

    @override_settings(WAGTAIL_USAGE_COUNT_ENABLED=True)
    def test_redirect_to_usage(self):
        response = self.client.get('/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/snippet_2F01/usage/')
        self.assertRedirects(response, '/admin/snippets/snippetstests/standardsnippetwithcustomprimarykey/usage/snippet_2F01/', status_code=301)

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

    def test_serialize(self):
        """The value of a SnippetChooserBlock (a snippet instance) should serialize to an ID"""
        block = SnippetChooserBlock(AdvertWithCustomPrimaryKey)
        test_advert = AdvertWithCustomPrimaryKey.objects.get(pk='advert/01')

        self.assertEqual(block.get_prep_value(test_advert), test_advert.pk)

        # None should serialize to None
        self.assertEqual(block.get_prep_value(None), None)

    def test_deserialize(self):
        """The serialized value of a SnippetChooserBlock (an ID) should deserialize to a snippet instance"""
        block = SnippetChooserBlock(AdvertWithCustomPrimaryKey)
        test_advert = AdvertWithCustomPrimaryKey.objects.get(pk='advert/01')

        self.assertEqual(block.to_python(test_advert.pk), test_advert)

        # None should deserialize to None
        self.assertEqual(block.to_python(None), None)

    def test_adapt(self):
        block = SnippetChooserBlock(AdvertWithCustomPrimaryKey, help_text="pick an advert, any advert")

        block.set_name('test_snippetchooserblock')
        js_args = FieldBlockAdapter().js_args(block)

        self.assertEqual(js_args[0], 'test_snippetchooserblock')
        self.assertIsInstance(js_args[1], AdminSnippetChooser)
        self.assertEqual(js_args[1].target_model, AdvertWithCustomPrimaryKey)
        self.assertEqual(js_args[2], {
            'label': 'Test snippetchooserblock',
            'required': True,
            'icon': 'snippet',
            'helpText': 'pick an advert, any advert',
            'classname': 'field model_choice_field widget-admin_snippet_chooser fieldname-test_snippetchooserblock',
            'showAddCommentButton': True,
            'strings': {'ADD_COMMENT': 'Add Comment'}
        })

    def test_form_response(self):
        block = SnippetChooserBlock(AdvertWithCustomPrimaryKey)
        test_advert = AdvertWithCustomPrimaryKey.objects.get(pk='advert/01')

        value = block.value_from_datadict({'advertwithcustomprimarykey': str(test_advert.pk)}, {}, 'advertwithcustomprimarykey')
        self.assertEqual(value, test_advert)

        empty_value = block.value_from_datadict({'advertwithcustomprimarykey': ''}, {}, 'advertwithcustomprimarykey')
        self.assertEqual(empty_value, None)

    def test_clean(self):
        required_block = SnippetChooserBlock(AdvertWithCustomPrimaryKey)
        nonrequired_block = SnippetChooserBlock(AdvertWithCustomPrimaryKey, required=False)
        test_advert = AdvertWithCustomPrimaryKey.objects.get(pk='advert/01')

        self.assertEqual(required_block.clean(test_advert), test_advert)
        with self.assertRaises(ValidationError):
            required_block.clean(None)

        self.assertEqual(nonrequired_block.clean(test_advert), test_advert)
        self.assertEqual(nonrequired_block.clean(None), None)

class TestSnippetChooserPanelWithCustomPrimaryKey(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.request = RequestFactory().get('/')
        user = AnonymousUser()  # technically, Anonymous users cannot access the admin
        self.request.user = user

        model = SnippetChooserModelWithCustomPrimaryKey
        self.advert_text = 'Test advert text'
        test_snippet = model.objects.create(
            advertwithcustomprimarykey=AdvertWithCustomPrimaryKey.objects.create(
                advert_id="advert/02",
                text=self.advert_text
            )
        )

        self.edit_handler = get_snippet_edit_handler(model)
        self.form_class = self.edit_handler.get_form_class()
        form = self.form_class(instance=test_snippet)
        edit_handler = self.edit_handler.bind_to(
            instance=test_snippet, form=form, request=self.request)

        self.snippet_chooser_panel = [
            panel for panel in edit_handler.children
            if getattr(panel, 'field_name', None) == 'advertwithcustomprimarykey'][0]

    def test_create_snippet_chooser_panel_class(self):
        self.assertIsInstance(self.snippet_chooser_panel, SnippetChooserPanel)

    def test_render_as_field(self):
        field_html = self.snippet_chooser_panel.render_as_field()
        self.assertIn(self.advert_text, field_html)
        self.assertIn("Choose advert with custom primary key", field_html)
        self.assertIn("Choose another advert with custom primary key", field_html)

    def test_render_as_empty_field(self):
        test_snippet = SnippetChooserModelWithCustomPrimaryKey()
        form = self.form_class(instance=test_snippet)
        edit_handler = self.edit_handler.bind_to(
            instance=test_snippet, form=form, request=self.request)

        snippet_chooser_panel = [
            panel for panel in edit_handler.children
            if getattr(panel, 'field_name', None) == 'advertwithcustomprimarykey'
        ][0]

        field_html = snippet_chooser_panel.render_as_field()
        self.assertIn("Choose advert with custom primary key", field_html)
        self.assertIn("Choose another advert with custom primary key", field_html)

    def test_render_js(self):
        self.assertIn('createSnippetChooser("id_advertwithcustomprimarykey");',
                      self.snippet_chooser_panel.render_as_field())

    def test_target_model_autodetected(self):
        result = SnippetChooserPanel(
            'advertwithcustomprimarykey'
        ).bind_to(model=SnippetChooserModelWithCustomPrimaryKey).target_model
        self.assertEqual(result, AdvertWithCustomPrimaryKey)

class TestSnippetChooseWithCustomPrimaryKey(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.login()

    def get(self, params=None):
        return self.client.get(reverse('wagtailsnippets:choose',
                                       args=('tests', 'advertwithcustomprimarykey')),
                               params or {})

    def test_simple(self):
        response = self.get()
        self.assertTemplateUsed(response, 'wagtailsnippets/chooser/choose.html')

    def test_ordering(self):
        """
        Listing should be ordered by PK if no ordering has been set on the model
        """
        AdvertWithCustomPrimaryKey.objects.all().delete()
        for i in range(10, 0, -1):
            AdvertWithCustomPrimaryKey.objects.create(pk=i, text="advert %d" % i)
        response = self.get()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context['items'][0].text, "advert 1")

class TestSnippetChosenWithCustomPrimaryKey(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.login()

    def get(self, pk, params=None):
        return self.client.get(reverse('wagtailsnippets:chosen',
                                       args=('tests', 'advertwithcustomprimarykey', quote(pk))),
                               params or {})

    def test_choose_a_page(self):
        response = self.get(pk=AdvertWithCustomPrimaryKey.objects.all()[0].pk)
        response_json = json.loads(response.content.decode())
        self.assertEqual(response_json['step'], 'chosen')

class TestSnippetChosenWithCustomUUIDPrimaryKey(TestCase, WagtailTestUtils):
    fixtures = ['test.json']

    def setUp(self):
        self.login()

    def get(self, pk, params=None):
        return self.client.get(reverse('wagtailsnippets:chosen',
                                       args=('tests', 'advertwithcustomuuidprimarykey', quote(pk))),
                               params or {})

    def test_choose_a_page(self):
        response = self.get(pk=AdvertWithCustomUUIDPrimaryKey.objects.all()[0].pk)
        response_json = json.loads(response.content.decode())
        self.assertEqual(response_json['step'], 'chosen')

class TestPanelConfigurationChecks(TestCase, WagtailTestUtils):

    def setUp(self):
        self.warning_id = 'wagtailadmin.W002'

        def get_checks_result():
            # run checks only with the 'panels' tag
            checks_result = checks.run_checks(tags=['panels'])
            return [
                warning for warning in
                checks_result if warning.id == self.warning_id]

        self.get_checks_result = get_checks_result

    def test_model_with_single_tabbed_panel_only(self):

        StandardSnippet.content_panels = [FieldPanel('text')]

        warning = checks.Warning(
            "StandardSnippet.content_panels will have no effect on snippets editing",
            hint="""Ensure that StandardSnippet uses `panels` instead of `content_panels`\
or set up an `edit_handler` if you want a tabbed editing interface.
There are no default tabs on non-Page models so there will be no\
 Content tab for the content_panels to render in.""",
            obj=StandardSnippet,
            id='wagtailadmin.W002',
        )

        checks_results = self.get_checks_result()

        self.assertEqual([warning], checks_results)

        # clean up for future checks
        delattr(StandardSnippet, 'content_panels')

redpajama_set_name: RedPajamaGithub