class General(TypesetBodyMixin, HeroMixin, SectionBodyMixin, Page): """ General page that that can be used with strategy and accounts pages """ template = 'general/general_page.html' other_pages_heading = models.CharField(blank=True, max_length=255, verbose_name='Heading', default='More about') show_breadcrumbs = models.BooleanField(default=False) content_panels = Page.content_panels + [ hero_panels(), FieldPanel('show_breadcrumbs'), StreamFieldPanel('body'), StreamFieldPanel('sections'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('other_pages', label='Related pages') ], heading='Other Pages/Related Links'), InlinePanel('page_notifications', label='Notifications') ] class Meta: verbose_name = 'General Page'
class SpotlightPage(HeroMixin, Page): class Meta(): verbose_name = 'Spotlight Page' parent_page_types = ['datasection.DataSectionPage'] subpage_types = ['spotlight.SpotlightLocationComparisonPage', 'spotlight.SpotlightTheme', 'general.General'] country_code = models.CharField(max_length=100, help_text='e.g. UG, KE', default='') country_name = models.CharField(max_length=255) currency_code = models.CharField(max_length=100, help_text='UGX, KES', default='') datasources_description = models.TextField( help_text='A description for data sources section', null=True, blank=True, verbose_name='Description') datasources_links = StreamField([('link', LinkBlock()), ], null=True, blank=True, verbose_name='Links') content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('country_code'), FieldPanel('country_name'), FieldPanel('currency_code') ], heading='Settings'), MultiFieldPanel([ FieldPanel('datasources_description'), StreamFieldPanel('datasources_links') ], heading='Data Sources Section') ] def serve(self, request, *args, **kwargs): return redirect(self.url)
class CountrySpotlight(TypesetBodyMixin, HeroMixin, Page): content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body') ] parent_page_types = ['datasection.DataSectionPage'] subpage_types = ['spotlight.SpotlightPage', 'general.General'] class Meta(): verbose_name = 'Country Spotlights Page' def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['spotlights'] = self.get_children().specific().type(SpotlightPage).live() return context class Meta(): verbose_name = 'Country Spotlight' def serve(self, request, *args, **kwargs): return redirect(self.get_parent().url)
class VacancyIndexPage(HeroMixin, Page): """ Shows a list of available vacancies """ class Meta: db_table = 'vacancies_page' verbose_name = 'Vacancy Index Page' parent_page_types = ['workforus.WorkForUsPage'] subpage_types = ['VacancyPage', 'general.General'] def get_context(self, request): context = super().get_context(request) context['vacancies'] = VacancyPage.objects.live() return context email_alert_confirmation_label = RichTextField( blank=True, verbose_name='Descriptive Text for Consent to Email Alerts', default= 'By checking this box you consent to us collecting the data submitted', features=RICHTEXT_FEATURES_NO_FOOTNOTES) content_panels = Page.content_panels + [ hero_panels(), FieldPanel('email_alert_confirmation_label'), InlinePanel('page_notifications', label='Notifications') ]
class EventIndexPage(TypesetBodyMixin, HeroMixin, Page): """ List of all events that have been created from events page """ content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body'), InlinePanel('page_notifications', label='Notifications') ] def get_context(self, request): context = super(EventIndexPage, self).get_context(request) page = request.GET.get('page', None) events = EventPage.objects.live().order_by('-start_date') paginator = Paginator(events, MAX_PAGE_SIZE) try: context['events'] = paginator.page(page) except PageNotAnInteger: context['events'] = paginator.page(1) except EmptyPage: context['events'] = paginator.page(paginator.num_pages) context['paginator_range'] = get_paginator_range( paginator, context['events']) return context class Meta: verbose_name = "Event Index Page" subpage_types = ['EventPage', 'general.General']
class StandardPage(SectionBodyMixin, TypesetBodyMixin, HeroMixin, Page): """ A generic content page. It could be used for any type of page content that only needs a hero, streamfield content, and related fields """ other_pages_heading = models.CharField( blank=True, max_length=255, verbose_name='Heading', default='Related content' ) content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body'), StreamFieldPanel('sections'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('other_pages', label='Related links') ], heading='Other Pages/Related Links'), InlinePanel('page_notifications', label='Notifications') ] def get_context(self, request): context = super().get_context(request) context['related_pages'] = get_related_pages(self, self.other_pages.all()) return context class Meta(): verbose_name = 'Standard Page'
class EventPage(TypesetBodyMixin, HeroMixin, Page): """ Content of each event """ start_date = models.DateField(default=datetime.now) end_date = models.DateField(default=datetime.now) time = models.CharField(max_length=160, help_text='Time information for event', blank=True, null=True) location = models.CharField(max_length=100, help_text='Physical location of event', blank=True, null=True) registration_link = models.URLField(blank=True, null=True) raw_content = models.TextField(null=True, blank=True) other_pages_heading = models.CharField( blank=True, max_length=255, verbose_name='Section Heading', default='Related content', ) content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('start_date'), FieldPanel('end_date'), FieldPanel('time'), FieldPanel('location'), ], heading='Event Details'), FieldPanel('registration_link'), StreamFieldPanel('body'), FieldPanel('raw_content'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('event_related_links', label='Related Pages', max_num=MAX_RELATED_LINKS) ], heading='Other Pages'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['general.General'] parent_page_types = ['EventIndexPage'] def get_context(self, request): context = super().get_context(request) context['related_pages'] = get_related_pages( self, self.event_related_links.all(), EventPage.objects) return context class Meta: verbose_name = "Event Page"
class WhatWeDoPage(TypesetBodyMixin, HeroMixin, Page): """ http://development-initiatives.surge.sh/page-templates/07-what-we-do """ class Meta: verbose_name = 'What We Do Page' sections = StreamField([('locations_map', LocationsMapBlock()), ('focus_area', FocusAreasBlock()), ('expertise', ExpertiseBlock()), ('banner', BannerBlock()), ('video_duo', VideoDuoTextBlock()), ('image_duo', ImageDuoTextBlock()), ('testimonial', TestimonialBlock())], verbose_name="Sections", null=True, blank=True) other_pages_heading = models.CharField(blank=True, max_length=255, verbose_name='Heading', default='More about') locations_where_we_work = StreamField( MapStreamBlock, verbose_name="Add Where We Work Locations", null=True, blank=True) content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body'), StreamFieldPanel('sections'), StreamFieldPanel('locations_where_we_work'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel( 'other_pages', label='Related pages', max_num=MAX_OTHER_PAGES) ], heading='Other Pages/Related Links'), InlinePanel('page_notifications', label='Notifications'), ] subpage_types = [ 'project.FocusAreasPage', 'whatwedo.ServicesPage', 'news.NewsIndexPage', 'events.EventIndexPage', 'place.PlacesPage', 'general.General' ] parent_page_types = ['home.HomePage']
class NewsIndexPage(HeroMixin, Page): intro = RichTextField(blank=True, null=True, help_text="Something about our newsletters", features=RICHTEXT_FEATURES_NO_FOOTNOTES) content_panels = Page.content_panels + [ hero_panels(), FieldPanel('intro'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['NewsStoryPage', 'general.General'] parent_page_types = ['whatwedo.WhatWeDoPage'] class Meta(): verbose_name = 'News Index Page' def get_context(self, request): context = super(NewsIndexPage, self).get_context(request) page = request.GET.get('page', None) topic_filter = request.GET.get('topic', None) if topic_filter: news_stories = NewsStoryPage.objects.live().filter( topics__slug=topic_filter).order_by('-published_date') else: news_stories = NewsStoryPage.objects.live().order_by( '-published_date') paginator = Paginator(news_stories, MAX_PAGE_SIZE) try: context['stories'] = paginator.page(page) except PageNotAnInteger: context['stories'] = paginator.page(1) except EmptyPage: context['stories'] = paginator.page(paginator.num_pages) news_content_type = ContentType.objects.get_for_model(NewsStoryPage) context['topics'] = Tag.objects.filter( news_newstopic_items__content_object__content_type=news_content_type ).distinct() context['selected_topic'] = topic_filter context['paginator_range'] = get_paginator_range( paginator, context['stories']) context['newsletter'] = NewsLetter.objects.first() return context
class BlogIndexPage(HeroMixin, Page): subpage_types = ['general.General', 'BlogArticlePage'] parent_page_types = ['home.HomePage'] class Meta(): verbose_name = 'Blog Index Page' def get_context(self, request): context = super(BlogIndexPage, self).get_context(request) page = request.GET.get('page', None) topic_filter = request.GET.get('topic', None) if topic_filter: articles = BlogArticlePage.objects.live().filter(topics__slug=topic_filter).order_by('-published_date') else: articles = BlogArticlePage.objects.live().order_by('-published_date') if not request.user.is_authenticated: articles = articles.public() paginator = Paginator(articles, MAX_PAGE_SIZE) try: context['articles'] = paginator.page(page) except PageNotAnInteger: context['articles'] = paginator.page(1) except EmptyPage: context['articles'] = paginator.page(paginator.num_pages) blog_content_type = ContentType.objects.get_for_model(BlogArticlePage) context['topics'] = Tag.objects.filter( blog_blogtopic_items__content_object__content_type=blog_content_type ).distinct().order_by('name') context['selected_topic'] = topic_filter context['paginator_range'] = get_paginator_range(paginator, context['articles']) return context content_panels = Page.content_panels + [ hero_panels(), InlinePanel('page_notifications', label='Notifications') ]
class MediaCenterPage(TypesetBodyMixin, HeroMixin, Page): contact_box = StreamField([('contact', ContactBlock())], null=True, blank=True) spokespeople = StreamField([('person', PersonBlock())], null=True, blank=True) def get_context(self, request): context = super(MediaCenterPage, self).get_context(request) page = request.GET.get('page', None) news_stories = NewsStoryPage.objects.live().filter( press_release=True).order_by('-published_date') paginator = Paginator(news_stories, MAX_PAGE_SIZE) try: context['stories'] = paginator.page(page) except PageNotAnInteger: context['stories'] = paginator.page(1) except EmptyPage: context['stories'] = paginator.page(paginator.num_pages) context['paginator_range'] = get_paginator_range( paginator, context['stories']) return context content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('contact_box'), StreamFieldPanel('body'), StreamFieldPanel('spokespeople'), InlinePanel('page_notifications', label='Notifications') ] class Meta(): verbose_name = 'Media Center Page' subpage_types = ['general.General'] parent_page_types = ['home.HomePage']
class OurTeamPage(HeroMixin, Page): """ List of Team Members Page """ other_pages_heading = models.CharField( blank=True, max_length=255, verbose_name='Heading', default='More about' ) def get_context(self, request): context = super(OurTeamPage, self).get_context(request) team_filter = request.GET.get('team-filter', None) if team_filter: context['profiles'] = TeamMemberPage.objects.live().filter(teammember_departments__department__slug=team_filter).order_by('name') else: context['profiles'] = TeamMemberPage.objects.live().order_by('name') context['departments'] = Department.objects.all() context['selected_team'] = team_filter return context class Meta: verbose_name = "Our Team Page" subpage_types = ['TeamMemberPage', 'general.General'] parent_page_types = ['about.WhoWeArePage'] content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('other_pages', label='Related pages') ], heading='Other Pages/Related Links'), InlinePanel('page_notifications', label='Notifications') ]
class VacancyPage(TypesetBodyMixin, SectionBodyMixin, HeroMixin, Page): vacancy = models.ForeignKey('users.JobTitle', null=True, on_delete=models.SET_NULL, related_name='+') duration = models.CharField(null=True, max_length=255) location = models.ForeignKey(OfficeLocation, null=True, on_delete=models.SET_NULL, related_name='+') salary_scale = models.CharField(blank=True, max_length=255) application_close = models.DateField(blank=True, null=True, auto_now=False, auto_now_add=False) first_interview = models.CharField(blank=True, null=True, max_length=255) second_interview = models.CharField(blank=True, null=True, max_length=255) job_start = models.DateField(blank=True, null=True, auto_now=False, auto_now_add=False) other_pages_heading = models.CharField( blank=True, max_length=255, verbose_name='Heading', default='Learn more about Development Initiatives') downloads_title = models.CharField( blank=True, max_length=255, default='Apply for this position', verbose_name='Title', help_text='Title for the downloads section on a vacancy page') downloads_description = RichTextField( blank=True, verbose_name='Description', help_text='Optional: a brief description of what to do in this section', features=RICHTEXT_FEATURES_NO_FOOTNOTES) content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('vacancy'), FieldPanel('duration'), FieldPanel('location'), FieldPanel('salary_scale') ], heading='Vacancy Info'), MultiFieldPanel([ FieldPanel('application_close'), FieldPanel('first_interview'), FieldPanel('second_interview'), FieldPanel('job_start') ], heading='Dates'), StreamFieldPanel('body'), MultiFieldPanel([ FieldPanel('downloads_title'), FieldPanel('downloads_description'), InlinePanel('page_downloads', label='Download', max_num=None) ], heading='Downloads'), StreamFieldPanel('sections'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('other_pages', label='Other Pages') ], heading='Other Pages'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['general.General'] parent_page_types = ['VacancyIndexPage'] @cached_property def get_page_downloads(self): return self.page_downloads.all() class Meta(): db_table = 'vacancy_page' verbose_name = 'Vacancy Page'
class ContactPage(TypesetBodyMixin, HeroMixin, Page): """ Form with pre-built form fields to handle contact us info """ template = 'contactus/contact_page.html' landing_template = 'contactus/contact_page_landing.html' success_alert = models.CharField( max_length=255, default='Your message was sent successfully', ) content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body'), FieldPanel('success_alert', classname="full"), InlinePanel('page_notifications', label='Notifications') ] class Meta(): verbose_name = 'Contact Us Page' subpage_types = ['general.General'] parent_page_types = ['home.HomePage'] def render_landing_page( self, request, form_submission=None, *args, **kwargs): """ Renders the landing page as used in wagtails default form implementation """ context = self.get_context(request) context['form_submission'] = form_submission return render( request, self.landing_template, context ) def generate_hubspot_object(self, form_object): today = date.today() subject = today.strftime("%Y%m%d") hubspot_payload = [] kv_p = {} kv_p['name'] = 'subject' kv_p['value'] = HS_TICKET_PREFIX + subject hubspot_payload.append(kv_p) kv_p2 = {} kv_p2['name'] = 'hs_pipeline' kv_p2['value'] = settings.HS_TICKET_PIPELINE hubspot_payload.append(kv_p2) kv_p3 = {} kv_p3['name'] = 'hs_pipeline_stage' kv_p3['value'] = settings.HS_TICKET_PIPELINE_STAGE hubspot_payload.append(kv_p3) kv_p4 = {} kv_p4['name'] = 'client_first_name' kv_p4['value'] = form_object.get('name') hubspot_payload.append(kv_p4) kv_p5 = {} kv_p5['name'] = 'organisation' kv_p5['value'] = form_object.get('organisation') hubspot_payload.append(kv_p5) kv_p6 = {} kv_p6['name'] = 'email_address' kv_p6['value'] = form_object.get('email_address') hubspot_payload.append(kv_p6) kv_p7 = {} kv_p7['name'] = 'contact_details' kv_p7['value'] = form_object.get('telephone') hubspot_payload.append(kv_p7) kv_p8 = {} kv_p8['name'] = 'content' kv_p8['value'] = form_object.get('message') hubspot_payload.append(kv_p8) return hubspot_payload def serve(self, request, *args, **kwargs): if request.method == 'POST': form = ContactUsForm(request.POST) """ Check if hidden field has been filled by robots, if its filled; then its spam, return the default form page to be refilled """ try: if request.POST.get(HONEYPOT_FORM_FIELD, '') != '': context = self.get_context(request) context['form'] = form return render( request, self.get_template(request), context ) except KeyError: # If honeypot fails, form should be marked as possible spam pass if form.is_valid(): form_submission = form.save() hs_obj = self.generate_hubspot_object(request.POST) create_new_ticket(hs_obj) return self.render_landing_page( request, form_submission, *args, **kwargs) else: context = self.get_context(request) context['form'] = form return render( request, self.get_template(request), context ) else: form = ContactUsForm() context = self.get_context(request) context['form'] = form return render( request, self.get_template(request), context )
class LegacyPublicationPage(HeroMixin, PublishedDateMixin, ParentPageSearchMixin, FilteredDatasetMixin, CallToActionMixin, ReportDownloadMixin, Page): class Meta: verbose_name = 'Legacy Publication' parent_page_types = ['PublicationIndexPage'] subpage_types = [] colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) authors = StreamField( [('internal_author', PageChooserBlock(required=False, target_model='ourteam.TeamMemberPage', icon='fa-user', label='Internal Author')), ('external_author', StructBlock([('name', CharBlock(required=False)), ('title', CharBlock(required=False)), ('photograph', ImageChooserBlock(required=False)), ('page', URLBlock(required=False))], icon='fa-user', label='External Author'))], blank=True) publication_type = models.ForeignKey(PublicationType, related_name="+", null=True, blank=False, on_delete=models.SET_NULL, verbose_name="Resource Type") topics = ClusterTaggableManager(through=LegacyPublicationTopic, blank=True, verbose_name="Topics") raw_content = models.TextField(null=True, blank=True) content = RichTextField(help_text='Content for the legacy report', null=True, blank=True, features=RICHTEXT_FEATURES) summary_image = WagtailImageField( required=False, help_text='Optimal minimum size 800x400px', ) content_panels = Page.content_panels + [ FieldPanel('colour'), hero_panels(), StreamFieldPanel('authors'), call_to_action_panel(), SnippetChooserPanel('publication_type'), FieldPanel('topics'), InlinePanel('page_countries', label="Countries"), PublishedDatePanel(), InlinePanel('publication_datasets', label='Datasets'), DownloadsPanel(heading='Reports', description='Report downloads for this legacy report.'), DownloadsPanel( related_name='data_downloads', heading='Data downloads', description='Optional: data download for this legacy report.'), ReportDownloadPanel(), MultiFieldPanel([ FieldPanel('content'), FieldPanel('raw_content'), ], heading='Summary', description='Summary for the legacy publication.'), InlinePanel('page_notifications', label='Notifications'), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), ] @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) @cached_property def page_publication_downloads(self): return self.publication_downloads.all() @cached_property def page_data_downloads(self): return self.data_downloads.all() def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.publication_related_links.all(), LegacyPublicationPage.objects) return context
class PublicationAppendixPage(HeroMixin, ReportChildMixin, FlexibleContentMixin, PageSearchMixin, UUIDMixin, FilteredDatasetMixin, ReportDownloadMixin, Page): class Meta: verbose_name = 'Publication Appendix' verbose_name_plural = 'Publication Appendices' parent_page_types = ['PublicationPage'] subpage_types = [] appendix_number = models.PositiveIntegerField( choices=[(i, num2words(i).title()) for i in range(1, 21)]) colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) content_panels = Page.content_panels + [ FieldPanel('colour'), hero_panels(), MultiFieldPanel( [ FieldPanel('appendix_number', widget=forms.Select), ], heading='Appendix number', description= 'Appendix number: this should be unique for each appendix of a report.' ), ContentPanel(), InlinePanel('publication_datasets', label='Datasets'), DownloadsPanel(heading='Downloads', description='Downloads for this appendix page.'), DownloadsPanel( related_name='data_downloads', heading='Data downloads', description='Optional: data download for this appendix page.'), ReportDownloadPanel(), InlinePanel('page_notifications', label='Notifications'), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), ] @cached_property def appendix_word(self): return num2words(self.appendix_number) @cached_property def label_type(self): return 'appendix' @cached_property def label(self): return 'appendix %s' % self.appendix_word @cached_property def label_num(self): return 'appendix %s' % str(self.appendix_number).zfill(2) @cached_property def nav_label(self): return 'appendix %s' % self.appendix_word @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) @cached_property def page_publication_downloads(self): return self.publication_downloads.all() @cached_property def page_data_downloads(self): return self.data_downloads.all() @cached_property def sections(self): sections = [] for block in self.content: if block.block_type == 'section_heading': sections.append(block) return sections
class PublicationSummaryPage(HeroMixin, ReportChildMixin, FlexibleContentMixin, PageSearchMixin, UniqueForParentPageMixin, UUIDMixin, FilteredDatasetMixin, ReportDownloadMixin, InheritCTAMixin, Page): class Meta: verbose_name = 'Publication Summary' verbose_name_plural = 'Publication Summaries' parent_page_types = ['PublicationPage'] subpage_types = [] colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) content_panels = Page.content_panels + [ FieldPanel('colour'), hero_panels(), ContentPanel(), InlinePanel('publication_datasets', label='Datasets'), DownloadsPanel(heading='Downloads', description='Downloads for this summary.'), DownloadsPanel( related_name='data_downloads', heading='Data downloads', description='Optional: data download for this summary.'), ReportDownloadPanel(), InlinePanel('page_notifications', label='Notifications'), ] @cached_property def label_type(self): return 'summary' @cached_property def label(self): return 'the executive summary' @cached_property def nav_label(self): return 'executive summary' @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) @cached_property def page_publication_downloads(self): return self.publication_downloads.all() @cached_property def page_data_downloads(self): return self.data_downloads.all() @cached_property def sections(self): sections = [] for block in self.content: if block.block_type == 'section_heading': sections.append(block) return sections
class PublicationForewordPage(HeroMixin, ReportChildMixin, FlexibleContentMixin, PageSearchMixin, UniqueForParentPageMixin, UUIDMixin, FilteredDatasetMixin, ReportDownloadMixin, InheritCTAMixin, Page): class Meta: verbose_name = 'Publication Foreword' parent_page_types = ['PublicationPage'] subpage_types = [] colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) content_panels = Page.content_panels + [ hero_panels(), FieldPanel('colour'), ContentPanel(), InlinePanel('publication_datasets', label='Datasets'), DownloadsPanel(heading='Downloads', description='Downloads for this foreword.'), DownloadsPanel( related_name='data_downloads', heading='Data downloads', description='Optional: data download for this foreword.', max_num=1, ), ReportDownloadPanel(), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), InlinePanel('page_notifications', label='Notifications') ] @cached_property def label(self): return 'the foreword' @cached_property def nav_label(self): return 'foreword' @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.publication_related_links.all(), PublicationForewordPage.objects) return context
class PublicationPage(HeroMixin, HeroButtonMixin, PublishedDateMixin, ParentPageSearchMixin, UUIDMixin, FilteredDatasetMixin, ReportDownloadMixin, Page): class Meta: verbose_name = 'Publication Page' parent_page_types = ['PublicationIndexPage', 'general.General'] subpage_types = [ 'PublicationForewordPage', 'PublicationSummaryPage', 'PublicationChapterPage', 'PublicationAppendixPage', ] colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) authors = StreamField( [('internal_author', PageChooserBlock(required=False, target_model='ourteam.TeamMemberPage', icon='fa-user', label='Internal Author')), ('external_author', StructBlock([('name', CharBlock(required=False)), ('title', CharBlock(required=False)), ('photograph', ImageChooserBlock(required=False)), ('page', URLBlock(required=False))], icon='fa-user', label='External Author'))], blank=True) publication_type = models.ForeignKey(PublicationType, related_name="+", null=True, blank=False, on_delete=models.SET_NULL, verbose_name="Resource Type") topics = ClusterTaggableManager(through=PublicationTopic, blank=True, verbose_name="Topics") content_panels = Page.content_panels + [ FieldPanel('colour'), hero_panels(), MultiFieldPanel([ FieldPanel('read_online_button_text'), FieldPanel('request_hard_copy_text'), ], heading='Hero Button Captions', description='Edit captions for hero buttons'), StreamFieldPanel('authors'), SnippetChooserPanel('publication_type'), FieldPanel('topics'), InlinePanel('publication_datasets', label='Datasets'), InlinePanel('page_countries', label="Countries"), PublishedDatePanel(), DownloadsPanel(heading='Downloads', description='Downloads for this report.'), DownloadsPanel(related_name='data_downloads', heading='Data downloads', description='Optional: data download for this report.'), ReportDownloadPanel(), UUIDPanel(), InlinePanel('page_notifications', label='Notifications'), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), InlinePanel('publication_cta', label='Call To Action', max_num=2), ] @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) @cached_property def page_publication_downloads(self): return self.publication_downloads.all() @cached_property def page_data_downloads(self): return self.data_downloads.all() @cached_property def foreword(self): return get_first_child_of_type(self, PublicationForewordPage) @cached_property def summary(self): return get_first_child_of_type(self, PublicationSummaryPage) @cached_property def chapters(self): return get_ordered_children_of_type( self, PublicationChapterPage, 'publicationchapterpage__chapter_number') @cached_property def appendices(self): return get_ordered_children_of_type( self, PublicationAppendixPage, 'publicationappendixpage__appendix_number') @cached_property def listing(self): children = [self.foreword, self.summary] children += list(self.chapters) return list(filter(None, children)) @cached_property def meta_and_appendices(self): children = list() children += list(self.appendices) return list(filter(None, children)) @cached_property def listing_and_appendicies(self): return self.listing + self.meta_and_appendices @cached_property def chapter_max(self): try: return max([chapter.chapter_number for chapter in self.chapters]) except ValueError: return 0 @cached_property def call_to_action(self): return self.publication_cta.all() @cached_property def call_to_action_has_top_position(self): for cta in self.publication_cta.all(): if cta.position == 'top': return True return False def save(self, *args, **kwargs): super(PublicationPage, self).save(*args, **kwargs) old_path = '/%s' % self.slug redirect = Redirect.objects.filter(old_path=old_path).first() if not redirect: Redirect(old_path=old_path, redirect_page=self).save() def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.publication_related_links.all(), PublicationPage.objects) return context
class PublicationIndexPage(HeroMixin, Page): content_panels = Page.content_panels + [ hero_panels(), InlinePanel('page_notifications', label='Notifications') ] subpage_types = [ 'PublicationPage', 'LegacyPublicationPage', 'ShortPublicationPage', 'general.General', 'AudioVisualMedia' ] parent_page_types = ['home.HomePage'] def get_context(self, request): context = super(PublicationIndexPage, self).get_context(request) search_filter = request.GET.get('q', None) if search_filter: sort_options = [('date_desc', 'newest first'), ('date_asc', 'oldest first'), ('score', 'relevance')] else: sort_options = [('date_desc', 'newest first'), ('date_asc', 'oldest first')] sort_ids = [sort_opt[0] for sort_opt in sort_options] page = request.GET.get('page', None) topic_filter = request.GET.get('topic', None) country_filter = request.GET.get('country', None) types_filter = request.GET.get('types', None) selected_sort = request.GET.get('sort', 'date_desc') if selected_sort not in sort_ids: selected_sort = 'date_desc' if topic_filter: stories = PublicationPage.objects.descendant_of( self).live().filter(topics__slug=topic_filter) legacy_pubs = LegacyPublicationPage.objects.descendant_of( self).live().filter(topics__slug=topic_filter) short_pubs = ShortPublicationPage.objects.descendant_of( self).live().filter(topics__slug=topic_filter) audio_visual_media = AudioVisualMedia.objects.descendant_of( self).live().filter(topics__slug=topic_filter) else: stories = PublicationPage.objects.descendant_of(self).live() legacy_pubs = LegacyPublicationPage.objects.descendant_of( self).live() short_pubs = ShortPublicationPage.objects.descendant_of( self).live() audio_visual_media = AudioVisualMedia.objects.descendant_of( self).live() if not request.user.is_authenticated: stories = stories.public() legacy_pubs = legacy_pubs.public() short_pubs = short_pubs.public() audio_visual_media = audio_visual_media.public() if country_filter: stories = stories.filter( page_countries__country__slug=country_filter) legacy_pubs = legacy_pubs.filter( page_countries__country__slug=country_filter) short_pubs = short_pubs.filter( page_countries__country__slug=country_filter) audio_visual_media = audio_visual_media.filter( page_countries__country__slug=country_filter) if types_filter: stories = stories.filter(publication_type__slug=types_filter) legacy_pubs = legacy_pubs.filter( publication_type__slug=types_filter) short_pubs = short_pubs.filter(publication_type__slug=types_filter) audio_visual_media = audio_visual_media.filter( publication_type__slug=types_filter) if search_filter: query = Query.get(search_filter) query.add_hit() if stories: child_count = reduce( operator.add, [len(pub.get_children()) for pub in stories]) if child_count: pub_children = reduce( operator.or_, [pub.get_children() for pub in stories]).live().specific().search( search_filter).annotate_score("_child_score") if pub_children: matching_parents = reduce(operator.or_, [ stories.parent_of(child).annotate( _score=models.Value( child._child_score, output_field=models.FloatField())) for child in pub_children ]) stories = list( chain( stories.exclude(id__in=matching_parents. values_list('id', flat=True)). search(search_filter).annotate_score("_score"), matching_parents)) else: stories = stories.search(search_filter).annotate_score( "_score") else: stories = stories.search(search_filter).annotate_score( "_score") legacy_pubs = legacy_pubs.search(search_filter).annotate_score( "_score") short_pubs = short_pubs.search(search_filter).annotate_score( "_score") audio_visual_media = audio_visual_media.search( search_filter).annotate_score('_score') story_list = list( chain(stories, legacy_pubs, short_pubs, audio_visual_media)) elasticsearch_is_active = True for story in story_list: if hasattr(story, "_score"): if story._score is None: elasticsearch_is_active = False if selected_sort == "score" and elasticsearch_is_active: story_list.sort(key=lambda x: x._score, reverse=True) elif selected_sort == "date_asc": story_list.sort(key=lambda x: x.published_date, reverse=False) else: story_list.sort(key=lambda x: x.published_date, reverse=True) promos = get_search_promotions(search_filter) promo_pages = [ promo.page.specific for promo in promos if promo.page.live and isinstance(promo.page.specific, ( PublicationPage, ShortPublicationPage, LegacyPublicationPage)) ] if promo_pages: story_list = [ story for story in story_list if story not in promo_pages ] story_list = list(chain(promo_pages, story_list)) paginator = Paginator(story_list, MAX_PAGE_SIZE) try: context['stories'] = paginator.page(page) except PageNotAnInteger: context['stories'] = paginator.page(1) except EmptyPage: context['stories'] = paginator.page(paginator.num_pages) pubs_content_type = ContentType.objects.get_for_model(PublicationPage) leg_pubs_content_type = ContentType.objects.get_for_model( LegacyPublicationPage) short_pubs_content_type = ContentType.objects.get_for_model( ShortPublicationPage) context['topics'] = Tag.objects.filter( models. Q(publications_publicationtopic_items__content_object__content_type =pubs_content_type) | models. Q(publications_legacypublicationtopic_items__content_object__content_type =leg_pubs_content_type) | models. Q(publications_shortpublicationtopic_items__content_object__content_type =short_pubs_content_type)).distinct().order_by('name') resource_types = PublicationType.objects.filter( show_in_filter=True).order_by('resource_category', 'name') context['resource_types'] = resource_types # ensure only used resource types are pushed to the page for resource_type in resource_types: if not self.resource_type_has_publications(resource_type): context['resource_types'] = context['resource_types'].exclude( id=resource_type.id) context['selected_type'] = types_filter context['selected_topic'] = topic_filter context['countries'] = Country.objects.all().order_by('region', 'name') context['selected_country'] = country_filter context['search_filter'] = search_filter context['selected_sort'] = selected_sort context['sort_options'] = sort_options context[ 'is_filtered'] = search_filter or topic_filter or country_filter or types_filter context['paginator_range'] = get_paginator_range( paginator, context['stories']) return context def resource_type_has_publications(self, resource_type): return (PublicationPage.objects.filter( publication_type=resource_type).first() or ShortPublicationPage.objects.filter( publication_type=resource_type).first() or LegacyPublicationPage.objects.filter( publication_type=resource_type).first() or AudioVisualMedia.objects.filter( publication_type=resource_type).first()) class Meta(): verbose_name = 'Resources Index Page'
class AudioVisualMedia(PublishedDateMixin, TypesetBodyMixin, HeroMixin, ParentPageSearchMixin, SectionBodyMixin, CallToActionMixin, Page): """ Audio Visual page to be used as a child of the Resources Index Page """ template = 'publications/audio_visual_media.html' publication_type = models.ForeignKey(PublicationType, related_name="+", null=True, blank=False, on_delete=models.SET_NULL, verbose_name="Resource Type") participants = StreamField( [('internal_participant', PageChooserBlock(required=False, target_model='ourteam.TeamMemberPage', icon='fa-user', label='Internal Participant')), ('external_participant', StructBlock([('name', CharBlock(required=False)), ('title', CharBlock(required=False)), ('photograph', ImageChooserBlock(required=False)), ('page', URLBlock(required=False))], icon='fa-user', label='External Participant'))], blank=True, help_text="The people involved in the podcast or webinar") topics = ClusterTaggableManager(through=AudioVisualMediaTopic, blank=True, verbose_name="Topics") content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('participants'), call_to_action_panel(), StreamFieldPanel('body'), StreamFieldPanel('sections'), FieldPanel('publication_type'), InlinePanel('page_countries', label="Countries"), FieldPanel('topics'), PublishedDatePanel(), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), InlinePanel('page_notifications', label='Notifications'), ] parent_page_types = ['PublicationIndexPage'] subpage_types = [] def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.publication_related_links.all(), AudioVisualMedia.objects) return context class Meta: verbose_name = 'Audio and Visual Media Page'
class DataSectionPage(TypesetBodyMixin, HeroMixin, Page): """ Main page for datasets """ quotes = StreamField(QuoteStreamBlock, verbose_name="Quotes", null=True, blank=True) dataset_info = RichTextField(null=True, blank=True, help_text='A description of the datasets', features=RICHTEXT_FEATURES_NO_FOOTNOTES) tools = StreamField([ ('tool', BannerBlock(template='datasection/tools_banner_block.html')) ], verbose_name="Tools", null=True, blank=True) other_pages_heading = models.CharField(blank=True, max_length=255, verbose_name='Heading', default='More about') content_panels = Page.content_panels + [ hero_panels(allowed_pages=['datasection.DataSetListing']), StreamFieldPanel('body'), FieldPanel('dataset_info'), StreamFieldPanel('tools'), StreamFieldPanel('quotes'), MultiFieldPanel([ FieldPanel('other_pages_heading'), InlinePanel('other_pages', label='Related pages') ], heading='Other Pages/Related Links'), InlinePanel('page_notifications', label='Notifications') ] parent_page_types = ['home.HomePage'] subpage_types = [ 'general.General', 'datasection.DataSetListing', 'spotlight.SpotlightPage', 'publications.ShortPublicationPage' ] class Meta: verbose_name = "Data Section Page" @cached_property def get_dataset_listing_page(self): return self.get_children().type(DataSetListing)[0] def count_quotes(self): quote_counter = 0 for quote in self.quotes: quote_counter = quote_counter + 1 return quote_counter def get_random_quote(self): number_of_quotes = self.count_quotes() if number_of_quotes == 1: for quote in self.quotes: return quote elif number_of_quotes >= 2: random_number = random.randint(0, number_of_quotes - 1) for index, quote in enumerate(self.quotes): if random_number == index: return quote return def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['random_quote'] = self.get_random_quote() context['dataset_count'] = DatasetPage.objects.live().count() return context
class ServicesPage(TypesetBodyMixin, HeroMixin, Page): """ http://development-initiatives.surge.sh/page-templates/09-consultancy-services """ def get_context(self, request): context = super(ServicesPage, self).get_context(request) topic_filter = request.GET.get('topic', None) if topic_filter: examples = ServicesPageRelatedExample.objects.filter( topics__slug=topic_filter) else: examples = ServicesPageRelatedExample.objects.all() context['topics'] = ExampleTopic.objects.all() context['selected_topic'] = topic_filter context['examples'] = examples context['related_news'] = get_related_pages( self, self.services_related_news.all(), NewsStoryPage.objects, min_len=0) return context contact_text = models.CharField( blank=True, null=True, max_length=250, default= 'Find out more about our consultancy services and what we can do for you' ) contact_button_text = models.CharField(blank=True, null=True, max_length=100, default='Get in touch') contact_email = models.EmailField(blank=True, null=True) specialities = StreamField([ ('speciality', StructBlock([('image', ImageChooserBlock(required=False)), ('heading', CharBlock(required=False)), ('body', RichTextBlock(required=False, features=RICHTEXT_FEATURES_NO_FOOTNOTES)) ])) ]) skills = StreamField([ ('skill', StructBlock([('heading', CharBlock(required=False)), ('body', RichTextBlock(required=False, features=RICHTEXT_FEATURES_NO_FOOTNOTES)) ])) ]) richtext_columns = StreamField( [('column', StructBlock([('heading', TextBlock(required=False, icon='title')), ('content', RichTextBlock(features=RICHTEXT_FEATURES_NO_FOOTNOTES, icon='fa-paragraph'))], template='blocks/richtext_column.html'))], null=True, blank=True) sections = StreamField(SectionStreamBlock(), verbose_name="Sections", null=True, blank=True) class Meta: verbose_name = 'Services Page' content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ FieldPanel('contact_text'), FieldPanel('contact_button_text'), FieldPanel('contact_email') ], heading='Contact aside'), StreamFieldPanel('body'), StreamFieldPanel('specialities'), StreamFieldPanel('skills'), InlinePanel('services_related_news', label="Related news"), InlinePanel('services_related_example', label="Project examples"), StreamFieldPanel('richtext_columns'), StreamFieldPanel('sections'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['general.General'] parent_page_types = [WhatWeDoPage]
class BlogArticlePage(TypesetBodyFootnoteMixin, HeroMixin, CallToActionMixin, Page): topics = ClusterTaggableManager(through=BlogTopic, blank=True) internal_author_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text="The author's page if the author has an internal profile. Photograph, job title, and page link will be drawn from this.", verbose_name='Internal Author' ) other_authors = StreamField( [ ('internal_author', PageChooserBlock( required=False, target_model='ourteam.TeamMemberPage', icon='user', label='Internal Author')), ('external_author', StructBlock([ ('name', CharBlock(required=False)), ('title', CharBlock(required=False)), ('photograph', ImageChooserBlock(required=False)), ('page', URLBlock(required=False)) ], icon='user', label='External Author')) ], blank=True, help_text="Additional authors. If order is important, please use this instead of internal author page.", verbose_name='Other Authors') published_date = models.DateTimeField( blank=True, default=now, help_text='This date will be used for display and ordering', ) content_panels = Page.content_panels + [ hero_panels(), MultiFieldPanel([ PageChooserPanel('internal_author_page', TeamMemberPage), StreamFieldPanel('other_authors') ], heading="Author information"), call_to_action_panel(), FieldPanel('topics'), StreamFieldPanel('body'), FieldPanel('published_date'), InlinePanel('blog_related_links', label='Related links', max_num=MAX_RELATED_LINKS), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['general.General'] parent_page_types = [ 'BlogIndexPage' ] class Meta(): verbose_name = 'Blog Article Page' def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.blog_related_links.all(), BlogArticlePage.objects) return context
class ShortPublicationPage(HeroMixin, PublishedDateMixin, FlexibleContentMixin, ParentPageSearchMixin, UUIDMixin, FilteredDatasetMixin, CallToActionMixin, ReportDownloadMixin, Page): class Meta: verbose_name = 'Short Publication' parent_page_types = ['PublicationIndexPage', 'datasection.DataSectionPage'] subpage_types = [] colour = models.CharField(max_length=256, choices=COLOUR_CHOICES, default=RED) authors = StreamField( [('internal_author', PageChooserBlock(required=False, target_model='ourteam.TeamMemberPage', icon='fa-user', label='Internal Author')), ('external_author', StructBlock([('name', CharBlock(required=False)), ('title', CharBlock(required=False)), ('photograph', ImageChooserBlock(required=False)), ('page', URLBlock(required=False))], icon='fa-user', label='External Author'))], blank=True) publication_type = models.ForeignKey(PublicationType, related_name="+", null=True, blank=False, on_delete=models.SET_NULL, verbose_name="Resource Type") topics = ClusterTaggableManager(through=ShortPublicationTopic, blank=True, verbose_name="Topics") content_panels = Page.content_panels + [ FieldPanel('colour'), hero_panels(), StreamFieldPanel('authors'), call_to_action_panel(), SnippetChooserPanel('publication_type'), FieldPanel('topics'), InlinePanel('page_countries', label="Countries"), PublishedDatePanel(), ContentPanel(), InlinePanel('publication_datasets', label='Datasets'), DownloadsPanel(heading='Downloads', description='Downloads for this chapter.'), DownloadsPanel( related_name='data_downloads', heading='Data downloads', description='Optional: data download for this chapter.'), ReportDownloadPanel(), InlinePanel('page_notifications', label='Notifications'), InlinePanel('publication_related_links', label='Related links', max_num=MAX_RELATED_LINKS), ] @cached_property def publication_downloads_title(self): return 'Downloads' @cached_property def publication_downloads_list(self): return get_downloads(self) @cached_property def data_downloads_title(self): return 'Data downloads' @cached_property def data_downloads_list(self): return get_downloads(self, with_parent=False, data=True) @cached_property def page_publication_downloads(self): return self.publication_downloads.all() @cached_property def page_data_downloads(self): return self.data_downloads.all() @cached_property def chapter_number(self): return 1 @cached_property def chapters(self): return [self] @cached_property def listing_and_appendicies(self): return [self] @cached_property def chapter_word(self): return num2words(self.chapter_number) @cached_property def label_type(self): return 'publication' @cached_property def label(self): return 'publication' @cached_property def label_num(self): return 'publication' @cached_property def sections(self): sections = [] for block in self.content: if block.block_type == 'section_heading': sections.append(block) return sections def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context['related_pages'] = get_related_pages( self, self.publication_related_links.all(), ShortPublicationPage.objects) return context
class DatasetPage(DataSetMixin, TypesetBodyMixin, HeroMixin, Page): """ Content of each dataset """ class Meta(): verbose_name = 'Data Set Page' dataset_id = models.CharField(max_length=255, unique=True, blank=True, null=True) dataset_title = models.TextField(unique=True, blank=True, null=True) related_datasets_title = models.CharField(blank=True, max_length=255, default='Related datasets', verbose_name='Section Title') content_panels = Page.content_panels + [ hero_panels(), FieldPanel('dataset_id'), FieldPanel('dataset_title'), FieldPanel('release_date'), StreamFieldPanel('body'), StreamFieldPanel('authors'), InlinePanel('dataset_downloads', label='Downloads', max_num=None), metadata_panel(), MultiFieldPanel([ FieldPanel('related_datasets_title'), InlinePanel('related_datasets', label="Related Datasets") ], heading='Related Datasets'), other_pages_panel(), InlinePanel('page_notifications', label='Notifications') ] def get_context(self, request): context = super().get_context(request) context['topics'] = [ orderable.topic for orderable in self.dataset_topics.all() ] context['related_datasets'] = get_related_dataset_pages( self.related_datasets.all(), self) context['reports'] = self.get_usages() return context @cached_property def get_dataset_downloads(self): return self.dataset_downloads.all() @cached_property def get_dataset_sources(self): return self.dataset_sources.all() def get_usages(self): reports = Page.objects.live().filter( models.Q( publicationpage__publication_datasets__dataset__slug=self.slug) | models.Q( legacypublicationpage__publication_datasets__dataset__slug=self .slug) | models. Q(publicationsummarypage__publication_datasets__dataset__slug=self. slug) | models. Q(publicationchapterpage__publication_datasets__dataset__slug=self. slug) | models. Q(publicationappendixpage__publication_datasets__dataset__slug=self .slug) | models.Q( shortpublicationpage__publication_datasets__dataset__slug=self. slug)).specific() return reports def get_download_name(self): return self.title
class WorkForUsPage(TypesetBodyMixin, HeroMixin, Page): class Meta(): verbose_name = 'Work For Us Page' benefits = StreamField(BenefitsStreamBlock, verbose_name="Benefits", null=True, blank=True) value_section_heading = models.CharField(blank=True, max_length=255, verbose_name='Value Heading', default='Our values') value_section_sub_heading = RichTextField( blank=True, verbose_name='Value Sub-heading', help_text='A brief description of the section contents', features=RICHTEXT_FEATURES_NO_FOOTNOTES) values = StreamField([ ('value', ValueBlock()), ], null=True, blank=True) team_story_section_heading = models.CharField( blank=True, max_length=255, verbose_name='Section Heading', default='Team stories') team_story_section_sub_heading = RichTextField( blank=True, verbose_name='Section Sub-heading', help_text='A brief description of the section contents', features=RICHTEXT_FEATURES_NO_FOOTNOTES) team_stories = StreamField(TeamStoryStreamBlock, verbose_name="Team Stories", null=True, blank=True) vacancy_section_heading = models.TextField(blank=True, max_length=255, verbose_name='Section Heading', default='Latest vacancies') vacancy_section_sub_heading = RichTextField( blank=True, verbose_name='Section Sub-heading', help_text='A brief description of the section contents', features=RICHTEXT_FEATURES_NO_FOOTNOTES) content_panels = Page.content_panels + [ hero_panels(), StreamFieldPanel('body'), StreamFieldPanel('benefits'), MultiFieldPanel([ FieldPanel('value_section_heading'), FieldPanel('value_section_sub_heading'), StreamFieldPanel('values') ], heading='Values Section'), MultiFieldPanel([ FieldPanel('team_story_section_heading'), FieldPanel('team_story_section_sub_heading'), StreamFieldPanel('team_stories') ], heading='Team Stories Section'), MultiFieldPanel([ FieldPanel('vacancy_section_heading'), FieldPanel('vacancy_section_sub_heading') ], heading='Vacancies Section'), InlinePanel('page_notifications', label='Notifications') ] subpage_types = ['vacancies.VacancyIndexPage', 'general.General'] def get_context(self, request): context = super().get_context(request) context['vacancies'] = VacancyPage.objects.live() return context