from unittest.mock import Mock, patch from django import forms from django.apps import apps from django.apps.registry import Apps from django.contrib.sites.models import Site from django.core.exceptions import ImproperlyConfigured from django.forms.models import ModelForm from django.template import TemplateSyntaxError from django.test.utils import override_settings from django.urls import reverse from django.utils.encoding import smart_str from django.utils.translation import gettext as _ from cms import app_registration from cms.api import create_page from cms.cms_wizards import cms_page_wizard, cms_subpage_wizard from cms.constants import MODAL_HTML_REDIRECT, TEMPLATE_INHERITANCE_MAGIC from cms.forms.wizards import CreateCMSPageForm, CreateCMSSubPageForm from cms.models import Page, PageContent, UserSettings from cms.models.permissionmodels import GlobalPagePermission from cms.test_utils.project.backwards_wizards.wizards import wizard from cms.test_utils.project.sampleapp.cms_wizards import sample_wizard from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase from cms.toolbar.utils import ( get_object_edit_url, get_object_preview_url, ) from cms.utils.conf import get_cms_setting from cms.utils.page_permissions import user_can_add_subpage from cms.utils.setup import setup_cms_apps from cms.utils.urlutils import admin_reverse from cms.wizards.forms import WizardStep1Form, WizardStep2BaseForm, step2_form_factory from cms.wizards.helpers import get_entries, get_entry from cms.wizards.wizard_base import Wizard from cms.wizards.wizard_pool import ( AlreadyRegisteredException, entry_choices, wizard_pool, ) CreateCMSPageForm = step2_form_factory( mixin_cls=WizardStep2BaseForm, entry_form_class=CreateCMSPageForm, ) CreateCMSSubPageForm = step2_form_factory( mixin_cls=WizardStep2BaseForm, entry_form_class=CreateCMSSubPageForm, ) class WizardForm(forms.Form): pass class ModelWizardForm(ModelForm): class Meta: model = UserSettings exclude = [] class BadModelForm(ModelForm): class Meta: pass class WizardTestMixin: page_wizard = None title_wizard = None def assertSequencesEqual(self, seq_a, seq_b): seq_a = list(seq_a) seq_b = list(seq_b) zipped = list(zip(seq_a, seq_b, strict=False)) if len(zipped) < len(seq_a) or len(zipped) < len(seq_b): self.fail("Sequence lengths are not the same.") for idx, (a, b) in enumerate(zipped): if a != b: self.fail(f"Sequences differ at index {idx}") @classmethod def setUpClass(cls): super().setUpClass() # This prevents auto-discovery, which would otherwise occur as soon as # tests start, creating unexpected starting conditions. wizard_pool._discovered = True class PageWizard(Wizard): pass # This is a basic Wizard cls.page_wizard = PageWizard( title=_("Page"), weight=100, form=WizardForm, model=Page, template_name="my_template.html", # This doesn't exist anywhere ) class SettingsWizard(Wizard): pass # This is a Wizard that uses a ModelForm to define the model cls.user_settings_wizard = SettingsWizard( title=_("UserSettings"), weight=200, form=ModelWizardForm, ) class PageContentWizard(Wizard): pass # This is a bad wizard definition as it neither defines a model, nor # uses a ModelForm that has model defined in Meta cls.title_wizard = PageContentWizard( title=_("Page"), weight=100, form=BadModelForm, template_name="my_template.html", # This doesn't exist anywhere ) def setUp(self): super().setUp() self.site = Site.objects.get_current() class TestWizardBase(WizardTestMixin, TransactionCMSTestCase): def test_user_has_add_permission(self): # Test does not have permission user = self.get_staff_user_with_no_permissions() self.assertFalse(self.page_wizard.user_has_add_permission(user)) # Test has permission user = self.get_superuser() self.assertTrue(self.page_wizard.user_has_add_permission(user)) def test_get_success_url(self): user = self.get_superuser() page = create_page( title="Sample Page", template=TEMPLATE_INHERITANCE_MAGIC, language="en", created_by=smart_str(user), parent=None, in_navigation=True, ) url = page.get_absolute_url(language="en") self.assertEqual(self.page_wizard.get_success_url(page, language="en"), url) # Now again without a language code url = page.get_absolute_url() self.assertEqual(self.page_wizard.get_success_url(page), url) def test_get_edit_url(self): user = self.get_superuser() page = create_page( title="Sample Page", template=TEMPLATE_INHERITANCE_MAGIC, language="en", created_by=smart_str(user), parent=None, in_navigation=True, ) extension = apps.get_app_config("cms").cms_extension with patch.object(extension, "toolbar_enabled_models", {Page: page}): url = self.page_wizard.get_success_url(page, language="en") self.assertEqual(url, get_object_edit_url(page, language="en")) def test_get_preview_url(self): wizard_preview_mode = Wizard( title=_("Page"), weight=100, form=WizardForm, model=Page, template_name="my_template.html", # This doesn't exist anywhere edit_mode_on_success=False, ) user = self.get_superuser() page = create_page( title="Sample Page", template=TEMPLATE_INHERITANCE_MAGIC, language="en", created_by=smart_str(user), parent=None, in_navigation=True, ) extension = apps.get_app_config("cms").cms_extension with patch.object(extension, "toolbar_enabled_models", {Page: page}): url = wizard_preview_mode.get_success_url(page, language="en") self.assertEqual(url, get_object_preview_url(page, language="en")) def test_get_model(self): self.assertEqual(self.page_wizard.get_model(), Page) self.assertEqual(self.user_settings_wizard.get_model(), UserSettings) with self.assertRaises(ImproperlyConfigured): self.title_wizard.get_model() def test_endpoint_auth_required(self): endpoint = reverse("admin:cms_wizard_create") staff_active = self._create_user("staff-active", is_staff=True, is_superuser=False, is_active=True) response = self.client.get(endpoint) self.assertEqual(response.status_code, 403) with self.login_user_context(staff_active): response = self.client.get(endpoint) self.assertEqual(response.status_code, 200) class TestWizardPool(WizardTestMixin, CMSTestCase): def tearDown(self): # Clean up in case anything has been removed or added to the # registered wizards, so other tests don't have problems extension = apps.get_app_config("cms").cms_extension # Reset the cached property 'wizards' if hasattr(extension, "wizards"): del extension.wizards configs_with_wizards = [ app.cms_config for app in app_registration.get_cms_config_apps() if hasattr(app.cms_config, "cms_wizards") ] for config in configs_with_wizards: extension.configure_wizards(config) # Clean up in case cached apps are different than the defaults app_registration.get_cms_extension_apps.cache_clear() app_registration.get_cms_config_apps.cache_clear() def test_is_registered_for_registered_wizard(self): """ Test for backwards compatibility of is_registered when checking a registered wizard. """ from django.apps import apps self.assertTrue(apps.get_app_config("cms").cms_extension.wizards) is_registered = wizard_pool.is_registered(cms_page_wizard) self.assertTrue(is_registered) def test_is_registered_for_unregistered_wizard(self): """ Test for backwards compatibility of is_registered when checking an unregistered wizard. """ is_registered = wizard_pool.is_registered(self.page_wizard) self.assertFalse(is_registered) def test_unregister_registered_wizard(self): """ Test for backwards compatibility of the unregister method. Removes a wizard from the wizards dict. """ was_unregistered = wizard_pool.unregister(cms_page_wizard) registered_wizards = apps.get_app_config("cms").cms_extension.wizards self.assertNotIn(cms_page_wizard.id, registered_wizards) self.assertTrue(was_unregistered) def test_unregister_unregistered_wizard(self): """ Test for backwards compatibility of the unregister method. Returns False if wizard not found. """ was_unregistered = wizard_pool.unregister(self.page_wizard) self.assertFalse(was_unregistered) def test_register_already_registered_wizard(self): """ Test for backwards compatibility of the register method. Raises AlreadyRegisteredException if the wizard is already registered. """ with self.assertRaises(AlreadyRegisteredException): wizard_pool.register(cms_page_wizard) def test_register_unregistered_wizard(self): """ Test for backwards compatibility of the register method. Adds the wizard to the wizards dict. """ wizard_pool.register(self.page_wizard) registered_wizards = apps.get_app_config("cms").cms_extension.wizards self.assertIn(self.page_wizard.id, registered_wizards) @patch("cms.wizards.wizard_pool.get_entry") def test_get_entry(self, mocked_get_entry): """ Test for backwards compatibility of wizard_pool.get_entry. Checking we use the new get_entry under the hood. """ wizard_pool.get_entry(cms_page_wizard) mocked_get_entry.assert_called_once() def test_old_registration_still_works(self): """ Integration-like test checking that if you register your wizard by adding wizard_pool.register(wizard) to cms_wizards.py it will correctly register the wizard. This ensures backwards compatibility. """ # NOTE: Because of how the override_settings decorator works, # we can't use it for this test as the app registry first # gets loaded with the default apps and then again with # the overridden ones. INSTALLED_APPS = [ "cms", "treebeard", "cms.test_utils.project.backwards_wizards", ] mocked_apps = Apps(installed_apps=INSTALLED_APPS) # clear out app registration cache now that installed apps have # changed app_registration.get_cms_extension_apps.cache_clear() app_registration.get_cms_config_apps.cache_clear() # Run the setup with the mocked installed apps. Due to the order # of imports and other fun things, apps have to be patched # in multiple places. If functions are moved around the code # this could get worse. with patch("cms.app_registration.apps", mocked_apps): with patch("cms.wizards.wizard_pool.apps", mocked_apps): setup_cms_apps() # Check the wizards built into the cms app and the wizard from # the backwards_wizards app have been picked up. # The backwards_wizards app does not have a cms_config.py but # registers its wizard by adding wizard_pool.register(wizard) # to cms_wizards.py which is the old way we want backwards # compatibility with. cms_app = mocked_apps.get_app_config("cms") expected_wizards = { cms_page_wizard.id: cms_page_wizard, cms_subpage_wizard.id: cms_subpage_wizard, wizard.id: wizard, } self.assertDictEqual(cms_app.cms_extension.wizards, expected_wizards) class TestPageWizard(WizardTestMixin, CMSTestCase): def test_str(self): self.assertEqual(str(cms_page_wizard), cms_page_wizard.title) def test_repr(self): self.assertIn("cms.cms_wizards.CMSPageWizard", repr(cms_page_wizard)) self.assertIn(f"id={cms_page_wizard.id}", repr(cms_page_wizard)) self.assertIn(hex(id(cms_page_wizard)), repr(cms_page_wizard)) def test_wizard_create_child_page(self): superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() parent_page = create_page( title="Parent", template=TEMPLATE_INHERITANCE_MAGIC, language="en", ) data = { "title": "Child", "slug": "child", "page_type": None, } form = CreateCMSSubPageForm( data=data, wizard_page=parent_page, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) child_page = form.save() self.assertEqual(child_page.depth, 2) self.assertEqual(child_page.parent, parent_page) self.assertEqual(child_page.get_title("en"), "Child") self.assertEqual(child_page.get_path("en"), "parent/child") def test_wizard_create_atomic(self): # Ref: https://github.com/divio/django-cms/issues/5652 # We'll simulate a scenario where a user creates a page with an # invalid template which causes Django to throw an error when the # template is scanned for placeholders and thus short circuits the # creation mechanism. superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() data = { "title": "page 1", "slug": "page_1", "page_type": None, } form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) self.assertFalse(Page.objects.filter(pagecontent_set__template=TEMPLATE_INHERITANCE_MAGIC).exists()) with patch.object(PageContent, "template_choices", [("col_invalid.html", "notvalid")]): self.assertRaises(TemplateSyntaxError, form.save) # The template raised an exception which should cause the database to roll back # instead of committing a page in a partial state. self.assertFalse(Page.objects.filter(pagecontent_set__template=TEMPLATE_INHERITANCE_MAGIC).exists()) def test_wizard_content_placeholder_setting(self): """ Tests that the PageWizard respects the CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER setting. """ superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() templates = get_cms_setting("TEMPLATES") # NOTE, there are 4 placeholders on this template, defined in this # order: 'header', 'content', 'sub-content', 'footer'. # 'footer' is a static-placeholder. templates.append( ( "page_wizard.html", "page_wizard.html", ) ) settings = { "CMS_TEMPLATES": templates, "CMS_PAGE_WIZARD_DEFAULT_TEMPLATE": "page_wizard.html", "CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER": "sub-content", } with override_settings(**settings): page = create_page("wizard home", "page_wizard.html", "en") content = "

