class ChicagoNeighborhoods(Page): # Database fields neighborhood_name = models.CharField(max_length=255, default='', null=False, blank=False) date = models.DateField("Updated On") introductory_text = models.CharField(max_length=255, default='', null=False, blank=False) introductory_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') overview = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ], default='') social_mix = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ]) public_space = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ]) amenities = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ]) connections = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ]) final_project = StreamField([ ('draw', DrawMapBlock()), ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ]) # Search index configuraiton search_fields = Page.search_fields + [ index.SearchField('neighborhood_name'), index.SearchField('introductory_text'), index.SearchField('overview'), index.SearchField('social_mix'), index.SearchField('public_space'), index.SearchField('amenities'), index.SearchField('connections'), index.SearchField('final_project'), index.FilterField('date'), ] # Editor panels configuration content_panels = Page.content_panels + [ FieldPanel('neighborhood_name'), FieldPanel('introductory_text'), ImageChooserPanel('introductory_image'), StreamFieldPanel('overview'), StreamFieldPanel('social_mix'), StreamFieldPanel('public_space'), StreamFieldPanel('amenities'), StreamFieldPanel('connections'), StreamFieldPanel('final_project'), FieldPanel('date'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ]) # Parent page / subpage type rules parent_page_types = ['neighborhoods.HomePage'] subpage_types = []
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"), ])
class ActivityPage(CFGOVPage): """ A model for the Activity Detail page. """ # Allow Activity pages to exist under the ActivityIndexPage or the Trash parent_page_types = [ActivityIndexPage, HomePage] subpage_types = [] objects = CFGOVPageManager() date = models.DateField('Updated', default=timezone.now) summary = models.TextField('Summary', blank=False) big_idea = RichTextField('Big idea', blank=False) essential_questions = RichTextField('Essential questions', blank=False) objectives = RichTextField('Objectives', blank=False) what_students_will_do = RichTextField('What students will do', blank=False) # noqa: E501 activity_file = models.ForeignKey('wagtaildocs.Document', null=True, blank=False, on_delete=models.SET_NULL, related_name='+', verbose_name='Teacher guide') # TODO: to figure out how to use Document choosers on ManyToMany fields handout_file = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Student file 1') handout_file_2 = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Student file 2') handout_file_3 = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Student file 3') building_block = ParentalManyToManyField( 'teachers_digital_platform.ActivityBuildingBlock', blank=False) # noqa: E501 school_subject = ParentalManyToManyField( 'teachers_digital_platform.ActivitySchoolSubject', blank=False) # noqa: E501 topic = ParentalTreeManyToManyField( 'teachers_digital_platform.ActivityTopic', blank=False) # noqa: E501 # Audience grade_level = ParentalManyToManyField( 'teachers_digital_platform.ActivityGradeLevel', blank=False) # noqa: E501 age_range = ParentalManyToManyField( 'teachers_digital_platform.ActivityAgeRange', blank=False) # noqa: E501 student_characteristics = ParentalManyToManyField( 'teachers_digital_platform.ActivityStudentCharacteristics', blank=True) # noqa: E501 # Activity Characteristics activity_type = ParentalManyToManyField( 'teachers_digital_platform.ActivityType', blank=False) # noqa: E501 teaching_strategy = ParentalManyToManyField( 'teachers_digital_platform.ActivityTeachingStrategy', blank=False) # noqa: E501 blooms_taxonomy_level = ParentalManyToManyField( 'teachers_digital_platform.ActivityBloomsTaxonomyLevel', blank=False) # noqa: E501 activity_duration = models.ForeignKey( ActivityDuration, blank=False, on_delete=models.PROTECT) # noqa: E501 # Standards taught jump_start_coalition = ParentalManyToManyField( 'teachers_digital_platform.ActivityJumpStartCoalition', blank=True, verbose_name='Jump$tart Coalition', ) council_for_economic_education = ParentalManyToManyField( 'teachers_digital_platform.ActivityCouncilForEconEd', blank=True, verbose_name='Council for Economic Education', ) content_panels = CFGOVPage.content_panels + [ FieldPanel('date'), FieldPanel('summary'), FieldPanel('big_idea'), FieldPanel('essential_questions'), FieldPanel('objectives'), FieldPanel('what_students_will_do'), MultiFieldPanel( [ DocumentChooserPanel('activity_file'), DocumentChooserPanel('handout_file'), DocumentChooserPanel('handout_file_2'), DocumentChooserPanel('handout_file_3'), ], heading="Download activity", ), FieldPanel('building_block', widget=forms.CheckboxSelectMultiple), FieldPanel('school_subject', widget=forms.CheckboxSelectMultiple), FieldPanel('topic', widget=forms.CheckboxSelectMultiple), MultiFieldPanel( [ FieldPanel('grade_level', widget=forms.CheckboxSelectMultiple), # noqa: E501 FieldPanel('age_range', widget=forms.CheckboxSelectMultiple), FieldPanel('student_characteristics', widget=forms.CheckboxSelectMultiple), # noqa: E501 ], heading="Audience", ), MultiFieldPanel( [ FieldPanel('activity_type', widget=forms.CheckboxSelectMultiple), # noqa: E501 FieldPanel('teaching_strategy', widget=forms.CheckboxSelectMultiple), # noqa: E501 FieldPanel('blooms_taxonomy_level', widget=forms.CheckboxSelectMultiple), # noqa: E501 FieldPanel('activity_duration'), ], heading="Activity characteristics", ), MultiFieldPanel( [ FieldPanel('council_for_economic_education', widget=forms.CheckboxSelectMultiple), # noqa: E501 FieldPanel('jump_start_coalition', widget=forms.CheckboxSelectMultiple), # noqa: E501 ], heading="National standards", ), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar/Footer'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) # admin use only search_fields = Page.search_fields + [ index.SearchField('summary'), index.SearchField('big_idea'), index.SearchField('essential_questions'), index.SearchField('objectives'), index.SearchField('what_students_will_do'), index.FilterField('date'), index.FilterField('building_block'), index.FilterField('school_subject'), index.FilterField('topic'), index.FilterField('grade_level'), index.FilterField('age_range'), index.FilterField('student_characteristics'), index.FilterField('activity_type'), index.FilterField('teaching_strategy'), index.FilterField('blooms_taxonomy_level'), index.FilterField('activity_duration'), index.FilterField('jump_start_coalition'), index.FilterField('council_for_economic_education'), ] def get_topics_list(self, parent=None): """ Get a hierarchical list of this activity's topics. parent: ActivityTopic """ if parent: descendants = set(parent.get_descendants()) & set( self.topic.all()) # noqa: E501 children = parent.get_children() children_list = [] # If this parent has descendants in self.topic, add its children. if descendants: for child in children: if set(child.get_descendants()) & set(self.topic.all()): children_list.append(self.get_topics_list(child)) elif child in self.topic.all(): children_list.append(child.title) if children_list: return parent.title + " (" + ', '.join( children_list) + ")" # noqa: E501 # Otherwise, just add the parent. else: return parent.title else: # Build root list of topics and recurse their children. topic_list = [] topic_ids = [topic.id for topic in self.topic.all()] ancestors = ActivityTopic.objects.filter( id__in=topic_ids).get_ancestors(True) # noqa: E501 roots = ActivityTopic.objects.filter(parent=None) & ancestors for root_topic in roots: topic_list.append(self.get_topics_list(root_topic)) if topic_list: return ', '.join(topic_list) else: return '' class Meta: verbose_name = "TDP Activity page"
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): return 'regulations3k/search-regulations.html' @route(r'^results/') def regulation_results_page(self, request): all_regs = Part.objects.order_by('part_number') regs = [] sqs = SearchQuerySet() if 'regs' in request.GET: regs = request.GET.getlist('regs') if len(regs) == 1: sqs = sqs.filter(part=regs[0]) elif regs: sqs = sqs.filter(part__in=regs) search_query = request.GET.get('q', '') # haystack cleans this string if search_query: query_sqs = sqs.filter(content=search_query).models(Section) else: query_sqs = sqs.models(Section) payload = { 'search_query': search_query, 'results': [], 'total_results': query_sqs.count(), 'regs': regs, 'all_regs': [{ 'name': "Regulation {}".format(reg.letter_code), 'id': reg.part_number, 'num_results': query_sqs.filter(part=reg.part_number).count(), 'selected': reg.part_number in regs } for reg in all_regs] } for hit in query_sqs: label_bits = hit.object.label.partition('-') _part, _section = label_bits[0], label_bits[2] letter_code = LETTER_CODES.get(_part) snippet = Truncator(hit.text).words(40, truncate=' ...') snippet = snippet.replace('*', '').replace('#', '') hit_payload = { 'id': hit.object.pk, 'reg': 'Regulation {}'.format(letter_code), 'label': hit.title, 'snippet': snippet, 'url': "/regulations/{}/{}/".format(_part, _section), } payload['results'].append(hit_payload) self.results = payload # if we want to paginate results: # def get_context(self, request, **kwargs): # context = super(RegulationsSearchPage, self).get_context( # request, **kwargs) # context.update(**kwargs) # paginator = Paginator(payload['results'], 25) # page_number = validate_page_number(request, paginator) # paginated_page = paginator.page(page_number) # context['current_page'] = page_number # context['paginator'] = paginator # context['results'] = paginated_page # return context context = self.get_context(request) return TemplateResponse(request, self.get_template(request), context)
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']
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.", 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.youtube.com/embed/video_id.") 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' @property def page_js(self): return super(EventPage, self).page_js + ['video-player.js'] 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 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 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', organisms.FullWidthText()), ], 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=None): """ Get the requested effective version if the user has permission """ query_filter = {} if date_str is None: query_filter['effective_date__lte'] = date.today() else: query_filter['effective_date'] = date_str draft_permission = self.can_serve_draft_versions(request) if not draft_permission: query_filter['draft'] = False effective_version = self.regulation.versions.filter( **query_filter).order_by('-effective_date').first() if effective_version is None: raise Http404 return effective_version def get_section_query(self, request=None, effective_version=None): """Query set for Sections in this regulation's effective version.""" if effective_version is None: effective_version = self.get_effective_version(request) 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, 'current_version': self.get_effective_version(request), '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=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) effective_version = self.get_effective_version(request, date_str=date_str) section_query = self.get_section_query( effective_version=effective_version) sections = list(section_query.all()) context = self.get_context(request) context.update({ 'requested_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(request=request) 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 """ effective_version = self.get_effective_version(request, date_str=date_str) section_query = self.get_section_query( effective_version=effective_version) 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({ 'requested_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': [{ 'short_name': reg.short_name, '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 short_name = all_regs.get(part_number=hit.part).short_name hit_payload = { 'id': hit.paragraph_id, 'part': hit.part, 'reg': short_name, '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)
class AnswerPage(CFGOVPage): """ Page type for Ask CFPB answers. """ from ask_cfpb.models import Answer question = RichTextField(blank=True, editable=False) answer = RichTextField(blank=True, editable=False) snippet = RichTextField( blank=True, help_text='Optional answer intro', editable=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) publish_date = models.DateTimeField(default=timezone.now) answer_base = models.ForeignKey( Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to = models.ForeignKey( Answer, blank=True, null=True, on_delete=models.SET_NULL, related_name='redirected_pages', help_text="Choose another Answer to redirect this page to") content = StreamField([ ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = CFGOVPage.content_panels + [ FieldPanel('redirect_to'), ] sidebar = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('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) sidebar_panels = [StreamFieldPanel('sidebar'), ] search_fields = Page.search_fields + [ index.SearchField('question'), index.SearchField('answer'), index.SearchField('answer_base'), index.FilterField('language') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar (English only)'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) objects = CFGOVPageManager() def get_context(self, request, *args, **kwargs): context = super(AnswerPage, self).get_context(request) context['answer_id'] = self.answer_base.id context['related_questions'] = self.answer_base.related_questions.all() context['description'] = self.snippet if self.snippet \ else Truncator(self.answer).words(40, truncate=' ...') context['audiences'] = [ {'text': audience.name, 'url': '/ask-cfpb/audience-{}'.format( slugify(audience.name))} for audience in self.answer_base.audiences.all()] if self.language == 'es': tag_dict = self.Answer.valid_tags(language='es') context['tags_es'] = [tag for tag in self.answer_base.tags_es if tag in tag_dict['valid_tags']] context['tweet_text'] = Truncator(self.question).chars( 100, truncate=' ...') context['disclaimer'] = get_reusable_text_snippet( SPANISH_DISCLAIMER_SNIPPET_TITLE) context['category'] = self.answer_base.category.first() elif self.language == 'en': # we're not using tags on English pages yet, so cut the overhead # tag_dict = self.Answer.valid_tags() # context['tags'] = [tag for tag in self.answer_base.tags # if tag in tag_dict['valid_tags']] context['about_us'] = get_reusable_text_snippet( ABOUT_US_SNIPPET_TITLE) context['disclaimer'] = get_reusable_text_snippet( ENGLISH_DISCLAIMER_SNIPPET_TITLE) context['last_edited'] = self.answer_base.last_edited # breadcrumbs and/or category should reflect # the referrer if it is a consumer tools portal or # ask category page context['category'], context['breadcrumb_items'] = \ get_question_referrer_data( request, self.answer_base.category.all()) subcategories = [] for subcat in self.answer_base.subcategory.all(): if subcat.parent == context['category']: subcategories.append(subcat) for related in subcat.related_subcategories.all(): if related.parent == context['category']: subcategories.append(related) context['subcategories'] = set(subcategories) return context def get_template(self, request): printable = request.GET.get('print', False) if self.language == 'es': if printable == 'true': return 'ask-cfpb/answer-page-spanish-printable.html' return 'ask-cfpb/answer-page-spanish.html' return 'ask-cfpb/answer-page.html' def __str__(self): if self.answer_base: return '{}: {}'.format(self.answer_base.id, self.title) else: return self.title @property def status_string(self): if self.redirect_to: if not self.live: return _("redirected but not live") else: return _("redirected") else: return super(AnswerPage, self).status_string # Returns an image for the page's meta Open Graph tag @property def meta_image(self): if self.answer_base.social_sharing_image: return self.answer_base.social_sharing_image if not self.answer_base.category.exists(): return None return self.answer_base.category.first().category_image
class HomePage(CFGOVPage): header = StreamField([ ('info_unit', molecules.InfoUnit()), ('half_width_link_blob', molecules.HalfWidthLinkBlob()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), InlinePanel( 'excluded_updates', label='Pages excluded from Latest Updates', help_text=('This block automatically displays the six most ' 'recently published blog posts, press releases, ' 'speeches, testimonies, events, or op-eds. If you want ' 'to exclude a page from appearing as a recent update ' 'in this block, add it as an excluded page.')), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) # Sets page to only be createable at the root parent_page_types = ['wagtailcore.Page'] objects = PageManager() search_fields = CFGOVPage.search_fields + [index.SearchField('header')] @property def page_js(self): return super(HomePage, self).page_js + ['home-page.js'] def get_category_name(self, category_icon_name): cats = dict(ref.limited_categories) return cats[str(category_icon_name)] def get_context(self, request): context = super(HomePage, self).get_context(request) context['latest_updates'] = self.get_latest_updates(request) return context def get_latest_updates(self, request): # TODO: There should be a way to express this as part of the query # rather than evaluating it in Python. excluded_updates_pks = [ e.excluded_page.pk for e in self.excluded_updates.all() ] latest_pages = CFGOVPage.objects.in_site( request.site).exclude(pk__in=excluded_updates_pks).filter( Q(content_type__app_label='v1') & (Q(content_type__model='blogpage') | Q(content_type__model='eventpage') | Q(content_type__model='newsroompage')), live=True, ).distinct().order_by('-first_published_at')[:6] return latest_pages def get_template(self, request): if flag_enabled('NEW_HOME_PAGE', request=request): return 'home-page/index_new.html' else: return 'home-page/index_%s.html' % self.language
class AnswerCategoryPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage): """ A routable page type for Ask CFPB category pages and their subcategories. """ from ask_cfpb.models import Answer, Audience, Category, SubCategory objects = CFGOVPageManager() content = StreamField([], null=True) ask_category = models.ForeignKey( Category, blank=True, null=True, on_delete=models.PROTECT, related_name='category_page') ask_subcategory = models.ForeignKey( SubCategory, blank=True, null=True, on_delete=models.PROTECT, related_name='subcategory_page') content_panels = CFGOVPage.content_panels + [ FieldPanel('ask_category', Category), StreamFieldPanel('content'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) def get_template(self, request): if self.language == 'es': return 'ask-cfpb/category-page-spanish.html' return 'ask-cfpb/category-page.html' def get_context(self, request, *args, **kwargs): context = super( AnswerCategoryPage, self).get_context(request, *args, **kwargs) sqs = SearchQuerySet().models(self.Category) if self.language == 'es': sqs = sqs.filter(content=self.ask_category.name_es) else: sqs = sqs.filter(content=self.ask_category.name) if sqs: facet_map = sqs[0].facet_map else: facet_map = self.ask_category.facet_map facet_dict = json.loads(facet_map) subcat_ids = facet_dict['subcategories'].keys() answer_ids = facet_dict['answers'].keys() audience_ids = facet_dict['audiences'].keys() subcats = self.SubCategory.objects.filter( pk__in=subcat_ids).values( 'id', 'slug', 'slug_es', 'name', 'name_es') answers = self.Answer.objects.filter( pk__in=answer_ids).order_by('-pk').values( 'id', 'question', 'question_es', 'slug', 'slug_es', 'answer_es') for a in answers: a['answer_es'] = Truncator(a['answer_es']).words( 40, truncate=' ...') audiences = self.Audience.objects.filter( pk__in=audience_ids).values('id', 'name') context.update({ 'answers': answers, 'audiences': audiences, 'facets': facet_dict, 'choices': subcats, 'results_count': answers.count(), 'get_secondary_nav_items': get_ask_nav_items }) if self.language == 'en': context['about_us'] = get_reusable_text_snippet( ABOUT_US_SNIPPET_TITLE) context['disclaimer'] = get_reusable_text_snippet( ENGLISH_DISCLAIMER_SNIPPET_TITLE) context['breadcrumb_items'] = get_ask_breadcrumbs() elif self.language == 'es': context['tags'] = self.ask_category.top_tags_es return context # Returns an image for the page's meta Open Graph tag @property def meta_image(self): return self.ask_category.category_image @route(r'^$') def category_page(self, request): context = self.get_context(request) paginator = Paginator(context.get('answers'), 20) page_number = validate_page_number(request, paginator) page = paginator.page(page_number) context.update({ 'paginator': paginator, 'current_page': page_number, 'questions': page, }) return TemplateResponse( request, self.get_template(request), context) @route(r'^(?P<subcat>[^/]+)/$') def subcategory_page(self, request, **kwargs): subcat = self.SubCategory.objects.filter( slug=kwargs.get('subcat')).first() if subcat: self.ask_subcategory = subcat else: raise Http404 context = self.get_context(request) id_key = str(subcat.pk) answers = context['answers'].filter( pk__in=context['facets']['subcategories'][id_key]) paginator = Paginator(answers, 20) page_number = validate_page_number(request, paginator) page = paginator.page(page_number) context.update({ 'paginator': paginator, 'current_page': page_number, 'results_count': answers.count(), 'questions': page, 'breadcrumb_items': get_ask_breadcrumbs( self.ask_category) }) return TemplateResponse( request, self.get_template(request), context)
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. Minimum size: 1200w x 630h.')) # 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 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): activity_log = CFGOVPage.objects.get(slug='activity-log').specific tags = [] tags = urlencode([('topics', tag) for tag in self.tags.slugs()]) return (get_protected_url({'request': request}, activity_log) + '?' + tags) 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 AnswerPage(CFGOVPage): """ Page type for Ask CFPB answers. """ from .django import Answer question = RichTextField(blank=True, editable=False) answer = RichTextField(blank=True, editable=False) snippet = RichTextField(blank=True, help_text='Optional answer intro', editable=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) publish_date = models.DateTimeField(default=timezone.now) answer_base = models.ForeignKey(Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to = models.ForeignKey( Answer, blank=True, null=True, on_delete=models.SET_NULL, related_name='redirected_pages', help_text="Choose another Answer to redirect this page to") content = StreamField([ ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = CFGOVPage.content_panels + [ FieldPanel('redirect_to'), StreamFieldPanel('content'), ] search_fields = Page.search_fields + [ index.SearchField('question'), index.SearchField('answer'), index.SearchField('answer_base'), index.FilterField('language') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'ask-cfpb/answer-page.html' objects = PageManager() def __str__(self): if self.answer_base: return '{}: {}'.format(self.answer_base.id, self.title) else: return self.title @property def status_string(self): if self.redirect_to: if not self.live: return _("redirected but not live") else: return _("redirected") else: return super(AnswerPage, self).status_string
class BrowsePage(CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', molecules.FeaturedContent()), ], blank=True) content = StreamField([ ('bureau_structure', organisms.BureauStructure()), ('info_unit_group', organisms.InfoUnitGroup()), ('well', organisms.Well()), ('full_width_text', organisms.FullWidthText()), ('expandable', organisms.Expandable()), ('expandable_group', organisms.ExpandableGroup()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('job_listing_table', JobListingTable()), ('feedback', v1_blocks.Feedback()), ('conference_registration_form', ConferenceRegistrationForm()), ('raw_html_block', blocks.RawHTMLBlock(label='Raw HTML block')), ('html_block', organisms.HTMLBlock()), ('chart_block', organisms.ChartBlock()), ('mortgage_chart_block', organisms.MortgageChartBlock()), ('mortgage_map_block', organisms.MortgageMapBlock()), ('mortgage_downloads_block', MortgageDataDownloads()), ('snippet_list', organisms.SnippetList()), ('data_snapshot', organisms.DataSnapshot()), ('image_text_25_75_group', organisms.ImageText2575Group()), ('image_text_50_50_group', organisms.ImageText5050Group()), ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()), ('third_width_link_blob_group', organisms.ThirdWidthLinkBlobGroup()), ], 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() @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
class ContributorListPage(Page): subpage_types = ['ContributorPage'] intro_text = RichTextField(blank=True) body = RichTextField(blank=True) def get_rows(self, contributors, number_of_columns=3, max_columns=4): rows = [] number_of_items = len(contributors) number_of_rows = number_of_items // number_of_columns row_remainder = number_of_items % number_of_columns if row_remainder > number_of_rows: number_of_rows += 1 elif row_remainder <= number_of_rows and row_remainder != 0: if number_of_columns < max_columns: number_of_columns += 1 else: number_of_rows += 1 for row_index in range(0, number_of_rows): row = contributors[( row_index * number_of_columns):(row_index * number_of_columns) + number_of_columns] rows.append(row) return rows @property def recent_contributors(self): endtime = timezone.now() starttime = endtime - datetime.timedelta(days=365) contributors = ( ContributorPage.objects.live().filter( # noqa featured=False, article_links__article__isnull=False, article_links__isnull=False, article_links__article__first_published_at__range=[ starttime, endtime ]).order_by('last_name', 'first_name').distinct()) return self.get_rows(contributors, number_of_columns=4) @property def nonfeatured_contributors(self): contributors = ContributorPage.objects.live().filter( featured=False).order_by('last_name', 'first_name') return self.get_rows(contributors) @property def featured_contributors(self): contributors = ContributorPage.objects.live().filter( featured=True).order_by('last_name', 'first_name') return self.get_rows(contributors) content_panels = Page.content_panels + [ FieldPanel('intro_text'), FieldPanel('body'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ])
class SublandingPage(CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ], blank=True) content = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', molecules.FeaturedContent()), ('info_unit_group', organisms.InfoUnitGroup()), ('image_text_25_75_group', organisms.ImageText2575Group()), ('image_text_50_50_group', organisms.ImageText5050Group()), ('full_width_text', organisms.FullWidthText()), ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()), ('third_width_link_blob_group', organisms.ThirdWidthLinkBlobGroup()), ('post_preview_snapshot', organisms.PostPreviewSnapshot()), ('well', organisms.Well()), ('table', organisms.Table(editable=False)), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('contact', organisms.MainContactInfo()), ('formfield_with_button', molecules.FormFieldWithButton()), ('reg_comment', organisms.RegComment()), ('feedback', v1_blocks.Feedback()), ('snippet_list', organisms.SnippetList()), ], blank=True) sidebar_breakout = StreamField([ ('slug', blocks.CharBlock(icon='title')), ('heading', blocks.CharBlock(icon='title')), ('paragraph', blocks.RichTextBlock(icon='edit')), ('breakout_image', blocks.StructBlock([ ('image', ImageChooserBlock()), ('is_round', blocks.BooleanBlock(required=False, default=True, label='Round?')), ('icon', blocks.CharBlock(help_text='Enter icon class name.')), ('heading', blocks.CharBlock(required=False, label='Introduction Heading')), ('body', blocks.TextBlock(required=False, label='Introduction Body')), ], heading='Breakout Image', icon='image')), ('related_posts', organisms.RelatedPosts()), ('job_listing_list', JobListingList()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] sidebar_panels = [ StreamFieldPanel('sidebar_breakout'), ] + CFGOVPage.sidefoot_panels # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidebar_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'sublanding-page/index.html' objects = PageManager() def get_browsefilterable_posts(self, limit): filter_pages = [ p.specific for p in self.get_appropriate_descendants() if 'FilterablePage' in p.specific_class.__name__ and 'archive' not in p.title.lower() ] posts_list = [] for page in filter_pages: eligible_children = AbstractFilterPage.objects.live().filter( CFGOVPage.objects.child_of_q(page)) form = FilterableListForm(filterable_pages=eligible_children) for post in form.get_page_set(): posts_list.append(post) return sorted(posts_list, key=lambda p: p.date_published, reverse=True)[:limit]
class ContributorPage(Page): 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="") subtitle = RichTextField(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('subtitle', partial_match=True), index.SearchField('short_bio', partial_match=True), index.SearchField('long_bio', partial_match=True), ] def search_result_text(self): if self.subtitle: self.search_result_text = self.subtitle elif 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) def get_admin_display_title(self): return '{} ({})'.format(self.title, self.id) 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") ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ])
class SublandingPage(CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ], blank=True) content = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', molecules.FeaturedContent()), ('image_text_25_75_group', organisms.ImageText2575Group()), ('image_text_50_50_group', organisms.ImageText5050Group()), ('full_width_text', organisms.FullWidthText()), ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()), ('post_preview_snapshot', organisms.PostPreviewSnapshot()), ('well', organisms.Well()), ('table', organisms.Table()), ('contact', organisms.MainContactInfo()), ('formfield_with_button', molecules.FormFieldWithButton()), ('reg_comment', organisms.RegComment()), ], blank=True) sidebar_breakout = StreamField([ ('slug', blocks.CharBlock(icon='title')), ('heading', blocks.CharBlock(icon='title')), ('paragraph', blocks.RichTextBlock(icon='edit')), ('breakout_image', blocks.StructBlock([ ('image', ImageChooserBlock()), ('is_round', blocks.BooleanBlock(required=False, default=True, label='Round?')), ('icon', blocks.CharBlock(help_text='Enter icon class name.')), ('heading', blocks.CharBlock(required=False, label='Introduction Heading')), ('body', blocks.TextBlock(required=False, label='Introduction Body')), ], heading='Breakout Image', icon='image')), ('related_posts', organisms.RelatedPosts()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] sidebar_panels = [ StreamFieldPanel('sidebar_breakout'), ] + CFGOVPage.sidefoot_panels # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidebar_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'sublanding-page/index.html' def get_browsefilterable_posts(self, request, limit): filter_pages = [ p.specific for p in self.get_appropriate_descendants(request.site.hostname) if 'FilterablePage' in p.specific_class.__name__ and 'archive' not in p.title.lower() ] filtered_controls = {} for page in filter_pages: id = str(util.get_form_id(page, request.GET)) if id not in filtered_controls.keys(): filtered_controls.update({id: []}) form_class = page.get_form_class() posts = filterable_context.get_page_set( page, form_class(parent=page, hostname=request.site.hostname), request.site.hostname) if filtered_controls[id]: filtered_controls[id] += posts else: filtered_controls[id] = posts posts_tuple_list = [(id, post) for id, posts in filtered_controls.iteritems() for post in posts] posts = sorted(posts_tuple_list, key=lambda p: p[1].date_published, reverse=True)[:limit] return posts
class PortalSearchPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage): """ A routable page type for Ask CFPB portal search ("see-all") pages. """ objects = CFGOVPageManager() portal_topic = models.ForeignKey(PortalTopic, blank=True, null=True, related_name='portal_search_pages', on_delete=models.SET_NULL) portal_category = None query_base = None glossary_terms = None overview = models.TextField(blank=True) content_panels = CFGOVPage.content_panels + [ FieldPanel('portal_topic'), FieldPanel('overview'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) @property def category_map(self): """ Return an ordered dictionary of translated-slug:object pairs. We use this custom sequence for categories in the navigation sidebar, controlled by the 'display_order' field of portal categories: - Basics - Key terms - Common issues - Know your rights - How-to guides """ categories = PortalCategory.objects.all() sorted_mapping = OrderedDict() for category in categories: sorted_mapping.update( {slugify(category.title(self.language)): category}) return sorted_mapping def results_message(self, count, heading, search_term): if search_term: _for_term = '{} "{}"'.format(_('for'), search_term) else: _for_term = '' if count == 1: _showing = _('Showing ') # trailing space triggers singular es _results = _('result') else: _showing = _('Showing') _results = _('results') if self.portal_category and search_term: return format_html( '{} {} {} {} {} {}' '<span class="results-link"><a href="../?search_term={}">' '{} {}</a></span>', _showing, count, _results, _for_term, _('within'), heading.lower(), search_term, _('See all results within'), self.portal_topic.title(self.language).lower()) elif self.portal_category: return '{} {} {} {} {}'.format(_showing, count, _results, _('within'), heading.lower()) return '{} {} {} {} {} {}'.format(_showing, count, _results, _for_term, _('within'), heading.lower()) def get_heading(self): if self.portal_category: return self.portal_category.title(self.language) else: return self.portal_topic.title(self.language) def get_context(self, request, *args, **kwargs): if self.language != 'en': activate(self.language) else: deactivate_all() return super(PortalSearchPage, self).get_context(request, *args, **kwargs) def get_nav_items(self, request, page): """Return sorted nav items for sidebar.""" sorted_categories = [{ 'title': category.title(self.language), 'url': "{}{}/".format(page.url, slug), 'active': (False if not page.portal_category else category.title( self.language) == page.portal_category.title(self.language)) } for slug, category in self.category_map.items()] return [{ 'title': page.portal_topic.title(self.language), 'url': page.url, 'active': False if page.portal_category else True, 'expanded': True, 'children': sorted_categories }], True def get_results(self, request): context = self.get_context(request) search_term = request.GET.get('search_term', '').strip() if not search_term or len(unquote(search_term)) == 1: results = self.query_base else: search = AskSearch(search_term=search_term, query_base=self.query_base) results = search.queryset if results.count() == 0: # No results, so let's try to suggest a better query search.suggest(request=request) results = search.queryset search_term = search.search_term search_message = self.results_message(results.count(), self.get_heading(), search_term) paginator = Paginator(results, 10) page_number = validate_page_number(request, paginator) context.update({ 'search_term': search_term, 'results_message': search_message, 'pages': paginator.page(page_number), 'paginator': paginator, 'current_page': page_number, 'get_secondary_nav_items': self.get_nav_items, }) return TemplateResponse(request, 'ask-cfpb/see-all.html', context) def get_glossary_terms(self): if self.language == 'es': terms = self.portal_topic.glossary_terms.order_by('name_es') else: terms = self.portal_topic.glossary_terms.order_by('name_en') for term in terms: if term.name(self.language) and term.definition(self.language): yield term @route(r'^$') def portal_topic_page(self, request): self.query_base = SearchQuerySet().filter( portal_topics=self.portal_topic.heading, language=self.language) self.portal_category = None return self.get_results(request) @route(r'^(?P<category>[^/]+)/$') def portal_category_page(self, request, **kwargs): category_slug = kwargs.get('category') if category_slug not in self.category_map: raise Http404 self.portal_category = self.category_map.get(category_slug) self.title = "{} {}".format( self.portal_topic.title(self.language), self.portal_category.title(self.language).lower()) if self.portal_category.heading == 'Key terms': self.glossary_terms = self.get_glossary_terms() context = self.get_context(request) context.update({'get_secondary_nav_items': self.get_nav_items}) return TemplateResponse(request, 'ask-cfpb/see-all.html', context) self.query_base = SearchQuerySet().filter( portal_topics=self.portal_topic.heading, language=self.language, portal_categories=self.portal_category.heading) return self.get_results(request)
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([], null=True) regulation = models.ForeignKey(Part, blank=True, null=True, on_delete=models.PROTECT, related_name='eregs3k_page') content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), 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'), ]) @cached_property def section_query(self): """Query set for Sections in this regulation's effective version.""" return Section.objects.filter( subpart__version=self.regulation.effective_version, ) @cached_property def sections(self): return list(self.section_query.all()) def get_context(self, request, *args, **kwargs): context = super(CFGOVPage, self).get_context(request, *args, **kwargs) context.update({ 'get_secondary_nav_items': get_reg_nav_items, 'regulation': self.regulation, 'section': None, 'breadcrumb_items': self.get_breadcrumbs(request) }) return context def get_breadcrumbs(self, request, section=None): landing_page = self.get_parent() crumbs = [{ 'href': landing_page.url, 'title': landing_page.title, }] if section is not None: crumbs = crumbs + [ { 'href': self.url, 'title': str(section.subpart.version.part), }, { 'title': section.subpart.title, }, ] return crumbs @route(r'^(?P<section_label>[0-9A-Za-z-]+)/$', name="section") def section_page(self, request, section_label): section = self.section_query.get(label=section_label) current_index = self.sections.index(section) context = self.get_context(request) content = regdown(section.contents, url_resolver=get_url_resolver(self), contents_resolver=get_contents_resolver(self), render_block_reference=partial( self.render_interp, context)) context.update({ 'version': self.regulation.effective_version, 'content': content, 'get_secondary_nav_items': get_reg_nav_items, 'next_section': get_next_section(self.sections, current_index), 'previous_section': get_previous_section(self.sections, current_index), 'section': section, 'breadcrumb_items': self.get_breadcrumbs(request, section), }) return TemplateResponse(request, self.template, context) 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)
class AnswerPage(CFGOVPage): """ Page type for Ask CFPB answers. """ from ask_cfpb.models import Answer last_edited = models.DateField( blank=True, null=True, help_text="Change the date to today if you make a significant change.") question = models.TextField(blank=True) statement = models.TextField( blank=True, help_text=( "(Optional) Use this field to rephrase the question title as " "a statement. Use only if this answer has been chosen to appear " "on a money topic portal (e.g. /consumer-tools/debt-collection).")) short_answer = RichTextField(blank=True, features=['link', 'document-link'], help_text='Optional answer intro') answer_content = StreamField(ask_blocks.AskAnswerContent(), blank=True, verbose_name='Answer') answer_base = models.ForeignKey(Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to_page = models.ForeignKey( 'self', blank=True, null=True, on_delete=models.SET_NULL, related_name='redirect_to_pages', help_text="Choose another AnswerPage to redirect this page to") featured = models.BooleanField( default=False, help_text=("Check to make this one of two featured answers " "on the landing page.")) featured_rank = models.IntegerField(blank=True, null=True) category = models.ManyToManyField( 'Category', blank=True, help_text=("Categorize this answer. " "Avoid putting into more than one category.")) search_tags = models.CharField( max_length=1000, blank=True, help_text="Search words or phrases, separated by commas") related_resource = models.ForeignKey(RelatedResource, blank=True, null=True, on_delete=models.SET_NULL) related_questions = ParentalManyToManyField( 'self', symmetrical=False, blank=True, related_name='related_question', help_text='Maximum of 3 related questions') portal_topic = ParentalManyToManyField( PortalTopic, blank=True, help_text='Limit to 1 portal topic if possible') primary_portal_topic = ParentalKey( PortalTopic, blank=True, null=True, on_delete=models.SET_NULL, related_name='primary_portal_topic', help_text=("Use only if assigning more than one portal topic, " "to control which topic is used as a breadcrumb.")) portal_category = ParentalManyToManyField(PortalCategory, blank=True) user_feedback = StreamField([ ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldPanel('last_edited'), FieldPanel('question'), FieldPanel('statement'), FieldPanel('short_answer') ], heading="Page content", classname="collapsible"), StreamFieldPanel('answer_content'), MultiFieldPanel([ SnippetChooserPanel('related_resource'), AutocompletePanel('related_questions', page_type='ask_cfpb.AnswerPage', is_single=False) ], heading="Related resources", classname="collapsible"), MultiFieldPanel([ FieldPanel('portal_topic', widget=forms.CheckboxSelectMultiple), FieldPanel('primary_portal_topic'), FieldPanel('portal_category', widget=forms.CheckboxSelectMultiple) ], heading="Portal tags", classname="collapsible"), MultiFieldPanel([FieldPanel('featured')], heading="Featured answer on Ask landing page", classname="collapsible"), MultiFieldPanel([ AutocompletePanel('redirect_to_page', page_type='ask_cfpb.AnswerPage') ], heading="Redirect to another answer", classname="collapsible"), MultiFieldPanel([StreamFieldPanel('user_feedback')], heading="User feedback", classname="collapsible collapsed"), ] sidebar = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('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) sidebar_panels = [ StreamFieldPanel('sidebar'), ] search_fields = Page.search_fields + [ index.SearchField('answer_content'), index.SearchField('short_answer') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'ask-cfpb/answer-page.html' objects = CFGOVPageManager() def get_sibling_url(self): if self.answer_base: if self.language == 'es': sibling = self.answer_base.english_page else: sibling = self.answer_base.spanish_page if sibling and sibling.live and not sibling.redirect_to_page: return sibling.url def get_context(self, request, *args, **kwargs): portal_topic = self.primary_portal_topic or self.portal_topic.first() context = super(AnswerPage, self).get_context(request) context['related_questions'] = self.related_questions.all() context['description'] = (self.short_answer if self.short_answer else Truncator(self.answer_content).words( 40, truncate=' ...')) context['last_edited'] = self.last_edited context['portal_page'] = get_portal_or_portal_search_page( portal_topic, language=self.language) context['breadcrumb_items'] = get_ask_breadcrumbs( language=self.language, portal_topic=portal_topic, ) context['about_us'] = get_standard_text(self.language, 'about_us') context['disclaimer'] = get_standard_text(self.language, 'disclaimer') context['sibling_url'] = self.get_sibling_url() return context def __str__(self): if self.answer_base: return '{}: {}'.format(self.answer_base.id, self.title) else: return self.title @property def clean_search_tags(self): return [tag.strip() for tag in self.search_tags.split(',')] @property def status_string(self): if self.redirect_to_page: if not self.live: return ("redirected but not live") else: return ("redirected") else: return super(AnswerPage, self).status_string # Returns an image for the page's meta Open Graph tag @property def meta_image(self): if self.social_sharing_image: return self.social_sharing_image if not self.category.exists(): return None return self.category.first().category_image # Overrides the default of page.id for comparing against split testing # clusters. See: core.feature_flags.in_split_testing_cluster @property def split_test_id(self): return self.answer_base.id
class TabbedSettings(TestSetting): edit_handler = TabbedInterface([ ObjectList([FieldPanel('title')], heading='First tab'), ObjectList([FieldPanel('email')], heading='Second tab'), ])
class ArticlePage(CFGOVPage): """ General article page type. """ category = models.CharField( choices=[ ('basics', 'Basics'), ('common_issues', 'Common issues'), ('howto', 'How to'), ('know_your_rights', 'Know your rights'), ], max_length=255, ) heading = models.CharField( max_length=255, blank=False, ) intro = models.TextField(blank=False) inset_heading = models.CharField(max_length=255, blank=True, verbose_name="Heading") sections = StreamField([ ('section', blocks.StructBlock([ ('heading', blocks.CharBlock(max_length=255, required=True, label='Section heading')), ('summary', blocks.TextBlock(required=False, blank=True, label='Section summary')), ('link_text', blocks.CharBlock(required=False, blank=True, label="Section link text")), ('url', blocks.CharBlock( required=False, blank=True, label='Section link URL', max_length=255, )), ('subsections', blocks.ListBlock( blocks.StructBlock([ ('heading', blocks.CharBlock(max_length=255, required=False, blank=True, label='Subsection heading')), ('summary', blocks.TextBlock(required=False, blank=True, label='Subsection summary')), ('link_text', blocks.CharBlock(required=True, label='Subsection link text')), ('url', blocks.CharBlock(required=True, label='Subsection link URL')) ]))) ])) ]) content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldPanel('category'), FieldPanel('heading'), FieldPanel('intro') ], heading="Heading", classname="collapsible"), MultiFieldPanel([ FieldPanel('inset_heading'), InlinePanel('article_links', label='Inset link', max_num=2), ], heading="Inset links", classname="collapsible"), StreamFieldPanel('sections'), ] sidebar = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('related_metadata', molecules.RelatedMetadata()), ('email_signup', organisms.EmailSignUp()), ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)), ], blank=True) sidebar_panels = [ StreamFieldPanel('sidebar'), ] search_fields = Page.search_fields + [ index.SearchField('title'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'ask-cfpb/article-page.html' objects = CFGOVPageManager() def get_context(self, request, *args, **kwargs): context = super(ArticlePage, self).get_context(request) context['about_us'] = get_standard_text(self.language, 'about_us') return context def __str__(self): return self.title
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"), ])
def setUp(self): fake_child = self.FakeChild() self.TabbedInterfaceClass = TabbedInterface([fake_child]) self.tabbed_interface = self.TabbedInterfaceClass(instance=True, form=True)
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"), ])
class FormPage(AbstractEmailForm): title_sv = models.CharField(max_length=255) translated_title = TranslatedField('title', 'title_sv') intro_en = StreamField( WAGTAIL_STATIC_BLOCKTYPES + [ ('contact_card', ContactCardBlock()), ], verbose_name=_('English Introduction'), blank=True, ) intro_sv = StreamField( WAGTAIL_STATIC_BLOCKTYPES + [ ('contact_card', ContactCardBlock()), ], verbose_name=_('Swedish Introduction'), blank=True, ) intro = TranslatedField('intro_en', 'intro_sv') thank_you_text_en = StreamField( WAGTAIL_STATIC_BLOCKTYPES + [ ('contact_card', ContactCardBlock()), ], verbose_name=_('English Thank You Text'), blank=True, ) thank_you_text_sv = StreamField( WAGTAIL_STATIC_BLOCKTYPES + [ ('contact_card', ContactCardBlock()), ], verbose_name=_('Swedish Thank You Text'), blank=True, ) thank_you_text = TranslatedField('thank_you_text_en', 'thank_you_text_sv') form_title_en = models.CharField( verbose_name=_('English Form Title'), max_length=255, blank=True ) form_title_sv = models.CharField( verbose_name=_('Swedish Form Title'), max_length=255, blank=True, ) form_title = TranslatedField('form_title_en', 'form_title_sv') general_panels = [ InlinePanel('form_fields', label="Form fields"), MultiFieldPanel([ FieldRowPanel([ FieldPanel('from_address', classname="col6"), FieldPanel('to_address', classname="col6"), ]), FieldPanel('subject'), ], "Email"), ] content_panels_en = AbstractEmailForm.content_panels + [ StreamFieldPanel('intro_en'), FieldPanel('form_title_en', classname="full title"), StreamFieldPanel('thank_you_text_en'), ] content_panels_sv = [ FieldPanel('title_sv', classname="full title"), StreamFieldPanel('intro_sv'), FieldPanel('form_title_sv', classname="full title"), StreamFieldPanel('thank_you_text_sv'), ] edit_handler = TabbedInterface([ ObjectList(general_panels, heading=_('General')), ObjectList(content_panels_en, heading=_('English')), ObjectList(content_panels_sv, heading=_('Swedish')), ObjectList(Page.promote_panels, heading=_('Promote')), ObjectList(Page.settings_panels, heading=_('Settings')), ])
class ActivityIndexPage(CFGOVPage): """ A model for the Activity Search page. """ subpage_types = ['teachers_digital_platform.ActivityPage'] objects = CFGOVPageManager() header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ], blank=True) results = {} content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar/Footer'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) @classmethod def can_create_at(cls, parent): # You can only create one of these! return super(ActivityIndexPage, cls).can_create_at(parent) \ and not cls.objects.exists() def get_template(self, request): template = 'teachers_digital_platform/activity_index_page.html' if 'partial' in request.GET: template = 'teachers_digital_platform/activity_search_facets_and_results.html' # noqa: E501 return template def get_context(self, request, *args, **kwargs): facet_map = ( ('building_block', (ActivityBuildingBlock, False, 10)), ('school_subject', (ActivitySchoolSubject, False, 25)), ('topic', (ActivityTopic, True, 25)), ('grade_level', (ActivityGradeLevel, False, 10)), ('age_range', (ActivityAgeRange, False, 10)), ('student_characteristics', (ActivityStudentCharacteristics, False, 10)), # noqa: E501 ('activity_type', (ActivityType, False, 10)), ('teaching_strategy', (ActivityTeachingStrategy, False, 25)), ('blooms_taxonomy_level', (ActivityBloomsTaxonomyLevel, False, 25)), # noqa: E501 ('activity_duration', (ActivityDuration, False, 10)), ('jump_start_coalition', (ActivityJumpStartCoalition, False, 25)), ('council_for_economic_education', (ActivityCouncilForEconEd, False, 25)), # noqa: E501 ) search_query = request.GET.get('q', '') # haystack cleans this string sqs = SearchQuerySet().models(ActivityPage).filter(live=True) total_activities = sqs.count() # Load selected facets selected_facets = {} facet_queries = {} for facet, facet_config in facet_map: sqs = sqs.facet(str(facet), size=facet_config[2]) if facet in request.GET and request.GET.get(facet): selected_facets[facet] = [ int(value) for value in request.GET.getlist(facet) if value.isdigit() ] facet_queries[facet] = facet + '_exact:' + ( " OR " + facet + "_exact:").join( [str(value) for value in selected_facets[facet]]) payload = { 'search_query': search_query, 'results': [], 'total_results': 0, 'total_activities': total_activities, 'selected_facets': selected_facets, 'facet_queries': facet_queries, 'all_facets': {}, } # Apply search query if it exists, but don't apply facets if search_query: sqs = sqs.filter(content=search_query).order_by( '-_score', '-date') # noqa: E501 else: sqs = sqs.order_by('-date') # Get all facets and their counts facet_counts = sqs.facet_counts() all_facets = self.get_all_facets(facet_map, sqs, facet_counts, facet_queries, selected_facets) # noqa: E501 # List all facet blocks that need to be expanded always_expanded = {'building_block', 'topic', 'school_subject'} conditionally_expanded = { facet_name for facet_name, facet_items in all_facets.items() if any(facet['selected'] is True for facet in facet_items) } expanded_facets = always_expanded.union(set(conditionally_expanded)) payload.update({ 'facet_counts': facet_counts, 'all_facets': all_facets, 'expanded_facets': expanded_facets, }) # Apply all the active facet values to our search results for facet_narrow_query in facet_queries.values(): sqs = sqs.narrow(facet_narrow_query) results = [activity.object for activity in sqs] total_results = sqs.count() payload.update({ 'results': results, 'total_results': total_results, }) self.results = payload results_per_page = validate_results_per_page(request) paginator = Paginator(payload['results'], results_per_page) current_page = validate_page_number(request, paginator) paginated_page = paginator.page(current_page) context = super(ActivityIndexPage, self).get_context(request) context.update({ 'facet_counts': facet_counts, 'facets': all_facets, 'activities': paginated_page, 'total_results': total_results, 'results_per_page': results_per_page, 'current_page': current_page, 'paginator': paginator, 'show_filters': bool(facet_queries), }) return context def get_all_facets(self, facet_map, sqs, facet_counts, facet_queries, selected_facets): # noqa: E501 all_facets = {} if 'fields' in facet_counts: for facet, facet_config in facet_map: class_object, is_nested, max_facet_count = facet_config all_facets_sqs = sqs other_facet_queries = [ facet_query for facet_query_name, facet_query in facet_queries.items() # noqa: E501 if facet != facet_query_name ] for other_facet_query in other_facet_queries: all_facets_sqs = all_facets_sqs.narrow( str(other_facet_query)) # noqa: E501 narrowed_facet_counts = all_facets_sqs.facet_counts() if 'fields' in narrowed_facet_counts and facet in narrowed_facet_counts[ 'fields']: # noqa: E501 narrowed_facets = [ value[0] for value in narrowed_facet_counts['fields'][facet] ] # noqa: E501 narrowed_selected_facets = selected_facets[ facet] if facet in selected_facets else [ ] # noqa: E501 if is_nested: all_facets[facet] = self.get_nested_facets( class_object, narrowed_facets, narrowed_selected_facets) else: all_facets[facet] = self.get_flat_facets( class_object, narrowed_facets, narrowed_selected_facets) return all_facets def get_flat_facets(self, class_object, narrowed_facets, selected_facets): final_facets = [{ 'selected': result['id'] in selected_facets, 'id': result['id'], 'title': result['title'], } for result in class_object.objects.filter( pk__in=narrowed_facets).values('id', 'title')] # noqa: E501 return final_facets def get_nested_facets(self, class_object, narrowed_facets, selected_facets, parent=None): # noqa: E501 if not parent: flat_final_facets = [{ 'selected': result['id'] in selected_facets, 'id': result['id'], 'title': result['title'], 'parent': result['parent'], } for result in class_object.objects.filter( pk__in=narrowed_facets).get_ancestors(True).values( 'id', 'title', 'parent')] # noqa: E501 final_facets = [] root_facets = [ root_facet for root_facet in flat_final_facets if root_facet['parent'] == None ] # noqa: E501 for root_facet in root_facets: children_list = self.get_nested_facets( class_object, narrowed_facets, selected_facets, root_facet['id']) # noqa: E501 child_selected = any(child['selected'] is True or child['child_selected'] is True for child in children_list # noqa: E501 ) final_facets.append({ 'selected': root_facet['selected'], 'child_selected': child_selected, 'id': root_facet['id'], 'title': root_facet['title'], 'parent': root_facet['parent'], 'children': children_list }) return final_facets else: children = [ { 'selected': result['id'] in selected_facets or result['parent'] in selected_facets, # noqa: E501 'id': result['id'], 'title': result['title'], 'parent': result['parent'], 'children': self.get_nested_facets(class_object, narrowed_facets, selected_facets, result['id']), # noqa: E501 'child_selected': any(child['selected'] is True or child['child_selected'] is True for child in # noqa: E501 self.get_nested_facets(class_object, narrowed_facets, selected_facets, result['id']) # noqa: E501 ) } for result in class_object.objects.filter( pk__in=narrowed_facets).filter( parent_id=parent).values('id', 'title', 'parent') ] # noqa: E501 return children class Meta: verbose_name = "TDP Activity search page"
class UrbanDesign(Page): # Database fields date = models.DateField("Updated On") introductory_text = models.CharField(max_length=255, default='', null=False, blank=False) introductory_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') city_beautiful = StreamField([ ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ('draw', DrawMapBlock()), ]) transects = StreamField([ ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ('draw', DrawMapBlock()), ]) diversity = StreamField([ ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ('draw', DrawMapBlock()), ]) civic_art = StreamField([ ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ('draw', DrawMapBlock()), ]) final_project = StreamField([ ('heading', blocks.CharBlock(classname="heading")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('video', EmbedBlock()), ('document', DocumentChooserBlock()), ('draw', DrawMapBlock()), ]) # Search index configuraiton search_fields = Page.search_fields + [ index.SearchField('introductory_text'), index.SearchField('city_beautiful'), index.SearchField('transects'), index.SearchField('diversity'), index.SearchField('civic_art'), index.SearchField('final_project'), index.FilterField('date'), ] # Editor panels configuration content_panels = Page.content_panels + [ FieldPanel('introductory_text'), ImageChooserPanel('introductory_image'), StreamFieldPanel('city_beautiful'), StreamFieldPanel('transects'), StreamFieldPanel('diversity'), StreamFieldPanel('civic_art'), StreamFieldPanel('final_project'), FieldPanel('date'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ]) # Parent page / subpage type rules parent_page_types = ['neighborhoods.HomePage'] subpage_types = []