def test_is_multipart(self): """ Check whether is_multipart returns True when an InlinePanel contains a FileInput and False otherwise """ SpeakerObjectList = ObjectList([ InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.FileInput), ]), ]).bind_to_model(EventPage) SpeakerInlinePanel = SpeakerObjectList.children[0] EventPageForm = SpeakerObjectList.get_form_class(EventPage) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) self.assertTrue(panel.is_multipart()) SpeakerObjectList = ObjectList([ InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.Textarea), ]), ]).bind_to_model(EventPage) SpeakerInlinePanel = SpeakerObjectList.children[0] EventPageForm = SpeakerObjectList.get_form_class(EventPage) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) self.assertFalse(panel.is_multipart())
def get_edit_handler_class(self): if hasattr(self.model, 'edit_handler'): edit_handler = self.model.edit_handler else: panels = extract_panel_definitions_from_model_class(self.model) edit_handler = ObjectList(panels) return edit_handler.bind_to_model(self.model)
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 """ SpeakerObjectList = ObjectList([InlinePanel('speakers', label="Speakers")]).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() 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 get_edit_handler_class(self): if hasattr(self.model, 'edit_handler'): edit_handler = self.model.edit_handler else: fields_to_exclude = self.model_admin.get_form_fields_exclude(request=self.request) panels = extract_panel_definitions_from_model_class(self.model, exclude=fields_to_exclude) edit_handler = ObjectList(panels) return edit_handler.bind_to_model(self.model)
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())
def get_snippet_edit_handler(model): if model not in SNIPPET_EDIT_HANDLERS: if hasattr(model, 'edit_handler'): # use the edit handler specified on the page class edit_handler = model.edit_handler else: panels = extract_panel_definitions_from_model_class(model) edit_handler = ObjectList(panels) SNIPPET_EDIT_HANDLERS[model] = edit_handler.bind_to_model(model) return SNIPPET_EDIT_HANDLERS[model]
def test_autodetect_page_type(self): # Model has a foreign key to EventPage, which we want to autodetect # instead of specifying the page type in PageChooserPanel MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result)
def test_override_page_type(self): # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel # to restrict the chooser to that page type MyPageObjectList = ObjectList([ PageChooserPanel('page', 'tests.EventPage') ]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result)
def setUp(self): # a custom ObjectList for EventPage self.EventPageObjectList = ObjectList([ FieldPanel('title', widget=forms.Textarea), FieldPanel('date_from'), FieldPanel('date_to'), InlinePanel('speakers', label="Speakers"), ], heading='Event details', classname="shiny").bind_to_model(EventPage)
def test_render_js_init_with_can_choose_root_true(self): # construct an alternative page chooser panel object, with can_choose_root=True MyPageObjectList = ObjectList([ PageChooserPanel('page', can_choose_root=True) ]).bind_to_model(PageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() # the canChooseRoot flag on createPageChooser should now be true expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, true);'.format( id="id_page", model="wagtailcore.page", parent=self.events_index_page.id) self.assertIn(expected_js, result)
class TestObjectList(TestCase): def setUp(self): # a custom ObjectList for EventPage self.EventPageObjectList = ObjectList([ FieldPanel('title', widget=forms.Textarea), FieldPanel('date_from'), FieldPanel('date_to'), InlinePanel('speakers', label="Speakers"), ], heading='Event details', classname="shiny").bind_to_model(EventPage) def test_get_form_class(self): EventPageForm = self.EventPageObjectList.get_form_class(EventPage) form = EventPageForm() # form must include the 'speakers' formset required by the speakers InlinePanel self.assertIn('speakers', form.formsets) # form must respect any overridden widgets self.assertEqual(type(form.fields['title'].widget), forms.Textarea) def test_render(self): EventPageForm = self.EventPageObjectList.get_form_class(EventPage) event = EventPage(title='Abergavenny sheepdog trials') form = EventPageForm(instance=event) object_list = self.EventPageObjectList( instance=event, form=form ) result = object_list.render() # result should contain ObjectList furniture self.assertIn('<ul class="objects">', result) # result should contain h2 headings (including labels) for children self.assertInHTML('<h2><label for="id_date_from">Start date</label></h2>', result) # result should include help text for children self.assertIn('<div class="object-help help">Not required if event is on a single day</div>', result) # result should contain rendered content from descendants self.assertIn('Abergavenny sheepdog trials</textarea>', result) # this result should not include fields that are not covered by the panel definition self.assertNotIn('signup_link', result)
def test_no_thousand_separators_in_js(self): """ Test that the USE_THOUSAND_SEPARATOR setting does not screw up the rendering of numbers (specifically maxForms=1000) in the JS initializer: https://github.com/torchbox/wagtail/pull/2699 """ 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) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) self.assertIn('maxForms: 1000', panel.render_js_init())
def setUp(self): model = PageChooserModel # a model with a foreign key to Page which we want to render as a page chooser # a PageChooserPanel class that works on PageChooserModel's 'page' field self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel) self.MyPageChooserPanel = self.EditHandler.children[0] # build a form class containing the fields that MyPageChooserPanel wants self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel) # a test instance of PageChooserModel, pointing to the 'christmas' page self.christmas_page = Page.objects.get(slug='christmas') self.events_index_page = Page.objects.get(slug='events') self.test_instance = model.objects.create(page=self.christmas_page) self.form = self.PageChooserForm(instance=self.test_instance) self.page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=self.form)
class ContributorPage(ThemeablePage): first_name = models.CharField(max_length=255, blank=True, default="") last_name = models.CharField(max_length=255, blank=True, default="") nickname = models.CharField(max_length=1024, blank=True, default="") email = models.EmailField(blank=True, default="") twitter_handle = models.CharField(max_length=16, blank=True, default="") short_bio = RichTextField(blank=True, default="") long_bio = RichTextField(blank=True, default="") headshot = models.ForeignKey( 'images.AttributedImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) featured = models.BooleanField(default=False) search_fields = Page.search_fields + [ index.SearchField('first_name', partial_match=True), index.SearchField('last_name', partial_match=True), index.SearchField('twitter_handle', partial_match=True), index.SearchField('short_bio', partial_match=True), index.SearchField('long_bio', partial_match=True), ] def search_result_text(self): if self.short_bio: self.search_result_text = self.short_bio else: self.search_result_text = self.long_bio return self.search_result_text def save(self, *args, **kwargs): if self.twitter_handle and not self.twitter_handle.startswith("@"): self.twitter_handle = "@{}".format(self.twitter_handle) super(ContributorPage, self).save(*args, **kwargs) @property def full_name(self): return "{} {}".format(self.first_name, self.last_name) @property def last_comma_first_name(self): return "{}, {}".format(self.last_name, self.first_name) @property def display_twitter_handle(self): if self.twitter_handle: return self.twitter_handle[1:] return self.twitter_handle def __str__(self): return "{} {} - {}".format(self.first_name, self.last_name, self.email) content_panels = Page.content_panels + [ FieldPanel('first_name'), FieldPanel('last_name'), FieldPanel('email'), FieldPanel('twitter_handle'), RichTextFieldPanel('short_bio'), RichTextFieldPanel('long_bio'), ImageChooserPanel('headshot'), ] promote_panels = Page.promote_panels + [ MultiFieldPanel( [ FieldPanel('featured'), ], heading="Featuring Settings" ) ] style_panels = ThemeablePage.style_panels 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"), ])
class TestPageChooserPanel(TestCase): fixtures = ['test.json'] def setUp(self): model = PageChooserModel # a model with a foreign key to Page which we want to render as a page chooser # a PageChooserPanel class that works on PageChooserModel's 'page' field self.EditHandler = ObjectList([PageChooserPanel('page') ]).bind_to_model(PageChooserModel) self.MyPageChooserPanel = self.EditHandler.children[0] # build a form class containing the fields that MyPageChooserPanel wants self.PageChooserForm = self.EditHandler.get_form_class( PageChooserModel) # a test instance of PageChooserModel, pointing to the 'christmas' page self.christmas_page = Page.objects.get(slug='christmas') self.events_index_page = Page.objects.get(slug='events') self.test_instance = model.objects.create(page=self.christmas_page) self.form = self.PageChooserForm(instance=self.test_instance) self.page_chooser_panel = self.MyPageChooserPanel( instance=self.test_instance, form=self.form) def test_page_chooser_uses_correct_widget(self): self.assertEqual(type(self.form.fields['page'].widget), AdminPageChooser) def test_render_js_init(self): result = self.page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="wagtailcore.page", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_render_js_init_with_can_choose_root_true(self): # construct an alternative page chooser panel object, with can_choose_root=True MyPageObjectList = ObjectList([ PageChooserPanel('page', can_choose_root=True) ]).bind_to_model(PageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class( EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() # the canChooseRoot flag on createPageChooser should now be true expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, true);'.format( id="id_page", model="wagtailcore.page", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_get_chosen_item(self): result = self.page_chooser_panel.get_chosen_item() self.assertEqual(result, self.christmas_page) def test_render_as_field(self): result = self.page_chooser_panel.render_as_field() self.assertIn('<p class="help">help text</p>', result) self.assertIn('<span class="title">Christmas</span>', result) self.assertIn( '<a href="/admin/pages/%d/edit/" class="edit-link button button-small button-secondary" target="_blank">' 'Edit this page</a>' % self.christmas_page.id, result) def test_render_as_empty_field(self): test_instance = PageChooserModel() form = self.PageChooserForm(instance=test_instance) page_chooser_panel = self.MyPageChooserPanel(instance=test_instance, form=form) result = page_chooser_panel.render_as_field() self.assertIn('<p class="help">help text</p>', result) self.assertIn('<span class="title"></span>', result) self.assertIn('Choose a page', result) def test_render_error(self): form = self.PageChooserForm({'page': ''}, instance=self.test_instance) self.assertFalse(form.is_valid()) page_chooser_panel = self.MyPageChooserPanel( instance=self.test_instance, form=form) self.assertIn('<span>This field is required.</span>', page_chooser_panel.render_as_field()) def test_override_page_type(self): # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel # to restrict the chooser to that page type MyPageObjectList = ObjectList([ PageChooserPanel('page', 'tests.EventPage') ]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class( EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_autodetect_page_type(self): # Model has a foreign key to EventPage, which we want to autodetect # instead of specifying the page type in PageChooserPanel MyPageObjectList = ObjectList([PageChooserPanel('page') ]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class( EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_target_models(self): result = PageChooserPanel('barbecue', 'wagtailcore.site').bind_to_model( PageChooserModel).target_models() self.assertEqual(result, [Site]) def test_target_models_malformed_type(self): result = PageChooserPanel('barbecue', 'snowman').bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_models) def test_target_models_nonexistent_type(self): result = PageChooserPanel( 'barbecue', 'snowman.lorry').bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_models)
class CFGOVPage(Page): authors = ClusterTaggableManager(through=CFGOVAuthoredPages, blank=True, verbose_name='Authors', help_text='A comma separated list of ' + 'authors.', related_name='authored_pages') tags = ClusterTaggableManager(through=CFGOVTaggedPages, blank=True, related_name='tagged_pages') shared = models.BooleanField(default=False) has_unshared_changes = models.BooleanField(default=False) language = models.CharField(choices=ref.supported_languagues, default='en', max_length=2) social_sharing_image = models.ForeignKey( 'v1.CFGOVImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=( 'Optionally select a custom image to appear when users share this ' 'page on social media websites. Recommended size: 1200w x 630h. ' 'Maximum size: 4096w x 4096h.')) # This is used solely for subclassing pages we want to make at the CFPB. is_creatable = False objects = CFGOVPageManager() # These fields show up in either the sidebar or the footer of the page # depending on the page type. sidefoot = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('related_posts', organisms.RelatedPosts()), ('related_metadata', molecules.RelatedMetadata()), ('email_signup', organisms.EmailSignUp()), ('sidebar_contact', organisms.SidebarContactInfo()), ('rss_feed', molecules.RSSFeed()), ('social_media', molecules.SocialMedia()), ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)), ], blank=True) # Panels promote_panels = Page.promote_panels + [ ImageChooserPanel('social_sharing_image'), ] sidefoot_panels = [ StreamFieldPanel('sidefoot'), ] settings_panels = [ MultiFieldPanel(promote_panels, 'Settings'), InlinePanel('categories', label="Categories", max_num=2), FieldPanel('tags', 'Tags'), FieldPanel('authors', 'Authors'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), FieldPanel('language', 'language'), ] # Tab handler interface guide because it must be repeated for each subclass edit_handler = TabbedInterface([ ObjectList(Page.content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar/Footer'), ObjectList(settings_panels, heading='Configuration'), ]) def clean(self): super(CFGOVPage, self).clean() validate_social_sharing_image(self.social_sharing_image) def get_authors(self): """ Returns a sorted list of authors. Default is alphabetical """ return self.alphabetize_authors() def alphabetize_authors(self): """ Alphabetize authors of this page by last name, then first name if needed """ # First sort by first name author_names = self.authors.order_by('name') # Then sort by last name return sorted(author_names, key=lambda x: x.name.split()[-1]) def generate_view_more_url(self, request): """Generate a URL to see more pages like this one. This method generates a link to the Activity Log page (which must exist and must have a unique site-wide slug of "activity-log") with filters set by the tags assigned to this page, like this: /activity-log/?topics=foo&topics=bar&topics=baz If for some reason a page with slug "activity-log" does not exist, this method will raise Page.DoesNotExist. """ activity_log = Page.objects.get(slug='activity-log') url = activity_log.get_url(request) tags = urlencode([('topics', tag) for tag in self.tags.slugs()]) if tags: url += '?' + tags return url def related_posts(self, block): from v1.models.learn_page import AbstractFilterPage def tag_set(related_page): return set([tag.pk for tag in related_page.tags.all()]) def match_all_topic_tags(queryset, page_tags): """Return pages that share every one of the current page's tags.""" current_tag_set = set([tag.pk for tag in page_tags]) return [ page for page in queryset if current_tag_set.issubset(tag_set(page)) ] related_types = [] related_items = {} if block.value.get('relate_posts'): related_types.append('blog') if block.value.get('relate_newsroom'): related_types.append('newsroom') if block.value.get('relate_events'): related_types.append('events') if not related_types: return related_items tags = self.tags.all() and_filtering = block.value['and_filtering'] specific_categories = block.value['specific_categories'] limit = int(block.value['limit']) queryset = AbstractFilterPage.objects.live().exclude( id=self.id).order_by('-date_published').distinct() for parent in related_types: # blog, newsroom or events # Include children of this slug that match at least 1 tag children = Page.objects.child_of_q(Page.objects.get(slug=parent)) filters = children & Q(('tags__in', tags)) if parent == 'events': # Include archived events matches archive = Page.objects.get(slug='archive-past-events') children = Page.objects.child_of_q(archive) filters |= children & Q(('tags__in', tags)) if specific_categories: # Filter by any additional categories specified categories = ref.get_appropriate_categories( specific_categories=specific_categories, page_type=parent) if categories: filters &= Q(('categories__name__in', categories)) related_queryset = queryset.filter(filters) if and_filtering: # By default, we need to match at least one tag # If specified in the admin, change this to match ALL tags related_queryset = match_all_topic_tags(related_queryset, tags) related_items[parent.title()] = related_queryset[:limit] # Return items in the dictionary that have non-empty querysets return {key: value for key, value in related_items.items() if value} def related_metadata_tags(self): # Set the tags to correct data format tags = {'links': []} filter_page = self.get_filter_data() for tag in self.specific.tags.all(): tag_link = {'text': tag.name, 'url': ''} if filter_page: relative_url = filter_page.relative_url(filter_page.get_site()) param = '?topics=' + tag.slug tag_link['url'] = relative_url + param tags['links'].append(tag_link) return tags def get_filter_data(self): for ancestor in self.get_ancestors().reverse().specific(): if ancestor.specific_class.__name__ in [ 'BrowseFilterablePage', 'SublandingFilterablePage', 'EventArchivePage', 'NewsroomLandingPage' ]: return ancestor return None def get_breadcrumbs(self, request): ancestors = self.get_ancestors() home_page_children = request.site.root_page.get_children() for i, ancestor in enumerate(ancestors): if ancestor in home_page_children: # Add top level parent page and `/process/` url segments # where necessary to OAH page breadcrumbs. # TODO: Remove this when OAH moves under /consumer-tools # and redirects are added after 2018 homebuying campaign. if ancestor.slug == 'owning-a-home': breadcrumbs = [] for ancestor in ancestors[i:]: ancestor_url = ancestor.relative_url(request.site) if ancestor_url.startswith( ('/owning-a-home/prepare', '/owning-a-home/explore', '/owning-a-home/compare', '/owning-a-home/close', '/owning-a-home/sources')): ancestor_url = ancestor_url.replace( 'owning-a-home', 'owning-a-home/process') breadcrumbs.append({ 'title': ancestor.title, 'href': ancestor_url, }) return breadcrumbs # END TODO return [ancestor for ancestor in ancestors[i + 1:]] return [] def get_appropriate_descendants(self, inclusive=True): return CFGOVPage.objects.live().descendant_of(self, inclusive) def get_appropriate_siblings(self, inclusive=True): return CFGOVPage.objects.live().sibling_of(self, inclusive) def get_context(self, request, *args, **kwargs): context = super(CFGOVPage, self).get_context(request, *args, **kwargs) for hook in hooks.get_hooks('cfgovpage_context_handlers'): hook(self, request, context, *args, **kwargs) return context def serve(self, request, *args, **kwargs): """ If request is ajax, then return the ajax request handler response, else return the super. """ if request.method == 'POST': return self.serve_post(request, *args, **kwargs) # Force the page's language on the request translation.activate(self.language) request.LANGUAGE_CODE = translation.get_language() return super(CFGOVPage, self).serve(request, *args, **kwargs) def _return_bad_post_response(self, request): if request.is_ajax(): return JsonResponse({'result': 'error'}, status=400) return HttpResponseBadRequest(self.url) def serve_post(self, request, *args, **kwargs): """Handle a POST to a specific form on the page. Attempts to retrieve form_id from the POST request, which must be formatted like "form-name-index" where the "name" part is the name of a StreamField on the page and the "index" part refers to the index of the form element in the StreamField. If form_id is found, it returns the response from the block method retrieval. If form_id is not found, it returns an error response. """ form_module = None form_id = request.POST.get('form_id', None) if form_id: form_id_parts = form_id.split('-') if len(form_id_parts) == 3: streamfield_name = form_id_parts[1] streamfield = getattr(self, streamfield_name, None) if streamfield is not None: try: streamfield_index = int(form_id_parts[2]) except ValueError: streamfield_index = None try: form_module = streamfield[streamfield_index] except IndexError: form_module = None if form_module is None: return self._return_bad_post_response(request) result = form_module.block.get_result(self, request, form_module.value, True) if isinstance(result, HttpResponse): return result context = self.get_context(request, *args, **kwargs) context['form_modules'][streamfield_name].update( {streamfield_index: result}) return TemplateResponse(request, self.get_template(request, *args, **kwargs), context) class Meta: app_label = 'v1' def parent(self): parent = self.get_ancestors(inclusive=False).reverse()[0].specific return parent # To be overriden if page type requires JS files every time @property def page_js(self): return [] @property def streamfield_js(self): js = [] block_cls_names = get_page_blocks(self) for block_cls_name in block_cls_names: block_cls = import_string(block_cls_name) if hasattr(block_cls, 'Media') and hasattr(block_cls.Media, 'js'): js.extend(block_cls.Media.js) return js # Returns the JS files required by this page and its StreamField blocks. @property def media(self): return sorted(set(self.page_js + self.streamfield_js)) # Returns an image for the page's meta Open Graph tag @property def meta_image(self): return self.social_sharing_image @property def post_preview_cache_key(self): return 'post_preview_{}'.format(self.id)
class TopicListPage(RoutablePageMixin, ThemeablePage): articles_per_page = models.IntegerField(default=20) @property def topics(self): popular_topics = Topic.objects.annotate( num_articles=Count('article_links') + Count('articles') + Count('series')).order_by("-num_articles")[:25] return sorted(popular_topics, key=lambda x: x.name) @route(r'^$', name="topic_list") def topics_list(self, request): context = { "self": self, } return render(request, "articles/topic_list_page.html", context) @route(r'^([\w-]+)/$', name="topic") def topic_view(self, request, topic_slug): topic = get_object_or_404(Topic, slug=topic_slug) articles = topic.item_list paginator = Paginator(articles, self.articles_per_page) page = request.GET.get('page') try: articles = paginator.page(page) except PageNotAnInteger: articles = paginator.page(1) except EmptyPage: articles = paginator.page(paginator.num_pages) context = { "self": self, "topic": topic, "articles": articles, } return render(request, "articles/topic_page.html", context) def get_cached_paths(self): yield '/' for topic in Topic.objects.all(): articles = ArticlePage.objects.live().filter( models.Q(primary_topic=topic) | models.Q(topic_links__topic=topic) ).order_by('-first_published_at').distinct() paginator = Paginator(articles, self.articles_per_page) topic_url = '/{}/'.format(topic.slug) yield topic_url for page_number in range(2, paginator.num_pages + 1): yield topic_url + '?page=' + str(page_number) content_panels = Page.content_panels + [ FieldPanel('articles_per_page'), ] style_panels = ThemeablePage.style_panels edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(style_panels, heading='Page Style Options'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ])
class Product(models.Model): title = models.CharField(unique=True, max_length=255, verbose_name=_('title')) description = models.TextField( blank=True, verbose_name=_('description'), help_text=_('For admin/backoffice purposes only.')) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') product_page = models.OneToOneField( ProductPage, null=True, blank=True, on_delete=models.SET_NULL, ) # # TODO or Parental categories = TreeManyToManyField( 'wagtailcommerce.category', null=True, blank=True, #on_delete=models.SET_NULL, #related_name='categories' ) sale_price = MoneyField( blank=True, null=True, default=None, max_digits=10, decimal_places=2, help_text= _("Base price to compute the customer price. Sometimes called the catalog price." )) cost_price = MoneyField(blank=True, null=True, default=None, max_digits=10, decimal_places=2, help_text=_("Cost of the product.")) sku = CharNullableField( unique=True, null=True, blank=True, max_length=255, verbose_name=_('SKU'), help_text=_('Stock Keeping Unit'), ) ean = CharNullableField( unique=True, null=True, blank=True, max_length=255, verbose_name=_('EAN'), help_text=_('European Article Number'), ) general_panels = [ FieldPanel('title'), ImageChooserPanel('image'), MultiFieldPanel( [ FieldPanel('sale_price', classname='fn'), FieldPanel('cost_price', classname='ln'), ], heading='Price', ), MultiFieldPanel( [ FieldPanel('sku'), FieldPanel('ean'), ], heading='Codes', ), FieldPanel('description'), ] catalog_panels = [ PageChooserOrCreatePanel('product_page'), FieldPanel('categories'), # categories # alternative products # accessoires/options # invoice confirm email (PageChooser) ] configurator_panels = [ #FieldPanel('categories') # alternative products # accessoires/options # invoice confirm email (PageChooser) ] edit_handler = TabbedInterface( [ ObjectList(general_panels, heading='General'), ObjectList(catalog_panels, heading='Catalog'), # ObjectList(configurator_panels, heading='configurator'), # ObjectList([], heading='Inventory'), # ObjectList([], heading='Shipping'), # ObjectList([], heading='Attributes'), ], base_form_class=ProductAdminModelForm) def __str__(self): return "%s" % (self.title)
# we use this to test that the 'promote' tab is left out of the output when empty StandardIndex.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('seo_title'), FieldPanel('slug'), InlinePanel('advert_placements', label="Adverts"), ] StandardIndex.promote_panels = [] class StandardChild(Page): pass # Test overriding edit_handler with a custom one StandardChild.edit_handler = TabbedInterface([ ObjectList(StandardChild.content_panels, heading='Content'), ObjectList(StandardChild.promote_panels, heading='Promote'), ObjectList(StandardChild.settings_panels, heading='Settings', classname='settings'), ObjectList([], heading='Dinosaurs'), ]) class BusinessIndex(Page): """ Can be placed anywhere, can only have Business children """ subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex'] class BusinessSubIndex(Page): """ Can be placed under BusinessIndex, and have BusinessChild children """ subpage_types = ['tests.BusinessChild'] parent_page_types = ['tests.BusinessIndex']
def edit_handler_class(self, model): """A GenericModelChooserPanel class that works on PageChooserModel's 'page' field """ object_list = ObjectList([GenericModelChooserPanel('page')]) return object_list.bind_to_model(model)
class TabbedSettings(TestSetting): edit_handler = TabbedInterface([ ObjectList([FieldPanel('title')], heading='First tab'), ObjectList([FieldPanel('email')], heading='Second tab'), ])
def get_setting_edit_handler(model): panels = extract_panel_definitions_from_model_class(model, ['site']) return ObjectList(panels).bind_to_model(model)
class JournalAdminCreateView(CreateView): """ Journals Create/Add view for wagtail CMS """ panels = [ MultiFieldPanel([ FieldPanel('name'), FieldPanel('access_length'), FieldPanel('organization'), FieldPanel('video_course_ids'), FieldPanel('price'), FieldPanel('currency'), ]) ] edit_handler = ObjectList(panels) def get_form_kwargs(self): """ Overridden to add request to form kwargs """ kwargs = super(JournalAdminCreateView, self).get_form_kwargs() kwargs.update({'request': self.request}) return kwargs def get_edit_handler_class(self): """ Overridden to use custom edit_handle """ return self.edit_handler.bind_to_model(self.model) def form_invalid(self, form): """ Overridden to fix for wagtail not displaying non-field related errors. Code snippet is taken from wagtail latest release (2.1.1) """ messages.validation_error(self.request, self.get_error_message(), form) return self.render_to_response(self.get_context_data()) def get_form_class(self): """ Returns the form class to use in this view """ return JournalCreateForm def create_journal_on_other_services(self, data): """ Using publish_journal command create Journal other services like discovery and e-commerce etc """ stdout, stderr = StringIO(), StringIO() try: management.call_command('publish_journals', create=data['name'], org=data['organization'].name, access_length=data['access_length'], price=data['price'], currency=data['currency'], publish=False, stdout=stdout, stderr=stderr) except CommandError as e: add_messages(self.request, 'error', [str(e)]) add_messages(self.request, 'success', stdout.getvalue().split('\n')) add_messages(self.request, 'error', stderr.getvalue().split('\n')) def post(self, request, *args, **kwargs): """ Overridden to call create journal command if form is valid """ form = self.get_form() if form.is_valid(): response = self.form_valid(form) self.create_journal_on_other_services(form.cleaned_data) return response else: return self.form_invalid(form)
class JournalAdminEditView(EditView): """ Edit view for Journal in wagtail CMS """ panels = [ MultiFieldPanel([ FieldPanel('name'), FieldPanel('status'), FieldPanel('video_course_ids'), ]) ] edit_handler = ObjectList(panels) def get_edit_handler_class(self): """ Overridden to get custom handler """ return self.edit_handler.bind_to_model(self.model) def get_initial(self): """ Overridden to get initial for journal status from discovery """ initials = super(JournalAdminEditView, self).get_initial() journal_discovery_results = get_discovery_journal( self.request.site.siteconfiguration.discovery_journal_api_client, self.instance.uuid) if journal_discovery_results: initials.update({ 'status': True if journal_discovery_results[0]['status'] == 'active' else False }) return initials def get_form_class(self): """ Returns the form class to use in this view """ return JournalEditForm def form_invalid(self, form): """ Overridden to fix for wagtail not displaying non-field related errors. Code snippet is taken from wagtail latest release (2.1.1) """ messages.validation_error(self.request, self.get_error_message(), form) return self.render_to_response(self.get_context_data()) def update_journal_on_other_services(self, data): """ Using publish_journal command update Journal other services like discovery and e-commerce etc """ stdout, stderr = StringIO(), StringIO() management.call_command('publish_journals', update=self.instance.uuid, publish=data['status'], stdout=stdout, stderr=stderr) add_messages(self.request, 'success', stdout.getvalue().split('\n')) add_messages(self.request, 'error', stderr.getvalue().split('\n')) def post(self, request, *args, **kwargs): """ Overridden to call update journal command if form is valid """ form = self.get_form() if form.is_valid(): response = self.form_valid(form) self.update_journal_on_other_services(form.cleaned_data) return response else: return self.form_invalid(form)
class EventPage(AbstractFilterPage): # General content fields body = RichTextField('Subheading', blank=True) archive_body = RichTextField(blank=True) live_body = RichTextField(blank=True) future_body = RichTextField(blank=True) start_dt = models.DateTimeField("Start", blank=True, null=True) end_dt = models.DateTimeField("End", blank=True, null=True) future_body = RichTextField(blank=True) archive_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_transcript = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') speech_transcript = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') flickr_url = models.URLField("Flickr URL", blank=True) youtube_url = models.URLField( "Youtube URL", blank=True, help_text= "Format: https://www.youtube.com/embed/video_id. It can be obtained by clicking on Share > Embed on Youtube.", # noqa: E501 validators=[ RegexValidator(regex='^https?:\/\/www\.youtube\.com\/embed\/.*$') ]) live_stream_availability = models.BooleanField("Streaming?", default=False, blank=True) live_stream_url = models.URLField( "URL", blank=True, help_text= "Format: https://www.ustream.tv/embed/video_id. It can be obtained by following the instructions listed here: https://support.ustream.tv/hc/en-us/articles/207851917-How-to-embed-a-stream-or-video-on-your-site", # noqa: E501 validators=[ RegexValidator(regex='^https?:\/\/www\.ustream\.tv\/embed\/.*$') ]) live_stream_date = models.DateTimeField("Go Live Date", blank=True, null=True) # Venue content fields venue_name = models.CharField(max_length=100, blank=True) venue_street = models.CharField(max_length=100, blank=True) venue_suite = models.CharField(max_length=100, blank=True) venue_city = models.CharField(max_length=100, blank=True) venue_state = USStateField(blank=True) venue_zip = models.IntegerField(blank=True, null=True) agenda_items = StreamField([('item', AgendaItemBlock())], blank=True) objects = CFGOVPageManager() # General content tab content_panels = CFGOVPage.content_panels + [ FieldPanel('body', classname="full"), FieldRowPanel([ FieldPanel('start_dt', classname="col6"), FieldPanel('end_dt', classname="col6"), ]), MultiFieldPanel([ FieldPanel('archive_body', classname="full"), ImageChooserPanel('archive_image'), DocumentChooserPanel('video_transcript'), DocumentChooserPanel('speech_transcript'), FieldPanel('flickr_url'), FieldPanel('youtube_url'), ], heading='Archive Information'), FieldPanel('live_body', classname="full"), FieldPanel('future_body', classname="full"), MultiFieldPanel([ FieldPanel('live_stream_availability'), FieldPanel('live_stream_url'), FieldPanel('live_stream_date'), ], heading='Live Stream Information'), ] # Venue content tab venue_panels = [ FieldPanel('venue_name'), MultiFieldPanel([ FieldPanel('venue_street'), FieldPanel('venue_suite'), FieldPanel('venue_city'), FieldPanel('venue_state'), FieldPanel('venue_zip'), ], heading='Venue Address'), ] # Agenda content tab agenda_panels = [ StreamFieldPanel('agenda_items'), ] # Promotion panels promote_panels = [ MultiFieldPanel(AbstractFilterPage.promote_panels, "Page configuration"), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(venue_panels, heading='Venue Information'), ObjectList(agenda_panels, heading='Agenda Information'), ObjectList(AbstractFilterPage.sidefoot_panels, heading='Sidebar'), ObjectList(AbstractFilterPage.settings_panels, heading='Configuration'), ]) template = 'events/event.html' def location_image_url(self, scale='2', size='276x155', zoom='12'): center = 'Washington, DC' if self.venue_city: center = self.venue_city if self.venue_state: center = center + ', ' + self.venue_state options = { 'center': center, 'scale': scale, 'size': size, 'zoom': zoom } url = 'https://maps.googleapis.com/maps/api/staticmap?' return '{url}{options}'.format(url=url, options=urlencode(options))
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', 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"), ])
class ServicePage(Page): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) content = RichTextField( features=WYSIWYG_FEATURES, verbose_name= 'Write out the steps a resident needs to take to use the service') extra_content = StreamField( [ ('content', blocks.RichTextBlock( features=WYSIWYG_FEATURES, help_text='Write any additional content describing the service' )), ('application_block', custom_blocks.SnippetChooserBlockWithAPIGoodness( 'base.ApplicationBlock')), ], verbose_name= 'Add any forms, maps, apps, or content that will help the resident use the service', ) topic = models.ForeignKey( 'base.Topic', on_delete=models.PROTECT, related_name='services', ) parent_page_types = ['base.HomePage'] subpage_types = [] base_form_class = custom_forms.ServicePageForm content_panels = [ FieldPanel('topic'), FieldPanel('title'), FieldPanel('content'), StreamFieldPanel('extra_content'), InlinePanel('contacts', label='Contacts'), ] api_fields = [ APIField('content'), APIField('extra_content'), APIField('topic'), APIField('contacts'), ] es_panels = [ # TODO: This field comes from Page and django-modeltranslation complains about it # FieldPanel('title_es'), FieldPanel('content_es'), ] vi_panels = [ # TODO: This field comes from Page and django-modeltranslation complains about it # FieldPanel('title_es'), FieldPanel('content_vi'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(es_panels, heading='Spanish', classname='translation-tab'), ObjectList(vi_panels, heading='Vietnamese', classname='translation-tab'), ObjectList(Page.promote_panels, heading='Promote'), # TODO: What should we do with the fields in settings? # ObjectList(Page.settings_panels, heading='Settings', classname='settings'), ])
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, ) 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 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', Topic), InlinePanel('topic_links', label="Secondary Topics"), ] 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"), ])
def get_invoice_edit_handler(Invoice): panels = extract_panel_definitions_from_model_class( Invoice, exclude=['invoiceindex']) EditHandler = ObjectList(panels).bind_to_model(Invoice) return EditHandler
class TestPageChooserPanel(TestCase): fixtures = ['test.json'] def setUp(self): model = PageChooserModel # a model with a foreign key to Page which we want to render as a page chooser # a PageChooserPanel class that works on PageChooserModel's 'page' field self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel) self.MyPageChooserPanel = self.EditHandler.children[0] # build a form class containing the fields that MyPageChooserPanel wants self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel) # a test instance of PageChooserModel, pointing to the 'christmas' page self.christmas_page = Page.objects.get(slug='christmas') self.events_index_page = Page.objects.get(slug='events') self.test_instance = model.objects.create(page=self.christmas_page) self.form = self.PageChooserForm(instance=self.test_instance) self.page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=self.form) def test_page_chooser_uses_correct_widget(self): self.assertEqual(type(self.form.fields['page'].widget), AdminPageChooser) def test_render_js_init(self): result = self.page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="wagtailcore.page", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_render_js_init_with_can_choose_root_true(self): # construct an alternative page chooser panel object, with can_choose_root=True MyPageObjectList = ObjectList([ PageChooserPanel('page', can_choose_root=True) ]).bind_to_model(PageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() # the canChooseRoot flag on createPageChooser should now be true expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, true);'.format( id="id_page", model="wagtailcore.page", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_get_chosen_item(self): result = self.page_chooser_panel.get_chosen_item() self.assertEqual(result, self.christmas_page) def test_render_as_field(self): result = self.page_chooser_panel.render_as_field() self.assertIn('<p class="help">help text</p>', result) self.assertIn('<span class="title">Christmas</span>', result) self.assertIn( '<a href="/admin/pages/%d/edit/" class="edit-link button button-small button-secondary" target="_blank">' 'Edit this page</a>' % self.christmas_page.id, result) def test_render_as_empty_field(self): test_instance = PageChooserModel() form = self.PageChooserForm(instance=test_instance) page_chooser_panel = self.MyPageChooserPanel(instance=test_instance, form=form) result = page_chooser_panel.render_as_field() self.assertIn('<p class="help">help text</p>', result) self.assertIn('<span class="title"></span>', result) self.assertIn('Choose a page', result) def test_render_error(self): form = self.PageChooserForm({'page': ''}, instance=self.test_instance) self.assertFalse(form.is_valid()) page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form) self.assertIn('<span>This field is required.</span>', page_chooser_panel.render_as_field()) def test_override_page_type(self): # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel # to restrict the chooser to that page type MyPageObjectList = ObjectList([ PageChooserPanel('page', 'tests.EventPage') ]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_autodetect_page_type(self): # Model has a foreign key to EventPage, which we want to autodetect # instead of specifying the page type in PageChooserPanel MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel) MyPageChooserPanel = MyPageObjectList.children[0] PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel) form = PageChooserForm(instance=self.test_instance) page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form) result = page_chooser_panel.render_as_field() expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false);'.format( id="id_page", model="tests.eventpage", parent=self.events_index_page.id) self.assertIn(expected_js, result) def test_target_models(self): result = PageChooserPanel( 'barbecue', 'wagtailcore.site' ).bind_to_model(PageChooserModel).target_models() self.assertEqual(result, [Site]) def test_target_models_malformed_type(self): result = PageChooserPanel( 'barbecue', 'snowman' ).bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_models) def test_target_models_nonexistent_type(self): result = PageChooserPanel( 'barbecue', 'snowman.lorry' ).bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_models) def test_target_content_type(self): with warnings.catch_warnings(record=True) as ws: warnings.simplefilter('always') result = PageChooserPanel( 'barbecue', 'wagtailcore.site' ).bind_to_model(PageChooserModel).target_content_type()[0] self.assertEqual(result.name, 'site') self.assertEqual(len(ws), 1) self.assertIs(ws[0].category, RemovedInWagtail17Warning) def test_target_content_type_malformed_type(self): with warnings.catch_warnings(record=True) as ws: warnings.simplefilter('always') result = PageChooserPanel( 'barbecue', 'snowman' ).bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_content_type) self.assertEqual(len(ws), 1) self.assertIs(ws[0].category, RemovedInWagtail17Warning) def test_target_content_type_nonexistent_type(self): with warnings.catch_warnings(record=True) as ws: warnings.simplefilter('always') result = PageChooserPanel( 'barbecue', 'snowman.lorry' ).bind_to_model(PageChooserModel) self.assertRaises(ImproperlyConfigured, result.target_content_type) self.assertEqual(len(ws), 1) self.assertIs(ws[0].category, RemovedInWagtail17Warning)
class RegulationPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage): """A routable page for serving an eregulations page by Section ID.""" objects = PageManager() parent_page_types = ['regulations3k.RegulationLandingPage'] subpage_types = [] template = 'regulations3k/browse-regulation.html' header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ], blank=True) content = StreamField([ ('info_unit_group', organisms.InfoUnitGroup()), ('full_width_text', RegulationsFullWidthText()), ], null=True, blank=True) regulation = models.ForeignKey(Part, blank=True, null=True, on_delete=models.PROTECT, related_name='page') content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), FieldPanel('regulation', Part), ] secondary_nav_exclude_sibling_pages = models.BooleanField(default=False) sidefoot_panels = CFGOVPage.sidefoot_panels + [ FieldPanel('secondary_nav_exclude_sibling_pages'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) def can_serve_draft_versions(self, request): perms = request.user.get_all_permissions() if (request.user.is_superuser or getattr(request, 'served_by_wagtail_sharing', False) or 'regulations3k.change_section' in perms): return True return False def get_versions_query(self, request): versions = self.regulation.versions if not self.can_serve_draft_versions(request): versions = versions.filter(draft=False) return versions def get_effective_version(self, request, date_str): """ Get the requested effective version if the user has permission """ try: effective_version = self.regulation.versions.get( effective_date=date_str) except EffectiveVersion.DoesNotExist: raise Http404 if (effective_version.draft and not self.can_serve_draft_versions(request)): raise PermissionDenied return effective_version def get_section_query(self, effective_version=None): """Query set for Sections in this regulation's effective version.""" if effective_version is None: effective_version = self.regulation.effective_version return Section.objects.filter(subpart__version=effective_version) def get_context(self, request, *args, **kwargs): context = super(RegulationPage, self).get_context(request, *args, **kwargs) context.update({ 'regulation': self.regulation, 'breadcrumb_items': self.get_breadcrumbs(request, *args, **kwargs), 'search_url': (self.get_parent().url + 'search-regulations/results/?regs=' + self.regulation.part_number), 'num_versions': self.get_versions_query(request).count(), }) return context def get_breadcrumbs(self, request, section=None, **kwargs): crumbs = super(RegulationPage, self).get_breadcrumbs(request) if section is not None: crumbs = crumbs + [ { 'href': self.url + self.reverse_subpage( 'index', kwargs={ k: v for k, v in kwargs.items() if k == 'date_str' }), 'title': str(section.subpart.version.part), }, ] return crumbs def get_urls_for_version(self, effective_version, section=None): base_url = self.get_full_url() versions_url = urljoin(base_url, 'versions') + '/' if effective_version.live_version: # This is the current version version_url = base_url else: # It's a past or future version, URLs have the date str date_str = str(effective_version.effective_date) version_url = urljoin(base_url, date_str) + '/' yield version_url if section is not None: yield urljoin(version_url, section.label) + '/' else: sections = self.get_section_query(effective_version) yield version_url yield versions_url for section in sections.all(): yield urljoin(version_url, section.label) + '/' def render_interp(self, context, raw_contents, **kwargs): template = get_template('regulations3k/inline_interps.html') # Extract the title from the raw regdown section_title_match = re.search(r'#+\s?(?P<section_title>.*)\s', raw_contents) if section_title_match is not None: context.update({'section_title': section_title_match.group(1)}) span = section_title_match.span() raw_contents = raw_contents[:span[0]] + raw_contents[span[1]:] context.update({'contents': regdown(raw_contents)}) context.update(kwargs) return template.render(context) @route(r'^(?:(?P<date_str>[0-9]{4}-[0-9]{2}-[0-9]{2})/)?$', name="index") def index_route(self, request, date_str=None): request.is_preview = getattr(request, 'is_preview', False) if date_str is not None: effective_version = self.get_effective_version(request, date_str) section_query = self.get_section_query( effective_version=effective_version) else: effective_version = self.regulation.effective_version section_query = self.get_section_query() sections = list(section_query.all()) context = self.get_context(request) context.update({ 'version': effective_version, 'sections': sections, 'get_secondary_nav_items': partial(get_secondary_nav_items, sections=sections, date_str=date_str), }) if date_str is not None: context['date_str'] = date_str return TemplateResponse(request, self.get_template(request), context) @route(r'^versions/(?:(?P<section_label>[0-9A-Za-z-]+)/)?$', name="versions") def versions_page(self, request, section_label=None): section_query = self.get_section_query() sections = list(section_query.all()) context = self.get_context(request, sections=sections) versions = [{ 'effective_date': v.effective_date, 'date_str': str(v.effective_date), 'sections': self.get_section_query(effective_version=v).all(), 'draft': v.draft } for v in self.get_versions_query(request).order_by('-effective_date') ] context.update({ 'versions': versions, 'section_label': section_label, 'get_secondary_nav_items': partial(get_secondary_nav_items, sections=sections), }) return TemplateResponse(request, self.template, context) @route(r'^(?:(?P<date_str>[0-9]{4}-[0-9]{2}-[0-9]{2})/)?' r'(?P<section_label>[0-9A-Za-z-]+)/$', name="section") def section_page(self, request, date_str=None, section_label=None): """ Render a section of the currently effective regulation """ if date_str is not None: effective_version = self.get_effective_version(request, date_str) section_query = self.get_section_query( effective_version=effective_version) else: effective_version = self.regulation.effective_version section_query = self.get_section_query() next_version = self.get_versions_query(request).filter( effective_date__gt=effective_version.effective_date).first() kwargs = {} if date_str is not None: kwargs['date_str'] = date_str try: section = section_query.get(label=section_label) except Section.DoesNotExist: return redirect(self.url + self.reverse_subpage("index", kwargs=kwargs)) sections = list(section_query.all()) current_index = sections.index(section) context = self.get_context(request, section, sections=sections, **kwargs) content = regdown( section.contents, url_resolver=get_url_resolver(self, date_str=date_str), contents_resolver=get_contents_resolver(effective_version), render_block_reference=partial(self.render_interp, context)) next_section = get_next_section(sections, current_index) previous_section = get_previous_section(sections, current_index) context.update({ 'version': effective_version, 'next_version': next_version, 'section': section, 'content': content, 'get_secondary_nav_items': partial(get_secondary_nav_items, sections=sections, date_str=date_str), 'next_section': next_section, 'next_url': get_section_url(self, next_section, date_str=date_str), 'previous_section': previous_section, 'previous_url': get_section_url(self, previous_section, date_str=date_str), }) return TemplateResponse(request, self.template, context)
class RegulationsSearchPage(RoutablePageMixin, CFGOVPage): """A page for the custom search interface for regulations.""" objects = PageManager() parent_page_types = ['regulations3k.RegulationLandingPage'] subpage_types = [] results = {} content_panels = CFGOVPage.content_panels edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) def get_template(self, request): template = 'regulations3k/search-regulations.html' if 'partial' in request.GET: template = 'regulations3k/search-regulations-results.html' return template @route(r'^results/') def regulation_results_page(self, request): all_regs = Part.objects.order_by('part_number') regs = validate_regs_list(request) order = validate_order(request) search_query = request.GET.get('q', '').strip() payload = { 'search_query': search_query, 'results': [], 'total_results': 0, 'regs': regs, 'all_regs': [], } if not search_query or len(urllib.parse.unquote(search_query)) == 1: self.results = payload return TemplateResponse(request, self.get_template(request), self.get_context(request)) sqs = SearchQuerySet().filter(content=search_query) payload.update({ 'all_regs': [{ 'letter_code': reg.letter_code, 'id': reg.part_number, 'num_results': sqs.filter( part=reg.part_number).models(SectionParagraph).count(), 'selected': reg.part_number in regs } for reg in all_regs] }) payload.update({ 'total_count': sum([reg['num_results'] for reg in payload['all_regs']]) }) if len(regs) == 1: sqs = sqs.filter(part=regs[0]) elif regs: sqs = sqs.filter(part__in=regs) if order == 'regulation': sqs = sqs.order_by('part', 'section_order') sqs = sqs.highlight(pre_tags=['<strong>'], post_tags=['</strong>']).models(SectionParagraph) for hit in sqs: try: snippet = Markup(" ".join(hit.highlighted)) except TypeError as e: logger.warning( "Query string {} produced a TypeError: {}".format( search_query, e)) continue letter_code = LETTER_CODES.get(hit.part) hit_payload = { 'id': hit.paragraph_id, 'part': hit.part, 'reg': 'Regulation {}'.format(letter_code), 'label': hit.title, 'snippet': snippet, 'url': "{}{}/{}/#{}".format(self.parent().url, hit.part, hit.section_label, hit.paragraph_id), } payload['results'].append(hit_payload) payload.update({'current_count': sqs.count()}) self.results = payload context = self.get_context(request) num_results = validate_num_results(request) paginator = Paginator(payload['results'], num_results) page_number = validate_page_number(request, paginator) paginated_page = paginator.page(page_number) context.update({ 'current_count': payload['current_count'], 'total_count': payload['total_count'], 'paginator': paginator, 'current_page': page_number, 'num_results': num_results, 'order': order, 'results': paginated_page, 'show_filters': any(reg['selected'] is True for reg in payload['all_regs']) }) return TemplateResponse(request, self.get_template(request), context)
MultiFieldPanel, ObjectList) from django.utils.translation import ugettext_lazy as _ menupage_panel = MultiFieldPanel(heading=_("Advanced menu behaviour"), classname="collapsible collapsed", children=( FieldPanel('repeat_in_subnav'), FieldPanel('repeated_item_text'), )) """ `settings_panels` arrangement, including new menu-related fields from the MenuPage abstract class. """ menupage_settings_panels = ( MultiFieldPanel(heading=_("Scheduled publishing"), classname="publishing", children=(FieldRowPanel(( FieldPanel('go_live_at', classname="col6"), FieldPanel('expire_at', classname="col6"), )), )), menupage_panel, ) """ The above `settings_panels` arrangement configured as tab, for easier integration into custom edit_handlers. """ menupage_settings_tab = ObjectList(menupage_settings_panels, heading=_("Settings"), classname="settings")
from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.utils.translation import ugettext as _ from wagtail.wagtailadmin.edit_handlers import ObjectList from wagtail.wagtailadmin.forms import SearchForm import models REDIRECT_EDIT_HANDLER = ObjectList(models.Redirect.content_panels) @permission_required('wagtailredirects.change_redirect') def index(request): p = request.GET.get("p", 1) q = None is_searching = False if 'q' in request.GET: form = SearchForm(request.GET, placeholder_suffix="redirects") if form.is_valid(): q = form.cleaned_data['q'] is_searching = True redirects = models.Redirect.get_for_site( site=request.site).prefetch_related('redirect_page').filter( old_path__icontains=q) if not is_searching:
class Category(MP_Node): title = models.CharField(unique=True, max_length=255, verbose_name=_('title')) active = models.BooleanField(verbose_name=_('active'), default=False) search_filter_menu = models.BooleanField( verbose_name=_('search filter menu'), default=False, help_text= _('Add Category to search-filters. Direct sub-categories appear as filter options' )) description = models.TextField( blank=True, verbose_name=_('description'), help_text=_('For admin/backoffice purposes only.')) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') category_page = models.OneToOneField( CategoryPage, null=True, blank=True, on_delete=models.SET_NULL, ) node_order_by = ['title'] """ Panels """ general_panels = [ FieldPanel('title'), FieldPanel('active'), ImageChooserPanel('image'), FieldPanel('description'), ] catalog_panels = [ FieldPanel('search_filter_menu'), PageChooserOrCreatePanel('category_page'), ] edit_handler = TabbedInterface([ ObjectList(general_panels, heading='General'), ObjectList(catalog_panels, heading='Catalog'), # products ]) base_form_class = MoveNodeForm class Meta: verbose_name_plural = _('categories') def __str__(self): return self.title def __unicode__(self): return self.title def create_page(self): parent = self.get_first_ancestor_with_category_page() category_page = CategoryPage( title=self.title, image=self.image, ) if parent is not None: parent.category_page.add_child(instance=category_page) else: # If no parent in any of ancerstors, then put it under the (root)index. category_index_page = Page.objects.type(CategoryIndexPage).first() category_index_page.add_child(instance=category_page) self.category_page = category_page # TODO Implement # Get products total/count in all active children and self def get_product_count(self): return 25 def get_first_ancestor_with_category_page(self): ancestors = self.get_ancestors() for a in reversed(ancestors): if a.category_page is not None: return a return None @classmethod def get_tree_search_filter_menu(self, ids=[]): tree = Category.get_tree_active(ids) menu = OrderedDict() for category in tree: paths = chunk_string_increment(category.path, Category.steplen) if category.search_filter_menu: menu[category.path] = OrderedDict() menu[category.path]['menu_object'] = category menu[category.path]['objects'] = [] if len(paths) > 1: ancestor_path = ''.join(paths[-2]) # Move up, to 'parent' if. And comment about the ancestor path, which is index [-2] if ancestor_path in menu: menu[ancestor_path]['objects'].append(category) return list(menu.values()) @classmethod def get_tree_active(self, ids=[]): """Get tree as DF (depth first) list""" if len(ids) > 0: categories = Category.objects.select_related( 'category_page').filter(pk__in=ids, active=True) else: categories = Category.objects.select_related( 'category_page').filter(active=True) """Ancestors""" """First determine per category if all ancsestors active""" all_ancestor_paths = [] # Collect all_ancestor_paths. Merge/combine by category ancestors. for c in categories: paths = chunk_string_increment(c.path, Category.steplen) ancestor_paths = paths[0:-1] # union all: of all_ancestors_paths and ancestor_paths of this category all_ancestor_paths = list( set(all_ancestor_paths) | set(ancestor_paths)) # Active ancestor_paths, by all active ancestor categories # Redundant in case all active categories where queries upfront anyway; len(ids) == 0 active_ancestor_categories = Category.objects.select_related( 'category_page').filter(path__in=all_ancestor_paths, active=True) active_ancestor_paths = [] for c in active_ancestor_categories: active_ancestor_paths.append(c.path) # Determine categories where all ancestors are active for idx, category in enumerate(categories): paths = chunk_string_increment(category.path, Category.steplen) ancestor_paths = paths[0:-1] # If any of (Category) ancestor_paths is NOT in active_ancestor_paths (So length is less then) if len(ancestor_paths) < len( list(set(ancestor_paths) & set(active_ancestor_paths))): del categories[idx] # From here all categories have all ancestors active. # For categories determine descendants (as DF tree), by active (in) tree-logic """Build tree""" tree = OrderedDict() for category in categories: # Potential performace risk of many queries # To reduce to raw SQL to determine the active-descendants-tree. # And then Category.objects.filter(pk__in=active_descendants_pk) # TODO check whether get_tree(category) only returns the sub-tree, under category cat_tree = Category.get_tree(category).select_related( 'category_page') # The first cat in cat_tree (DF) list, is category (from container loop), # as argument for get_tree(category) for cat in cat_tree: if cat.active: path = chunk_string_increment(cat.path, Category.steplen) ancestor_paths = path[0:-1] # If active and all ancestor_paths in union/overlap if len(ancestor_paths) == len( list( set(ancestor_paths) & set(active_ancestor_paths))): # Append current (active) Category path to active_ancestor_paths, because we traverse # its children (in next loop) too. if path[-1] not in active_ancestor_paths: active_ancestor_paths.append(path[-1]) if cat.path not in tree: tree[cat.path] = cat return list(tree.values())
class BrowsePage(CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ], blank=True) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('info_unit_group', organisms.InfoUnitGroup()), ('expandable_group', organisms.ExpandableGroup()), ('expandable', organisms.Expandable()), ('well', organisms.Well()), ('video_player', organisms.VideoPlayer()), ('snippet_list', organisms.ResourceList()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('feedback', v1_blocks.Feedback()), ('raw_html_block', blocks.RawHTMLBlock(label='Raw HTML block')), ('conference_registration_form', ConferenceRegistrationForm()), ('chart_block', organisms.ChartBlock()), ('mortgage_chart_block', organisms.MortgageChartBlock()), ('mortgage_map_block', organisms.MortgageMapBlock()), ('mortgage_downloads_block', MortgageDataDownloads()), ('data_snapshot', organisms.DataSnapshot()), ('job_listing_table', JobListingTable()), ('bureau_structure', organisms.BureauStructure()), ('yes_checklist', YESChecklist()), ], blank=True) secondary_nav_exclude_sibling_pages = models.BooleanField(default=False) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] sidefoot_panels = CFGOVPage.sidefoot_panels + [ FieldPanel('secondary_nav_exclude_sibling_pages'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'browse-basic/index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ] @property def page_js(self): return (super(BrowsePage, self).page_js + ['secondary-navigation.js']) def get_context(self, request, *args, **kwargs): context = super(BrowsePage, self).get_context(request, *args, **kwargs) context.update({'get_secondary_nav_items': get_secondary_nav_items}) return context
def test_object_list(self): object_list = ObjectList(['foo']) self.assertTrue(issubclass(object_list, BaseObjectList))