sub-content content.

" data = { "title": "page 1", "slug": "page_1", "page_type": None, "content": content, } form = CreateCMSPageForm( data=data, wizard_page=page, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) page = form.save() with self.login_user_context(superuser): url = page.get_absolute_url("en") expected = f'
{content}
' unexpected = f'
{content}
' response = self.client.get(url) self.assertContains(response, expected, status_code=200) self.assertNotContains(response, unexpected, status_code=200) def test_wizard_content_placeholder_bad_setting(self): """ Tests that the PageWizard won't respect a 'bad' setting such as targeting a static-placeholder. In this case, will just fail to add the content (without error). """ superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() templates = get_cms_setting("TEMPLATES") # NOTE, there are 4 placeholders on this template, defined in this # order: 'header', 'content', 'sub-content', 'footer'. # 'footer' is a static-placeholder. templates.append( ( "page_wizard.html", "page_wizard.html", ) ) settings = { "CMS_TEMPLATES": templates, "CMS_PAGE_WIZARD_DEFAULT_TEMPLATE": "page_wizard.html", # This is a bad setting. "CMS_PAGE_WIZARD_CONTENT_PLACEHOLDER": "footer", } with override_settings(**settings): page = create_page("wizard home", "page_wizard.html", "en") content = "

footer content.

" data = { "title": "page 1", "slug": "page_1", "page_type": None, "content": content, } form = CreateCMSPageForm( data=data, wizard_page=page, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) page = form.save() with self.login_user_context(superuser): url = page.get_absolute_url("en") response = self.client.get(url) self.assertNotContains(response, content, status_code=200) def test_create_page_with_empty_fields(self): superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() data = { "title": "", "slug": "", "page_type": None, } form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertFalse(form.is_valid()) def test_create_page_with_existing_slug(self): superuser = self.get_superuser() with self.login_user_context(superuser): request = self.get_request() data = { "title": "page", "slug": "page", "page_type": None, } create_page("page", "nav_playground.html", language="en", slug="page") # slug -> page-1 form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) self.assertTrue(form.save().get_urls().filter(slug="page-2")) # slug -> page-2 form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) self.assertTrue(form.save().get_urls().filter(slug="page-3")) # Now explicitly request the page-2 slug data["slug"] = "page-2" # slug -> page-2-2 form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) self.assertTrue(form.save().get_urls().filter(slug="page-2-2")) # slug -> page-2-3 form = CreateCMSPageForm( data=data, wizard_page=None, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertTrue(form.is_valid()) self.assertTrue(form.save().get_urls().filter(slug="page-2-3")) class TestPageWizardSubmission(CMSTestCase): def test_page_wizard_submission(self): from cms.wizards.helpers import get_entries page = create_page("home", "nav_playground.html", "en") entry_id = get_entries()[0].id endpoint = admin_reverse("cms_wizard_create") data_step1 = { "wizard_create_view-current_step": "0", "0-entry": entry_id, "0-language": "en", "0-page": page.pk, } data_step2 = { "wizard_create_view-current_step": "1", "1-title": "my test page", "1-slug": "my-test-page", "1-content": "", } with self.login_user_context(self.get_superuser()): response = self.client.post(endpoint, data_step1, follow=True) self.assertEqual(response.status_code, 200) response = self.client.post(endpoint, data_step2) edit_endpoint = get_object_edit_url(Page.objects.exclude(pk=page.pk).last().get_content_obj("en"), "en") self.assertContains(response, MODAL_HTML_REDIRECT.format(url=edit_endpoint)) def test_subpage_wizard_submission(self): """Regression test for #8697: the subpage wizard must run through both steps and create the new page as a child of the origin page. The subpage entry is only offered while the wizard knows the origin page, so a regression that drops the page between steps both hides the entry (step 0 fails to validate) and detaches the created page. """ parent = create_page("home", "nav_playground.html", "en") entry_id = cms_subpage_wizard.id endpoint = admin_reverse("cms_wizard_create") data_step1 = { "wizard_create_view-current_step": "0", "0-entry": entry_id, "0-language": "en", "0-page": parent.pk, } data_step2 = { "wizard_create_view-current_step": "1", "1-title": "my child page", "1-slug": "my-child-page", "1-content": "", } with self.login_user_context(self.get_superuser()): # Step 0 must advance to step 1 -- i.e. the subpage entry stays a # valid choice because the origin page survives the transition. response = self.client.post(endpoint, data_step1, follow=True) self.assertEqual(response.status_code, 200) self.assertNotContains(response, "Please choose an option") response = self.client.post(endpoint, data_step2) new_page = Page.objects.exclude(pk=parent.pk).get() self.assertEqual(new_page.parent_id, parent.pk) edit_endpoint = get_object_edit_url(new_page.get_content_obj("en"), "en") self.assertContains(response, MODAL_HTML_REDIRECT.format(url=edit_endpoint)) @override_settings( CMS_PERMISSION=True, CMS_LANGUAGES={ 1: [{"code": "en", "name": "English"}], 2: [{"code": "en", "name": "English"}], }, ) class TestWizardSiteIsolation(CMSTestCase): def setUp(self): self.site = Site.objects.get_current() self.other_site, _ = Site.objects.get_or_create( pk=2, defaults={"domain": "other.example", "name": "Other site"}, ) self.local_parent = create_page("Local parent", "nav_playground.html", "en", site=self.site) self.other_parent = create_page("Other parent", "nav_playground.html", "en", site=self.other_site) self.other_child = create_page( "Other child", "nav_playground.html", "en", site=self.other_site, parent=self.other_parent, ) self.writer = self._create_user( "site_writer", is_staff=True, permissions=["add_page", "change_page"], ) permission = GlobalPagePermission.objects.create(user=self.writer, can_add=True, can_change=True) permission.sites.add(self.site) def test_cross_site_origin_is_rejected(self): self.assertFalse(user_can_add_subpage(self.writer, self.other_parent, site=self.other_site)) endpoint = admin_reverse("cms_wizard_create") page_count = Page.objects.count() with self.login_user_context(self.writer): response = self.client.post(endpoint, { "wizard_create_view-current_step": "0", "0-entry": cms_page_wizard.id, "0-language": "en", "0-page": self.other_child.pk, }) self.assertEqual(response.status_code, 200) self.assertIn("page", response.context["wizard"]["form"].errors) # Posting step 1 directly must not bypass the rejected origin. response = self.client.post(endpoint, { "wizard_create_view-current_step": "1", "1-title": "Cross-site sibling", "1-slug": "cross-site-sibling", }) self.assertEqual(response.status_code, 200) self.assertEqual(Page.objects.count(), page_count) def test_same_site_subpage_submission(self): endpoint = admin_reverse("cms_wizard_create") with self.login_user_context(self.writer): response = self.client.post(endpoint, { "wizard_create_view-current_step": "0", "0-entry": cms_subpage_wizard.id, "0-language": "en", "0-page": self.local_parent.pk, }) self.assertEqual(response.status_code, 200) self.assertEqual(response.context["wizard"]["steps"].current, "1") response = self.client.post(endpoint, { "wizard_create_view-current_step": "1", "1-title": "Local child", "1-slug": "local-child", }) self.assertEqual(response.status_code, 200) child = Page.objects.get(pagecontent_set__title="Local child") self.assertEqual(child.site_id, self.site.pk) self.assertEqual(child.parent_id, self.local_parent.pk) def test_cross_site_parent_is_rejected_by_creation_forms(self): # Tree integrity must also hold for direct form use, including # superusers and projects without CMS per-page permissions. with self.login_user_context(self.get_superuser()): request = self.get_request() for permissions_enabled in (True, False): for form_class, origin in ( (CreateCMSPageForm, self.other_child), (CreateCMSSubPageForm, self.other_parent), ): with self.subTest(permissions=permissions_enabled, form=form_class): with override_settings(CMS_PERMISSION=permissions_enabled): form = form_class( data={"title": "Cross-site page", "slug": "cross-site-page"}, wizard_page=origin, wizard_site=self.site, wizard_language="en", wizard_request=request, ) self.assertFalse(form.is_valid()) self.assertIn("parent_page", form.errors) class TestWizardHelpers(CMSTestCase): def setUp(self): # The results of get_cms_extension_apps and get_cms_config_apps # are cached. Clear this cache because installed apps change # between tests and therefore unlike in a live environment, # results of this function can change between tests app_registration.get_cms_extension_apps.cache_clear() app_registration.get_cms_config_apps.cache_clear() def test_get_entries_orders_by_weight(self): """ The get_entries function returns the registered wizards ordered by weight. """ # The test setup registers two wizards from cms itself # (cms_page_wizard and cms_subpage_wizard) and one from # test_utils.project.sampleapp (sample_wizard) # We know these are definitely being ordered by weight if # sample_wizard is in the middle because app registration # would first add the wizards from cms to a list and then add # those from sampleapp, so the sampleapp wizard could not # be in the middle by default expected = [cms_page_wizard, sample_wizard, cms_subpage_wizard] entries = get_entries() self.assertListEqual(entries, expected) def test_get_entry_returns_wizard_by_id(self): """ The get_entry function returns the wizard when a wizard id is supplied. """ entry = get_entry(sample_wizard.id) self.assertEqual(entry, sample_wizard) class TestEntryChoices(CMSTestCase): def test_generates_choices_in_weighted_order(self): """ The entry_choices function returns the wizards ordered by weight """ user = self.get_superuser() page = create_page("home", "nav_playground.html", "en") wizard_choices = [option for option in entry_choices(user, page)] expected = [ (cms_page_wizard.id, cms_page_wizard.title), (sample_wizard.id, sample_wizard.title), (cms_subpage_wizard.id, cms_subpage_wizard.title), ] self.assertListEqual(wizard_choices, expected) @patch.object(cms_page_wizard, "user_has_add_permission", Mock(return_value=False)) def test_doesnt_generate_choice_if_user_doesnt_have_permission(self): """ The entry_choices function only returns the wizards that the user has permissions for """ user = self.get_superuser() page = create_page("home", "nav_playground.html", "en") wizard_choices = [option for option in entry_choices(user, page)] expected = [ # Missing cms_page_wizard entry (sample_wizard.id, sample_wizard.title), (cms_subpage_wizard.id, cms_subpage_wizard.title), ] self.assertListEqual(wizard_choices, expected) def test_wizard_step1_uses_selected_site_for_root_page_entries(self): site1 = Site.objects.get_current() site2 = Site.objects.create(domain="second.example.com", name="Second", pk=2) user = self._create_user("wizard-staff", is_staff=True, is_superuser=False) self.add_permission(user, "add_page") self.add_permission(user, "change_page") global_permission = self.add_global_permission(user, can_add=True, can_change=True) global_permission.sites.set([site1]) with self.login_user_context(user): request = self.get_request() form_site1 = WizardStep1Form( wizard_page=None, wizard_site=site1, wizard_language="en", wizard_request=request, ) form_site2 = WizardStep1Form( wizard_page=None, wizard_site=site2, wizard_language="en", wizard_request=request, ) choices_site1 = [choice[0] for choice in form_site1.fields["entry"].choices] choices_site2 = [choice[0] for choice in form_site2.fields["entry"].choices] self.assertIn(cms_page_wizard.id, choices_site1) self.assertNotIn(cms_page_wizard.id, choices_site2)