class WhyInvestInTheUKPagePanels: content_panels = [ MultiFieldPanel( heading="Hero and Intro", classname='collapsible', children=[ FieldPanel('title'), ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), FieldPanel('strapline'), FieldPanel('introduction'), ImageChooserPanel('intro_image'), ], ), MultiFieldPanel( heading="UK Strengths", classname='collapsible', children=[ FieldPanel('uk_strength_title'), FieldPanel('uk_strength_intro'), StreamFieldPanel('uk_strength_panels'), ], ), SearchEngineOptimisationPanel() ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface(content_panels=content_panels, settings_panels=settings_panels)
class AboutUkRegionListingPagePanels: image_panels = [ ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video') ] content_panels = [ FieldPanel('title'), FieldPanel('breadcrumbs_label'), MultiFieldPanel( heading="Hero", children=[ FieldPanel('hero_title'), ], ), FieldPanel('intro'), MultiFieldPanel(heading="Contact us section", classname="collapsible", children=[ FieldPanel('contact_title'), FieldPanel('contact_text'), FieldPanel('contact_cta_text'), FieldPanel('contact_cta_link'), ]), MultiFieldPanel( heading="Explore more of the Investment Atlas section", classname="collapsible", children=[ PageChooserPanel('related_page_one', [ 'great_international.WhyInvestInTheUKPage', 'great_international.InternationalTopicLandingPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage' ]), PageChooserPanel('related_page_two', [ 'great_international.WhyInvestInTheUKPage', 'great_international.InternationalTopicLandingPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage' ]), PageChooserPanel('related_page_three', [ 'great_international.WhyInvestInTheUKPage', 'great_international.InternationalTopicLandingPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage' ]), ]), SearchEngineOptimisationPanel() ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface(content_panels=content_panels, settings_panels=settings_panels, other_panels=[ ObjectList(image_panels, heading='Images'), ])
class FeatureAspect(ClusterableModel): title = models.CharField(max_length=255) video_url = models.URLField(blank=True) screenshot = models.ForeignKey( "images.WagtailIOImage", models.SET_NULL, null=True, blank=True, related_name="+", ) video = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') def __str__(self): return self.title panels = [ FieldPanel("title"), InlinePanel("bullets", label="Bullets"), ImageChooserPanel("screenshot"), FieldPanel("video_url"), MediaChooserPanel('video'), ]
class HeroVideoFieldsLogo(HeroVideoFields): hero_logo = models.ForeignKey( 'images.CustomImage', null=True, blank=True, related_name='+', help_text="Shows the brand logo on the hero instead of a text heading.", on_delete=models.SET_NULL, verbose_name="Brand Logo") content_panels = [ MultiFieldPanel([ MediaChooserPanel('hero_video'), ImageChooserPanel('hero_fallback_image'), ImageChooserPanel('hero_logo'), FieldPanel('hero_strapline'), FieldPanel('hero_strapline_hex'), MultiFieldPanel([ PageChooserPanel('link_page'), FieldPanel('link_youtube'), FieldPanel('link_text'), ], 'Hero Clickthrough Link') ], 'Hero Video'), ] class Meta: abstract = True
def _init_edit_handler(self, media_type=None): my_page_object_list = ObjectList( [MediaChooserPanel("featured_media", media_type=media_type)] ).bind_to(model=BlogStreamPage) my_media_chooser_panel = my_page_object_list.children[0] form = my_page_object_list.get_form_class()(instance=self.test_instance) media_chooser_panel = my_media_chooser_panel.bind_to( instance=self.test_instance, form=form, request=self.request ) return form, media_chooser_panel
class BlogPage(GrapplePageMixin, Page): author = models.CharField(max_length=255) date = models.DateField("Post date") advert = models.ForeignKey('home.Advert', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') cover = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') book_file = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') featured_media = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') body = StreamField([ ("heading", blocks.CharBlock(classname="full title")), ("paraagraph", blocks.RichTextBlock()), ("image", ImageChooserBlock()), ("decimal", blocks.DecimalBlock()), ("date", blocks.DateBlock()), ("datetime", blocks.DateTimeBlock()), ]) content_panels = Page.content_panels + [ FieldPanel("author"), FieldPanel("date"), ImageChooserPanel('cover'), StreamFieldPanel("body"), InlinePanel('related_links', label="Related links"), SnippetChooserPanel('advert'), DocumentChooserPanel('book_file'), MediaChooserPanel('featured_media'), ] graphql_fields = [ GraphQLString("heading"), GraphQLString("date"), GraphQLString("author"), GraphQLStreamfield("body"), GraphQLForeignKey("related_links", "home.blogpagerelatedlink", True), GraphQLSnippet('advert', 'home.Advert'), GraphQLImage('cover'), GraphQLDocument('book_file'), GraphQLMedia('featured_media'), ]
class InternationalTopicLandingPagePanels: content_panels = [ FieldPanel('title'), FieldPanel('landing_page_title'), MultiFieldPanel(heading='Hero content', children=[ ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), FieldPanel('hero_teaser') ]), MultiFieldPanel( heading="Explore more of the Investment Atlas section", classname="collapsible", children=[ PageChooserPanel('related_page_one', [ 'great_international.WhyInvestInTheUKPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage', 'great_international.InvestmentGeneralContentPage', 'great_international.AboutUkRegionListingPage', 'great_international.InvestmentOpportunityListingPage' ]), PageChooserPanel('related_page_two', [ 'great_international.WhyInvestInTheUKPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage', 'great_international.InvestmentGeneralContentPage', 'great_international.AboutUkRegionListingPage', 'great_international.InvestmentOpportunityListingPage' ]), PageChooserPanel('related_page_three', [ 'great_international.WhyInvestInTheUKPage', 'great_international.AboutUkRegionPage', 'great_international.InvestmentOpportunityPage', 'great_international.InvestmentGeneralContentPage', 'great_international.AboutUkRegionListingPage', 'great_international.InvestmentOpportunityListingPage' ]), ]), SearchEngineOptimisationPanel(), ] settings_panels = [ FieldPanel('slug'), FieldPanel('tags', widget=CheckboxSelectMultiple) ] edit_handler = make_translated_interface(content_panels=content_panels, settings_panels=settings_panels)
class TestPage(AbstractIonPage): document_field = models.ForeignKey(IonDocument, blank=True, null=True, on_delete=models.SET_NULL) image_field = models.ForeignKey(IonImage, blank=True, null=True, on_delete=models.SET_NULL) media_field = models.ForeignKey(IonMedia, blank=True, null=True, on_delete=models.SET_NULL) content_panels = AbstractIonPage.content_panels + [ DocumentChooserPanel('document_field'), ImageChooserPanel('image_field'), MediaChooserPanel('media_field'), ] parent_page_types = [ 'IonLanguage', 'TestPage', ]
class InternationalHomePagePanels: content_panels = [ FieldPanel('title'), FieldPanel('hero_title'), MediaChooserPanel('hero_video'), StreamFieldPanel('homepage_link_panels'), SearchEngineOptimisationPanel(), ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface(content_panels=content_panels, settings_panels=settings_panels)
class PodEpisodeBirdPage(Page, BirdMixin): body = StreamField([ ('paragraph', blocks.RichTextBlock( required=False, null=True, features=[ 'h2', 'h3', 'h4', 'bold', 'italic', 'superscript', 'subscript', 'strikethrough', 'ol', 'ul', 'hr', 'link', 'document-link', 'blockquote', 'embed', 'image' ])), ], blank=True, null=True) enclosure = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') enclosure_length = models.IntegerField(blank=True, null=True) enclosure_mime_type = models.CharField(max_length=50, blank=True, null=True) explicit = models.BooleanField(default=False) tags = ClusterTaggableManager(through=PodEpisodeBirdPageTag, blank=True) search_fields = Page.search_fields + BirdMixin.search_fields + [ index.SearchField('body'), ] content_panels = Page.content_panels + BirdMixin.content_panels + [ StreamFieldPanel('body'), MediaChooserPanel('enclosure'), FieldPanel('enclosure_length'), FieldPanel('enclosure_mime_type'), ] promote_panels = Page.promote_panels + [ FieldPanel('tags'), ] settings_panels = Page.settings_panels + BirdMixin.settings_panels + [ FieldPanel('explicit'), ] def get_sitemap_urls(self, request=None): if self.exclude_from_sitemap: return [] else: return super(PodEpisodeBirdPage, self).get_sitemap_urls(request=request)
class EventPageRelatedMedia(Orderable): page = ParentalKey('wagtailmedia_tests.EventPage', related_name='related_media', on_delete=models.CASCADE) title = models.CharField(max_length=255, help_text="Link title") link_media = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, related_name='+', on_delete=models.CASCADE) @property def link(self): return self.link_media.url panels = [ FieldPanel('title'), MediaChooserPanel('link_media'), ]
def setUpTestData(cls): cls.request = RequestFactory().get("/") cls.request.user = ( AnonymousUser() ) # technically, Anonymous users cannot access the admin fake_file = ContentFile("Test") fake_file.name = "test.mp3" cls.audio = Media.objects.create( title="Test audio", duration=1000, file=fake_file, type="audio" ) fake_file = ContentFile("Test") fake_file.name = "test.mp4" cls.video = Media.objects.create( title="Test video", duration=1024, file=fake_file, type="video" ) # a MediaChooserPanel class that works on BlogStreamPage's 'video' field cls.edit_handler = ObjectList([MediaChooserPanel("featured_media")]).bind_to( model=BlogStreamPage, request=cls.request ) cls.my_media_chooser_panel = cls.edit_handler.children[0] # build a form class containing the fields that MyPageChooserPanel wants cls.MediaChooserForm = cls.edit_handler.get_form_class() root_page = Page.objects.first() cls.test_instance = BlogStreamPage( title="Post", slug="post", author="Joe Bloggs", date="1984-01-01", featured_media=cls.video, ) root_page.add_child(instance=cls.test_instance) cls.form = cls.MediaChooserForm(instance=cls.test_instance) cls.media_chooser_panel = cls.my_media_chooser_panel.bind_to( instance=cls.test_instance, form=cls.form )
class InvestmentGeneralContentPagePanels: content_panels = [ MultiFieldPanel( heading="Hero and Intro", classname='collapsible', children=[ FieldPanel('title'), ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), FieldPanel('strapline'), FieldPanel('introduction'), ImageChooserPanel('intro_image'), ], ), StreamFieldPanel('main_content'), SearchEngineOptimisationPanel(), ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface(content_panels=content_panels, settings_panels=settings_panels)
class InvestmentOpportunityListingPagePanels: content_panels = [ MultiFieldPanel( heading='Opportunity Listing Page Title', children=[ FieldPanel('title'), FieldPanel('search_results_title'), FieldPanel('breadcrumbs_label'), ], ), MultiFieldPanel( heading='Hero content', children=[ MediaChooserPanel('hero_video'), FieldPanel('hero_text'), ], ), MultiFieldPanel( heading='CTA content', children=[ FieldPanel('contact_cta_title'), FieldPanel('contact_cta_text'), FieldPanel('contact_cta_link'), ], ), SearchEngineOptimisationPanel(), ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface( content_panels=content_panels, settings_panels=settings_panels, )
class TranslationPage(Page): body = RichTextField(blank=True) author = models.CharField(max_length=100, blank=True, null=True) translators = models.CharField(max_length=100, blank=True, null=True) original_link = models.CharField(max_length=100, blank=True, null=True) rfatz_id = models.PositiveSmallIntegerField(null=True, blank=True) on_vk = models.BooleanField(default=False) readthesequences_link = models.CharField(max_length=100, blank=True, null=True) audio = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') parent_page_types = [ 'translations.BookPage', 'translations.TranslationIndexPage' ] subpage_types = [] content_panels = Page.content_panels + [ FieldPanel('body', classname="full"), FieldPanel('author'), FieldPanel('translators'), FieldPanel('original_link'), FieldPanel('rfatz_id'), FieldPanel('on_vk'), FieldPanel('readthesequences_link'), MediaChooserPanel('audio'), ] def get_url_parts(self, *args, **kwargs): (site_id, root_url, _) = super().get_url_parts(*args, **kwargs) return (site_id, root_url, '/w/' + self.slug)
class BlogStreamPage(Page): author = models.CharField(max_length=255) date = models.DateField("Post date") body = StreamField([ ("heading", blocks.CharBlock(classname="full title", icon="title")), ("paragraph", blocks.RichTextBlock(icon="pilcrow")), ("media", TestMediaBlock(icon="media")), ("video", VideoChooserBlock(icon="media")), ("audio", AudioChooserBlock(icon="media")), ]) featured_media = models.ForeignKey("wagtailmedia.Media", on_delete=models.PROTECT, related_name="+") content_panels = Page.content_panels + [ FieldPanel("author"), FieldPanel("date"), StreamFieldPanel("body"), MediaChooserPanel("featured_media"), # the following are left here for local testing convenience # MediaChooserPanel("featured_media", media_type="audio"), # MediaChooserPanel("featured_media", media_type="video"), ]
class InvestmentAtlasLandingPagePanels: content_panels = [ MultiFieldPanel(heading="Title and breadcrumbs", classname='collapsible', children=[ FieldPanel('title'), FieldPanel('breadcrumbs_label'), ]), MultiFieldPanel(heading='Hero', classname='collapsible', children=[ ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), ImageChooserPanel('mobile_hero_image'), FieldPanel('hero_title'), FieldPanel('hero_strapline'), ]), MultiFieldPanel( heading='Downpage content panels', classname='collapsible', children=[ StreamFieldPanel('downpage_sections'), ], ), SearchEngineOptimisationPanel(), ] settings_panels = [ FieldPanel('slug'), ] edit_handler = make_translated_interface( content_panels=content_panels, settings_panels=settings_panels, )
class HomePage(Page): #parent_page_types = [] # Nothing can have a homepage as a child video = models.ForeignKey('wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') @property def feed_image(self): return self.hero.all()[0].background class Meta: verbose_name = 'Homepage' content_panels = Page.content_panels + [ InlinePanel('hero', label='Hero'), InlinePanel('promo', label='Promo'), InlinePanel('featured', label='Featured'), MediaChooserPanel('video'), ] promote_panels = Page.promote_panels
class ShortVideoPage(AllDotaPageMixin, LogoContainingPageMixin, MetadataPageMixin, MultilingualPageMixin, Page): video = models.ForeignKey('wagtailmedia.Media', null=True, blank=False, on_delete=models.SET_NULL, related_name='+') video_thumbnail = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') @property def thumbnail(self): if self.video_thumbnail: return self.video_thumbnail else: return self.update_thumbnail() def update_thumbnail(self): generated_file = False if self.video.thumbnail: file = open(self.video.thumbnail.path, 'rb') file = File(file) else: clip = cv2.VideoCapture(self.video.file.path) ret, frame = clip.read() generated_file = 'thumbnail.jpeg' cv2.imwrite(generated_file, frame) file = open(generated_file, 'rb') file = File(file) thumbnail = Image(title=text_processing.html_to_str( self.english_title), file=file) thumbnail.save() self.video_thumbnail = thumbnail self.save() if generated_file: os.remove(generated_file) return thumbnail english_title = RichTextField(features=[], blank=False, null=True) farsi_title = RichTextField(features=[], blank=False, null=True, help_text='It has to start with a farsi word') english_caption = RichTextField( features=[], blank=False, null=True, ) farsi_caption = RichTextField( features=[], blank=False, null=True, help_text='It has to start with a farsi word') english_tags = ClusterTaggableManager(through=ShortVideoPageEnglishTag, blank=True, related_name='english_tags') farsi_tags = ClusterTaggableManager(through=ShortVideoPageFarsiTag, blank=True, related_name='farsi_tags') content_panels = [ MultiFieldPanel([ MediaChooserPanel('video'), ], heading='video', classname='collapsible collapsed'), MultiFieldPanel([ FieldPanel('farsi_title'), RichTextFieldPanel('farsi_caption'), FieldPanel('farsi_tags'), ], heading='farsi', classname='collapsible collapsed'), MultiFieldPanel([ FieldPanel('english_title'), RichTextFieldPanel('english_caption'), FieldPanel('english_tags'), ], heading='english', classname='collapsible collapsed'), ] promote_panels = [] settings_panels = [] @property def farsi_url(self): return super().get_farsi_url() @property def english_url(self): return super().get_english_url() @property def template(self): return super().template @property def farsi_translated(self): return True @property def english_translated(self): return True def serve(self, request, *args, **kwargs): language = translation.get_language() if language == 'fa': self.search_description = text_processing.html_to_str( self.english_caption) self.seo_title = text_processing.html_to_str(self.english_title) else: self.search_description = text_processing.html_to_str( self.farsi_caption) self.seo_title = text_processing.html_to_str(self.farsi_title) return super().serve(request, *args, **kwargs) def clean(self): super().clean() if self.english_title: self.title = text_processing.html_to_str(self.english_title) pages = ShortVideoPage.objects.filter(title=self.title) if pages: if not self.id: self.set_uuid4() self.slug = slugify('{}_{}'.format(self.title, self.uuid4)) else: self.slug = slugify('{}_{}'.format(self.title, self.id)) uuid4 = models.TextField(default='', blank=True) def set_uuid4(self): uuid4 = uuid.uuid4() while ShortVideoPage.objects.filter(uuid4=uuid4).exists(): uuid4 = uuid.uuid4() self.uuid4 = str(uuid4) parent_page_types = ['home.ShortVideosPage'] subpage_types = [] def __str__(self): return self.english_title
class ArticlePage( BasicPageAbstract, ContentPage, FeatureablePageAbstract, FromTheArchivesPageAbstract, ShareablePageAbstract, ThemeablePageAbstract, ): class ArticleTypes(models.TextChoices): CIGI_IN_THE_NEWS = ('cigi_in_the_news', 'CIGI in the News') INTERVIEW = ('interview', 'Interview') NEWS_RELEASE = ('news_release', 'News Release') OP_ED = ('op_ed', 'Op-Ed') OPINION = ('opinion', 'Opinion') class Languages(models.TextChoices): DA = ('da', 'Danish') DE = ('de', 'German') EL = ('el', 'Greek') EN = ('en', 'English') ES = ('es', 'Spanish') FR = ('fr', 'French') ID = ('id', 'Indonesian') IT = ('it', 'Italian') NL = ('nl', 'Dutch') PL = ('pl', 'Polish') PT = ('pt', 'Portugese') RO = ('ro', 'Romanian') SK = ('sk', 'Slovak') SV = ('sv', 'Swedish') TR = ('tr', 'Turkish') ZH = ('zh', 'Chinese') class HeroTitlePlacements(models.TextChoices): BOTTOM = ('bottom', 'Bottom') TOP = ('top', 'Top') article_series = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Opinion series', ) article_type = models.ForeignKey( 'articles.ArticleTypePage', null=True, blank=False, on_delete=models.SET_NULL, related_name='articles', ) body = StreamField( BasicPageAbstract.body_default_blocks + [ BasicPageAbstract.body_accordion_block, BasicPageAbstract.body_autoplay_video_block, BasicPageAbstract.body_chart_block, BasicPageAbstract.body_embedded_tiktok_block, BasicPageAbstract.body_external_quote_block, BasicPageAbstract.body_external_video_block, BasicPageAbstract.body_extract_block, BasicPageAbstract.body_highlight_title_block, BasicPageAbstract.body_image_full_bleed_block, BasicPageAbstract.body_image_scroll_block, BasicPageAbstract.body_poster_block, BasicPageAbstract.body_pull_quote_left_block, BasicPageAbstract.body_pull_quote_right_block, BasicPageAbstract.body_recommended_block, BasicPageAbstract.body_text_border_block, BasicPageAbstract.body_tool_tip_block, BasicPageAbstract.body_tweet_block, ], blank=True, ) embed_youtube = models.URLField( blank=True, verbose_name='YouTube Embed', help_text= 'Enter the YouTube URL (https://www.youtube.com/watch?v=4-Xkn1U1DkA) or short URL (https://youtu.be/o5acQ2GxKbQ) to add an embedded video.', ) embed_youtube_label = models.CharField( max_length=255, blank=True, help_text='Add a label to appear below the embedded video.', ) footnotes = RichTextField( blank=True, features=[ 'bold', 'endofarticle', 'h3', 'h4', 'italic', 'link', 'ol', 'ul', 'subscript', 'superscript', 'anchor', ], ) hero_title_placement = models.CharField( blank=True, max_length=16, choices=HeroTitlePlacements.choices, verbose_name='Hero Title Placement', help_text= 'Placement of the title within the hero section. Currently only works on the Longform 2 theme.', ) hide_excerpt = models.BooleanField( default=False, verbose_name='Hide Excerpt', help_text= 'For "CIGI in the News" only: when enabled, hide excerpt and display full article instead', ) image_banner = models.ForeignKey( 'images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Image', ) image_banner_small = models.ForeignKey('images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Image Small') image_poster = models.ForeignKey( 'images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Poster Image', help_text='A poster image used in feature sections', ) interviewers = StreamField( [ ('interviewer', PageChooserBlock(required=True, page_type='people.PersonPage')), ], blank=True, ) language = models.CharField( blank=True, max_length=2, choices=Languages.choices, verbose_name='Language', help_text= 'If this content is in a language other than English, please select the language from the list.', ) multimedia_series = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) related_files = StreamField( [ ('file', DocumentChooserBlock()), ], blank=True, ) short_description = RichTextField( blank=True, null=False, features=['bold', 'italic', 'link'], ) video_banner = models.ForeignKey( 'wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Video', ) website_button_text = models.CharField( blank=True, max_length=64, help_text= 'Override the button text for the article website. If empty, the button will read "View Full Article".' ) website_url = models.URLField(blank=True, max_length=512) works_cited = RichTextField( blank=True, features=[ 'bold', 'endofarticle', 'h3', 'h4', 'italic', 'link', 'ol', 'ul', 'subscript', 'superscript', ], ) # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) @property def cigi_people_mentioned_ids(self): return [item.person.id for item in self.cigi_people_mentioned.all()] @property def expired_image(self): if self.publishing_date: return self.publishing_date < datetime.datetime( 2017, 1, 1).astimezone(pytz.timezone('America/Toronto')) return False @property def article_series_description(self): if self.article_series: return self.article_series.specific.series_items_description return None @property def article_series_disclaimer(self): if self.article_series: for series_item in self.article_series.specific.article_series_items: if series_item.content_page.specific == self and not series_item.hide_series_disclaimer: return self.article_series.specific.series_items_disclaimer return None def is_opinion(self): return self.article_type.title in [ 'Op-Eds', 'Opinion', ] def get_template(self, request, *args, **kwargs): standard_template = super(ArticlePage, self).get_template(request, *args, **kwargs) if self.theme: return f'themes/{self.get_theme_dir()}/article_page.html' return standard_template content_panels = [ BasicPageAbstract.title_panel, MultiFieldPanel([ FieldPanel('short_description'), StreamFieldPanel('body'), FieldPanel('footnotes'), FieldPanel('works_cited'), ], heading='Body', classname='collapsible collapsed'), MultiFieldPanel( [ PageChooserPanel( 'article_type', ['articles.ArticleTypePage'], ), FieldPanel('hide_excerpt'), FieldPanel('publishing_date'), FieldPanel('website_url'), FieldPanel('website_button_text'), FieldPanel('language'), ], heading='General Information', classname='collapsible collapsed', ), ContentPage.authors_panel, MultiFieldPanel( [ ImageChooserPanel('image_hero'), ImageChooserPanel('image_poster'), ImageChooserPanel('image_banner'), ImageChooserPanel('image_banner_small'), ], heading='Images', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('embed_youtube'), FieldPanel('embed_youtube_label'), MediaChooserPanel('video_banner'), ], heading='Media', classname='collapsible collapsed', ), ContentPage.recommended_panel, MultiFieldPanel( [ FieldPanel('topics'), FieldPanel('projects'), PageChooserPanel( 'article_series', ['articles.ArticleSeriesPage'], ), PageChooserPanel( 'multimedia_series', ['multimedia.MultimediaSeriesPage'], ), InlinePanel('cigi_people_mentioned', label='People Mentioned'), StreamFieldPanel('interviewers'), StreamFieldPanel('related_files'), ], heading='Related', classname='collapsible collapsed', ), FromTheArchivesPageAbstract.from_the_archives_panel, ] promote_panels = Page.promote_panels + [ FeatureablePageAbstract.feature_panel, ShareablePageAbstract.social_panel, SearchablePageAbstract.search_panel, ] settings_panels = Page.settings_panels + [ ThemeablePageAbstract.theme_panel, ] search_fields = BasicPageAbstract.search_fields \ + ContentPage.search_fields \ + [ index.FilterField('article_type'), index.FilterField('cigi_people_mentioned_ids'), index.FilterField('publishing_date'), ] parent_page_types = ['articles.ArticleListPage'] subpage_types = [] templates = 'articles/article_page.html' @property def is_title_bottom(self): return self.title in [ 'Can the G20 Save Globalization\'s Waning Reputation?', 'Shoshana Zuboff on the Undetectable, Indecipherable World of Surveillance Capitalism' ] @property def article_series_category(self): category = '' for series_item in self.article_series.specific.article_series_items: if series_item.category_title: category = series_item.category_title if series_item.content_page.id == self.id: return category class Meta: verbose_name = 'Opinion' verbose_name_plural = 'Opinions'
class BlogPage(HeadlessPreviewMixin, Page): date = models.DateField("Post date") advert = models.ForeignKey( "home.Advert", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) cover = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) book_file = models.ForeignKey( "wagtaildocs.Document", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) featured_media = models.ForeignKey( "wagtailmedia.Media", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) author = models.ForeignKey(AuthorPage, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") body = StreamField(StreamFieldBlock()) content_panels = Page.content_panels + [ FieldPanel("date"), ImageChooserPanel("cover"), StreamFieldPanel("body"), InlinePanel("related_links", label="Related links"), InlinePanel("authors", label="Authors"), FieldPanel("author"), SnippetChooserPanel("advert"), DocumentChooserPanel("book_file"), MediaChooserPanel("featured_media"), ] @property def copy(self): return self graphql_fields = [ GraphQLString("heading"), GraphQLString("date", required=True), GraphQLStreamfield("body"), GraphQLCollection(GraphQLForeignKey, "related_links", "home.blogpagerelatedlink", required=True, item_required=True), GraphQLCollection(GraphQLString, "related_urls", source="related_links.url"), GraphQLCollection(GraphQLString, "authors", source="authors.person.name"), GraphQLSnippet("advert", "home.Advert"), GraphQLImage("cover"), GraphQLDocument("book_file"), GraphQLMedia("featured_media"), GraphQLForeignKey("copy", "home.BlogPage"), GraphQLPage("author"), ]
class Banner(Orderable): page = ParentalKey( 'HomePage', related_name='banners', on_delete=models.CASCADE, blank=False, ) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video = models.ForeignKey( 'wagtailmedia.Media', help_text=_("""Banner video media. If video is not supported by the browser, the image is shown instead."""), null=True, blank=True, on_delete=models.SET_NULL, related_name='+') title_en = models.CharField( max_length=255, verbose_name=_('English banner title'), help_text=_('Enter the title to be shown on the banner.'), blank=True, ) title_sv = models.CharField( max_length=255, verbose_name=_('Swedish banner title'), help_text=_('Enter the title to be shown on the banner.'), blank=True, ) title = TranslatedField('title_en', 'title_sv') text_en = models.TextField( verbose_name=_('English banner text'), help_text=_('Enter a text to be shown on the banner.'), blank=True, ) text_sv = models.TextField( verbose_name=_('Swedish banner text'), help_text=_('Enter a text to be shown on the banner.'), blank=True, ) text = TranslatedField('text_en', 'text_sv') link = models.URLField( verbose_name=_('Button URL'), blank=True, ) button_en = models.TextField( verbose_name=_('English button text'), help_text=_('Enter the text to be displayed on the button.'), blank=True, ) button_sv = models.TextField( verbose_name=_('Swedish button text'), help_text=_('Enter the text to be displayed on the button.'), blank=True, ) button = TranslatedField('button_en', 'button_sv') # ------ Administrator settings ------ panels = [ MultiFieldPanel([ ImageChooserPanel('image'), MediaChooserPanel('video'), FieldRowPanel([ FieldPanel('title_en'), FieldPanel('title_sv'), ]), FieldPanel('text_en'), FieldPanel('text_sv'), FieldPanel('link'), FieldRowPanel([ FieldPanel('button_en'), FieldPanel('button_sv'), ]), ]) ]
class ArticleSeriesPage( BasicPageAbstract, ContentPage, FeatureablePageAbstract, FromTheArchivesPageAbstract, ShareablePageAbstract, ThemeablePageAbstract, ): credits = RichTextField( blank=True, features=[ 'bold', 'italic', 'link', 'name', ], ) credits_stream_field = StreamField( [('title', StructBlock([ ('title', CharBlock()), ('people', StreamBlock([('name', CharBlock())])), ]))], blank=True, ) credits_artwork = models.CharField( max_length=255, blank=True, ) featured_items = StreamField( [ ('featured_item', PageChooserBlock( required=True, page_type=[ 'articles.ArticlePage', 'multimedia.MultimediaPage' ], )), ], blank=True, ) image_banner = models.ForeignKey( 'images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Image', ) image_banner_small = models.ForeignKey('images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Image Small') image_poster = models.ForeignKey( 'images.CigionlineImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Poster image', help_text= 'A poster image which will be used in the highlights section of the homepage.', ) short_description = RichTextField( blank=True, null=False, features=['bold', 'italic', 'link'], ) series_items_description = RichTextField( blank=True, null=True, features=['bold', 'italic', 'link'], ) series_videos_description = RichTextField( blank=True, null=True, features=['bold', 'italic', 'link'], help_text= 'To be displayed on video/multimedia pages of the series in place of Series Items Description' ) series_items_disclaimer = RichTextField( blank=True, null=True, features=['bold', 'italic', 'link'], ) video_banner = models.ForeignKey( 'wagtailmedia.Media', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Banner Video', ) @property def image_poster_caption(self): return self.image_poster.caption @property def image_poster_url(self): return self.image_poster.get_rendition('fill-672x895').url @property def article_series_items(self): return self.series_items.prefetch_related( 'content_page', 'content_page__authors__author', ).all() # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) def get_template(self, request, *args, **kwargs): standard_template = super(ArticleSeriesPage, self).get_template(request, *args, **kwargs) if self.theme: return f'themes/{self.get_theme_dir()}/article_series_page.html' return standard_template content_panels = [ BasicPageAbstract.title_panel, MultiFieldPanel( [ FieldPanel('short_description'), StreamFieldPanel('body'), ], heading='Body', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('publishing_date'), ], heading='General Information', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('series_items_description'), FieldPanel('series_videos_description'), FieldPanel('series_items_disclaimer'), InlinePanel('series_items'), ], heading='Series Items', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('credits'), FieldPanel('credits_artwork'), StreamFieldPanel('credits_stream_field'), ], heading='Credits', classname='collapsible collapsed', ), MultiFieldPanel( [ ImageChooserPanel('image_hero'), ImageChooserPanel('image_banner'), ImageChooserPanel('image_banner_small'), ImageChooserPanel('image_poster'), ], heading='Image', classname='collapsible collapsed', ), MultiFieldPanel( [ MediaChooserPanel('video_banner'), ], heading='Media', classname='collapsible collapsed', ), MultiFieldPanel( [ StreamFieldPanel('featured_items'), ], heading='Featured Series Items', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('topics'), ], heading='Related', classname='collapsible collapsed', ), ] promote_panels = Page.promote_panels + [ FeatureablePageAbstract.feature_panel, ShareablePageAbstract.social_panel, SearchablePageAbstract.search_panel, ] settings_panels = Page.settings_panels + [ ThemeablePageAbstract.theme_panel, ] search_fields = Page.search_fields \ + BasicPageAbstract.search_fields \ + ContentPage.search_fields parent_page_types = ['home.HomePage'] subpage_types = [] templates = 'articles/article_series_page.html' @property def series_contributors_by_article(self): series_contributors = [] item_people = set() for series_item in self.article_series_items: people = series_item.content_page.authors.all() people_string = '' for person in people: person_string = person.author.title people_string += person_string # Add each person as well so if there's an article with just # a single author who's already been in another article in # collaboration, then we won't add their name to the list # again. if len(people) > 1: item_people.add(person_string) if people_string not in item_people: series_contributors.append({ 'item': series_item.content_page, 'contributors': people }) item_people.add(people_string) return series_contributors @property def series_contributors(self): series_contributors = [] item_people = set() for series_item in self.article_series_items: people = series_item.content_page.authors.all() for person in people: if person.author.title not in item_people: series_contributors.append({ 'id': person.author.id, 'title': person.author.title, 'url': person.author.url, }) item_people.add(person.author.title) return series_contributors @property def series_contributors_by_person(self): # Series contributors ordered by last name series_contributors = [] item_people = set() for series_item in self.article_series_items: people = series_item.content_page.authors.all() # Skip items that have more than 2 authors/speakers. For # example, in the After COVID series, there is an introductory # video with many authors. if len(people) > 2: continue else: for person in people: if person.author.title not in item_people: series_contributors.append({ 'item': series_item.content_page, 'contributors': [person.author], 'last_name': person.author.last_name, }) item_people.add(person.author.title) series_contributors.sort(key=lambda x: x['last_name']) return series_contributors @property def series_authors(self): series_authors = [] series_people = set() for series_item in self.article_series_items: people = series_item.content_page.authors.all() for person in people: if person.author.title not in series_people: series_authors.append(person.author) series_people.add(person.author.title) return series_authors class Meta: verbose_name = 'Opinion Series' verbose_name_plural = 'Opinion Series'
class InvestmentOpportunityPagePanels: content_panels = [ MultiFieldPanel( heading='Opportunity Page title, intro and summary', classname='collapsible', children=[ FieldPanel('title'), FieldPanel('breadcrumbs_label'), ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), FieldPanel('strapline'), FieldPanel('introduction'), ImageChooserPanel('intro_image'), FieldPanel('opportunity_summary'), ], ), MultiFieldPanel( heading="Key facts", classname='collapsible', children=[ FieldRowPanel([ FieldPanel('promoter'), FieldPanel('location'), MultiFieldPanel(heading='Scale', children=[ FieldPanel('scale'), FieldPanel('scale_value'), ]), ]), FieldRowPanel([ FieldPanel('planning_status'), FieldPanel('investment_type'), FieldPanel('time_to_investment_decision'), ]), HelpPanel( 'In addition to these, please also set a location in the Location tab' ), ], ), MultiFieldPanel( heading='Opportunity Contact', classname='collapsible', children=[ FieldPanel('contact_name'), ImageChooserPanel('contact_avatar'), FieldPanel('contact_job_title'), FieldPanel('contact_link'), ], ), MultiFieldPanel( heading='The Opportunity', classname='collapsible', children=[ StreamFieldPanel('main_content'), FieldPanel('important_links'), ], ), SearchEngineOptimisationPanel(), ] related_entities_panels = [ FieldRowPanel(heading='Location and Relevant Regions', classname='collapsible', children=[ StreamFieldPanel('regions_with_locations'), ]), FieldRowPanel( heading="Sectors and Sub-sectors", classname='collapsible collapsed', children=[ InlinePanel('related_sectors', label="Related Sectors"), InlinePanel('related_sub_sectors', label="Related Sub-sectors") ], ), ] settings_panels = [ FieldPanel('slug'), FieldPanel('priority_weighting'), ] edit_handler = make_translated_interface( content_panels=content_panels, other_panels=related_entities_panels, # These are shown as separate tabs settings_panels=settings_panels, )
class HeroVideoFields(models.Model): hero_video = models.ForeignKey( 'utils.CustomMedia', null=True, blank=True, on_delete=models.SET_NULL, help_text= "Short Hero Video to show on top of page. Recommended size 12Mb or under.", related_name='+') hero_fallback_image = models.ForeignKey( 'images.CustomImage', null=True, blank=True, related_name='+', help_text="Hero Image to be used as fallback for video.", on_delete=models.SET_NULL) hero_strapline = models.TextField( blank=True, max_length=255, help_text= "Shows text over the hero. If no strapline is entered, no page title will show." ) hero_strapline_hex = models.CharField( blank=True, max_length=7, help_text="Add valid hex to change colour of strapline.") link_page = models.ForeignKey( Page, blank=True, null=True, related_name='+', on_delete=models.SET_NULL, help_text="Optional page link as clickthrough for hero video.", verbose_name="Page Link") link_youtube = models.URLField( blank=True, help_text="Optional URL for a full length YouTube video goes here,\ which will open in a modal window.", verbose_name="YouTube Link") link_text = models.CharField(blank=True, max_length=255) search_fields = Page.search_fields + [ index.SearchField('hero_strapline'), ] content_panels = [ MultiFieldPanel([ MediaChooserPanel('hero_video'), ImageChooserPanel('hero_fallback_image'), FieldPanel('hero_strapline'), FieldPanel('hero_strapline_hex'), MultiFieldPanel([ PageChooserPanel('link_page'), FieldPanel('link_youtube'), FieldPanel('link_text'), ], 'Hero Clickthrough Link') ], 'Hero Video'), ] def clean(self): from girleffect.utils.blocks import validate_hex if self.hero_strapline_hex: if not validate_hex(self.hero_strapline_hex): raise ValidationError( {'hero_strapline_hex': _('Please enter a valid hex code')}) # Validating if URL is a valid YouTube URL youtube_embed = self.link_youtube if youtube_embed: youtube_finder = OEmbedFinder(providers=[oembed_providers.youtube]) if not youtube_finder.accept(youtube_embed): raise ValidationError( {'link_youtube': _('Please supply a valid YouTube URL.')}) else: try: embed = get_embed(youtube_embed) self.link_youtube_html = embed.html except EmbedException: raise ValidationError( {'link_youtube': _('Embed cannot be found.')}) # Validating links populated_fields = [] for link_field in [self.link_page, self.link_youtube]: if link_field: populated_fields.append(link_field) # Only only one or less fields can be selected if len(populated_fields) > 1: error_message = 'Please choose only one of Link Page or Link YouTube as destination.' raise ValidationError({ 'link_page': error_message, 'link_youtube': error_message }) # Link fields should have link text if len(populated_fields) >= 1 and not self.link_text: raise ValidationError({ 'link_text': 'Link text is required if link destination has been selected' }) return super(HeroVideoFields, self).clean() class Meta: abstract = True
class BlogPage(HeadlessPreviewMixin, Page): date = models.DateField("Post date") advert = models.ForeignKey( "home.Advert", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) hero_image = models.ForeignKey( "images.CustomImage", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) book_file = models.ForeignKey( "wagtaildocs.Document", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) featured_media = models.ForeignKey( "wagtailmedia.Media", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) author = models.ForeignKey(AuthorPage, null=True, blank=True, on_delete=models.SET_NULL, related_name="+") body = StreamField(StreamFieldBlock()) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) content_panels = Page.content_panels + [ FieldPanel("date"), ImageChooserPanel("hero_image"), StreamFieldPanel("body"), FieldPanel("tags"), InlinePanel("related_links", label="Related links"), InlinePanel("authors", label="Authors"), FieldPanel("author"), SnippetChooserPanel("advert"), DocumentChooserPanel("book_file"), MediaChooserPanel("featured_media"), ] @property def copy(self): return self def paginated_authors(self, info, **kwargs): return resolve_paginated_queryset(self.authors, info, **kwargs) graphql_fields = [ GraphQLString("date", required=True), GraphQLStreamfield("body"), GraphQLTag("tags"), GraphQLCollection( GraphQLForeignKey, "related_links", "home.blogpagerelatedlink", required=True, item_required=True, ), GraphQLCollection(GraphQLString, "related_urls", source="related_links.url"), GraphQLCollection(GraphQLString, "authors", source="authors.person.name"), GraphQLCollection( GraphQLForeignKey, "paginated_authors", "home.Author", is_paginated_queryset=True, ), GraphQLSnippet("advert", "home.Advert"), GraphQLImage("hero_image"), GraphQLDocument("book_file"), GraphQLMedia("featured_media"), GraphQLForeignKey("copy", "home.BlogPage"), GraphQLPage("author"), ]
class InternationalInvestmentSectorPagePanels: content_panels = [ FieldPanel('title'), MultiFieldPanel(heading='Heading', classname='collapsible', children=[ FieldPanel('heading'), ImageChooserPanel('hero_image'), MediaChooserPanel('hero_video'), FieldPanel('standfirst'), FieldPanel('featured_description') ]), MultiFieldPanel(heading='Intro', classname='collapsible', children=[ FieldPanel('intro_text'), ImageChooserPanel('intro_image'), ]), MultiFieldPanel(heading='Contact details', classname='collapsible', children=[ FieldPanel('contact_name'), ImageChooserPanel('contact_avatar'), FieldPanel('contact_job_title'), FieldPanel('contact_link'), FieldPanel('contact_link_button_preamble'), FieldPanel('contact_link_button_label'), ]), MultiFieldPanel( heading='Related opportunities', classname='collapsible', children=[ FieldPanel('related_opportunities_header'), HelpPanel( 'See the dedicated tab for selecting the opportunities themselves' ), ]), StreamFieldPanel('downpage_content'), MultiFieldPanel(heading='Early opportunities', classname='collapsible', children=[ FieldPanel('early_opportunities_header'), StreamFieldPanel('early_opportunities'), ]), SearchEngineOptimisationPanel() ] settings_panels = [ FieldPanel('slug'), FieldPanel('tags'), ] related_entities_panels = [ FieldRowPanel( heading='Related Opportunities', children=[ StreamFieldPanel('manually_selected_related_opportunities'), ]), ] edit_handler = make_translated_interface( content_panels=content_panels, other_panels=related_entities_panels, # These are shown as separate tabs settings_panels=settings_panels)