def test_render(self): inline_panel = InlinePanel(self.mock_model, 'formset', label='foo')( instance=self.fake_instance, form=self.fake_field) self.assertIn('Add foo', inline_panel.render())
def test_render_js(self): inline_panel = InlinePanel(self.mock_model, 'formset')( instance=self.fake_instance, form=self.fake_field) self.assertIn('var panel = InlinePanel({', inline_panel.render_js())
def test_render(self): """ Check that the inline panel renders the panels set on the model when no 'panels' parameter is passed in the InlinePanel definition """ SpeakerInlinePanel = InlinePanel('speakers', label="Speakers").bind_to_model(EventPage) EventPageForm = SpeakerInlinePanel.get_form_class(EventPage) # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset self.assertEqual(['speakers'], list(EventPageForm.formsets.keys())) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) result = panel.render_as_field() self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result) self.assertIn('value="Father"', result) self.assertIn('<label for="id_speakers-0-last_name">Surname:</label>', result) self.assertIn('<label for="id_speakers-0-image">Image:</label>', result) self.assertIn('Choose an image', result) # rendered panel must also contain hidden fields for id, DELETE and ORDER self.assertIn('<input id="id_speakers-0-id" name="speakers-0-id" type="hidden"', result) self.assertIn('<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden"', result) self.assertIn('<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden"', result) # rendered panel must contain maintenance form for the formset self.assertIn('<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden"', result) # render_js_init must provide the JS initializer self.assertIn('var panel = InlinePanel({', panel.render_js_init())
def test_old_style_inlinepanel_declaration(self): """ Check that the deprecated form of InlinePanel declaration (where the base model is passed as the first arg) still works """ self.reset_warning_registry() with warnings.catch_warnings(record=True) as w: SpeakerInlinePanelDef = InlinePanel(EventPage, 'speakers', label="Speakers") # Check that a RemovedInWagtail12Warning has been triggered self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, RemovedInWagtail12Warning)) self.assertTrue("InlinePanel(EventPage, 'speakers') should be changed to InlinePanel('speakers')" in str(w[-1].message)) SpeakerInlinePanel = SpeakerInlinePanelDef.bind_to_model(EventPage) EventPageForm = SpeakerInlinePanel.get_form_class(EventPage) # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset self.assertEqual(['speakers'], list(EventPageForm.formsets.keys())) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) result = panel.render_as_field() self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result) self.assertIn('value="Father"', result)
def test_get_panel_definitions_no_panels(self): """ Check that get_panel_definitions returns the panels set on the model when no panels are set on the InlinePanel """ inline_panel = InlinePanel(self.mock_model, 'formset')( instance=self.fake_instance, form=self.fake_field) result = inline_panel.get_panel_definitions() self.assertEqual(result[0].name, 'mock panel')
def test_get_panel_definitions(self): """ Check that get_panel_definitions returns the panels set on InlinePanel """ other_mock_panel = MagicMock() other_mock_panel.name = 'other mock panel' inline_panel = InlinePanel(self.mock_model, 'formset', panels=[other_mock_panel])( instance=self.fake_instance, form=self.fake_field) result = inline_panel.get_panel_definitions() self.assertEqual(result[0].name, 'other mock panel')
def test_render_with_panel_overrides(self): """ Check that inline panel renders the panels listed in the InlinePanel definition where one is specified """ SpeakerInlinePanel = InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.Textarea), ImageChooserPanel('image'), ]).bind_to_model(EventPage) EventPageForm = SpeakerInlinePanel.get_form_class(EventPage) # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset self.assertEqual(['speakers'], list(EventPageForm.formsets.keys())) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) result = panel.render_as_field() # rendered panel should contain first_name rendered as a text area, but no last_name field self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result) self.assertIn('Father</textarea>', result) self.assertNotIn('<label for="id_speakers-0-last_name">Surname:</label>', result) # test for #338: surname field should not be rendered as a 'stray' label-less field self.assertNotIn('<input id="id_speakers-0-last_name"', result) self.assertIn('<label for="id_speakers-0-image">Image:</label>', result) self.assertIn('Choose an image', result) # rendered panel must also contain hidden fields for id, DELETE and ORDER self.assertIn('<input id="id_speakers-0-id" name="speakers-0-id" type="hidden"', result) self.assertIn('<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden"', result) self.assertIn('<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden"', result) # rendered panel must contain maintenance form for the formset self.assertIn('<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden"', result) # render_js_init must provide the JS initializer self.assertIn('var panel = InlinePanel({', panel.render_js_init())
] class EventPage(Page): date_from = models.DateField("Start date", null=True) date_to = models.DateField( "End date", null=True, blank=True, help_text="Not required if event is on a single day") time_from = models.TimeField("Start time", null=True, blank=True) time_to = models.TimeField("End time", null=True, blank=True) location = models.CharField(max_length=255) body = RichTextField(blank=True) cost = models.CharField(max_length=255) signup_link = models.URLField(blank=True) EventPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('date_from'), FieldPanel('date_to'), FieldPanel('time_from'), FieldPanel('time_to'), FieldPanel('location'), FieldPanel('cost'), FieldPanel('signup_link'), FieldPanel('body', classname="full"), InlinePanel('related_media', label="Related media"), ]
class ArticlePage(ThemeablePage, FeatureStyleFields, Promotable, ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin): excerpt = RichTextField(blank=True, default="") body = article_fields.BodyField() chapters = article_fields.ChapterField(blank=True, null=True) table_of_contents_heading = models.TextField(blank=True, default="Table of Contents") citations_heading = models.TextField(blank=True, default="Works Cited") endnotes_heading = models.TextField(blank=True, default="End Notes") endnote_identifier_style = models.CharField( max_length=20, default="roman-lower", choices=( ('roman-lower', 'Roman Numerals - Lowercase'), ('roman-upper', 'Roman Numerals - Uppercase'), ('numbers', 'Numbers') ) ) main_image = models.ForeignKey( 'images.AttributedImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) feature_image = models.ForeignKey( 'images.AttributedImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) primary_topic = models.ForeignKey( 'articles.Topic', null=True, blank=True, on_delete=models.SET_NULL, related_name='articles' ) category = models.ForeignKey( 'articles.ArticleCategory', related_name='%(class)s', on_delete=models.SET_NULL, null=True, default=1 ) include_author_block = models.BooleanField(default=True) visualization = models.BooleanField(default=False) interview = models.BooleanField(default=False) video = models.BooleanField(default=False) number_of_related_articles = models.PositiveSmallIntegerField(default=6, verbose_name="Number of Related Articles to Show") json_file = article_fields.WagtailFileField(max_length=255, blank=True, null=True, verbose_name='JSON file', help_text="Only provide if you know your template will be filled with the contents of a JSON data file.") project = models.ForeignKey( "projects.ProjectPage", null=True, blank=True, on_delete=models.SET_NULL, ) _response_to = False search_fields = Page.search_fields + [ index.SearchField('excerpt', partial_match=True), index.SearchField('body', partial_match=True), index.SearchField('chapters', partial_match=True), index.SearchField('get_primary_topic_name', partial_match=True), index.SearchField('get_category_name', partial_match=True), index.SearchField('get_topic_names', partial_match=True), index.SearchField('get_author_names', partial_match=True), ] def get_primary_topic_name(self): if self.primary_topic: return self.primary_topic.name return "" def get_category_name(self): if self.category: return self.category.name return "" def get_topic_names(self): return '\n'.join([link.topic.name if link.topic else "" for link in self.topic_links.all()]) def get_author_names(self): return '\n'.join( [author_link.author.full_name if author_link.author else "" for author_link in self.author_links.all()]) @property def authors(self): author_list = [] for link in self.author_links.all(): if link.author: author_list.append((link.author)) return author_list @property def series_articles(self): related_series_data = [] for link in self.series_links.all(): series_page = link.series series_articles = series_page.articles series_articles.remove(self) related_series_data.append((series_page, series_articles)) return related_series_data @property def topics(self): primary_topic = self.primary_topic all_topics = [link.topic for link in self.topic_links.all()] if primary_topic: all_topics.append(primary_topic) all_topics = list(set(all_topics)) if len(all_topics) > 0: all_topics.sort(key=attrgetter('name')) return all_topics @property def response_to(self): if self._response_to is False: response_to_count = self.response_to_links.count() if response_to_count > 1: logger.warning( 'ArticlePage(pk={0}) appears to be a response to multiple articles. Only the first one is being returned.'.format( self.pk ) ) if response_to_count != 0: self._response_to = self.response_to_links.first().response_to else: self._response_to = None return self._response_to @property def is_response(self): return self.response_to is not None def responses(self): return [link.response for link in self.response_links.all()] def related_articles(self, number): included = [self.id] article_list = [] if self.primary_topic: articles = ArticlePage.objects.live().filter(primary_topic=self.primary_topic).exclude( id=self.id).distinct().order_by('-first_published_at')[:number] article_list.extend(articles.all()) included.extend([article.id for article in articles.all()]) current_total = len(article_list) if current_total < number: # still don't have enough, so pick using secondary topics topics = Topic.objects.filter(article_links__article=self) if topics: additional_articles = ArticlePage.objects.live().filter(primary_topic__in=topics).exclude( id__in=included).distinct().order_by('-first_published_at')[:number - current_total] article_list.extend(additional_articles.all()) current_total = len(article_list) included.extend([article.id for article in additional_articles.all()]) if current_total < number: authors = ContributorPage.objects.live().filter(article_links__article=self) if authors: additional_articles = ArticlePage.objects.live().filter(author_links__author__in=authors).exclude( id__in=included).distinct().order_by('-first_published_at')[:number - current_total] article_list.extend(additional_articles.all()) current_total = len(article_list) included.extend([article.id for article in additional_articles.all()]) if current_total < number: # still don't have enough, so just pick the most recent additional_articles = ArticlePage.objects.live().exclude(id__in=included).order_by('-first_published_at')[:number - current_total] article_list.extend(additional_articles.all()) return article_list content_panels = Page.content_panels + [ FieldPanel('excerpt'), InlinePanel('author_links', label="Authors"), PageChooserPanel('project'), ImageChooserPanel('main_image'), ImageChooserPanel('feature_image'), DocumentChooserPanel('video_document'), StreamFieldPanel('body'), SnippetChooserPanel('primary_topic'), InlinePanel('topic_links', label="Secondary Topics"), InlinePanel('response_links', label="Responses"), ] advanced_content_panels = [ FieldPanel('json_file'), MultiFieldPanel( [ FieldPanel('table_of_contents_heading'), StreamFieldPanel('chapters'), ], heading="Chapters Section" ), MultiFieldPanel( [ FieldPanel('endnotes_heading'), FieldPanel('endnote_identifier_style'), InlinePanel('endnote_links', label="End Notes"), ], heading="End Notes Section" ), MultiFieldPanel( [ FieldPanel('citations_heading'), InlinePanel('citation_links', label="Citations"), ], heading="Citations Section" ), ] promote_panels = Page.promote_panels + [ MultiFieldPanel( [ FieldPanel('sticky'), FieldPanel('sticky_for_type_section'), FieldPanel('slippery'), FieldPanel('slippery_for_type_section'), FieldPanel('editors_pick'), FieldPanel('feature_style'), FieldPanel('title_size'), FieldPanel('fullbleed_feature'), FieldPanel('image_overlay_opacity'), ], heading="Featuring Settings" ), ] style_panels = ThemeablePage.style_panels + [ MultiFieldPanel( [ FieldPanel('include_main_image'), FieldPanel('include_main_image_overlay'), FieldPanel('full_bleed_image_size'), FieldPanel('include_caption_in_footer'), ], heading="Main Image" ), MultiFieldPanel( [ InlinePanel('background_image_links', label="Background Images"), ], heading="Background Images" ), MultiFieldPanel( [ FieldPanel('include_author_block'), FieldPanel('number_of_related_articles') ], heading="Sections" ), MultiFieldPanel( [ FieldPanel('interview'), FieldPanel('video'), FieldPanel('visualization'), ], heading="Categorization" ) ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(advanced_content_panels, heading='Advanced Content'), ObjectList(style_panels, heading='Page Style Options'), ObjectList(promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ])
) password_required_template = 'tests/event_page_password_required.html' EventPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('date_from'), FieldPanel('date_to'), FieldPanel('time_from'), FieldPanel('time_to'), FieldPanel('location'), FieldPanel('audience'), FieldPanel('cost'), FieldPanel('signup_link'), InlinePanel('carousel_items', label="Carousel items"), FieldPanel('body', classname="full"), InlinePanel('speakers', label="Speakers"), InlinePanel('related_links', label="Related links"), ] EventPage.promote_panels = [ MultiFieldPanel(COMMON_PANELS, "Common page configuration"), ImageChooserPanel('feed_image'), ] # Just to be able to test multi table inheritance class SingleEventPage(EventPage): excerpt = models.TextField( max_length=255,
class SectionedRichTextPage(Page): content_panels = [ FieldPanel('title', classname="full title"), InlinePanel('sections') ]
class TourPage(Page): search_fields = Page.search_fields + [ index.SearchField('title'), index.SearchField('tour_description'), ] tour_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Album cover image') tour_listing_introduction = models.TextField( "A listing introduction for the tour", blank=True, max_length=254) tour_description = RichTextField("A description for the tour") content_panels = Page.content_panels + [ InlinePanel('tour_artist_relationship', label="Arist(s)", panels=None, min_num=1), InlinePanel('tour_album_relationship', label="Album(s)", panels=None, min_num=0), ImageChooserPanel('tour_image'), FieldPanel('tour_listing_introduction'), FieldPanel('tour_description'), ] tour_panels = [ InlinePanel('tourdates', label="Tour dates", help_text="Enter your tour dates", min_num=1), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Tour details", classname="content"), ObjectList(tour_panels, heading="Tour dates"), ObjectList(Page.promote_panels, heading="Promote"), ObjectList(Page.settings_panels, heading="Settings", classname="settings"), ]) # We iterate within the model over the artists, genres and subgenres # so they can be accessible to the template via a for loop def artists(self): artists = [n.artists for n in self.tour_artist_relationship.all()] return artists def albums(self): albums = [n.albums for n in self.tour_album_relationship.all()] return albums @property def album_image(self): # fail silently if there is no profile pic or the rendition file can't # be found. Note @richbrennan worked out how to do this... try: return self.image.get_rendition('fill-400x400').img_tag() except: return '' parent_page_types = [ 'tours.TourIndexPage' # app.model ] subpage_types = []
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 FormField(AbstractFormField): page = ParentalKey('FormPage', related_name='form_fields') class FormPage(AbstractEmailForm): subpage_types = [] parent_page_types = [HomePage] intro = RichTextField(blank=True) thank_you_text = RichTextField(blank=True) FormPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('intro', classname="full"), InlinePanel('form_fields', label="Form fields"), FieldPanel('thank_you_text', classname="full"), MultiFieldPanel([ FieldPanel('to_address', classname="full"), FieldPanel('from_address', classname="full"), FieldPanel('subject', classname="full"), ], "Email") ] @register_setting(icon='icon-group') class SocialMediaSettings(BaseSetting): facebook = models.URLField(blank=True, help_text='Complete URL for facebook page') twitter = models.CharField(blank=True, max_length=127,
class AbstractFilterPage(CFGOVPage): header = StreamField([ ('article_subheader', blocks.RichTextBlock(icon='form')), ('text_introduction', molecules.TextIntroduction()), ('item_introduction', organisms.ItemIntroduction()), ], blank=True) preview_title = models.CharField(max_length=255, null=True, blank=True) preview_subheading = models.CharField(max_length=255, null=True, blank=True) preview_description = RichTextField(null=True, blank=True) secondary_link_url = models.CharField(max_length=500, null=True, blank=True) secondary_link_text = models.CharField(max_length=255, null=True, blank=True) preview_image = models.ForeignKey('v1.CFGOVImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') date_published = models.DateField(default=date.today) date_filed = models.DateField(null=True, blank=True) comments_close_by = models.DateField(null=True, blank=True) # Configuration tab panels settings_panels = [ MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'), InlinePanel('categories', label="Categories", max_num=2), FieldPanel('tags', 'Tags'), MultiFieldPanel([ FieldPanel('preview_title', classname="full"), FieldPanel('preview_subheading', classname="full"), FieldPanel('preview_description', classname="full"), FieldPanel('secondary_link_url', classname="full"), FieldPanel('secondary_link_text', classname="full"), ImageChooserPanel('preview_image'), ], heading='Page Preview Fields', classname='collapsible'), FieldPanel('authors', 'Authors'), MultiFieldPanel([ FieldPanel('date_published'), FieldPanel('date_filed'), FieldPanel('comments_close_by'), ], 'Relevant Dates', classname='collapsible'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), ] # This page class cannot be created. is_creatable = False objects = CFGOVPageManager() @classmethod def generate_edit_handler(self, content_panel): content_panels = [ StreamFieldPanel('header'), content_panel, ] return TabbedInterface([ ObjectList(self.content_panels + content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(self.settings_panels, heading='Configuration'), ]) # Returns an image for the page's meta Open Graph tag @property def meta_image(self): parent_meta = super(AbstractFilterPage, self).meta_image return parent_meta or self.preview_image
class Meta: verbose_name = "Homepage" class HomePageCarouselItem(Orderable, AbstractCarouselItem): page = ParentalKey('HomePage', related_name='carousel_items') class HomePageRelatedLink(Orderable, AbstractRelatedLink): page = ParentalKey('HomePage', related_name='related_links') HomePage.content_panels = Page.content_panels + [ FieldPanel('body', classname="full"), InlinePanel(HomePage, 'carousel_items', label="Carousel items"), InlinePanel(HomePage, 'related_links', label="Related links"), ] # Standard pages class StandardPage(Page): intro = RichTextField(blank=True) body = RichTextField(blank=True) feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
class CountryPage(Page, HeroImageFields, SocialFields, ListingFields): body = StreamField(StoryBlock()) partners_title = models.CharField( blank=True, null=True, max_length=255, help_text='Title text to appear as Partnerships heading.') partners_description = RichTextField( blank=True, null=True, help_text= 'Description text to appear below Partnerships heading for Partnership block.', features=['bold', 'italic', 'link', 'justify']) person_category = models.ForeignKey( 'people.PersonCategory', null=True, blank=True, on_delete=models.SET_NULL, help_text="Select a person category to display its team members.", related_name='+', ) call_to_action = models.ForeignKey('utils.CallToActionSnippet', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') featured_article = models.ForeignKey( 'articles.ArticlePage', verbose_name="Featured News", null=True, blank=True, on_delete=models.SET_NULL, help_text= "Select a featured article to display first in article section", related_name='+', ) @cached_property def articles(self): # returns articles that have solution selected as a related page all_articles = ArticlePage.objects.filter( related_pages__page=self).live().public().order_by( '-publication_date') if self.featured_article_id: all_articles = all_articles.exclude(pk=self.featured_article_id) return all_articles[:3] @cached_property def people(self): if self.person_category: return self.person_category.people else: return None @cached_property def partners(self): partners = [p.related_partner for p in self.related_partners.all()] return partners @cached_property def article_customisations(self): return self.articles_customisation.first() @cached_property def partners_customisations(self): return self.partners_customisation.first() search_fields = Page.search_fields + HeroImageFields.search_fields + [ index.SearchField('body') ] content_panels = Page.content_panels + HeroImageFields.content_panels + [ StreamFieldPanel('body'), MultiFieldPanel([ InlinePanel('articles_customisation', label="Articles Listing Customisation", max_num=1), PageChooserPanel('featured_article'), ], 'Articles Listing'), MultiFieldPanel([ FieldPanel('partners_title'), FieldPanel('partners_description'), InlinePanel('related_partners', label="Related partners"), InlinePanel('partners_customisation', label="Partners Customisation", max_num=1), ], 'Partners Listing'), SnippetChooserPanel('call_to_action'), ] promote_panels = Page.promote_panels + SocialFields.promote_panels \ + ListingFields.promote_panels
class InlinePanelPage(WagtailPage): content_panels = [ InlinePanel('related_page_model') ]
class InlinePanelSnippet(models.Model): panels = [ InlinePanel('related_snippet_model') ]
class HomePage(Page, LocalorePromoteFields): site_intro = RichTextField(blank=True) related_content_title = models.CharField( verbose_name="title", max_length=255, ) related_content_page = models.ForeignKey('wagtailcore.Page', verbose_name="page to link to", null=True, blank=True, on_delete=models.SET_NULL, related_name='+') related_content_subtitle = models.CharField(verbose_name="subtitle", max_length=255, blank=True, default="Across America") video_poster_image = models.ForeignKey('localore_admin.LocaloreImage', verbose_name="poster image", null=True, on_delete=models.SET_NULL, related_name='+') video_poster_image_mobile = models.ForeignKey( 'localore_admin.LocaloreImage', verbose_name="poster image (mobile)", null=True, on_delete=models.SET_NULL, related_name='+') video_mp4 = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_webm = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_ogv = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_youtube_id = models.CharField( verbose_name="YouTube video ID", max_length=12, default="j6IIjLK-8fU", help_text=format_html( "The part in bold: " "https://www.youtube.com/watch?v=<b>j6IIjLK-8fU</b>"), ) video_is_360 = models.BooleanField( "360˚ video", default=False, help_text="This is a 360-degree video.", ) view_more_title = models.CharField( verbose_name='"View more" link title', max_length=255, help_text='For example, "View more connections"', ) view_more_page = models.ForeignKey('wagtailcore.Page', verbose_name="Page to link to", null=True, on_delete=models.SET_NULL, related_name='+') content_panels = Page.content_panels + [ FieldPanel('site_intro', classname="full"), MultiFieldPanel([ FieldPanel('related_content_title'), FieldPanel('related_content_subtitle'), PageChooserPanel('related_content_page'), ], "Featured content"), MultiFieldPanel([ ImageChooserPanel('video_poster_image'), ImageChooserPanel('video_poster_image_mobile'), DocumentChooserPanel('video_mp4'), DocumentChooserPanel('video_webm'), DocumentChooserPanel('video_ogv'), ], "Hero section"), MultiFieldPanel([ FieldPanel('video_youtube_id'), FieldPanel('video_is_360'), FieldPanel('view_more_title'), PageChooserPanel('view_more_page'), ], "Fullscreen video"), InlinePanel( 'featured_pages', label="Featured Pages", min_num=3, max_num=3, ), ] promote_panels = LocalorePromoteFields.promote_panels parent_page_types = [] @property def video_poster_image_file_extension(self): return self.video_poster_image.file.url.split('.')[-1] @property def preview_modes(self): return super(HomePage, self).preview_modes + [ ('no-video', 'Preview poster image'), ] def serve_preview(self, request, mode_name): if mode_name == 'no-video': self.video_mp4 = None return super(HomePage, self).serve_preview(request, mode_name) class Meta: verbose_name = "Homepage"
class NavigationMenuManager(models.Manager): def get_by_natural_key(self, name): return self.get(menu_name=name) @register_snippet @python_2_unicode_compatible class NavigationMenu(ClusterableModel): objects = NavigationMenuManager() menu_name = models.CharField(max_length=255, null=False, blank=False) @property def items(self): return self.menu_items.all() def __str__(self): return self.menu_name class Meta: verbose_name = "Navigation menu" NavigationMenu.panels = [ FieldPanel('menu_name', classname='full title'), InlinePanel('menu_items', label="Menu Items", help_text='Set the menu items for the current menu.') ]
def test_render_with_panel_overrides(self): """ Check that inline panel renders the panels listed in the InlinePanel definition where one is specified """ SpeakerObjectList = ObjectList([ InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.Textarea), ImageChooserPanel('image'), ]), ]).bind_to_model(EventPage) SpeakerInlinePanel = SpeakerObjectList.children[0] EventPageForm = SpeakerObjectList.get_form_class(EventPage) # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset self.assertEqual(['speakers'], list(EventPageForm.formsets.keys())) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) result = panel.render_as_field() # rendered panel should contain first_name rendered as a text area, but no last_name field self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result) self.assertIn('Father</textarea>', result) self.assertNotIn( '<label for="id_speakers-0-last_name">Surname:</label>', result) # test for #338: surname field should not be rendered as a 'stray' label-less field self.assertTagInHTML('<input id="id_speakers-0-last_name">', result, count=0, allow_extra_attrs=True) self.assertIn('<label for="id_speakers-0-image">Image:</label>', result) self.assertIn('Choose an image', result) # rendered panel must also contain hidden fields for id, DELETE and ORDER self.assertTagInHTML( '<input id="id_speakers-0-id" name="speakers-0-id" type="hidden">', result, allow_extra_attrs=True) self.assertTagInHTML( '<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden">', result, allow_extra_attrs=True) self.assertTagInHTML( '<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden">', result, allow_extra_attrs=True) # rendered panel must contain maintenance form for the formset self.assertTagInHTML( '<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden">', result, allow_extra_attrs=True) # render_js_init must provide the JS initializer self.assertIn('var panel = InlinePanel({', panel.render_js_init())
class ArticlePage(Page): parent_page_types = ["ArticleIndexPage"] subpage_types = [] author = models.ForeignKey( 'AuthorPage', null=True, on_delete=models.SET_NULL, related_name='+' ) main_image = models.ForeignKey( 'images.CustomImage', null=True, on_delete=models.SET_NULL, related_name='+' ) date = models.DateField("Post date") intro = models.TextField( max_length=250, help_text='This will only appear in article previews, not with the full article. This text will be formatted with markdown.') body = StreamField([ ('text', blocks.TextBlock(icon='pilcrow', help_text='This text will be formatted with markdown.')), ('image', CaptionedImageBlock()), ('embed', EmbedBlock(icon='media')), ('extra_information', ExtraInformationBlock()), ]) search_fields = Page.search_fields + ( index.SearchField('intro'), index.SearchField('body'), index.SearchField('author') ) content_panels = Page.content_panels + [ PageChooserPanel('author'), InlinePanel('subjects', label='Subjects'), FieldPanel('date'), InlinePanel('source_links', label="Sources"), ImageChooserPanel('main_image'), FieldPanel('intro'), StreamFieldPanel('body'), ] class Meta: verbose_name = "Article" def __unicode__(self): return self.title def article_index(self): # Find closest ancestor which is article page return self.get_ancestors().type(ArticleIndexPage).last() """def subject(self): # TODO: Replace/remove subject = ArticleIndexPage.objects.ancestor_of(self).last().subject if subject is not None: return subject else: return "" """ def all_subjects(self): # TODO: Replace/remove subjects = [] for s in SubjectSnippet.objects.all(): subjects.append(ArticleIndexPage.objects.filter(subject=s)[0]) return subjects
def test_invalid_inlinepanel_declaration(self): with self.ignore_deprecation_warnings(): self.assertRaises(TypeError, lambda: InlinePanel(label="Speakers")) self.assertRaises( TypeError, lambda: InlinePanel( EventPage, 'speakers', label="Speakers", bacon="chunky"))
value = models.CharField(max_length=255) project = ParentalKey('portfolio.Project',related_name='metafields') def __unicode__(self): return self.project.__unicode__() + u'\'s ' + self.key.__unicode__() + u': ' + self.value class Project(Page): description = RichTextField(blank=True) indexed_fields = ('description', ) # possibly "metafields.list('values')" (a callable to return list of values) # or create some other Project function -- look at how wagtail does tag indexing Project.content_panels = Page.content_panels + [ FieldPanel('description', classname="full"), InlinePanel(Project, 'metafields', label="MetaFields"), InlinePanel(Project, 'images', label="Images"), ] class ProjectCategory(Page): class Meta: verbose_name_plural = 'Portfolio Categories' subpage_types = ['portfolio.Project'] ProjectCategory.content_panels = Page.content_panels + [ InlinePanel(ProjectCategory, 'default_metafields', label="Default MetaFields"), ] class ProjectCategoryIndex(Page): subpage_types = ['portfolio.ProjectCategory']
class HomePage(Page): title_text = RichTextField(null=True, blank=True) body = RichTextField(null=True, blank=True) search_fields = Page.search_fields + ( index.SearchField('body'), ) class Meta: verbose_name = "Homepage" HomePage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('title_text', classname="full"), FieldPanel('body', classname="full"), InlinePanel('carousel_items', label="Carousel items"), InlinePanel('content_items', label="Content Blocks"), InlinePanel('related_links', label="Related links"), ] HomePage.promote_panels = Page.promote_panels class StandardIndexPageRelatedLink(Orderable, RelatedLink): page = ParentalKey('pages.StandardIndexPage', related_name='related_links') class StandardIndexPage(Page): subtitle = models.CharField(max_length=255, blank=True) intro = RichTextField(blank=True) feed_image = models.ForeignKey(
class FlatMenu(Menu): site = models.ForeignKey( 'wagtailcore.Site', verbose_name=_('site'), related_name="flat_menus", db_index=True, on_delete=models.CASCADE ) title = models.CharField( max_length=255, help_text=_("For internal reference only.")) handle = models.SlugField( max_length=100, help_text=_( "Used to reference this menu in templates etc. Must be unique " "for the selected site." ) ) heading = models.CharField( max_length=255, blank=True, help_text=_("If supplied, appears above the menu when rendered.") ) max_levels = models.PositiveSmallIntegerField( verbose_name=_('maximum levels'), choices=app_settings.MAX_LEVELS_CHOICES, default=1, help_text=mark_safe(_( "The maximum number of levels to display when rendering this " "menu. The value can be overidden by supplying a different " "<code>max_levels</code> value to the <code>{% flat_menu %}" "</code> tag in your templates." )) ) use_specific = models.PositiveSmallIntegerField( verbose_name=_('specific page usage'), choices=app_settings.USE_SPECIFIC_CHOICES, default=app_settings.USE_SPECIFIC_AUTO, help_text=mark_safe(_( "Controls how 'specific' pages objects are fetched and used when " "rendering this menu. This value can be overidden by supplying a " "different <code>use_specific</code> value to the <code>" "{% flat_menu %}</code> tag in your templates." )) ) base_form_class = FlatMenuAdminForm class Meta: unique_together = ("site", "handle") verbose_name = _("flat menu") verbose_name_plural = _("flat menus") @classmethod def get_for_site(cls, handle, site, fall_back_to_default_site_menus=False): """ Get a FlatMenu instance with a matching `handle` for the `site` provided - or for the 'default' site if not found. """ menu = cls.objects.filter(handle__exact=handle, site=site).first() if( menu is None and fall_back_to_default_site_menus and not site.is_default_site ): return cls.objects.filter( handle__exact=handle, site__is_default_site=True ).first() return menu def __str__(self): return '%s (%s)' % (self.title, self.handle) def clean(self, *args, **kwargs): # Raise validation error for unique_together constraint, as it's not # currently handled properly by wagtail clashes = FlatMenu.objects.filter(site=self.site, handle=self.handle) if self.pk: clashes = clashes.exclude(pk__exact=self.pk) if clashes.count(): msg = _("Site and handle must create a unique combination. A menu " "already exists with these same two values.") raise ValidationError({ 'site': [msg], 'handle': [msg], }) super(FlatMenu, self).clean(*args, **kwargs) panels = ( MultiFieldPanel( heading=_("Settings"), children=( FieldPanel('title'), FieldPanel('site'), FieldPanel('handle'), FieldPanel('heading'), ) ), InlinePanel('menu_items', label=_("menu items")), MultiFieldPanel( heading=_("Advanced settings"), children=(FieldPanel('max_levels'), FieldPanel('use_specific')), classname="collapsible collapsed", ), )
search_name = 'Index Page' subpage_types = ['IndexPage', 'RichTextPage', 'BlogIndexPage'] class IndexPageRelatedLink(Orderable, AbstractRelatedLink): page = ParentalKey('wagtailbase.IndexPage', related_name='related_links') class IndexPageAttachment(Orderable, AbstractAttachment): page = ParentalKey('wagtailbase.IndexPage', related_name='attachments') IndexPage.content_panels = [ FieldPanel('title', classname='full title'), FieldPanel('introduction', classname='full'), InlinePanel(IndexPage, 'related_links', label='Related links'), InlinePanel(IndexPage, 'attachments', label='Attachments') ] IndexPage.promote_panels = [ MultiFieldPanel(BaseIndexPage.promote_panels, "Common page configuration"), ] class RichTextPage(BaseRichTextPage): search_name = 'Rich Text Page' subpage_types = [] class RichTextPageRelatedLink(Orderable, AbstractRelatedLink): page = ParentalKey('wagtailbase.RichTextPage',
class InlineStreamPage(Page): content_panels = [ FieldPanel('title', classname="full title"), InlinePanel('sections') ]
youtube_blurb = RichTextField(blank=True, default='') get_started_now_page = models.ForeignKey(Page, blank=True, null=True, on_delete=models.SET_NULL, related_name='homepages') HomePage.content_panels = [ MultiFieldPanel( [FieldPanel('title')], heading='Title', classname='collapsible collapsed', ), MultiFieldPanel( [InlinePanel('hero_items')], heading='Hero images', classname='collapsible collapsed', ), MultiFieldPanel( [InlinePanel('highlights')], heading='Highlights', classname='collapsible collapsed', ), MultiFieldPanel( [ FieldPanel('youtube_video_id'), FieldPanel('youtube_video_title'), FieldPanel('youtube_blurb'), ], heading='Youtube video information',
index.SearchField('body'), ) password_required_template = 'tests/event_page_password_required.html' EventPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('date_from'), FieldPanel('date_to'), FieldPanel('time_from'), FieldPanel('time_to'), FieldPanel('location'), FieldPanel('audience'), FieldPanel('cost'), FieldPanel('signup_link'), InlinePanel(EventPage, 'carousel_items', label="Carousel items"), FieldPanel('body', classname="full"), InlinePanel(EventPage, 'speakers', label="Speakers"), InlinePanel(EventPage, 'related_links', label="Related links"), ] EventPage.promote_panels = [ MultiFieldPanel(COMMON_PANELS, "Common page configuration"), ImageChooserPanel('feed_image'), ] # Event index (has a separate AJAX template, and a custom template context) class EventIndex(Page): intro = RichTextField(blank=True) ajax_template = 'tests/includes/event_listing.html'
class ForwardLookingActivity(Study): parent_page_types = ['pages.StaticIndex'] phase_of_policy = models.ForeignKey( 'PhasesOfPolicy', verbose_name='phases of policy cycle', null=True, blank=True, on_delete=models.SET_NULL ) environmental_themes = ParentalManyToManyField( 'flis_metadata.EnvironmentalTheme', verbose_name='topics', blank=True) additional_information_phase = models.TextField( ('additional information about the application'), blank=True) foresight_approaches = ParentalManyToManyField( 'ForesightApproach', verbose_name='foresight approaches used') stakeholder_participation = models.BooleanField( 'stakeholder participation', default=False) additional_information_stakeholder = models.TextField( 'additional information about stakeholder involvement', blank=True) content_panels = Study.content_panels + [ FieldPanel('requested_by'), MultiFieldPanel([FieldRowPanel([ FieldPanel('start_date'), FieldPanel('end_date') ])] , 'Timing' ), MultiFieldPanel([ FieldPanel('environmental_themes'), FieldPanel('geographical_scope'), FieldPanel('countries', widget=SelectMultiple), ], 'Scope of study'), MultiFieldPanel([ FieldPanel('lead_author'), FieldPanel('other'), ], 'References and contact information'), MultiFieldPanel([ FieldPanel('purpose_and_target'), FieldPanel('additional_information'), ], 'Purpose and target audience'), MultiFieldPanel([ FieldPanel('phase_of_policy'), FieldPanel('additional_information_phase'), ], 'Application of forward looking information in policy cycle'), MultiFieldPanel([ FieldPanel('foresight_approaches'), FieldPanel('stakeholder_participation'), FieldPanel('additional_information_stakeholder'), ], 'Methods and methodology used'), InlinePanel('outcomes', label='Outcomes'), ] class Meta: verbose_name_plural = 'Forward looking activities'
class SeriesPage(ThemeablePage, FeatureStyleFields, Promotable, ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin): subtitle = RichTextField(blank=True, default="") short_description = RichTextField(blank=True, default="") body = article_fields.BodyField(blank=True, default="") main_image = models.ForeignKey( 'images.AttributedImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) feature_image = models.ForeignKey( 'images.AttributedImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) primary_topic = models.ForeignKey( 'articles.Topic', null=True, blank=True, on_delete=models.SET_NULL, related_name='series' ) project = models.ForeignKey( "projects.ProjectPage", null=True, blank=True, on_delete=models.SET_NULL, ) search_fields = Page.search_fields + [ index.SearchField('subtitle', partial_match=True), index.SearchField('body', partial_match=True), index.SearchField('get_primary_topic_name', partial_match=True), index.SearchField('get_topic_names', partial_match=True), ] number_of_related_articles = models.PositiveSmallIntegerField(default=6, verbose_name="Number of Related Articles to Show") def get_primary_topic_name(self): if self.primary_topic: return self.primary_topic.name else: "" def get_topic_names(self): return '\n'.join([topic.name if topic else "" for topic in self.topics]) def get_author_names(self): return '\n'.join([author.full_name if author else "" for author in self.authors]) @property def articles(self): article_list = [] for article_link in self.related_article_links.all(): if article_link.article: article_link.article.override_text = article_link.override_text article_link.article.override_image = article_link.override_image article_list.append(article_link.article) return article_list @property def authors(self): author_list = [] for article_link in self.related_article_links.all(): if article_link.article: if article_link.article: for author_link in article_link.article.author_links.all(): if author_link.author: if author_link.author not in author_list: author_list.append(author_link.author) author_list.sort(key=attrgetter('last_name')) return author_list @property def topics(self): all_topics = [] if self.primary_topic: all_topics.append(self.primary_topic) for article_link in self.related_article_links.all(): if article_link.article: all_topics.extend(article_link.article.topics) all_topics = list(set(all_topics)) if all_topics: all_topics.sort(key=attrgetter('name')) return all_topics @property def related_series(self): related_series_list = [] if self.project: related_series_list = self.project.get_related_series(self) return related_series_list def related_articles(self, number): articles = [] if self.primary_topic: articles = list(ArticlePage.objects.live().filter(primary_topic=self.primary_topic).distinct().order_by( '-first_published_at')[:number]) current_total = len(articles) if current_total < number: for article in self.articles: articles.extend(list(article.related_articles(number))) articles = list(set(articles))[:number] current_total = len(articles) if current_total >= number: return articles return articles content_panels = Page.content_panels + [ FieldPanel('subtitle'), FieldPanel('short_description'), PageChooserPanel('project'), ImageChooserPanel('main_image'), ImageChooserPanel('feature_image'), DocumentChooserPanel('video_document'), StreamFieldPanel('body'), InlinePanel('related_article_links', label="Articles"), SnippetChooserPanel('primary_topic'), ] promote_panels = Page.promote_panels + [ MultiFieldPanel( [ FieldPanel('sticky'), FieldPanel('sticky_for_type_section'), FieldPanel('slippery'), FieldPanel('slippery_for_type_section'), FieldPanel('editors_pick'), FieldPanel('feature_style'), FieldPanel('title_size'), FieldPanel('fullbleed_feature'), FieldPanel('image_overlay_opacity'), ], heading="Featuring Settings" ) ] style_panels = ThemeablePage.style_panels + [ MultiFieldPanel( [ FieldPanel('include_main_image'), FieldPanel('include_main_image_overlay'), FieldPanel('full_bleed_image_size'), FieldPanel('include_caption_in_footer'), ], heading="Main Image" ), MultiFieldPanel( [ FieldPanel('number_of_related_articles'), ], heading="Sections" ) ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(style_panels, heading='Page Style Options'), ObjectList(promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ])
feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') indexed_fields = ('get_audience_display', 'location', 'body') search_name = "Event" EventPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('date_from'), FieldPanel('date_to'), FieldPanel('time_from'), FieldPanel('time_to'), FieldPanel('location'), FieldPanel('audience'), FieldPanel('cost'), FieldPanel('signup_link'), InlinePanel(EventPage, 'carousel_items', label="Carousel items"), FieldPanel('body', classname="full"), InlinePanel(EventPage, 'speakers', label="Speakers"), InlinePanel(EventPage, 'related_links', label="Related links"), ] EventPage.promote_panels = [ MultiFieldPanel(COMMON_PANELS, "Common page configuration"), ImageChooserPanel('feed_image'), ]
def test_required_formsets(self): inline_panel = InlinePanel(self.mock_model, 'formset')( instance=self.fake_instance, form=self.fake_field) self.assertEqual(inline_panel.required_formsets(), ['formset'])
class Office(JobLocation): panels = [ FieldPanel('abbreviation'), FieldPanel('name'), InlinePanel('cities', label="Office location", max_num=1), ]
[ FieldPanel('commenting_state'), FieldPanel('commenting_open_time'), FieldPanel('commenting_close_time'), ], heading="Commenting Settings", ), MultiFieldPanel( [ FieldPanel('social_media_title'), FieldPanel('social_media_description'), ImageChooserPanel('social_media_image'), ], heading="Social Media", ), InlinePanel('related_sections', label="Related Sections"), ] ArticlePage.promote_panels = [ MultiFieldPanel(ArticlePage.featured_promote_panels, "Featuring"), MultiFieldPanel(ArticlePage.metedata_promote_panels, "Metadata"), MultiFieldPanel(Page.promote_panels, "Common page configuration", "collapsible collapsed") ] class ArticlePageRelatedSections(Orderable): page = ParentalKey(ArticlePage, related_name='related_sections') section = models.ForeignKey( 'wagtailcore.Page', null=True,
return self.url def get_blog_index(self): # Find closest ancestor which is a blog index return self.get_ancestors().type(BlogIndexPage).last() def get_context(self, request, *args, **kwargs): context = super(BlogPage, self).get_context(request, *args, **kwargs) context['blogs'] = self.get_blog_index().blogindexpage.blogs context = get_blog_context(context) context['COMMENTS_APP'] = COMMENTS_APP return context class Meta: verbose_name = _('Blog page') verbose_name_plural = _('Blog pages') parent_page_types = ['blog.BlogIndexPage'] BlogPage.content_panels = [ FieldPanel('title', classname="full title"), MultiFieldPanel([ FieldPanel('tags'), InlinePanel('categories', label=_("Categories")), ], heading="Tags and Categories"), ImageChooserPanel('header_image'), FieldPanel('body', classname="full"), FieldPanel('show_comments') ]