class Group(BasePage): parent_page_types = ["GroupIndexPage", "SimpleGroupIndexPage", "Group"] subpage_types = ["Project", "StandardPage", "Group"] # title is auto added subtitle = models.CharField( "Untertitel", max_length=80, null=True, blank=True, help_text="Z.B. eine sehr kurze Beschreibung", ) body = StreamField( StandardStreamBlock(), blank=True, verbose_name="Inhalt", ) logo = models.ForeignKey( Image, on_delete=models.SET_NULL, null=True, blank=True, related_name="+", verbose_name="Logo", # help_text="" ) contact_mail = models.EmailField( "E-Mail", null=True, blank=True, ) contact_name = models.CharField( "Name", max_length=255, null=True, blank=True, ) contact_phone = PhoneNumberField( "Telefonnummer", null=True, blank=True, ) facebook_url = FacebookProfileURLField( "Facebook-Profil", null=True, blank=True, ) instagram_url = models.URLField( "Instagram-Profil", null=True, blank=True, ) website = PrettyURLField( "externe Website", null=True, blank=True, ) address = models.CharField( "Adresse", help_text="Adresse, an dem die Gruppe zu finden ist", max_length=255, null=True, blank=True, ) list_on_group_index_page = models.BooleanField( default="True", verbose_name="Auf Netzwerk & Projekte auflisten?", ) objects = GroupManager() search_fields = BasePage.search_fields + [ index.SearchField("subtitle"), index.SearchField("body"), index.SearchField("address"), index.SearchField("contact_mail"), index.SearchField("contact_name"), index.SearchField("website"), ] def get_context(self, request): context = super().get_context(request) context["parent_group"] = Group.objects.parent_of(self).first() context["projects"] = Group.objects.live().child_of(self) context["subpages"] = (Page.objects.live().child_of(self).exclude( pk__in=context["projects"]).specific()) context["upcoming_events"] = self.get_related_upcoming_events() context["articles"] = self.articles.all().live() return context def get_related_upcoming_events(self): from events.models import EventPage now = timezone.now() return (EventPage.objects.filter( related_groups__group=self).live().filter( Q(start_datetime__date__gte=now) | Q(end_datetime__date__gte=now))) content_panels = [ FieldPanel("title", classname="full title"), FieldPanel("subtitle"), ImageChooserPanel("logo"), FieldPanel("facebook_url"), FieldPanel("instagram_url"), FieldPanel("website"), FieldPanel("address"), MultiFieldPanel( [ FieldPanel("contact_name"), FieldPanel("contact_mail"), FieldPanel("contact_phone", widget=PhoneNumberInternationalFallbackWidget), ], heading="Kontakt", ), StreamFieldPanel("body"), ] promote_panels = BasePage.promote_panels + [ FieldPanel("list_on_group_index_page"), ] class Meta: verbose_name = "Gruppe" verbose_name_plural = "Gruppen"
class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanceMixin, Page): header = models.CharField(max_length=250, blank=True) banner = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='mozfest_primary_banner', verbose_name='Hero Image', help_text= 'Choose an image that\'s bigger than 4032px x 1152px with aspect ratio 3.5:1', ) intro = RichTextField(help_text='Page intro content', blank=True) signup = models.ForeignKey( Signup, related_name='mozfestpage', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose an existing, or create a new, sign-up form') body = StreamField(base_fields) content_panels = Page.content_panels + [ FieldPanel('header'), ImageChooserPanel('banner'), FieldPanel('intro'), SnippetChooserPanel('signup'), StreamFieldPanel('body'), ] subpage_types = [ 'MozfestPrimaryPage', ] show_in_menus_default = True use_wide_template = models.BooleanField( default=False, help_text= "Make the body content wide, useful for components like directories") settings_panels = Page.settings_panels + [FieldPanel('use_wide_template')] def get_template(self, request): if self.use_wide_template: return 'mozfest/mozfest_primary_page_wide.html' return 'mozfest/mozfest_primary_page.html' def get_context(self, request, bypass_menu_buildstep=False): context = super().get_context(request) context = set_main_site_nav_information(self, context, 'MozfestHomepage') context = get_page_tree_information(self, context) # primary nav information context['menu_root'] = self context['menu_items'] = self.get_children().live().in_menu() # Also make sure that these pages always tap into the mozfest newsletter for the footer! mozfest_footer = Signup.objects.filter( name_en__iexact='mozfest').first() context['mozfest_footer'] = mozfest_footer if not bypass_menu_buildstep: context = set_main_site_nav_information(self, context, 'MozfestHomepage') return context
class Article(Page): strap = models.TextField(blank=True) content = RichTextField(blank=True, verbose_name="Content - Deprecated. Use 'MODULAR CONTENT' instead.") modular_content = StreamField([ ('paragraph', ParagraphBlock()), ('n_column_paragraph', NColumnParagraphBlock()), ('paragraph_with_map', ParagraphWithMapBlock()), ('paragraph_with_page', ParagraphWithPageBlock()), ('paragraph_with_quote', ParagraphWithBlockQuoteBlock()), ('full_width_quote', FullWidthBlockQuote()), ('video_with_quote', VideoWithQuoteBlock()), ('image_with_quote_and_paragraph', ImageWithQuoteAndParagraphBlock()), ('full_width_image', FullWidthImageBlock()), ('columnar_image_with_text', NColumnImageWithTextBlock()), ('full_width_embed', FullWidthEmbedBlock()), ('paragraph_with_embed', ParagraphWithEmbedBlock()), ('paragraph_with_raw_embed', ParagraphWithRawEmbedBlock()), ], null=True, blank=True) show_modular_content = models.BooleanField(default=False) language = models.CharField(max_length=7, choices=settings.LANGUAGES) original_published_date = models.DateField(null=True, blank=True) show_day = models.BooleanField(default=True) show_month = models.BooleanField(default=True) show_year = models.BooleanField(default=True) featured_image = models.ForeignKey('core.AffixImage', null=True, blank=True, on_delete=models.SET_NULL) show_featured_image = models.BooleanField(default=True) categories = ParentalManyToManyField("category.Category", related_name="articles_by_category") locations = ParentalManyToManyField("location.Location", related_name="articles_by_location", blank=True) content_panels = Page.content_panels + [ FieldPanel('strap'), InlinePanel('authors', label='Authors', min_num=1), FieldPanel('language'), MultiFieldPanel( [ FieldPanel('original_published_date'), FieldRowPanel( [ FieldPanel('show_day', classname="col4"), FieldPanel('show_month', classname="col4"), FieldPanel('show_year', classname="col4") ]) ], 'Date'), FieldPanel('content'), MultiFieldPanel( [ ImageChooserPanel('featured_image'), FieldPanel('show_featured_image'), ], 'Cover Image'), FieldPanel('categories'), M2MFieldPanel('locations'), ] tags = ClusterTaggableManager(through=ArticleTag, blank=True) promote_panels = Page.promote_panels + [ FieldPanel('tags'), ] search_fields = Page.search_fields + [ index.SearchField('title', partial_match=True, boost=SearchBoost.TITLE), index.SearchField('get_authors', partial_match=True, boost=SearchBoost.AUTHOR), index.SearchField('strap', partial_match=True, boost=SearchBoost.DESCRIPTION), index.SearchField('content', partial_match=True, boost=SearchBoost.CONTENT), index.SearchField('modular_content', partial_match=True, boost=SearchBoost.CONTENT), index.SearchField('get_district_from_location', partial_match=True, boost=SearchBoost.LOCATION), index.SearchField('language', partial_match=True), index.FilterField('language'), index.FilterField('get_search_type'), index.FilterField('get_categories'), index.FilterField('get_minimal_locations'), index.FilterField('get_authors_or_photographers'), index.FilterField('title'), index.FilterField('get_state_from_locations') ] def __str__(self): return self.title def get_authors(self): return [article_author.author.name for article_author in self.authors.all()] def get_author_role_map(self, **kwargs): authors_with_role = kwargs['authors_with_role'] temp = defaultdict(list) for awr in authors_with_role: role = awr.role if role: temp[role.name].append(awr.author) else: temp['None'].append(awr.author) return dict((key, tuple(val)) for key, val in temp.items()) def beginning_authors_with_role(self): return self.get_author_role_map(authors_with_role=self.authors.filter(show_in_beginning=True).all()) def end_authors_with_role(self): return self.get_author_role_map(authors_with_role=self.authors.filter(show_in_end=True).all()) def get_authors_or_photographers(self): return self.get_authors() def get_district_from_location(self): return [location.address for location in self.locations.all()] def get_minimal_locations(self): return [location.minimal_address for location in self.locations.all()] def get_state_from_locations(self): return [location.state for location in self.locations.all()] def get_context(self, request, *args, **kwargs): try: site = Site.objects.get(hostname=request.get_host()) except Site.DoesNotExist: site = Site.objects.filter(is_default_site=True)[0] return { 'article': self, 'beginning_authors_with_role': self.beginning_authors_with_role(), 'end_authors_with_role': self.end_authors_with_role(), 'request': request, 'site': site } def get_absolute_url(self): name = "article-detail" return reverse(name, kwargs={"slug": self.slug}) def get_translation(self): return get_translations_for_page(self) # Elastic search related methods def get_search_type(self): categories = [category.name for category in self.categories.all()] if 'VideoZone' in categories: return 'video' elif 'AudioZone' in categories: return 'audio' else: return 'article' def get_categories(self): return [category.name for category in self.categories.all()] def related_articles(self): if not self.pk: # In preview mode return [] max_results = getattr(settings, "MAX_RELATED_RESULTS", 4) es_backend = get_search_backend() mapping = Elasticsearch6Mapping(self.__class__) minimal_locations = "" if (self.get_minimal_locations()): minimal_locations = self.get_minimal_locations() state_locations = "" if (self.get_state_from_locations()): state_locations = self.get_state_from_locations() authors_of_article = "" if self.authors: authors_of_article = self.get_authors() query = { "track_scores": "true", "query": { "bool": { "must": [ { "multi_match": { "fields": ["*language_filter"], "query": self.language } }, {"term": {"live_filter": "true"}} ], "must_not": [ {"term": {"title_filter": self.title}} ], "should": [ { "multi_match": { "fields": ["*get_authors_or_photographers_filter"], "query": ' '.join(authors_of_article), "type": "cross_fields", "operator": "or" } }, { "multi_match": { "fields": ["*get_authors"], "query": ' '.join(authors_of_article), "type": "cross_fields", "operator": "or" } }, { "multi_match": { "fields": ["*get_minimal_locations_filter"], "query": ' '.join(minimal_locations), "type": "cross_fields", "operator": "and" } }, { "multi_match": { "fields": ["*get_state_from_locations_filter"], "query": ' '.join(state_locations), "type": "cross_fields", "operator": "and" } }, { "match": { "title": self.title } } ], "minimum_should_match": 1 } }, "sort": [ {"_score": {"order": "desc"}}, {"article_article__get_authors_or_photographers_filter": {"order": "desc"}}, {"album_album__get_authors_or_photographers_filter": {"order": "desc"}}, {"face_face__get_authors_or_photographers_filter": {"order": "desc"}}, {"first_published_at_filter": "desc"} ] } try: mlt = es_backend.es.search( doc_type=mapping.get_document_type(), body=query ) except ConnectionError: return [] # Get pks from results pks = [hit['_source']['pk'] for hit in mlt['hits']['hits']][:max_results] # Initialise results dictionary results = dict((str(pk), None) for pk in pks) # Find objects in database and add them to dict queryset = self._meta.default_manager.filter(pk__in=pks) for obj in queryset: results[str(obj.pk)] = obj # Return results in order given by ElasticSearch return [results[str(pk)] for pk in pks if results[str(pk)]]
class MeetingPage(Page): OPEN = 'O' EXECUTIVE = 'E' HEARING = 'H' MEETING_TYPE_CHOICES = ( (OPEN, 'Open meeting'), (EXECUTIVE, 'Executive session'), (HEARING, 'Hearing'), ) date = models.DateField(default=datetime.date.today) end_date = models.DateField(null=True, blank=True) time = models.TimeField(null=True, blank=True, help_text='If no time is entered,\ the time will be set to 10 a.m.') meeting_type = models.CharField(max_length=2, choices=MEETING_TYPE_CHOICES, default=OPEN) additional_information = models.TextField(blank=True, help_text='This field\ accepts html') draft_minutes_links = models.TextField( blank=True, help_text='URLs separated by a newline') approved_minutes_date = models.DateField(null=True, blank=True) approved_minutes_link = models.URLField(blank=True) sunshine_act_links = models.TextField( blank=True, help_text='URLs separated by a newline') live_video_embed = models.CharField(blank=True, max_length=255,\ help_text='Youtube video ID of the live-stream of the meeting') imported_html = StreamField([('html_block', blocks.RawHTMLBlock())], null=True, blank=True) sunshine_act_doc_upld = StreamField( [('sunshine_act_upld', DocumentChooserBlock(required=False))], null=True, blank=True, ) full_video_url = models.URLField(blank=True) full_audio_url = models.URLField(blank=True) mtg_transcript_url = models.URLField(blank=True) homepage_hide = models.BooleanField(default=False) agenda = StreamField( [('agenda_item', blocks.StructBlock([ ('item_title', blocks.TextBlock(required=True)), ('item_text', blocks.RichTextBlock(required=False)), ('item_audio', DocumentChooserBlock(required=False)), ('item_video', blocks.URLBlock(required=False, help_text='Add a Youtube URL to a specific\ time in a video for this agenda item')), ]))], blank=True, null=True) content_panels = Page.content_panels + [ FieldPanel('additional_information'), StreamFieldPanel('agenda'), MultiFieldPanel([ FieldPanel('date'), FieldPanel('end_date'), FieldPanel('time'), FieldPanel('meeting_type'), ], heading='Meeting details', classname='collapsible collapsed'), MultiFieldPanel( [ #FieldPanel('sunshine_act_links'), StreamFieldPanel('sunshine_act_doc_upld'), ], heading='Sunshine notices', classname='collapsible collapsed'), MultiFieldPanel([ FieldPanel('draft_minutes_links'), FieldPanel('approved_minutes_link'), FieldPanel('approved_minutes_date'), ], heading='Minutes', classname='collapsible collapsed'), MultiFieldPanel([ FieldPanel('full_video_url'), FieldPanel('full_audio_url'), FieldPanel('mtg_transcript_url'), FieldPanel('live_video_embed') ], heading='Meeting media', classname='collapsible collapsed'), MultiFieldPanel([ FieldPanel('imported_html'), ], heading='Imported meeting content', classname='collapsible collapsed') ] promote_panels = Page.promote_panels + [FieldPanel('homepage_hide')] search_fields = Page.search_fields + [ index.FilterField('title'), index.FilterField('meeting_type'), index.FilterField('date'), index.SearchField('imported_html'), index.SearchField('agenda') ] @property def get_update_type(self): return constants.update_types['commission-meeting'] @property def content_section(self): return 'about' @property def social_image_identifier(self): return 'meeting-page'
class CommissionerPage(Page): first_name = models.CharField(max_length=255, default='', blank=False) middle_initial = models.CharField(max_length=255, blank=True) last_name = models.CharField(max_length=255, default='', blank=False) picture = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') sworn_in = models.DateField(null=True, blank=True) term_expiration = models.DateField(null=True, blank=True) reappointed_dates = models.CharField(max_length=255, blank=True) party_affiliation = models.CharField(max_length=2, choices=( ('D', 'Democrat'), ('R', 'Republican'), ('I', 'Independent'), )) commissioner_title = models.CharField(max_length=255, blank=True) commissioner_bio = StreamField([('paragraph', blocks.RichTextBlock())], null=True, blank=True) commissioner_email = models.CharField(max_length=255, blank=True) commissioner_phone = models.CharField(max_length=255, null=True, blank=True) commissioner_twitter = models.CharField(max_length=255, null=True, blank=True) content_panels = Page.content_panels + [ FieldPanel('first_name'), FieldPanel('middle_initial'), FieldPanel('last_name'), ImageChooserPanel('picture'), FieldPanel('sworn_in'), FieldPanel('term_expiration'), FieldPanel('reappointed_dates'), FieldPanel('party_affiliation'), FieldPanel('commissioner_title'), StreamFieldPanel('commissioner_bio'), FieldPanel('commissioner_email'), FieldPanel('commissioner_phone'), FieldPanel('commissioner_twitter'), ] def get_context(self, request): context = super(CommissionerPage, self).get_context(request) # Breadcrumbs for Commissioner pages context['ancestors'] = [{ 'title': 'About the FEC', 'url': '/about/', }, { 'title': 'Leadership and Structure', 'url': '/about/leadership-and-structure', }, { 'title': 'All Commissioners', 'url': '/about/leadership-and-structure/commissioners', }] return context
class BlogPage(Page): """ A Blog Page We access the People object with an inline panel that references the ParentalKey's related_name in BlogPeopleRelationship. More docs: http://docs.wagtail.io/en/latest/topics/pages.html#inline-models """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) subtitle = models.CharField(blank=True, max_length=255) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date_published = models.DateField("Date article published", blank=True, null=True) content_panels = Page.content_panels + [ FieldPanel('subtitle', classname="full"), FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('date_published'), InlinePanel('blog_person_relationship', label="Author(s)", panels=None, min_num=1), FieldPanel('tags'), ] search_fields = Page.search_fields + [ index.SearchField('body'), ] def authors(self): """ Returns the BlogPage's related People. Again note that we are using the ParentalKey's related_name from the BlogPeopleRelationship model to access these objects. This allows us to access the People objects with a loop on the template. If we tried to access the blog_person_ relationship directly we'd print `blog.BlogPeopleRelationship.None` """ authors = [n.people for n in self.blog_person_relationship.all()] return authors @property def get_tags(self): """ Similar to the authors function above we're returning all the tags that are related to the blog post into a list we can access on the template. We're additionally adding a URL to access BlogPage objects with that tag """ tags = self.tags.all() for tag in tags: tag.url = '/' + '/'.join( s.strip('/') for s in [self.get_parent().url, 'tags', tag.slug]) return tags # Specifies parent to BlogPage as being BlogIndexPages parent_page_types = ['BlogIndexPage'] # Specifies what content types can exist as children of BlogPage. # Empty list means that no child content types are allowed. subpage_types = []
class Story(Page): summary = models.CharField(max_length=1000, blank=True) intro = models.CharField(max_length=1000, blank=True) body = StreamField(StoryStreamBlock(), blank=True) skip_home = models.BooleanField(default=None) # location = models.CharField(max_length=255, blank=True) translation_language = models.CharField(max_length=2, blank=True) translation_for = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True, related_name='translations') format = models.CharField(max_length=50, blank=True) tags = ClusterTaggableManager(through=StoryTag, blank=True) author_ids = JSONField(default=list) type = models.ForeignKey(StoryType, on_delete=models.SET_NULL, blank=True, null=True) template = models.ForeignKey(StoryTemplate, on_delete=models.PROTECT) dossier = models.ForeignKey(StoryDossier, on_delete=models.SET_NULL, blank=True, null=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='stories_related' ) feed_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) @property def authors(self): rv = [] for pk in self.author_ids: try: author = Author.objects.get(pk=pk) except Author.DoesNotExist: continue else: rv.append(author) return rv @property def stories_index(self): # Find closest ancestor which is a blog index return self.get_ancestors().type(StoriesIndex).last() @property def summary_or_intro(self): return self.summary or self.intro search_fields = Page.search_fields + [ index.SearchField('summary'), index.SearchField('intro'), index.SearchField('body'), index.SearchField('authors'), ] content_panels = Page.content_panels + [ FieldPanel('summary', classname='full'), FieldPanel('intro', classname='full'), ImageChooserPanel('image'), MultiFieldPanel([ FieldPanel('author_ids', widget=AuthorsWidget), FieldPanel('type'), FieldPanel('dossier'), FieldPanel('first_published_at'), ]), InlinePanel('locations', label="Locations"), ] promote_panels = Page.promote_panels + [ MultiFieldPanel([ FieldPanel('tags'), ImageChooserPanel('feed_image'), FieldPanel('skip_home'), ], heading="Extra"), MultiFieldPanel([ InlinePanel('related_pages', label="Related Pages"), InlinePanel('related_links', label="External links"), ], heading="Related"), ] blocks_panels = [ StreamFieldPanel('body', classname='full'), ] settings_panels = Page.settings_panels + [ PageChooserPanel('translation_for'), FieldPanel('template'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content details'), ObjectList(blocks_panels, heading='Content'), ObjectList(promote_panels, heading='Promote'), ObjectList(settings_panels, heading='Settings', classname="settings"), ]) def serve(self, request): template = f'blacktail/story/{self.template}.html' return render(request, template, { 'page': self, })
class HomePage(BasePage): # Only allow creating HomePages at the root level parent_page_types = ['wagtailcore.Page'] NUM_RELATED = 6 strapline = models.CharField(blank=True, max_length=255) strapline_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT) strapline_link_text = models.CharField(max_length=255) news_title = models.CharField(blank=True, max_length=255) news_link = models.ForeignKey('wagtailcore.Page', blank=True, null=True, related_name='+', on_delete=models.PROTECT) news_link_text = models.CharField(blank=True, max_length=255) our_work_title = models.CharField(max_length=255) our_work = StreamField([ ('work', OurWorkBlock()), ]) our_work_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT) our_work_link_text = models.CharField(max_length=255) funds_title = models.CharField(max_length=255) funds_intro = models.TextField(blank=True) funds_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT) funds_link_text = models.CharField(max_length=255) labs_title = models.CharField(max_length=255) labs_intro = models.TextField(blank=True) labs_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT) labs_link_text = models.CharField(max_length=255) rfps_title = models.CharField(max_length=255) rfps_intro = models.TextField(blank=True) search_fields = BasePage.search_fields + [ index.SearchField('strapline'), ] content_panels = BasePage.content_panels + [ MultiFieldPanel([ FieldPanel('strapline'), PageChooserPanel('strapline_link'), FieldPanel('strapline_link_text'), ], heading='Introduction'), MultiFieldPanel([ FieldPanel('news_title'), PageChooserPanel('news_link'), FieldPanel('news_link_text'), ], heading='News'), MultiFieldPanel([ FieldPanel('our_work_title'), StreamFieldPanel('our_work'), PageChooserPanel('our_work_link'), FieldPanel('our_work_link_text'), ], heading='Our Work'), MultiFieldPanel([ FieldPanel('funds_title'), FieldPanel('funds_intro'), InlinePanel( 'promoted_funds', label='Promoted Funds', max_num=NUM_RELATED), PageChooserPanel('funds_link'), FieldPanel('funds_link_text'), ], heading='Funds'), MultiFieldPanel([ FieldPanel('labs_title'), FieldPanel('labs_intro'), InlinePanel( 'promoted_labs', label='Promoted Labs', max_num=NUM_RELATED), PageChooserPanel('labs_link'), FieldPanel('labs_link_text'), ], heading='Labs'), MultiFieldPanel([ FieldPanel('rfps_title'), FieldPanel('rfps_intro'), InlinePanel( 'promoted_rfps', label='Promoted RFPs', max_num=NUM_RELATED), ], heading='Labs'), ] def get_related(self, page_type, base_list): related = page_type.objects.filter( id__in=base_list.values_list('page')).live().public() yield from related selected = list(related.values_list('id', flat=True)) extra_needed = self.NUM_RELATED - len(selected) extra_qs = page_type.objects.public().live().exclude(id__in=selected) displayed = 0 for page in self.sorted_by_deadline(extra_qs): if page.is_open: yield page displayed += 1 if displayed >= extra_needed: break def sorted_by_deadline(self, qs): def sort_by_deadline(value): try: return value.deadline or datetime.date.max except AttributeError: return datetime.date.max yield from sorted(qs, key=sort_by_deadline) def pages_from_related(self, related): for related in related.all(): if related.page.live and related.page.public: yield related.page.specific def get_context(self, *args, **kwargs): context = super().get_context(*args, **kwargs) context['lab_list'] = list( self.get_related(LabPage, self.promoted_labs)) context['fund_list'] = list( self.get_related(FundPage, self.promoted_funds)) context['rfps_list'] = list( self.get_related(RFPPage, self.promoted_rfps)) return context
class ResetNetworkResourcePage(ResetNetworkBasePage): class Meta: verbose_name = "Reset Network Resource Page" parent_page_types = ['reset_network_resources.ResetNetworkResourcesPage'] subpage_types = [] content_heading = models.CharField(verbose_name='Heading', max_length=255, blank=False) content_image = models.ForeignKey('images.CustomImage', verbose_name='Image', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) content_text = StreamField([('text', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('embed', EmbedBlock())]) category = models.CharField(verbose_name='Tag', max_length=255, null=True, blank=True) categories = ClusterTaggableManager( through=ResetNetworkResourcePageCategory, blank=True) content_assets_heading = models.CharField(verbose_name='Heading', max_length=255, blank=False) content_links_heading = models.CharField(verbose_name='Heading', max_length=255, blank=False) card_heading = models.CharField(max_length=100, blank=False) card_text = models.TextField(blank=True) card_image = models.ForeignKey('images.CustomImage', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('content_heading'), ImageChooserPanel('content_image'), StreamFieldPanel('content_text'), FieldPanel('categories'), ], heading='Content'), MultiFieldPanel([ FieldPanel('content_assets_heading'), InlinePanel('reset_network_resource_page_assets', label='Asset', heading='Assets'), ], 'Content - Assets'), MultiFieldPanel([ FieldPanel('content_links_heading'), InlinePanel('reset_network_resource_page_links', label='Link', heading='Links'), ], 'Content - Links'), MultiFieldPanel([ FieldPanel('card_heading'), FieldPanel('card_text'), ImageChooserPanel('card_image'), FieldPanel('category'), ], heading='Card Details'), ]
class Track(index.Indexed, TimeStampMixin): title = models.CharField(null=True, blank=False, max_length=250) audio_file = models.FileField(upload_to="tracks/", blank=True, validators=[validate_audio_file]) audio_channel = models.CharField(null=True, blank=True, max_length=250) audio_format = models.CharField(null=True, blank=True, max_length=250) audio_codec = models.CharField(null=True, blank=True, max_length=250) audio_bitrate = models.CharField(null=True, blank=True, max_length=250) description = models.TextField(null=True, blank=True) tags = StreamField([("tag", TagBlock(required=True, icon="tag"))], blank=True) attendees = StreamField( [("attendee", AttendeeBlock(required=True, icon="user"))], blank=True) transcript = models.TextField(null=True, blank=True) pac = ParentalKey("ProjectAudioChannel", related_name="tracks", on_delete=models.CASCADE) graphql_fields = [ GraphQLString("title", required=True), GraphqlDatetime("created_at", required=True), GraphQLString("audio_file_url"), GraphQLString("audio_channel"), GraphQLString("audio_format"), GraphQLString("audio_codec"), GraphQLString("audio_bitrate"), GraphQLString("description"), GraphQLStreamfield("tags"), GraphQLStreamfield("attendees"), GraphQLString("transcript"), GraphQLForeignKey("pac", "track.ProjectAudioChannel"), ] search_fields = [ index.SearchField("title"), index.SearchField("created_at"), index.SearchField("description"), index.SearchField("tags"), index.SearchField("attendees"), index.SearchField("transcript"), index.FilterField("pac"), index.FilterField("snekuser_id"), ] panels = [ FieldPanel("title"), ReadOnlyPanel("created_at"), FieldPanel("audio_file"), FieldPanel("audio_channel"), FieldPanel("audio_format"), FieldPanel("audio_codec"), FieldPanel("audio_bitrate"), FieldPanel("description"), StreamFieldPanel("tags"), StreamFieldPanel("attendees"), FieldPanel("transcript"), FieldPanel("pac"), ] def audio_file_url(self, info, **kwargs): return ("%s%s" % (settings.BASE_URL, self.audio_file.url) if self.audio_file.name else None) def __str__(self): return f"{self.title}" @classmethod @login_required def bifrost_queryset(cls, info, **kwargs): return cls.objects.filter(pac__members=info.context.user)
class ArticlePage(RoutablePageMixin, Page): headline = RichTextField(features=["italic"]) subdeck = RichTextField(features=["italic"], null=True, blank=True) kicker = models.ForeignKey(Kicker, null=True, blank=True, on_delete=models.PROTECT) body = StreamField( [ ("paragraph", RichTextBlock()), ("photo", PhotoBlock()), ("photo_gallery", ListBlock(GalleryPhotoBlock(), icon="image")), ("embed", EmbeddedMediaBlock()), ], blank=True, ) summary = RichTextField( features=["italic"], null=True, blank=True, help_text= "Displayed on the home page or other places to provide a taste of what the article is about.", ) featured_image = models.ForeignKey( CustomImage, null=True, blank=True, on_delete=models.PROTECT, help_text="Shown at the top of the article and on the home page.", ) featured_caption = RichTextField(features=["italic"], blank=True, null=True) content_panels = [ MultiFieldPanel( [FieldPanel("headline", classname="title"), FieldPanel("subdeck")]), MultiFieldPanel( [ InlinePanel( "authors", panels=[ AutocompletePanel("author", target_model="core.Contributor") ], label="Author", ), AutocompletePanel("kicker", target_model="core.Kicker"), ImageChooserPanel("featured_image"), FieldPanel("featured_caption"), ], heading="Metadata", classname="collapsible", ), FieldPanel("summary"), StreamFieldPanel("body"), ] search_fields = Page.search_fields + [ index.SearchField("headline"), index.SearchField("subdeck"), index.SearchField("body"), index.SearchField("summary"), index.RelatedFields("kicker", [index.SearchField("title")]), index.SearchField("get_author_names", partial_match=True), ] subpage_types = [] def clean(self): super().clean() soup = BeautifulSoup(self.headline, "html.parser") self.title = soup.text @route(r"^$") def post_404(self, request): """Return an HTTP 404 whenever the page is accessed directly. This is because it should instead by accessed by its date-based path, i.e. `<year>/<month>/<slug>/`.""" raise Http404 def set_url_path(self, parent): """Make sure the page knows its own path. The published date might not be set, so we have to take that into account and ignore it if so.""" date = self.get_published_date() or timezone.now() self.url_path = f"{parent.url_path}{date.year}/{date.month:02d}/{self.slug}/" return self.url_path def serve_preview(self, request, mode_name): request.is_preview = True return self.serve(request) def get_context(self, request): context = super().get_context(request) context["authors"] = self.get_authors() return context def get_authors(self): return [r.author for r in self.authors.select_related("author")] def get_author_names(self): return [a.name for a in self.get_authors()] def get_published_date(self): return (self.go_live_at or self.first_published_at or getattr(self.get_latest_revision(), "created_at", None)) def get_text_html(self): """Get the HTML that represents paragraphs within the article as a string.""" builder = "" for block in self.body: if block.block_type == "paragraph": builder += str(block.value) return builder def get_plain_text(self): builder = "" soup = BeautifulSoup(self.get_text_html(), "html.parser") for para in soup.findAll("p"): builder += para.text builder += " " return builder[:-1] def get_related_articles(self): found_articles = [] related_articles = [] current_article_text = self.get_plain_text() if current_article_text is not None: current_article_words = set(current_article_text.split(" ")) authors = self.get_authors() for author in authors: articles = author.get_articles() for article in articles: if article.headline != self.headline: text_to_match = article.get_plain_text() article_words = set(text_to_match.split(" ")) found_articles.append(( article, len( list( current_article_words.intersection( article_words))), )) found_articles.sort(key=operator.itemgetter(1), reverse=True) for i in range(min(4, len(found_articles))): related_articles.append(found_articles[i][0]) return related_articles def get_first_chars(self, n=100): """Convert the body to HTML, extract the text, and then build a string out of it until we have at least n characters. If this isn't possible, then return None.""" text = self.get_plain_text() if len(text) < n: return None punctuation = {".", "!"} for i in range(n, len(text)): if text[i] in punctuation: if i + 1 == len(text): return text elif text[i + 1] == " ": return text[:i + 1] return None def get_meta_tags(self): tags = {} tags["og:type"] = "article" tags["og:title"] = self.title tags["og:url"] = self.full_url tags["og:site_name"] = self.get_site().site_name # description: either the article's summary or first paragraph if self.summary is not None: soup = BeautifulSoup(self.summary, "html.parser") tags["og:description"] = soup.get_text() tags["twitter:description"] = soup.get_text() else: first_chars = self.get_first_chars() if first_chars is not None: tags["og:description"] = first_chars tags["twitter:description"] = first_chars # image if self.featured_image is not None: # pylint: disable=E1101 rendition = self.featured_image.get_rendition("min-1200x1200") rendition_url = self.get_site().root_url + rendition.url tags["og:image"] = rendition_url tags["twitter:image"] = rendition_url else: tags["og:image"] = (self.get_site().root_url + "/static/images/minimal_logo_tag_padding.png") tags["twitter:image"] = ( self.get_site().root_url + "static/images/minimal_logo_tag_padding.png") tags["twitter:site"] = "@rpipoly" tags["twitter:title"] = self.title if "twitter:description" in tags and "twitter:image" in tags: tags["twitter:card"] = "summary_large_image" else: tags["twitter:card"] = "summary" return tags
class PromoLandingPage(Page): TMP_CURRENCIES = ( ('USD', '$'), ('GBP', '£'), ('EUR', '€'), ) hero_title = models.CharField(max_length=100) hero_subtitle = models.CharField(max_length=200, blank=True, null=True) overview = StreamField(blocks.StreamBlock( [('freeform', blocks.RichTextBlock()), ('two_three_column_grid', ColumnBlocks())], max_num=3), null=True, blank=True) body = StreamField(blocks.StreamBlock([('content', ContentBlock())], max_num=3), verbose_name="Features") extra_content = StreamField(blocks.StreamBlock( [('freeform', blocks.RichTextBlock()), ('two_three_column_grid', ColumnBlocks())], max_num=3), null=True, blank=True) hero_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') plan = models.ForeignKey(PromoPlan, null=True, blank=True, on_delete=models.SET_NULL) price = models.DecimalField(decimal_places=2, max_digits=10) currency = models.CharField(max_length=3, choices=TMP_CURRENCIES) promo_duration = models.DateTimeField( help_text="Set the time when you want this to expire.", blank=True, null=True) coupon = models.CharField(max_length=50, blank=True, null=True) benefits = RichTextField(features=['h4', 'bold', 'italic', 'ol', 'ul'], blank=True, null=True) CTA = models.CharField(max_length=200, default="Start Your Plan Now.") created_date = models.DateTimeField(editable=False, auto_now_add=True) modified_date = models.DateTimeField(editable=False, db_index=True, auto_now=True) content_panels = [ ImageChooserPanel('hero_image'), FieldPanel('title'), FieldPanel('hero_title'), FieldPanel('hero_subtitle'), StreamFieldPanel('overview'), StreamFieldPanel('body'), StreamFieldPanel('extra_content'), MultiFieldPanel([ FieldPanel('plan'), FieldPanel('price'), FieldPanel('currency'), FieldPanel('coupon'), FieldPanel('promo_duration'), FieldPanel('CTA'), FieldPanel('benefits') ], heading="Promo Plan Details", classname="collapsible"), ]
class PrimaryPage(FoundationMetadataPageMixin, Page): """ Basically a straight copy of modular page, but with restrictions on what can live 'under it'. Ideally this is just PrimaryPage(ModularPage) but setting that up as a migration seems to be causing problems. """ header = models.CharField(max_length=250, blank=True) banner = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='primary_banner', verbose_name='Hero Image', help_text= 'Choose an image that\'s bigger than 4032px x 1152px with aspect ratio 3.5:1', ) intro = models.CharField( max_length=250, blank=True, help_text='Intro paragraph to show in hero cutout box') narrowed_page_content = models.BooleanField( default=False, help_text= 'For text-heavy pages, turn this on to reduce the overall width of the content on the page.' ) zen_nav = models.BooleanField( default=False, help_text= 'For secondary nav pages, use this to collapse the primary nav under a toggle hamburger.' ) body = StreamField(base_fields) settings_panels = Page.settings_panels + [ MultiFieldPanel([ FieldPanel('narrowed_page_content'), ]), MultiFieldPanel([ FieldPanel('zen_nav'), ]) ] content_panels = Page.content_panels + [ FieldPanel('header'), ImageChooserPanel('banner'), FieldPanel('intro'), StreamFieldPanel('body'), ] subpage_types = ['PrimaryPage', 'RedirectingPage', 'BanneredCampaignPage'] show_in_menus_default = True def get_context(self, request): context = super().get_context(request) context = set_main_site_nav_information(self, context, 'Homepage') context = get_page_tree_information(self, context) return context
class SimpleGroupIndexPage(BasePage): """GroupIndexPage für Lüneburg""" # title is auto added subpage_types = ["Group", "Project"] parent_page_types = ["HomePage"] max_count_per_parent = 1 heading = models.CharField("Überschrift", max_length=255, blank=True) highlight_in_heading = models.CharField( "Hervorhebungen in der Überschrift", help_text= "Wiederhole Text aus der Überschrift der farblich hervorgehoben werden soll", blank=True, max_length=255, ) subtitle = models.CharField("Untertitel", max_length=255, blank=True) groups_text = RichTextField( blank=True, verbose_name="Gruppen-Text", help_text="Wird bei den Gruppen angezeigt.", ) projects_text = RichTextField( blank=True, verbose_name="Projekte-Text", help_text="Wird bei den Projekten angezeigt.", ) coops_text = RichTextField( blank=True, verbose_name="Kooperationen-Text", help_text="Wird bei den Kooperationen angezeigt.", ) coops = StreamField([("coop", CoopBlock())], blank=True, verbose_name="Kooperationen") class Meta: verbose_name = "Auflistung von Gruppen (Lüneburg)" def get_context(self, request): context = super().get_context(request) context["projects"] = ( Project.objects.descendant_of(self).live().filter( list_on_group_index_page=True)) context["groups"] = (Group.objects.child_of(self).live().filter( list_on_group_index_page=True).exclude( pk__in=[p.pk for p in context["projects"]])) return context content_panels = [ FieldPanel("title"), MultiFieldPanel( [ FieldPanel("heading"), FieldPanel("highlight_in_heading"), FieldPanel("subtitle"), ], "Header", ), FieldPanel("groups_text"), FieldPanel("projects_text"), FieldPanel("coops_text"), StreamFieldPanel("coops"), ]
class HomePage(Page): parent_page_types = ["wagtailcore.Page"] subpage_types = [ "flex.FlexPage", "services.service_listing_page", "contact.ContactPage" ] max_count = 1 lead_text = models.CharField( max_length=140, blank=True, help_text='Subheading text under the banner title', ) button = models.ForeignKey( 'wagtailcore.Page', blank=True, null=True, related_name='+', help_text='Select an optional page to link to', on_delete=models.SET_NULL, ) button_text = models.CharField( max_length=50, default='Read more', blank=False, help_text='Button text', ) banner_background_image = models.ForeignKey( 'wagtailimages.Image', blank=False, null=True, related_name='+', help_text='The banner background image', on_delete=models.SET_NULL, ) body = StreamField( [("title", blocks.TitleBlock()), ("cards", blocks.CardsBlock()), ("image_and_text", blocks.ImageAndTextBlock()), ("cta", blocks.CallToActionBlock()), ("testimonial", SnippetChooserBlock(target_model='testimonials.Testimonial', template="streams/testimonial_block.html")), ("pricing_table", blocks.PricingTableBlock(table_options=new_table_options, ))], null=True, blank=True) content_panels = Page.content_panels + [ FieldPanel("lead_text"), PageChooserPanel("button"), FieldPanel("button_text"), ImageChooserPanel("banner_background_image"), StreamFieldPanel("body"), ] def save(self, *args, **kwargs): key = make_template_fragment_key( "home_page_streams", [self.id], ) cache.delete(key) return super().save(*args, **kwargs)
class NewsArticle(Page): date = models.DateField("Post date") heading = models.CharField(max_length=250, help_text="Heading displayed on website") subheading = models.CharField(max_length=250, blank=True, null=True) author = models.CharField(max_length=250) featured_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text="Image should be 1200 x 600") featured_image_alt_text = models.CharField(max_length=250, blank=True, null=True) def get_article_image(self): return build_image_url(self.featured_image) article_image = property(get_article_image) tags = ClusterTaggableManager(through=NewsArticleTag, blank=True) body = StreamField(BlogStreamBlock()) pin_to_top = models.BooleanField(default=False) promote_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') @property def first_paragraph(self): paragraphs = [] for block in self.body: if block.block_type == 'paragraph': paragraphs.append(str(block.value)) first_paragraph_parsed = [] soup = BeautifulSoup(paragraphs[0], "html.parser") for tag in soup.findAll('p'): first_paragraph_parsed.append(tag) return str(first_paragraph_parsed[0]) search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('tags'), ] content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('title'), FieldPanel('heading'), FieldPanel('subheading'), FieldPanel('author'), ImageChooserPanel('featured_image'), FieldPanel('featured_image_alt_text'), FieldPanel('tags'), StreamFieldPanel('body'), FieldPanel('pin_to_top'), ] promote_panels = [ FieldPanel('slug'), FieldPanel('seo_title'), FieldPanel('search_description'), ImageChooserPanel('promote_image') ] api_fields = [ APIField('date'), APIField('title'), APIField('heading'), APIField('subheading'), APIField('author'), APIField('article_image'), APIField('featured_image_small', serializer=ImageRenditionField('width-420', source='featured_image')), APIField('featured_image_alt_text'), APIField('tags'), APIField('body'), APIField('pin_to_top'), APIField('slug'), APIField('seo_title'), APIField('search_description'), APIField('promote_image') ] parent_page_types = ['news.NewsIndex'] def save(self, *args, **kwargs): if self.pin_to_top: current_pins = self.__class__.objects.filter(pin_to_top=True) for pin in current_pins: if pin != self: pin.pin_to_top = False pin.save() return super(NewsArticle, self).save(*args, **kwargs) def get_sitemap_urls(self, request=None): return [{ 'location': '{}/blog/{}/'.format( Site.find_for_request(request).root_url, self.slug), 'lastmod': (self.last_published_at or self.latest_revision_created_at), }]
class MentorPage(Page): CATEGORY_CHOICES = (('signing', '签约导师'), ('cooperation', '合作导师'), ('infinite', '无界导师')) date = models.DateField('发表日期') intro = RichTextField('简介', blank=True) category = models.CharField('导师类别', choices=CATEGORY_CHOICES, max_length=16, default='infinite') mentortitle = models.CharField('导师头衔', max_length=64, blank=True) body = StreamField([ ('标题', blocks.CharBlock(classname="full title")), ('段落', blocks.RichTextBlock()), ('图片', ImageChooserBlock()), ]) tags = ClusterTaggableManager(through=MentorPageTag, blank=True) class Meta: verbose_name = '老师详情页' verbose_name_plural = verbose_name def thumbnail_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None def banner_image(self): gallery_items = self.gallery_images.all() if len(gallery_items) < 2: return None else: return gallery_items[1].image search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), ] content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('date'), FieldPanel('tags'), ], heading="内容属性"), MultiFieldPanel([ FieldPanel('mentortitle'), FieldPanel('category'), FieldPanel('intro'), ], heading='导师简介'), StreamFieldPanel('body'), InlinePanel('gallery_images', label="Gallery images"), ] def get_context(self, request): wallpaper = Image.objects.filter( tags__name="导师壁纸").order_by('-created_at')[0] # Update template context context = super().get_context(request) context['wallpaper'] = wallpaper return context
class PressIndex(Page): press_kit = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') def get_press_kit(self): return build_image_url(self.press_kit) press_kit_url = property(get_press_kit) press_inquiry_name = models.CharField(max_length=255, blank=True, null=True) press_inquiry_phone = models.CharField(max_length=255) press_inquiry_email = models.EmailField() experts_heading = models.CharField(max_length=255) experts_blurb = models.TextField() mentions = StreamField([ ('mention', NewsMentionBlock(icon='document')), ], null=True) promote_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') def get_sitemap_urls(self, request=None): return [{ 'location': '{}/press/'.format(Site.find_for_request(request).root_url), 'lastmod': (self.last_published_at or self.latest_revision_created_at), }] @property def releases(self): releases = PressRelease.objects.live().child_of(self) releases_data = {} for release in releases: releases_data['press/{}'.format(release.slug)] = { 'detail_url': '/apps/cms/api/v2/pages/{}/'.format(release.pk), 'date': release.date, 'heading': release.heading, 'excerpt': release.excerpt, 'author': release.author, } return releases_data content_panels = Page.content_panels + [ DocumentChooserPanel('press_kit'), FieldPanel('press_inquiry_name'), FieldPanel('press_inquiry_phone'), FieldPanel('press_inquiry_email'), FieldPanel('experts_heading'), FieldPanel('experts_blurb'), InlinePanel('experts_bios', label="Experts"), StreamFieldPanel('mentions'), InlinePanel('mission_statements', label="Mission Statement"), ] promote_panels = [ FieldPanel('slug'), FieldPanel('seo_title'), FieldPanel('search_description'), ImageChooserPanel('promote_image') ] api_fields = [ APIField('press_kit'), APIField('press_kit_url'), APIField('releases'), APIField('slug'), APIField('seo_title'), APIField('search_description'), APIField('promote_image'), APIField('experts_heading'), APIField('experts_blurb'), APIField('experts_bios'), APIField('mentions'), APIField('mission_statements'), APIField('press_inquiry_name'), APIField('press_inquiry_phone'), APIField('press_inquiry_email') ] subpage_types = ['news.PressRelease'] parent_page_types = ['pages.HomePage'] max_count = 1
class ExternalArticle(ExternalContent): class Meta: verbose_name = "External Post" verbose_name_plural = "External Posts" resource_type = "article" # if you change this, amend the related CSS, too date = DateField( "External Post date", default=datetime.date.today, help_text="The date the external post was published", ) authors = StreamField( StreamBlock( [ ("author", PageChooserBlock(target_model="people.Person")), ("external_author", ExternalAuthorBlock()), ], required=False, ), blank=True, null=True, help_text=("Optional list of the external post's authors. " "Use ‘External author’ to add " "guest authors without creating a profile on the system"), ) read_time = CharField( max_length=30, blank=True, help_text=("Optional, approximate read-time for this external post, " "e.g. “2 mins”. This is shown as a small hint when the " "external post is displayed as a card."), ) meta_panels = [ FieldPanel("date"), StreamFieldPanel("authors"), MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text="The topic pages this external post will appear on", ), FieldPanel("read_time"), ] settings_panels = BasePage.settings_panels + [FieldPanel("slug")] edit_handler = TabbedInterface([ ObjectList(ExternalContent.card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) @property def article(self): return self @property def month_group(self): return self.date.replace(day=1) def has_author(self, person): for author in self.authors: # pylint: disable=not-an-iterable if author.block_type == "author" and str(author.value) == str( person.title): return True return False
class PressRelease(Page): date = models.DateField("PR date") heading = models.CharField(max_length=250, help_text="Heading displayed on website") subheading = models.CharField(max_length=250, blank=True, null=True) author = models.CharField(max_length=250) featured_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) featured_image_alt_text = models.CharField(max_length=250, blank=True, null=True) def get_article_image(self): return build_image_url(self.featured_image) article_image = property(get_article_image) excerpt = models.CharField(max_length=255) body = StreamField(BlogStreamBlock()) promote_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') def get_sitemap_urls(self, request=None): return [{ 'location': '{}/press/{}'.format( Site.find_for_request(request).root_url, self.slug), 'lastmod': (self.last_published_at or self.latest_revision_created_at), }] search_fields = Page.search_fields + [ index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('title'), FieldPanel('heading'), FieldPanel('subheading'), FieldPanel('author'), ImageChooserPanel('featured_image'), FieldPanel('featured_image_alt_text'), FieldPanel('excerpt'), StreamFieldPanel('body'), ] promote_panels = [ FieldPanel('slug'), FieldPanel('seo_title'), FieldPanel('search_description'), ImageChooserPanel('promote_image') ] api_fields = [ APIField('date'), APIField('title'), APIField('heading'), APIField('subheading'), APIField('author'), APIField('article_image'), APIField('featured_image_alt_text'), APIField('excerpt'), APIField('body'), APIField('slug'), APIField('seo_title'), APIField('search_description'), APIField('promote_image') ]
class Post(Page): introduction = models.TextField(help_text='Text to describe the post', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) subtitle = models.CharField(max_length=255) tags = ClusterTaggableManager(through='PostTag', blank=True) date_published = models.DateField("Date article published", blank=True, null=True) categories = ParentalManyToManyField('blog.Category', blank=True) content_panels = Page.content_panels + [ FieldPanel('subtitle', classname='full'), FieldPanel('introduction', classname='full'), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('date_published'), InlinePanel('post_author_relationship', label="Author(s)", panels=None, min_num=1), MultiFieldPanel([ FieldPanel('categories', widget=forms.CheckboxSelectMultiple), FieldPanel('tags'), ], heading="Blog information"), ] search_field = Page.search_fields + [ index.SearchField('body'), ] def authors(self): authors = [n.author for n in self.post_author_relationship.all()] return authors @property def get_tags(self): tags = self.tags.all() for tag in tags: tag.url = '/' + '/'.join( s.strip('/') for s in [self.get_parent().url, 'tags', tag.slug]) return tags @property def get_categories(self): categories = self.categories.all() for category in categories: category.url = '/' + '/'.join( s.strip('/') for s in [self.get_parent().url, 'categories', category.slug]) return categories parent_page_types = ['BlogIndex'] subpage_types = []
class InvalidStreamModel(models.Model): body = StreamField([ ('heading', blocks.CharBlock()), ('rich text', blocks.RichTextBlock()), ])
class CustomPage(Page): """Flexible customizable page.""" author = models.CharField(max_length=255) date = models.DateField('Creation date') body = StreamField([ ('heading', blocks.CharBlock(classname='full title')), ('paragraph', blocks.RichTextBlock()), ('html', blocks.RawHTMLBlock()), ('example_image', ExampleImage()), ('image', ImageChooserBlock()), ('table', TableBlock(table_options=core_table_options)), ('example_paragraph', ExampleParagraph()), ('example_forms', ExampleForms()), ('reporting_example_cards', ReportingExampleCards()), ('contact_info', ContactInfoBlock()), ('internal_button', InternalButtonBlock()), ('external_button', ExternalButtonBlock()), ('contribution_limits_table', SnippetChooserBlock('home.EmbedTableSnippet', template='blocks/embed-table.html', icon='table')), ], null=True) sidebar = stream_factory(null=True, blank=True) related_topics = StreamField( [('related_topics', blocks.ListBlock(blocks.PageChooserBlock(label="Related topic")))], null=True, blank=True) citations = StreamField( [('citations', blocks.ListBlock(CitationsBlock()))], null=True, blank=True) record_articles = StreamField( [('record_articles', blocks.ListBlock(blocks.PageChooserBlock(target_model=RecordPage)))], null=True, blank=True) continue_learning = StreamField([ ('continue_learning', blocks.ListBlock(ThumbnailBlock(), icon='doc-empty')), ], null=True, blank=True) show_contact_link = models.BooleanField(max_length=255, default=True, null=False, blank=False, choices=[ (True, 'Show contact link'), (False, 'Do not show contact link') ]) content_panels = Page.content_panels + [ FieldPanel('author'), FieldPanel('date'), StreamFieldPanel('body'), StreamFieldPanel('related_topics'), StreamFieldPanel('citations'), StreamFieldPanel('continue_learning'), MultiFieldPanel([ StreamFieldPanel('sidebar'), StreamFieldPanel('record_articles'), FieldPanel('show_contact_link'), ], heading="Sidebar", classname="collapsible") ] # Adds a settings field for making a custom title that displays in the Wagtail page explorer menu_title = models.CharField(max_length=255, null=True) settings_panels = Page.settings_panels + [FieldPanel('menu_title')] def get_admin_display_title(self): return self.menu_title if self.menu_title else self.title @property def content_section(self): return get_content_section(self)
def test_blank_field_is_not_required(self): field = StreamField([('paragraph', blocks.CharBlock())], blank=True) self.assertFalse(field.stream_block.required)
class ResourcePage(Page): # Class for pages that include a side nav, multiple sections and citations date = models.DateField(default=datetime.date.today) intro = StreamField([('paragraph', blocks.RichTextBlock())], null=True, blank=True) sidebar_title = models.CharField(max_length=255, null=True, blank=True) related_pages = StreamField( [('related_pages', blocks.ListBlock(blocks.PageChooserBlock()))], null=True, blank=True) sections = StreamField([('sections', ResourceBlock())], null=True, blank=True) citations = StreamField( [('citations', blocks.ListBlock(CitationsBlock()))], null=True, blank=True) related_topics = StreamField( [('related_topics', blocks.ListBlock(blocks.PageChooserBlock(label="Related topic")))], null=True, blank=True) category = models.CharField( max_length=255, choices=constants.report_child_categories.items(), help_text='If this is a report, add a category', blank=True, null=True) #breadcrumb_style removed from promote-panel in favor of controlling breadcrumbs by parent section breadcrumb_style = models.CharField(max_length=255, choices=[('primary', 'Blue'), ('secondary', 'Red')], default='primary') show_contact_card = models.BooleanField(max_length=255, default=False, null=False, blank=False, choices=[ (True, 'Show contact card'), (False, 'Do not show contact card') ]) content_panels = Page.content_panels + [ StreamFieldPanel('intro'), FieldPanel('sidebar_title'), StreamFieldPanel('related_pages'), StreamFieldPanel('sections'), StreamFieldPanel('citations'), StreamFieldPanel('related_topics'), FieldPanel('show_contact_card') ] promote_panels = Page.promote_panels + [ FieldPanel('category'), FieldPanel('date') ] # Adds a settings field for making a custom title that displays in the Wagtail page explorer menu_title = models.CharField(max_length=255, null=True) settings_panels = Page.settings_panels + [FieldPanel('menu_title')] def get_admin_display_title(self): return self.menu_title if self.menu_title else self.title @property def display_date(self): return self.date.strftime('%B %Y') @property def content_section(self): return get_content_section(self)
class ApplicationSubmission( WorkflowHelpers, BaseStreamForm, AccessFormData, AbstractFormSubmission, metaclass=ApplicationSubmissionMetaclass, ): field_template = 'funds/includes/submission_field.html' form_data = JSONField(encoder=StreamFieldDataEncoder) form_fields = StreamField(ApplicationCustomFormFieldsBlock()) page = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT) round = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT, related_name='submissions', null=True) lead = models.ForeignKey( settings.AUTH_USER_MODEL, limit_choices_to=LIMIT_TO_STAFF, related_name='submission_lead', on_delete=models.PROTECT, ) next = models.OneToOneField('self', on_delete=models.CASCADE, related_name='previous', null=True) reviewers = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='submissions_reviewer', limit_choices_to=LIMIT_TO_STAFF_AND_REVIEWERS, blank=True, ) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True) search_data = models.TextField() # Workflow inherited from WorkflowHelpers status = FSMField(default=INITIAL_STATE, protected=True) is_draft = False live_revision = models.OneToOneField( 'ApplicationRevision', on_delete=models.CASCADE, related_name='live', null=True, editable=False, ) draft_revision = models.OneToOneField( 'ApplicationRevision', on_delete=models.CASCADE, related_name='draft', null=True, editable=False, ) # Meta: used for migration purposes only drupal_id = models.IntegerField(null=True, blank=True, editable=False) objects = ApplicationSubmissionQueryset.as_manager() def not_progressed(self): return not self.next @transition( status, source='*', target=RETURN_VALUE(INITIAL_STATE, 'draft_proposal', 'invited_to_proposal'), permission=make_permission_check({UserPermissions.ADMIN}), ) def restart_stage(self, **kwargs): """ If running form the console please include your user using the kwarg "by" u = User.objects.get(email="<*****@*****.**>") for a in ApplicationSubmission.objects.all(): a.restart_stage(by=u) a.save() """ if hasattr(self, 'previous'): return 'draft_proposal' elif self.next: return 'invited_to_proposal' return INITIAL_STATE @property def stage(self): return self.phase.stage @property def phase(self): return self.workflow.get(self.status) @property def active(self): return self.status in active_statuses def ensure_user_has_account(self): if self.user and self.user.is_authenticated: self.form_data['email'] = self.user.email self.form_data['full_name'] = self.user.get_full_name() else: # Rely on the form having the following must include fields (see blocks.py) email = self.form_data.get('email') full_name = self.form_data.get('full_name') User = get_user_model() if 'skip_account_creation_notification' in self.form_data: self.form_data.pop('skip_account_creation_notification', None) self.user, _ = User.objects.get_or_create( email=email, defaults={'full_name': full_name} ) else: self.user, _ = User.objects.get_or_create_and_notify( email=email, site=self.page.get_site(), defaults={'full_name': full_name} ) def get_from_parent(self, attribute): try: return getattr(self.round.specific, attribute) except AttributeError: # We are a lab submission return getattr(self.page.specific, attribute) def progress_application(self, **kwargs): target = None for phase in STAGE_CHANGE_ACTIONS: transition = self.get_transition(phase) if can_proceed(transition): # We convert to dict as not concerned about transitions from the first phase # See note in workflow.py target = dict(PHASES)[phase].stage if not target: raise ValueError('Incorrect State for transition') submission_in_db = ApplicationSubmission.objects.get(id=self.id) self.id = None self.form_fields = self.get_from_parent('get_defined_fields')(target) self.live_revision = None self.draft_revision = None self.save() submission_in_db.next = self submission_in_db.save() def new_data(self, data): self.is_draft = False self.form_data = data return self def from_draft(self): self.is_draft = True self.form_data = self.deserialised_data(self.draft_revision.form_data, self.form_fields) return self def create_revision(self, draft=False, force=False, by=None, **kwargs): # Will return True/False if the revision was created or not self.clean_submission() current_submission = ApplicationSubmission.objects.get(id=self.id) current_data = current_submission.form_data if current_data != self.form_data or force: if self.live_revision == self.draft_revision: revision = ApplicationRevision.objects.create(submission=self, form_data=self.form_data, author=by) else: revision = self.draft_revision revision.form_data = self.form_data revision.author = by revision.save() if draft: self.form_data = current_submission.form_data else: self.live_revision = revision self.draft_revision = revision self.save() return revision return None def clean_submission(self): self.process_form_data() self.ensure_user_has_account() self.process_file_data(self.form_data) def process_form_data(self): for field_name, field_id in self.named_blocks.items(): response = self.form_data.pop(field_id, None) if response: self.form_data[field_name] = response def extract_files(self): files = {} for field in self.form_fields: if isinstance(field.block, UploadableMediaBlock): files[field.id] = self.data(field.id) or [] self.form_data.pop(field.id, None) return files def process_file_data(self, data): for field in self.form_fields: if isinstance(field.block, UploadableMediaBlock): file = self.process_file(data.get(field.id, [])) folder = os.path.join('submission', str(self.id), field.id) try: file.save(folder) except AttributeError: for f in file: f.save(folder) self.form_data[field.id] = file def save(self, *args, update_fields=list(), **kwargs): if update_fields and 'form_data' not in update_fields: # We don't want to use this approach if the user is sending data return super().save(*args, update_fields=update_fields, **kwargs) if self.is_draft: raise ValueError('Cannot save with draft data') creating = not self.id if creating: # We are creating the object default to first stage self.workflow_name = self.get_from_parent('workflow_name') # Copy extra relevant information to the child self.lead = self.get_from_parent('lead') # We need the submission id to correctly save the files files = self.extract_files() self.clean_submission() # add a denormed version of the answer for searching self.search_data = ' '.join(self.prepare_search_values()) super().save(*args, **kwargs) if creating: self.process_file_data(files) self.reviewers.set(self.get_from_parent('reviewers').all()) first_revision = ApplicationRevision.objects.create( submission=self, form_data=self.form_data, author=self.user, ) self.live_revision = first_revision self.draft_revision = first_revision self.save() @property def missing_reviewers(self): return self.reviewers.exclude(id__in=self.reviews.submitted().values('author')) @property def staff_not_reviewed(self): return self.missing_reviewers.staff() @property def reviewers_not_reviewed(self): return self.missing_reviewers.reviewers().exclude(id__in=self.staff_not_reviewed) def reviewed_by(self, user): return self.reviews.submitted().filter(author=user).exists() def has_permission_to_review(self, user): if user.is_apply_staff: return True if user in self.reviewers_not_reviewed: return True return False def can_review(self, user): if self.reviewed_by(user): return False return self.has_permission_to_review(user) def prepare_search_values(self): for field_id in self.question_field_ids: field = self.field(field_id) data = self.data(field_id) value = field.block.get_searchable_content(field.value, data) if value: if isinstance(value, list): yield ', '.join(value) else: yield value # Add named fields into the search index for field in ['full_name', 'email', 'title']: yield getattr(self, field) def get_absolute_url(self): return reverse('funds:submissions:detail', args=(self.id,)) def __str__(self): return f'{self.title} from {self.full_name} for {self.page.title}' def __repr__(self): return f'<{self.__class__.__name__}: {self.user}, {self.round}, {self.page}>' # Methods for accessing data on the submission def get_data(self): # Updated for JSONField - Not used but base get_data will error form_data = self.form_data.copy() form_data.update({ 'submit_time': self.submit_time, }) return form_data # Template methods for metaclass def _get_REQUIRED_display(self, name): return self.render_answer(name) def _get_REQUIRED_value(self, name): return self.form_data[name]
class HomePage(Page): # Hero section of HomePage image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Homepage image') hero_text = models.CharField( max_length=255, help_text='Write an introduction for the Site') # Body section of the HomePage body = StreamField(BaseStreamBlock(), verbose_name="Home content block", blank=True) bottom = StreamField(BaseStreamBlock(), verbose_name="Home content block", blank=True) # Promo section of the HomePage promo_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Promo image') promo_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') promo_text = RichTextField(null=True, blank=True, help_text='Write some promotional copy') featured_section_1_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_1 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='First featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 1') featured_section_2_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_2 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Second featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 2') featured_section_3_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_3 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Third featured section for the homepage. Will display up to ' 'six child items.', verbose_name='Featured section 3') content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('image'), FieldPanel('hero_text', classname="full"), ], heading="Hero section"), MultiFieldPanel([ ImageChooserPanel('promo_image'), FieldPanel('promo_title'), FieldPanel('promo_text'), ], heading="Promo section"), StreamFieldPanel('body'), MultiFieldPanel([ MultiFieldPanel([ FieldPanel('featured_section_1_title'), PageChooserPanel('featured_section_1'), ]), MultiFieldPanel([ FieldPanel('featured_section_2_title'), PageChooserPanel('featured_section_2'), ]), MultiFieldPanel([ FieldPanel('featured_section_3_title'), PageChooserPanel('featured_section_3'), ]) ], heading="Featured homepage sections", classname="collapsible"), StreamFieldPanel('bottom') ] def __str__(self): return self.title class Meta: verbose_name = "homepage"
class HomePage(Page): """ The Home Page. This looks slightly more complicated than it is. You can see if you visit your site and edit the homepage that it is split between a: - Hero area - Body area - A promotional area - Moveable featured site sections """ # Hero section of HomePage image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Homepage image') hero_text = models.CharField( max_length=255, help_text='Write an introduction for the bakery') hero_cta = models.CharField(verbose_name='Hero CTA', max_length=255, help_text='Text to display on Call to Action') hero_cta_link = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Hero CTA link', help_text='Choose a page to link to for the Call to Action') # Body section of the HomePage body = StreamField(BaseStreamBlock(), verbose_name="Home content block", blank=True) # Promo section of the HomePage promo_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Promo image') promo_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') promo_text = RichTextField(null=True, blank=True, help_text='Write some promotional copy') # Featured sections on the HomePage # You will see on templates/base/home_page.html that these are treated # in different ways, and displayed in different areas of the page. # Each list their children items that we access via the children function # that we define on the individual Page models e.g. BlogIndexPage featured_section_1_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_1 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='First featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 1') featured_section_2_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_2 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Second featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 2') featured_section_3_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_3 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Third featured section for the homepage. Will display up to ' 'six child items.', verbose_name='Featured section 3') content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('image'), FieldPanel('hero_text', classname="full"), MultiFieldPanel([ FieldPanel('hero_cta'), PageChooserPanel('hero_cta_link'), ]), ], heading="Hero section"), MultiFieldPanel([ ImageChooserPanel('promo_image'), FieldPanel('promo_title'), FieldPanel('promo_text'), ], heading="Promo section"), StreamFieldPanel('body'), MultiFieldPanel([ MultiFieldPanel([ FieldPanel('featured_section_1_title'), PageChooserPanel('featured_section_1'), ]), MultiFieldPanel([ FieldPanel('featured_section_2_title'), PageChooserPanel('featured_section_2'), ]), MultiFieldPanel([ FieldPanel('featured_section_3_title'), PageChooserPanel('featured_section_3'), ]), ], heading="Featured homepage sections", classname="collapsible") ] def __str__(self): return self.title
class Edition(RoutablePageMixin, EditionSubpage): start_date = models.DateTimeField(null=True, blank=True, verbose_name=_('edition start date')) end_date = models.DateTimeField(null=True, blank=True, verbose_name=_('edition end date')) content = StreamField([ ('agenda', AgendaBlock()), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('event_index', EventIndexBlock()), ('event_schedule', EventScheduleBlock()), ('section_title', SectionTitleBlock()), ('section_subtitle', SectionSubtitleBlock()), ('section_divider', SectionDividerBlock()), ('dropdown', DropdownBlock()), ('photo_gallery', PhotoGallery()), ('photo_slider', PhotoSlider()), ('map', MapBlock()), ('raw_html', RawHTMLBlock()), ], null=True, blank=True, verbose_name=_('content')) default_featured_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('default featured image')) edition_footer = StreamField([ ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('section_title', SectionTitleBlock()), ('section_subtitle', SectionSubtitleBlock()), ('section_divider', SectionDividerBlock()), ('dropdown', DropdownBlock()), ('photo_gallery', PhotoGallery()), ('photo_slider', PhotoSlider()), ('raw_html', blocks.RawHTMLBlock()), ], null=True, blank=True, verbose_name=_('edition footer')) generate_podcast_feed = models.BooleanField( default=False, verbose_name=_('generate podcast feed')) def get_edition_footer(self): return self.edition_footer def get_edition(self): return self content_panels = SFIPage.content_panels + [ FieldPanel('start_date'), FieldPanel('end_date'), StreamFieldPanel('content'), ImageChooserPanel('default_featured_image'), InlinePanel('event_categories', label='Event categories'), StreamFieldPanel('edition_footer'), ] settings_panels = SFIPage.settings_panels + [ FieldPanel('generate_podcast_feed') ] parent_page_types = ['EditionIndex'] subpage_types = ['EventIndex'] class Meta: verbose_name = _('edition') verbose_name_plural = _('editions') @route(r'^c/([\w\-_]+)/$') def get_category_event_index(self, request, category_name): indexes = EventIndex.objects.child_of(self).live().all() categories = Category.objects.filter( edition=self).order_by('name').all() category = categories.filter(slug=category_name).first() if not category: raise Http404 events = Event.objects.live().public().descendant_of(self).filter( event_category=category).order_by('date') posts = paginate(events, request, EventIndex.EVENTS_PER_PAGE) relative_url = self.reverse_subpage('get_category_event_index', (category_name, )) return TemplateResponse( request, 'agenda/event_index.html', { 'posts': posts, 'indexes': indexes, 'categories': categories, 'title': '{} - {}'.format(category.name, self.title), 'description': '{} - {}'.format(category.name, self.title), 'canonical_url': self.full_url + relative_url, 'featured_image': self.get_featured_image(), 'locales': [(lang_code, lang_name, url + relative_url, full_url + relative_url) for lang_code, lang_name, url, full_url in self._get_locales()], })
class HomePage(RoutablePageMixin, Page): """Home page model.""" template = "home/home_page.html" subpage_types = [ 'blog.BlogListingPage', 'contact.ContactPage', 'flex.FlexPage', ] parent_page_type = [ 'wagtailcore.Page' ] banner_title = models.CharField(max_length=100, blank=False, null=True) banner_subtitle = RichTextField(features=["bold", "italic"]) banner_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=False, on_delete=models.SET_NULL, related_name="+", ) banner_cta = models.ForeignKey( "wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) content = StreamField([ ("cta", blocks.CTABlock()), ], null=True, blank=True) api_fields = [ APIField("banner_title"), APIField("banner_subtitle"), APIField("banner_image"), APIField("banner_cta"), APIField("carousel_images"), APIField("content"), ] content_panels = Page.content_panels + [ MultiFieldPanel( [InlinePanel("carousel_images", max_num=5, min_num=1, label="Image")], heading="Carousel Images", ), StreamFieldPanel("content"), ] # This is how you'd normally hide promote and settings tabs # promote_panels = [] # settings_panels = [] banner_panels = [ MultiFieldPanel( [ FieldPanel("banner_title"), FieldPanel("banner_subtitle"), ImageChooserPanel("banner_image"), PageChooserPanel("banner_cta"), ], heading="Banner Options", ), ] edit_handler = TabbedInterface( [ ObjectList(content_panels, heading='Content'), ObjectList(banner_panels, heading="Banner Settings"), ObjectList(Page.promote_panels, heading='Promotional Stuff'), ObjectList(Page.settings_panels, heading='Settings Stuff'), ] ) class Meta: verbose_name = "Home Page" verbose_name_plural = "Home Pages" @route(r'^subscribe/$') def the_subscribe_page(self, request, *args, **kwargs): context = self.get_context(request, *args, **kwargs) return render(request, "home/subscribe.html", context) def get_admin_display_title(self): return "Custom Home Page Title"