class LandingPage(CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ('text_introduction', molecules.TextIntroduction()), ], blank=True) content = StreamField([ ('info_unit_group', organisms.InfoUnitGroup()), ('well', organisms.Well()), ('feedback', v1_blocks.Feedback()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'landing-page/index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ]
class SublandingFilterablePage(FilterableFeedPageMixin, FilterableListMixin, CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ], blank=True) content = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('full_width_text', organisms.FullWidthText()), ('filter_controls', organisms.FilterControls()), ('featured_content', molecules.FeaturedContent()), ('feedback', v1_blocks.Feedback()), ]) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'sublanding-page/index.html' objects = PageManager()
class LandingPage(CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ('text_introduction', molecules.TextIntroduction()), ], blank=True) content = StreamField([ ('info_unit_group', organisms.InfoUnitGroup()), ('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()), ('well', organisms.Well()), ('feedback', v1_blocks.Feedback()), ], blank=True) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'landing-page/index.html' objects = PageManager()
class FormExplainerPage(CFGOVPage): header = StreamField([ ('hero', molecules.Hero()), ('text_introduction', molecules.TextIntroduction()), ], blank=True) content = StreamField(FormExplainerContent) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'form-explainer/index.html' objects = CFGOVPageManager() search_fields = CFGOVPage.search_fields + [index.SearchField('header')]
class SublandingFilterableContent(StreamBlock): """Defines the StreamField blocks for SublandingFilterablePage content. Pages can have at most one filterable list. """ text_introduction = molecules.TextIntroduction() full_width_text = organisms.FullWidthText() filter_controls = organisms.FilterableList() featured_content = organisms.FeaturedContent() feedback = v1_blocks.Feedback() class Meta: block_counts = { 'filter_controls': { 'max_num': 1 }, }
class BrowseFilterablePage(FilterableFeedPageMixin, FilterableListMixin, CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ]) content = StreamField(BrowseFilterableContent) 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='SideFoot'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'browse-filterable/index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ] @property def page_js(self): return ( super(BrowseFilterablePage, self).page_js + ['secondary-navigation.js'] )
class PayingForCollegePage(CFGOVPage): """A base class for our suite of PFC pages.""" header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ], blank=True) content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) objects = CFGOVPageManager() class Meta: abstract = True
class BrowseFilterablePage(FilterableFeedPageMixin, FilterableListMixin, CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', molecules.FeaturedContent()), ]) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('filter_controls', organisms.FilterControls()), ('feedback', v1_blocks.Feedback()), ]) 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='SideFoot'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'browse-filterable/index.html' objects = PageManager() def add_page_js(self, js): super(BrowseFilterablePage, self).add_page_js(js) js['template'] += ['secondary-navigation.js']
class CollegeCostsPage(PayingForCollegePage): """Breaking down financial aid and loans for prospective students.""" header = StreamField([ ('hero', molecules.Hero()), ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ], blank=True) content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), # ObjectList(, heading='School and living situation'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) objects = CFGOVPageManager() content = StreamField(PayingForCollegeContent, blank=True) template = 'paying-for-college/college-costs.html'
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 BrowsePage(CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ], blank=True) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('info_unit_group', organisms.InfoUnitGroup()), ('expandable_group', organisms.ExpandableGroup()), ('expandable', organisms.Expandable()), ('well', organisms.Well()), ('video_player', organisms.VideoPlayer()), ('snippet_list', organisms.ResourceList()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('feedback', v1_blocks.Feedback()), ('raw_html_block', blocks.RawHTMLBlock(label='Raw HTML block')), ('conference_registration_form', ConferenceRegistrationForm()), ('chart_block', organisms.ChartBlock()), ('mortgage_chart_block', organisms.MortgageChartBlock()), ('mortgage_map_block', organisms.MortgageMapBlock()), ('mortgage_downloads_block', MortgageDataDownloads()), ('data_snapshot', organisms.DataSnapshot()), ('job_listing_table', JobListingTable()), ('bureau_structure', organisms.BureauStructure()), ('yes_checklist', YESChecklist()), ], blank=True) secondary_nav_exclude_sibling_pages = models.BooleanField(default=False) share_and_print = models.BooleanField( default=False, help_text="Include share and print buttons above page content.") # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), FieldPanel('share_and_print'), StreamFieldPanel('content'), ] sidefoot_panels = CFGOVPage.sidefoot_panels + [ FieldPanel('secondary_nav_exclude_sibling_pages'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'browse-basic/index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ] @property def page_js(self): return (super(BrowsePage, self).page_js + ['secondary-navigation.js']) def get_context(self, request, *args, **kwargs): context = super(BrowsePage, self).get_context(request, *args, **kwargs) context.update({'get_secondary_nav_items': get_secondary_nav_items}) return context
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 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 get_effective_version(self, request, date_str): """ Get the requested effective version if the user has permission """ effective_version = self.regulation.versions.get( effective_date=date_str) if effective_version.draft: page_perms = self.permissions_for_user(request.user) if not page_perms.can_edit(): raise PermissionDenied return effective_version def get_section_query(self, effective_version=None): """Query set for Sections in this regulation's effective version.""" if effective_version is None: effective_version = self.regulation.effective_version return Section.objects.filter(subpart__version=effective_version) def get_context(self, request, *args, **kwargs): context = super(RegulationPage, self).get_context(request, *args, **kwargs) context.update({ 'regulation': self.regulation, 'breadcrumb_items': self.get_breadcrumbs(request) }) return context def get_breadcrumbs(self, request, section=None): crumbs = super(RegulationPage, self).get_breadcrumbs(request) if section is not None: crumbs = crumbs + [ { 'href': self.url, 'title': str(section.subpart.version.part), }, ] return crumbs 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})/)?$') def index_route(self, request, date_str=None, *args, **kwargs): request.is_preview = getattr(request, 'is_preview', False) if date_str is not None: effective_version = self.get_effective_version(request, date_str) section_query = self.get_section_query( effective_version=effective_version) else: section_query = self.get_section_query() sections = list(section_query.all()) context = self.get_context(request, *args, **kwargs) context.update({ 'get_secondary_nav_items': partial(get_reg_nav_items, sections=sections, date_str=date_str), }) return TemplateResponse(request, self.get_template(request, *args, **kwargs), context) @route(r'^(?:(?P<date_str>[0-9]{4}-[0-9]{2}-[0-9]{2})/)?' r'(?P<section_label>[0-9A-Za-z-]+)/$', name="section") def section_page(self, request, date_str=None, section_label=None): """ Render a section of the currently effective regulation """ if date_str is not None: effective_version = self.get_effective_version(request, date_str) section_query = self.get_section_query( effective_version=effective_version) else: section_query = self.get_section_query() sections = list(section_query.all()) section = section_query.get(label=section_label) current_index = sections.index(section) context = self.get_context(request, sections=sections) 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': partial(get_reg_nav_items, sections=sections, date_str=date_str), 'next_section': get_next_section(sections, current_index), 'previous_section': get_previous_section(sections, current_index), 'section': section, 'breadcrumb_items': self.get_breadcrumbs(request, section), 'search_url': (self.get_parent().url + 'search-regulations/results/?regs=' + self.regulation.part_number) }) return TemplateResponse(request, self.template, context)
class SublandingPage(CFGOVPage): portal_topic = models.ForeignKey( 'v1.PortalTopic', blank=True, null=True, related_name='portal_pages', on_delete=models.SET_NULL, help_text='Select a topic if this is a MONEY TOPICS portal page.') header = StreamField([ ('hero', molecules.Hero()), ], blank=True) content = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('notification', molecules.Notification()), ('featured_content', organisms.FeaturedContent()), ('full_width_text', organisms.FullWidthText()), ('info_unit_group', organisms.InfoUnitGroup()), ('well', organisms.Well()), ('snippet_list', organisms.ResourceList()), ('post_preview_snapshot', organisms.PostPreviewSnapshot()), ('contact', organisms.MainContactInfo()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('reg_comment', organisms.RegComment()), ('feedback', v1_blocks.Feedback()), ], 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'), FieldPanel('portal_topic'), ] 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() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ] 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: posts_list.extend(AbstractFilterPage.objects.live().filter( CFGOVPage.objects.child_of_q(page))) return sorted(posts_list, key=lambda p: p.date_published, reverse=True)[:limit]
class AbstractFilterPage(CFGOVPage): header = StreamField([ ('article_subheader', blocks.RichTextBlock(icon='form')), ('text_introduction', molecules.TextIntroduction()), ('item_introduction', organisms.ItemIntroduction()), ], blank=True) preview_title = models.CharField(max_length=255, null=True, blank=True) preview_subheading = models.CharField(max_length=255, null=True, blank=True) preview_description = RichTextField(null=True, blank=True) secondary_link_url = models.CharField(max_length=500, null=True, blank=True) secondary_link_text = models.CharField(max_length=255, null=True, blank=True) preview_image = models.ForeignKey('v1.CFGOVImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') date_published = models.DateField(default=date.today) date_filed = models.DateField(null=True, blank=True) comments_close_by = models.DateField(null=True, blank=True) # Configuration tab panels settings_panels = [ MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'), InlinePanel('categories', label="Categories", max_num=2), FieldPanel('tags', 'Tags'), MultiFieldPanel([ FieldPanel('preview_title', classname="full"), FieldPanel('preview_subheading', classname="full"), FieldPanel('preview_description', classname="full"), FieldPanel('secondary_link_url', classname="full"), FieldPanel('secondary_link_text', classname="full"), ImageChooserPanel('preview_image'), ], heading='Page Preview Fields', classname='collapsible'), FieldPanel('authors', 'Authors'), MultiFieldPanel([ FieldPanel('date_published'), FieldPanel('date_filed'), FieldPanel('comments_close_by'), ], 'Relevant Dates', classname='collapsible'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), ] # This page class cannot be created. is_creatable = False objects = CFGOVPageManager() @classmethod def generate_edit_handler(self, content_panel): content_panels = [ StreamFieldPanel('header'), content_panel, ] return TabbedInterface([ ObjectList(self.content_panels + content_panels, heading='General Content'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(self.settings_panels, heading='Configuration'), ]) # Returns an image for the page's meta Open Graph tag @property def meta_image(self): parent_meta = super(AbstractFilterPage, self).meta_image return parent_meta or self.preview_image
class 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>' + label_re_str + r')/)?$', 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>' + label_re_str + r')/$', 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 ActivityIndexPage(CFGOVPage): """A model for the Activity Search page.""" subpage_types = ['teachers_digital_platform.ActivityPage'] objects = CFGOVPageManager() header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('notification', molecules.Notification()), ], blank=True) header_sidebar = StreamField([ ('image', TdpSearchHeroImage()), ], blank=True) results = {} activity_setups = None content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('header_sidebar'), ] 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 dsl_search(self, request, *args, **kwargs): """Search using Elasticsearch 7 and django-elasticsearch-dsl.""" all_facets = copy.copy(self.activity_setups.facet_setup) selected_facets = {} card_setup = self.activity_setups.ordered_cards total_activities = len(card_setup) search_query = request.GET.get('q', '') facet_called = any( [request.GET.get(facet, '') for facet in FACET_LIST] ) # If there's no query or facet request, we can return cached setups: if not search_query and not facet_called: payload = { 'search_query': search_query, 'results': list(card_setup.values()), 'total_results': total_activities, 'total_activities': total_activities, 'selected_facets': selected_facets, 'all_facets': all_facets, 'expanded_facets': ALWAYS_EXPANDED, } 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_update = { 'facets': all_facets, 'activities': paginated_page, 'total_results': total_activities, 'results_per_page': results_per_page, 'current_page': current_page, 'paginator': paginator, 'show_filters': bool(selected_facets), } return context_update dsl_search = ActivityPageDocument().search() if search_query: terms = search_query.split() for term in terms: dsl_search = dsl_search.query( "bool", must=Q("multi_match", query=term, fields=SEARCH_FIELDS) ) else: dsl_search = dsl_search.sort('-date') for facet, facet_config in FACET_MAP: if facet in request.GET and request.GET.get(facet): facet_ids = [ value for value in request.GET.getlist(facet) if value.isdigit() ] selected_facets[facet] = facet_ids for facet, pks in selected_facets.items(): dsl_search = dsl_search.query( "bool", should=[Q("match", **{facet: pk}) for pk in pks] ) facet_search = dsl_search.update_from_dict(FACET_DICT) total_results = dsl_search.count() dsl_search = dsl_search[:total_results] response = dsl_search.execute() results = [ card_setup[str(hit.id)] for hit in response[:total_results] ] facet_response = facet_search.execute() facet_counts = {facet: getattr( facet_response.aggregations, f"{facet}_terms").buckets for facet in FACET_LIST} all_facets = parse_dsl_facets( all_facets, facet_counts, selected_facets ) payload = { 'search_query': search_query, 'results': results, 'total_results': total_results, 'total_activities': total_activities, 'selected_facets': selected_facets, 'all_facets': all_facets, } # List all facet blocks that need to be expanded 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({ 'expanded_facets': expanded_facets, }) 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_update = { '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(selected_facets), } return context_update def get_context(self, request, *args, **kwargs): if not self.activity_setups: self.activity_setups = get_activity_setup() context_update = self.dsl_search(request, *args, **kwargs) context = super(ActivityIndexPage, self).get_context(request) context.update(context_update) return context class Meta: verbose_name = "TDP Activity search page"
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]