class CampaignIndexPage(IndexPage): """ The campaign index is specifically for campaign-related pages """ subpage_types = [ 'BanneredCampaignPage', 'CampaignPage', 'DearInternetPage', 'OpportunityPage', 'YoutubeRegretsPage', 'YoutubeRegretsReporterPage', 'PublicationPage', 'ArticlePage' ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields from IndexPage TranslatableField('title'), TranslatableField('intro'), TranslatableField('header'), SynchronizedField('page_size'), ] template = 'wagtailpages/index_page.html' def get_context(self, request): # bootstrap the render context context = super().get_context(request) entries = self.get_all_entries().not_type(PublicationPage) context['entries'] = entries[0:self.page_size] # Which pagetype to exclude during the "load more" ajax request: PublicationPage context['exclude_pagetype'] = 'publicationpage' return context
class TestPage(Page): test_charfield = models.CharField(__("char field"), max_length=255, blank=True) test_textfield = models.TextField(blank=True) test_emailfield = models.EmailField(blank=True) test_slugfield = models.SlugField(blank=True) test_urlfield = models.URLField(blank=True) test_richtextfield = RichTextField(blank=True) test_streamfield = StreamField(TestStreamBlock, blank=True) test_snippet = models.ForeignKey(TestSnippet, null=True, blank=True, on_delete=models.SET_NULL) test_customfield = TestCustomField(blank=True) test_synchronized_charfield = models.CharField(max_length=255, blank=True) test_synchronized_textfield = models.TextField(blank=True) test_synchronized_emailfield = models.EmailField(blank=True) test_synchronized_slugfield = models.SlugField(blank=True) test_synchronized_urlfield = models.URLField(blank=True) test_synchronized_richtextfield = RichTextField(blank=True) test_synchronized_streamfield = StreamField(TestStreamBlock, blank=True) test_synchronized_snippet = models.ForeignKey(TestSnippet, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_synchronized_customfield = TestCustomField(blank=True) translatable_fields = [ TranslatableField("test_charfield"), TranslatableField("test_textfield"), TranslatableField("test_emailfield"), TranslatableField("test_slugfield"), TranslatableField("test_urlfield"), TranslatableField("test_richtextfield"), TranslatableField("test_streamfield"), TranslatableField("test_snippet"), TranslatableField("test_childobjects"), TranslatableField("test_customfield"), SynchronizedField("test_synchronized_charfield"), SynchronizedField("test_synchronized_textfield"), SynchronizedField("test_synchronized_emailfield"), SynchronizedField("test_synchronized_slugfield"), SynchronizedField("test_synchronized_urlfield"), SynchronizedField("test_synchronized_richtextfield"), SynchronizedField("test_synchronized_streamfield"), SynchronizedField("test_synchronized_snippet"), SynchronizedField("test_synchronized_childobjects"), SynchronizedField("test_synchronized_customfield"), ]
class CampaignPage(DonationPage): template = 'pages/core/campaign_page.html' parent_page_types = ['core.LandingPage', 'core.CampaignPage'] subpage_types = ['core.CampaignPage'] submit_to_pontoon_on_publish = False hero_image = models.ForeignKey( 'wagtailimages.Image', models.PROTECT, related_name='+', ) lead_text = models.CharField(max_length=800) intro = RichTextField() content_panels = Page.content_panels + [ FieldPanel('project'), ImageChooserPanel('hero_image'), FieldPanel('lead_text'), FieldPanel('intro'), InlinePanel('donation_amounts', label='Donation amount overrides'), ] translatable_fields = [ TranslatableField('title'), TranslatableField('seo_title'), TranslatableField('search_description'), SynchronizedField('project'), SynchronizedField('campaign_id'), SynchronizedField('hero_image'), TranslatableField('lead_text'), TranslatableField('intro'), ] @classmethod def amount_stream_to_list(cls, stream): return [Decimal(child.value) for child in stream] @classmethod def get_presets(cls, override): return { 'single': cls.amount_stream_to_list(override.single_options), 'monthly': cls.amount_stream_to_list(override.monthly_options), } @cached_property def currencies(self): currencies = super().currencies # Apply overrides for preset options for override in self.donation_amounts.all(): currencies[override.currency]['presets'] = self.get_presets( override) return currencies
class CampaignPage(MiniSiteNameSpace): """ these pages come with sign-a-petition CTAs """ cta = models.ForeignKey( 'Petition', related_name='page', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose existing or create new sign-up form') def get_donation_modal_json(self): modals = self.donation_modals.all() # This is where we can do server-side A/B testing, # by either sending all modals down the pipe, or # selectively only sending a single one based on # things like geolocation, time of day, etc. modals_json = [m.to_simple_dict() for m in modals] return json.dumps(modals_json) content_panels = Page.content_panels + [ FieldPanel('header'), SnippetChooserPanel('cta'), InlinePanel('donation_modals', label='Donation Modal', max_num=4), StreamFieldPanel('body'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('cta'), TranslatableField('title'), TranslatableField('header'), SynchronizedField('narrowed_page_content'), SynchronizedField('zen_nav'), TranslatableField('body'), TranslatableField('donation_modals'), ] subpage_types = [ 'CampaignPage', 'RedirectingPage', 'PublicationPage', 'ArticlePage' ]
class OpportunityPage(MiniSiteNameSpace): content_panels = Page.content_panels + [ FieldPanel('header'), StreamFieldPanel('body'), ] subpage_types = [ 'OpportunityPage', 'RedirectingPage', 'PublicationPage', 'ArticlePage' ] translatable_fields = [ # Promote tab fields TranslatableField('seo_title'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('header'), TranslatableField('body'), ] class Meta: verbose_name = "Default Page" verbose_name_plural = "Default pages"
class LandingPage(TranslatablePageRoutingMixin, DonationPage): template = 'pages/core/landing_page.html' # Only allow creating landing pages at the root level parent_page_types = ['wagtailcore.Page'] subpage_types = [ 'core.CampaignPage', 'core.ContentPage', 'core.ContributorSupportPage', ] featured_image = models.ForeignKey( 'wagtailimages.Image', models.PROTECT, related_name='+', ) intro = RichTextField() content_panels = Page.content_panels + [ FieldPanel('project'), ImageChooserPanel('featured_image'), FieldPanel('intro'), ] translatable_fields = [ TranslatableField('title'), TranslatableField('seo_title'), TranslatableField('search_description'), SynchronizedField('project'), SynchronizedField('campaign_id'), SynchronizedField('featured_image'), TranslatableField('intro'), ] def save(self, *args, **kwargs): # This slug isn't used anywhere but it does need to be unique for each language language_code = self.locale.language.code.lower() if language_code == 'en-us': # Needs to be something nice as translators will see it self.slug = 'mozilla-donate' else: self.slug = 'mozilla-donate-' + language_code super().save(*args, **kwargs)
class YoutubeRegretsReporterPage(FoundationMetadataPageMixin, Page): headline = models.CharField( max_length=500, help_text='Page headline', blank=True, ) intro_text = StreamField([ ('text', blocks.CharBlock()), ]) intro_images = StreamField([ ('image', customblocks.ImageBlock()), ]) content_panels = Page.content_panels + [ FieldPanel('headline'), StreamFieldPanel('intro_text'), StreamFieldPanel('intro_images'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('headline'), TranslatableField('intro_text'), TranslatableField('intro_images'), ] zen_nav = True def get_context(self, request): context = super().get_context(request) return set_main_site_nav_information(self, context, 'Homepage') template = 'wagtailpages/pages/youtube_regrets_reporter_page.html'
def test(self): translatable_fields = get_translatable_fields( TestOverrideTranslatableFieldsPage) self.assertEqual( translatable_fields, [ TranslatableField("title"), TranslatableField("slug"), TranslatableField("seo_title"), SynchronizedField("show_in_menus"), TranslatableField("search_description"), SynchronizedField("test_charfield"), # Overriden! SynchronizedField("test_charfield_with_choices"), TranslatableField("test_textfield"), TranslatableField("test_emailfield"), # Overriden! TranslatableField("test_slugfield"), SynchronizedField("test_urlfield"), TranslatableField("test_richtextfield"), TranslatableField("test_streamfield"), TranslatableField("test_snippet"), SynchronizedField("test_nontranslatablesnippet"), TranslatableField("test_customfield"), TranslatableField("test_translatable_childobjects"), SynchronizedField("test_nontranslatable_childobjects"), ], )
class YoutubeRegrets2021Page(FoundationMetadataPageMixin, Page): template = 'wagtailpages/pages/youtube-regrets-2021/youtube_regrets_2021.html' max_count = 1 zen_nav = True translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), ] def get_context(self, request): context = super().get_context(request) return set_main_site_nav_information(self, context, 'Homepage') class Meta: verbose_name = "YouTube Regrets 2021 Page" verbose_name_plural = "YouTube Regrets 2021 Pages"
class ContributorSupportPage(Page): template = 'pages/core/contributor_support_page.html' parent_page_types = ['core.LandingPage'] override_translatable_fields = [ SynchronizedField('slug'), ] # This page does not have subpages def get_context(self, request): ctx = super().get_context(request) ctx.update({ 'orgid': settings.SALESFORCE_ORGID, 'record_type_id': settings.SALESFORCE_CASE_RECORD_TYPE_ID, }) return ctx
class ContentPage(Page): template = 'pages/core/content_page.html' parent_page_types = ['core.LandingPage'] subpage_types = ['core.ContentPage'] call_to_action_text = models.CharField(max_length=255, blank=True) call_to_action_url = models.URLField(blank=True) body = StreamField(ContentBlock) content_panels = Page.content_panels + [ FieldPanel('call_to_action_text'), FieldPanel('call_to_action_url'), StreamFieldPanel('body'), ] override_translatable_fields = [ SynchronizedField('slug'), ]
class ContentPage(TranslatablePageMixin, Page): template = 'pages/core/content_page.html' parent_page_types = ['core.LandingPage'] subpage_types = ['core.ContentPage'] call_to_action_text = models.CharField(max_length=255, blank=True) call_to_action_url = models.URLField(blank=True) body = StreamField(ContentBlock) content_panels = Page.content_panels + [ FieldPanel('call_to_action_text'), FieldPanel('call_to_action_url'), StreamFieldPanel('body'), ] translatable_fields = [ TranslatableField('title'), TranslatableField('seo_title'), TranslatableField('search_description'), TranslatableField('call_to_action_text'), SynchronizedField('call_to_action_url'), TranslatableField('body'), ]
def test(self): translatable_fields = get_translatable_fields( TestGenerateTranslatableFieldsPage) self.assertEqual(translatable_fields, [ TranslatableField('title'), TranslatableField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), TranslatableField('test_charfield'), SynchronizedField('test_charfield_with_choices'), TranslatableField('test_textfield'), SynchronizedField('test_emailfield'), TranslatableField('test_slugfield'), SynchronizedField('test_urlfield'), TranslatableField('test_richtextfield'), TranslatableField('test_streamfield'), TranslatableField('test_snippet'), SynchronizedField('test_nontranslatablesnippet'), TranslatableField('test_customfield'), TranslatableField('test_translatable_childobjects'), SynchronizedField('test_nontranslatable_childobjects'), ])
class DonationPage(Page): PROJECT_MOZILLAFOUNDATION = 'mozillafoundation' PROJECT_THUNDERBIRD = 'thunderbird' PROJECT_CHOICES = ( (PROJECT_MOZILLAFOUNDATION, 'Mozilla Foundation'), (PROJECT_THUNDERBIRD, 'Thunderbird'), ) project = models.CharField( max_length=25, choices=PROJECT_CHOICES, default=PROJECT_MOZILLAFOUNDATION, help_text= 'The project that donations from this campaign should be associated with' ) campaign_id = models.CharField( max_length=255, blank=True, help_text='Used for analytics and reporting') settings_panels = Page.settings_panels + [ FieldPanel('campaign_id'), ] override_translatable_fields = [ SynchronizedField('slug'), SynchronizedField('campaign_id'), ] @cached_property def currencies(self): currencies = deepcopy(constants.CURRENCIES) # Re-order currency `single` and `monthly` amounts for currency in currencies: currencies[currency]['presets']['single'].sort() currencies[currency]['presets']['monthly'].sort() return currencies def get_initial_currency(self, request): # Query argument takes first preference if request.GET.get('currency') in constants.CURRENCIES: return request.GET['currency'] # Otherwise use the language code determined by Django return get_default_currency(getattr(request, 'LANGUAGE_CODE', '')) def serve(self, request, *args, **kwargs): response = super().serve(request, *args, **kwargs) if request.GET.get('subscribed') == '1': # Set a cookie that expires at the end of the session response.set_cookie('subscribed', '1', httponly=True) return response def get_initial_frequency(self, request): frequency = request.GET.get('frequency', '') return frequency if frequency in dict( constants.FREQUENCY_CHOICES) else constants.FREQUENCY_SINGLE def get_initial_currency_info(self, request, initial_currency, initial_frequency): initial_currency_info = self.currencies[initial_currency] # Check if presets have been specified in a query arg custom_presets = request.GET.get('presets', '').split(',') try: min_amount = initial_currency_info.get('minAmount', 0) custom_presets = [ amount for amount in [ Decimal(value).quantize(Decimal('0.01')) if float(value) >= min_amount else None for value in custom_presets ] if amount ] except (InvalidOperation, ValueError): return initial_currency_info if not custom_presets: return initial_currency_info if len(custom_presets) < 4: return initial_currency_info sorting = request.GET.get('sort', False) if sorting == 'reverse': custom_presets.sort(reverse=True) initial_currency_info['presets'][ initial_frequency] = custom_presets[:4] return initial_currency_info def default_initial_amount(self, initial_currency_info, initial_frequency): """ The default donation amount is the second lowest amount in the list of amounts. """ return sorted(initial_currency_info['presets'][initial_frequency])[1] def get_initial_amount(self, request, initial_currency_info, initial_frequency): """ When called with ?amount=..., that value will be preselected if: 1. it's a real number, and 2. that number can be found in the current list of possibles values. If not, the default initial amount is used """ amount = request.GET.get('amount', False) if amount is False or 'e' in amount: return self.default_initial_amount(initial_currency_info, initial_frequency) try: value = Decimal(amount).quantize(Decimal('0.01')) except InvalidOperation: return self.default_initial_amount(initial_currency_info, initial_frequency) if value in initial_currency_info['presets'][initial_frequency]: return value return self.default_initial_amount(initial_currency_info, initial_frequency) def get_initial_values(self, request): frequency = self.get_initial_frequency(request) currency = self.get_initial_currency(request) currency_info = self.get_initial_currency_info(request, currency, frequency) amount = self.get_initial_amount(request, currency_info, frequency) return { "frequency": frequency, "currency": currency, "currency_info": currency_info, "amount": amount, } def get_context(self, request): ctx = super().get_context(request) values = self.get_initial_values(request) ctx.update({ 'currencies': self.currencies, 'initial_currency_info': values['currency_info'], 'initial_frequency': values['frequency'], 'initial_amount': values['amount'], 'braintree_params': settings.BRAINTREE_PARAMS, 'braintree_form': BraintreePaypalPaymentForm( initial={ 'landing_url': request.build_absolute_uri(), 'project': self.project, 'campaign_id': self.campaign_id, }), 'currency_form': CurrencyForm(initial={'currency': values['currency']}), 'recaptcha_site_key': settings.RECAPTCHA_SITE_KEY if settings.RECAPTCHA_ENABLED else None, }) return ctx class Meta: abstract = True
class Petition(TranslatableMixin, CTA): campaign_id = models.CharField( max_length=20, help_text='Which campaign identifier should this petition be tied to?', null=True, blank=True, ) requires_country_code = models.BooleanField( default=False, help_text='Will this petition require users to specify their country?', ) requires_postal_code = models.BooleanField( default=False, help_text= 'Will this petition require users to specify their postal code?', ) COMMENT_CHOICES = ( ('none', 'No comments'), ('optional', 'Optional comments'), ('required', 'Required comments'), ) comment_requirements = models.CharField( choices=COMMENT_CHOICES, default='none', help_text='What is the comments policy for this petition?', max_length=8, ) checkbox_1 = models.CharField( editable=False, max_length=1024, help_text='label for the first checkbox option (may contain HTML)', blank=True, ) checkbox_2 = models.CharField( editable=False, max_length=1024, help_text='label for the second checkbox option (may contain HTML)', blank=True, ) share_link = models.URLField( max_length=1024, help_text='Link that will be put in share button', blank=True, editable=False, ) share_link_text = models.CharField( max_length=20, help_text='Text content of the share button', default='Share this', blank=True, editable=False, ) share_twitter = models.CharField( max_length=20, help_text= 'Share Progress id for twitter button, including the sp_... prefix', blank=True, ) share_facebook = models.CharField( max_length=20, help_text= 'Share Progress id for facebook button, including the sp_... prefix', blank=True, ) share_email = models.CharField( max_length=20, help_text= 'Share Progress id for email button, including the sp_... prefix', blank=True, ) thank_you = models.CharField( max_length=140, help_text='Message to show after thanking people for signing', default='Thank you for signing too!', ) translatable_fields = [ # This models fields SynchronizedField('requires_country_code'), SynchronizedField('requires_postal_code'), TranslatableField('comment_requirements'), TranslatableField('checkbox_1'), TranslatableField('checkbox_2'), SynchronizedField('share_twitter'), SynchronizedField('share_facebook'), SynchronizedField('share_email'), TranslatableField('thank_you'), # Fields from the CTA model TranslatableField('header'), TranslatableField('description'), ] class Meta(TranslatableMixin.Meta): verbose_name = 'petition snippet'
class PublicationPage(FoundationMetadataPageMixin, Page): """ This is the root page of a publication. From here the user can browse to the various sections (called chapters). It will have information on the publication, its authors, and metadata from it's children Publications are collections of Articles Publications can also be broken down into Chapters, which are really just child publication pages Each of those Chapters may have several Articles An Article can only belong to one Chapter/Publication Page """ subpage_types = ['ArticlePage', 'PublicationPage'] toc_thumbnail_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='toc_thumbnail_image', verbose_name='Table of Content Thumbnail', help_text= 'Thumbnail image to show on table of content. Use square image of 320×320 pixels or larger.', ) hero_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='publication_hero_image', verbose_name='Publication Hero Image', ) subtitle = models.CharField( blank=True, max_length=250, ) secondary_subtitle = models.CharField( blank=True, max_length=250, ) publication_date = models.DateField("Publication date", null=True, blank=True) publication_file = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) additional_author_copy = models.CharField( help_text="Example: with contributing authors", max_length=100, blank=True, ) intro_notes = RichTextField(blank=True, features=['link', 'bold', 'italic', 'h4']) notes = RichTextField( blank=True, features=['link', 'bold', 'italic', 'h4', 'ol', 'ul']) contents_title = models.CharField( blank=True, default="Table of Contents", max_length=250, ) content_panels = Page.content_panels + [ MultiFieldPanel( [ FieldPanel('subtitle'), FieldPanel('secondary_subtitle'), FieldPanel('publication_date'), ImageChooserPanel('toc_thumbnail_image'), ImageChooserPanel('hero_image'), DocumentChooserPanel('publication_file'), InlinePanel('authors', label='Author'), FieldPanel('additional_author_copy'), ], heading='Hero', ), FieldPanel('intro_notes'), FieldPanel('contents_title'), FieldPanel('notes'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField("title"), TranslatableField("subtitle"), TranslatableField('secondary_subtitle'), SynchronizedField('toc_thumbnail_image'), SynchronizedField('hero_image'), SynchronizedField('publication_date'), SynchronizedField('publication_file'), TranslatableField('additional_author_copy'), TranslatableField('intro_notes'), TranslatableField('contents_title'), TranslatableField('notes'), ] @property def is_publication_page(self): """ Returning true to let publication_hero.html to show breadcrumbs """ return True @property def is_chapter_page(self): """ A PublicationPage nested under a PublicationPage is considered to be a "ChapterPage". The templates used very similar logic and structure, and all the fields are the same. """ parent = self.get_parent().specific return parent.__class__ is PublicationPage @property def next_page(self): """ Only applies to Chapter Publication (sub-Publication Pages). Returns a Page object or None. """ next_page = self.get_parent() if self.is_chapter_page: sibling = self.get_siblings().filter(path__gt=self.path, live=True).first() if sibling: # If there is no more chapters. Return the parent page. next_page = sibling return next_page @property def prev_page(self): """ Only applies to Chapter Publication (sub-Publication Pages). Returns a Page object or None. """ prev_page = self.get_parent() if self.is_chapter_page: sibling = self.get_siblings().filter(path__lt=self.path, live=True).reverse().first() if sibling: # If there is no more chapters. Return the parent page. prev_page = sibling return prev_page @property def zen_nav(self): return True def breadcrumb_list(self): """ Get all the parent PublicationPages and return a QuerySet """ return Page.objects.ancestor_of(self).type(PublicationPage).live() def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) pages = [] for page in self.get_children(): if request.user.is_authenticated: # User is logged in, and can preview a page. Get all pages, even drafts. pages.append({ 'child': page, 'grandchildren': page.get_children() }) elif page.live: # User is not logged in AND this page is live. Only fetch live grandchild pages. pages.append({ 'child': page, 'grandchildren': page.get_children().live() }) context['child_pages'] = pages return set_main_site_nav_information(self, context, 'Homepage')
class BanneredCampaignPage(PrimaryPage): """ title, header, intro, and body are inherited from PrimaryPage """ # Note that this is a different related_name, as the `page` # name is already taken as back-referenced to CampaignPage. cta = models.ForeignKey( 'Petition', related_name='bcpage', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose an existing, or create a new, pettition form') signup = models.ForeignKey( 'Signup', related_name='bcpage', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose an existing, or create a new, sign-up form') tags = ClusterTaggableManager(through=BanneredCampaignTag, blank=True) panel_count = len(PrimaryPage.content_panels) n = panel_count - 1 content_panels = PrimaryPage.content_panels[:n] + [ SnippetChooserPanel('cta'), SnippetChooserPanel('signup'), ] + PrimaryPage.content_panels[n:] promote_panels = FoundationMetadataPageMixin.promote_panels + [ FieldPanel('tags'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('header'), TranslatableField('intro'), TranslatableField('body'), TranslatableField("title"), SynchronizedField("banner"), SynchronizedField("narrowed_page_content"), SynchronizedField("zen_nav"), TranslatableField("cta"), TranslatableField("signup"), ] subpage_types = [ 'BanneredCampaignPage', 'RedirectingPage', 'PublicationPage', 'OpportunityPage', 'ArticlePage' ] show_in_menus_default = True def get_context(self, request): context = super().get_context(request) context['related_posts'] = get_content_related_by_tag(self) return get_page_tree_information(self, context) class Meta: verbose_name = "Banner Page" verbose_name_plural = "Banner pages"
class ArticlePage(FoundationMetadataPageMixin, Page): """ Articles can belong to any page in the Wagtail Tree. An ArticlePage can have no children If not a child of a Publication Page, page nav at bottom of page and breadcrumbs will not render. """ subpage_types = [] body = StreamField(article_fields) toc_thumbnail_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Table of Content Thumbnail', help_text='Thumbnail image to show on table of content. Use square image of 320×320 pixels or larger.', ) hero_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Publication Hero Image', ) subtitle = models.CharField( blank=True, max_length=250, ) secondary_subtitle = models.CharField( blank=True, max_length=250, ) publication_date = models.DateField("Publication date", null=True, blank=True) article_file = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) show_side_share_buttons = models.BooleanField( default=True, help_text="Show social share buttons on the side" ) content_panels = [ FieldPanel( "title", classname="full title", widget=TitleWidget(attrs={"class": "max-length-warning", "data-max-length": 60}) ), MultiFieldPanel([ InlinePanel("authors", label="Author", min_num=0) ], heading="Author(s)"), MultiFieldPanel([ ImageChooserPanel("toc_thumbnail_image"), ], heading="Table of Content Thumbnail"), MultiFieldPanel([ ImageChooserPanel("hero_image"), FieldPanel('subtitle'), FieldPanel('secondary_subtitle'), FieldPanel('publication_date'), DocumentChooserPanel('article_file'), ], heading="Hero"), FieldPanel('show_side_share_buttons'), StreamFieldPanel('body'), InlinePanel("footnotes", label="Footnotes"), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), SynchronizedField('toc_thumbnail_image'), SynchronizedField('hero_image'), TranslatableField('subtitle'), SynchronizedField('article_file'), TranslatableField('body'), TranslatableField('footnotes'), ] @property def is_publication_article(self): parent = self.get_parent().specific return parent.__class__ is PublicationPage @property def next_page(self): """ Get the next page for a publication. Details below: Check the parent page type. If the parent page type is a "Chapter Page", then look for siblings of `this` page. If no next sibling can be found look for the parent page next sibling. And if that cannot be found, return the Chapter Page's parent (Publication Page). Otherwise if the parent page is a Publication page: look for the next sibling, if there is no next sibling page, return this pages' parent. """ parent = self.get_parent().specific next_page = self.get_siblings().filter(path__gt=self.path, live=True).first() if parent.is_chapter_page: # if there is no next page look for the next chapter if not next_page: next_page = parent.get_siblings().filter(path__gt=self.path, live=True).first() # if there is no next chapter return to the parent.get_parent() if not next_page: next_page = parent.get_parent() else: # Parent is a PublicationPage, not a chapter page # if there is no next page, return the parent if not next_page: next_page = parent return next_page @property def prev_page(self): """ Get the previous page for a publication. Details below: Check the parent page type. If the parent page type is a "Chapter Page", then look for siblings of `this` page. If no previous sibling can be found look for the parent page previous sibling. And if that cannot be found, return the Chapter Page's parent (Publication Page). Otherwise if the parent page is a Publication page: look for the previous sibling, if there is no previous sibling page, return this pages' parent. """ parent = self.get_parent().specific prev_page = self.get_siblings().filter(path__lt=self.path, live=True).reverse().first() if parent.is_chapter_page: # look for the previous page in this chapter # if there is no previous page look for the previous chapter if not prev_page: prev_page = parent.get_siblings().filter(path__lt=self.path, live=True).reverse().first() # if there is no previous chapter return to the parent.get_parent() if not prev_page: prev_page = parent.get_parent() else: # Parent is a PublicationPage, not a chapter page # look for the previous page in this publication # if there is no previous page, return the parent if not prev_page: prev_page = parent return prev_page def breadcrumb_list(self): """ Get all the parent PublicationPages and return a QuerySet """ return Page.objects.ancestor_of(self).type(PublicationPage).live() @property def zen_nav(self): return True def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) # Add get_titles to the page context. This is in get_context() because # we need access to the `request` object # menu_items is required for zen_nav in the templates context['get_titles'] = get_plaintext_titles(request, self.body, "content") return set_main_site_nav_information(self, context, 'Homepage')
class YoutubeRegretsPage(FoundationMetadataPageMixin, Page): headline = models.CharField( max_length=500, help_text='Page headline', blank=True, ) intro_text = StreamField([ ('text', blocks.CharBlock()), ]) intro_images = StreamField([ ('image', customblocks.ImageBlock()), ]) faq = StreamField( [('paragraph', blocks.RichTextBlock(features=[ 'bold', 'italic', 'h2', 'h3', 'h4', 'h5', 'ol', 'ul', 'link', 'hr', ]))], blank=True, ) regret_stories = StreamField([ ('regret_story', customblocks.YoutubeRegretBlock()), ]) content_panels = Page.content_panels + [ FieldPanel('headline'), StreamFieldPanel('intro_text'), StreamFieldPanel('intro_images'), StreamFieldPanel('faq'), StreamFieldPanel('regret_stories'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('headline'), TranslatableField('intro_text'), TranslatableField('intro_images'), TranslatableField('faq'), TranslatableField('regret_stories'), ] zen_nav = True def get_context(self, request): context = super().get_context(request) return set_main_site_nav_information(self, context, 'Homepage') template = 'wagtailpages/pages/youtube_regrets_page.html'
class DearInternetPage(FoundationMetadataPageMixin, Page): intro_texts = StreamField([('intro_text', blocks.RichTextBlock(features=[ 'bold', 'italic', 'link', ]))], ) letters_section_heading = models.CharField( max_length=300, default='Stories from around the world', ) letters = StreamField([ ('letter', customblocks.DearInternetLetterBlock()), ]) cta = models.CharField(max_length=500, ) cta_button_text = models.CharField(max_length=100, ) cta_button_link = models.URLField() content_panels = Page.content_panels + [ StreamFieldPanel('intro_texts'), FieldPanel('letters_section_heading'), StreamFieldPanel('letters'), MultiFieldPanel( [ FieldPanel('cta'), FieldPanel('cta_button_text'), FieldPanel('cta_button_link'), ], heading='CTA', ), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('intro_texts'), TranslatableField('letters_section_heading'), TranslatableField('letters'), TranslatableField('cta'), TranslatableField('cta_button_text'), SynchronizedField('cta_button_link'), ] zen_nav = True def get_context(self, request): context = super().get_context(request) return set_main_site_nav_information(self, context, 'Homepage') template = 'wagtailpages/pages/dear_internet_page.html'
class MozfestHomepage(MozfestPrimaryPage): """ MozFest Homepage 'banner_video_type' determines what version of banner design the page should load """ # this tells the templates to load a hardcoded, pre-defined video in the banner background banner_video_type = "hardcoded" cta_button_label = models.CharField( max_length=250, blank=True, help_text='Label text for the CTA button in the primary nav bar', ) cta_button_destination = models.CharField( max_length=2048, blank=True, help_text='The URL for the page that the CTA button in the primary nav bar should redirect to.' 'E.g., /proposals, https://example.com/external-link', ) banner_heading = models.CharField( max_length=250, blank=True, help_text='A banner heading specific to the homepage' ) banner_guide_text = models.CharField( max_length=1000, blank=True, help_text='A banner paragraph specific to the homepage' ) banner_video_url = models.URLField( max_length=2048, blank=True, help_text='The video to play when users click "watch video"' ) subpage_types = [ 'MozfestPrimaryPage', 'MozfestHomepage', ] # Put everything above the body parent_panels = MozfestPrimaryPage.content_panels panel_count = len(parent_panels) n = panel_count - 1 all_panels = parent_panels[:n] + [ FieldPanel('cta_button_label'), FieldPanel('cta_button_destination'), FieldPanel('banner_heading'), FieldPanel('banner_guide_text'), FieldPanel('banner_video_url'), ] + parent_panels[n:] if banner_video_type == "hardcoded": # Hide all the panels that aren't relevant for the video banner version of the MozFest Homepage content_panels = [ field for field in all_panels if field.field_name not in ['banner', 'header', 'intro', 'banner_guide_text', 'banner_video_url'] ] else: content_panels = all_panels # Because we inherit from PrimaryPage, but the "use_wide_template" property does nothing # we should hide it and make sure we use the right template settings_panels = Page.settings_panels translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('cta_button_label'), SynchronizedField('cta_button_destination'), TranslatableField('banner_heading'), TranslatableField('banner_guide_text'), SynchronizedField('banner_video_url'), TranslatableField('title'), TranslatableField('search_description'), TranslatableField('search_image'), TranslatableField('signup'), TranslatableField('body'), TranslatableField('footnotes'), ] def get_context(self, request): context = super().get_context(request) context['banner_video_type'] = self.specific.banner_video_type return context def get_template(self, request): return 'mozfest/mozfest_homepage.html'
class ModularPage(FoundationMetadataPageMixin, Page): """ This base class offers universal component picking. Note: this is a legacy class, see https://github.com/mozilla/foundation.mozilla.org/issues/5071#issuecomment-675720719 """ header = models.CharField( max_length=250, blank=True ) narrowed_page_content = models.BooleanField( default=False, help_text='For text-heavy pages, turn this on to reduce the overall width of the content on the page.' ) zen_nav = models.BooleanField( default=True, help_text='For secondary nav pages, use this to collapse the primary nav under a toggle hamburger.' ) body = StreamField(base_fields) settings_panels = Page.settings_panels + [ MultiFieldPanel( [ FieldPanel('narrowed_page_content'), ], classname="collapsible" ), MultiFieldPanel( [ FieldPanel('zen_nav'), ], classname="collapsible" ) ] content_panels = Page.content_panels + [ FieldPanel('header'), StreamFieldPanel('body'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('header'), SynchronizedField('narrowed_page_content'), SynchronizedField('zen_nav'), ] show_in_menus_default = True def get_context(self, request): context = super().get_context(request) return set_main_site_nav_information(self, context, 'Homepage')
class TestPage(Page): test_charfield = models.CharField(gettext_lazy("char field"), max_length=255, blank=True, null=True, default='') test_textfield = models.TextField(blank=True) test_emailfield = models.EmailField(blank=True) test_slugfield = models.SlugField(blank=True) test_urlfield = models.URLField(blank=True) test_richtextfield = RichTextField(blank=True) test_streamfield = StreamField(TestStreamBlock, blank=True) test_snippet = models.ForeignKey(TestSnippet, null=True, blank=True, on_delete=models.SET_NULL) test_customfield = TestCustomField(blank=True) test_synchronized_charfield = models.CharField(max_length=255, blank=True) test_synchronized_textfield = models.TextField(blank=True) test_synchronized_emailfield = models.EmailField(blank=True) test_synchronized_slugfield = models.SlugField(blank=True) test_synchronized_urlfield = models.URLField(blank=True) test_synchronized_richtextfield = RichTextField(blank=True) test_synchronized_streamfield = StreamField(TestStreamBlock, blank=True) test_synchronized_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_synchronized_document = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_synchronized_snippet = models.ForeignKey(TestSnippet, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_page = models.ForeignKey(Page, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_page_specific_type = models.ForeignKey('TestHomePage', null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_page_with_restricted_types = models.ForeignKey( Page, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") test_synchronized_customfield = TestCustomField(blank=True) translatable_fields = [ TranslatableField("test_charfield"), TranslatableField("test_textfield"), TranslatableField("test_emailfield"), TranslatableField("test_slugfield"), TranslatableField("test_urlfield"), TranslatableField("test_richtextfield"), TranslatableField("test_streamfield"), TranslatableField("test_snippet"), TranslatableField("test_childobjects"), TranslatableField("test_customfield"), SynchronizedField("test_synchronized_charfield"), SynchronizedField("test_synchronized_textfield"), SynchronizedField("test_synchronized_emailfield"), SynchronizedField("test_synchronized_slugfield"), SynchronizedField("test_synchronized_urlfield"), SynchronizedField("test_synchronized_richtextfield"), SynchronizedField("test_synchronized_streamfield"), SynchronizedField("test_synchronized_image"), SynchronizedField("test_synchronized_document"), SynchronizedField("test_synchronized_snippet"), SynchronizedField("test_synchronized_childobjects"), SynchronizedField('test_page'), SynchronizedField('test_page_specific_type'), SynchronizedField('test_page_with_restricted_types'), SynchronizedField("test_synchronized_customfield"), ] content_panels = Page.content_panels + [ FieldPanel("test_charfield"), FieldPanel("test_textfield"), FieldPanel("test_emailfield"), FieldPanel("test_slugfield"), FieldPanel("test_urlfield"), FieldPanel("test_richtextfield"), FieldPanel("test_streamfield"), FieldPanel("test_snippet"), InlinePanel("test_childobjects"), FieldPanel("test_customfield"), FieldPanel("test_synchronized_charfield"), FieldPanel("test_synchronized_textfield"), FieldPanel("test_synchronized_emailfield"), FieldPanel("test_synchronized_slugfield"), FieldPanel("test_synchronized_urlfield"), FieldPanel("test_synchronized_richtextfield"), FieldPanel("test_synchronized_streamfield"), FieldPanel("test_synchronized_image"), FieldPanel("test_synchronized_document"), FieldPanel("test_synchronized_snippet"), InlinePanel("test_synchronized_childobjects"), PageChooserPanel('test_page'), PageChooserPanel('test_page_specific_type'), PageChooserPanel('test_page_with_restricted_types', [ 'wagtail_localize_test.TestHomePage', 'wagtail_localize_test.TestPage' ]), FieldPanel("test_synchronized_customfield"), ]
class TestOverrideTranslatableFieldsPage(TestGenerateTranslatableFieldsPage): override_translatable_fields = [ SynchronizedField('test_charfield'), TranslatableField('test_emailfield'), ]
class PrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanceMixin, Page): """ Basically a straight copy of modular page, but with restrictions on what can live 'under it'. Ideally this is just PrimaryPage(ModularPage) but setting that up as a migration seems to be causing problems. """ header = models.CharField(max_length=250, blank=True) banner = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='primary_banner', verbose_name='Hero Image', help_text= 'Choose an image that\'s bigger than 4032px x 1152px with aspect ratio 3.5:1', ) intro = models.CharField( max_length=350, blank=True, help_text='Intro paragraph to show in hero cutout box') narrowed_page_content = models.BooleanField( default=False, help_text= 'For text-heavy pages, turn this on to reduce the overall width of the content on the page.' ) zen_nav = models.BooleanField( default=False, help_text= 'For secondary nav pages, use this to collapse the primary nav under a toggle hamburger.' ) body = StreamField(base_fields) settings_panels = Page.settings_panels + [ MultiFieldPanel([ FieldPanel('narrowed_page_content'), ], classname="collapsible"), MultiFieldPanel([ FieldPanel('zen_nav'), ], classname="collapsible") ] content_panels = Page.content_panels + [ FieldPanel('header'), ImageChooserPanel('banner'), FieldPanel('intro'), StreamFieldPanel('body'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('title'), TranslatableField('header'), SynchronizedField('banner'), TranslatableField('intro'), TranslatableField('body'), SynchronizedField('narrowed_page_content'), SynchronizedField('zen_nav'), ] subpage_types = [ 'PrimaryPage', 'RedirectingPage', 'BanneredCampaignPage', 'OpportunityPage', 'ArticlePage' ] show_in_menus_default = True def get_context(self, request): context = super().get_context(request) context = set_main_site_nav_information(self, context, 'Homepage') context = get_page_tree_information(self, context) return context