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()), ('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()), ], 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, request, limit): hostname = request.site.hostname filter_pages = [p.specific for p in self.get_appropriate_descendants(hostname) if 'FilterablePage' in p.specific_class.__name__ and 'archive' not in p.title.lower()] posts_tuple_list = [] for page in filter_pages: base_query = AbstractFilterPage.objects.live_shared(hostname).filter(CFGOVPage.objects.child_of_q(page)) logger.info('Filtering by parent {}'.format(page)) form_id = str(page.form_id()) form = FilterableListForm(hostname=hostname, base_query=base_query) for post in form.get_page_set(): posts_tuple_list.append((form_id, post)) return sorted(posts_tuple_list, key=lambda p: p[1].date_published, reverse=True)[:limit]
class Content(Page): body = StreamField([ ('heading', blocks.CharBlock(classname='full title')), ('rich_text', blocks.RichTextBlock()), ('raw', blocks.RawHTMLBlock()), ('include_content', blocks.CharBlock()), ('content_list', blocks.CharBlock()), ], null=True, blank=True) # body_simple = StreamField([('rich_text', blocks.RichTextBlock())],null=True, blank=True) body_simple = RichTextField(null=True, blank=True) date = models.DateField('Content updated date', default=timezone.now) background_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') template_filename = models.CharField(max_length=64, choices=( ('content.html', 'content.html'), ('f6-content.html', 'f6-content.html'), ('f6-vue.html', 'f6-vue.html'), ), default='content.html') tags = ClusterTaggableManager(through=ContentTag, blank=True) def get_template(self, request, *args, **kwargs): template_name = request.GET.get('template', self.template_filename) #force_template = request.COOKIES.get('force_template'); #if force_template == 'f6': # template_name = 'f6-content.html' if self.body_simple is not None: if len(self.body_simple) > 1: template_name = 'f6-content.html' return '{}/{}'.format(self.__class__._meta.app_label, template_name) promote_panels = Page.promote_panels + [ FieldPanel('date'), FieldPanel('tags'), ] content_panels = Page.content_panels + [ StreamFieldPanel('body'), FieldPanel('body_simple'), ImageChooserPanel('background_image') ] settings_panels = Page.settings_panels + [FieldPanel('template_filename')] search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('body_simple'), index.FilterField('url_path') ] def serve(self, request): if 'draft' in request.GET: return HttpResponseRedirect('/admin/pages/{}/view_draft/'.format( self.pk)) response = super(Content, self).serve(request) if 'embed' in request.GET: with open( os.path.join(settings.MEDIA_ROOT, 'images', self.slug + '.html')) as output: output.write(response.content) return response class Meta: ordering = ('date', )
class FullWidthPage(MenuPage): body = StreamField(MyStreamBlock(), blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ]
class HTMLPage(CustomPage): """General page model containing blocks of HTML content.""" content = StreamField(HTMLBlock()) content_panels = Page.content_panels + [StreamFieldPanel("content"), ]
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=r'^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_coords = models.CharField(max_length=100, blank=True) 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_zipcode = models.CharField(max_length=12, blank=True) agenda_items = StreamField([('item', AgendaItemBlock())], blank=True) objects = CFGOVPageManager() search_fields = AbstractFilterPage.search_fields + [ index.SearchField('body'), index.SearchField('archive_body'), index.SearchField('live_stream_url'), index.SearchField('flickr_url'), index.SearchField('youtube_url'), index.SearchField('future_body'), index.SearchField('agenda_items') ] # 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_zipcode'), ], 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'): if not self.venue_coords: self.venue_coords = get_venue_coords(self.venue_city, self.venue_state) api_url = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static' static_map_image_url = '{}/{},{}/{}?access_token={}'.format( api_url, self.venue_coords, zoom, size, settings.MAPBOX_ACCESS_TOKEN) return static_map_image_url def save(self, *args, **kwargs): self.venue_coords = get_venue_coords(self.venue_city, self.venue_state) return super(EventPage, self).save(*args, **kwargs)
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'] template = 'index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [index.SearchField('header')] 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
class InvalidStreamModel(models.Model): body = StreamField([ ('heading', blocks.CharBlock()), ('rich text', blocks.RichTextBlock()), ])
class AskPage(PublicBasePage, ContactFields): """ Page type for Ask A Librarian pages. """ ask_widget_name = models.CharField(max_length=100, blank=True) reference_resources = RichTextField(blank=True) body = StreamField(DefaultBodyFields()) phone_regex = RegexValidator(regex=PHONE_FORMAT, message=PHONE_ERROR_MSG) secondary_phone_number = models.CharField(validators=[phone_regex], max_length=12, blank=True) schedule_appointment_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Link to a contact form') visit_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Link to a location or hours page') subpage_types = ['public.StandardPage', 'public.PublicRawHTMLPage'] content_panels = Page.content_panels + [ FieldPanel('ask_widget_name'), FieldPanel('reference_resources'), MultiFieldPanel([ PageChooserPanel('link_page'), FieldPanel('link_external'), ], heading='Contact Form'), MultiFieldPanel([ FieldPanel('email'), FieldPanel('phone_number'), FieldPanel('secondary_phone_number'), PageChooserPanel('visit_page'), PageChooserPanel('schedule_appointment_page'), ], heading='General Contact'), StreamFieldPanel('body'), ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('ask_widget_name'), index.SearchField('reference_resources'), index.SearchField('body'), index.SearchField('email'), index.SearchField('email_label'), index.SearchField('phone_number'), index.SearchField('body'), ] @property def ask_form_name(self): """ Get the name of the chat widget. Returns: String, name of the ask widget. """ return self.ask_widget_name @property def contact_link(self): """ Return an html link for contacting a librarian by email. """ text = '<i class="fa fa-envelope-o fa-2x"></i> Email' if self.link_page: return '<a href="%s">%s</a>' % (self.link_page.url, text) elif self.email: return '<a href="mailto:%s">%s</a>' % (self.email, text) else: return False @property def has_right_sidebar(self): """ Determine if a right sidebar should be displayed on AskPages. Returns: boolean """ fields = [ self.contact_link, self.phone_number, self.secondary_phone_number, self.schedule_appointment_page, self.visit_page ] if self.base_has_right_sidebar(): return True else: for field in fields: if field: return True return False def get_context(self, request): context = super(AskPage, self).get_context(request) context['ask_pages'] = AskPage.objects.live() return context
class Product(UnchainedProductDetailsMixin, TranslatablePage): parent_page_types = [ProductListing] content = StreamField( [ ('row', SnippetChooserBlock('grid.Row')), ] + LIVE_CLEAN_PANEL_BLOCKS, default=[], ) content_panels = UnchainedProductDetailsMixin.content_panels + [ StreamFieldPanel('content'), ] def get_resource_info(self, *args, **kwargs): """Returns the product information""" if 'product_slug' in kwargs and 'details_page' in kwargs: product_information = get_product_info(kwargs['product_slug']) category_names = self.extract_category_info( product_information['category_id_list']) product_information['category_names'] = category_names product_information['attributes'] = self.get_attributes( product_information['attributes']) related_products = list() for related_product in product_information['related_products']: related_product_info = get_product_info( related_product['slug']) related_product_info['relation_type'] = related_product[ 'relation_type'] related_products.append(related_product_info) product_information['related_products'] = related_products return product_information elif 'product_slug' in kwargs: return get_product_info(kwargs['product_slug']) else: return {} def product_details(self, request, *args, **kwargs): resource_info = self.get_resource_info(product_slug=kwargs['slug']) if 'slug' in kwargs and resource_info: context = self.get_context(request, *args, **kwargs) self.og_title = resource_info.get( 'name').title() if resource_info.get('name') else None self.og_title += ' | Live Clean' self.og_description = resource_info.get('description') self.og_url = (request.META['HTTP_HOST'] + resource_info.get('url') if resource_info.get('url') else None) self.og_type = 'product.item' if self.og_title: self.title = self.og_title if self.og_description: self.search_description = self.og_description if resource_info.get('images'): images_dict = dict(resource_info.get('images')[0]) if images_dict.get('url'): self.og_image_url = images_dict.get('url') context['content'] = WagtailObjectParser.render( self, request=request, is_page=True, page_id=self.id, product_slug=kwargs['slug'], details_page=True) return TemplateResponse( request, 'pages/generic/generic_page.html', context=context, ) else: raise Http404 def extract_category_info(self, category_id_list): """Extracts the category names""" categories = get_category_info() category_info = list() for category_id in category_id_list: category = self.get_category(category_id, categories) category_info.append(category['name']) return category_info def get_category(self, category_id, categories): """Extracts a category from a list of categories if the category's id is present.""" for category in categories: if category['id'] == category_id: return category return {} def get_attributes(self, attributes): """Returns attributes with presentation names, if present.""" product_properties = ProductProperties.objects.all() for attribute in attributes: attribute_presentation = self.get_attribute_presentation( name=attribute['name'], product_properties=product_properties) # Check if presentation name is present. if attribute_presentation and attribute_presentation.presentation_name: attribute[ 'presentation_name'] = attribute_presentation.presentation_name else: attribute['presentation_name'] = attribute['name'] # Check if description presentation is present. if attribute_presentation and attribute_presentation.description: attribute['description'] = attribute_presentation.description # For attributes under attributes if attribute['attributes']: for attr in attribute['attributes']: attr_presentation = self.get_attribute_presentation( name=attr['name'], product_properties=product_properties) # Check if presentation name is present. if attr_presentation and attr_presentation.presentation_name: attr[ 'presentation_name'] = attr_presentation.presentation_name else: attribute['presentation_name'] = attribute['name'] # Check if description presentation is present. if attr_presentation and attr_presentation.description: attr['description'] = attr_presentation.description return attributes def get_attribute_presentation(self, name, product_properties): attribute = next( (attr for attr in product_properties if attr.name == name), None) if attribute: return attribute else: return
class ShowPage(Page): class Meta: verbose_name = 'Show' description = 'A show microsite' api_fields = [ 'description', 'accent_color', 'about_content', 'logo', ] description = models.CharField(max_length=140, help_text='Describe the show in a tweet (140 characters)') accent_color = models.CharField(max_length=7, blank=True, null=True, verbose_name='Accent color') about_content = StreamField([ ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()) ]) logo = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) cover_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Cover Image' ) social_facebook_url = models.TextField(blank=True, null=True, verbose_name='Facebook URL') social_twitter_handle = models.TextField(blank=True, null=True, verbose_name='Twitter handle') social_mixcloud_handle = models.TextField(blank=True, null=True, verbose_name='Mixcloud handle') social_soundcloud_handle = models.TextField(blank=True, null=True, verbose_name='Soundcloud handle') social_youtube_url = models.TextField(blank=True, null=True, verbose_name='YouTube URL') feature_interaction = models.BooleanField( default=False, verbose_name='Interaction', help_text='Interaction promotes the URF Text number and @urfstudio in the Player.', ) content_panels = Page.content_panels + [ FieldPanel('description', classname="full"), StreamFieldPanel('about_content') ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), InlinePanel('slots', label='Scheduling Slots'), MultiFieldPanel([ ImageChooserPanel('logo'), ImageChooserPanel('cover_image'), FieldPanel('accent_color', widget=forms.TextInput(attrs={'type': 'color', 'style': 'height: 50px'})) ], 'Branding & design'), MultiFieldPanel([ FieldPanel('feature_interaction'), ], heading='Features'), MultiFieldPanel([ FieldPanel('social_facebook_url'), FieldPanel('social_twitter_handle'), FieldPanel('social_mixcloud_handle'), FieldPanel('social_soundcloud_handle'), FieldPanel('social_youtube_url'), ], heading='Social Pages') ] parent_page_types = ['shows.ShowIndexPage'] subpage_types = ['shows.ShowAudioSeriesIndexPage', 'shows.ShowContentPage'] def has_social(self): return self.social_facebook_url\ or self.social_twitter_handle\ or self.social_mixcloud_handle\ or self.social_soundcloud_handle\ or self.social_youtube_url def get_human_time(self): slots = self.slots.all() slots_human = [] # TODO: Clean up, inprove english. # Fridays 9pm to 10pm # Mondays & Wednesdays 10:30am to 11am # Weekdays 10:30am to 11am and 10pm to 11pm for slot in slots: time_display = TimeFormat(slot.from_time).format('g:i a') relative = ((slot.day - datetime.now().weekday()) + 7) % 7 relative_word = '' if relative == 0: relative_word = 'Today, and every' elif relative == 1: relative_word = 'Tomorrow, and every' slots_human.append('{} {day}{relative_coma} at {time}'.format( relative_word, day=slot.get_day_display(), relative_coma=',' if relative == 1 or relative == 0 else '', time=time_display)) return ', '.join(slots_human) def css_style(self, styles): return ''.join(['{}:{};'.format(prop, val) for (prop, val) in styles.items()]) @property def safe_accent_color(self): if not self.has_accent_color(): return '#A50027' return self.accent_color def generate_branding_style(self): styles = [] styles.append('background-color:{}'.format(self.safe_accent_color)) return ';'.join(styles) def generate_branding_style_secondary(self): styles = dict(); if self.has_accent_color: accent = Color(self.safe_accent_color) accent.luminance = accent.luminance * 0.9 if accent.luminance * 0.9 >= 0 else 0; accent.saturation = accent.saturation * 1.1 if accent.saturation * 1.1 <= 1 else 1 styles['background-color'] = accent.hex return self.css_style(styles); def has_accent_color(self): return self.accent_color is not None and self.accent_color != '#000000' def tone_from_accent(self): dark_tone = dark_tone_from_accent(self.safe_accent_color[1:]) return 'dark' if dark_tone else 'light' @property def tone(self): return self.tone_from_accent() def name_group(self): return self.title and self.title[0] or ''
class AbstractArticle(models.Model, RenderInlineMixin): is_featured = models.BooleanField( verbose_name = _("Is Featured on home page"), default=False ) subtitle = models.CharField( verbose_name=_('subtitle'), max_length=255, help_text=_("The subtitle of the page"), blank=True ) featured = StreamField([ ('featured_image', FeaturedImageBlock()), ('featured_video', FeaturedVideoBlock()), ('featured_audio', FeaturedAudioBlock()), ]) author = models.ForeignKey( 'articles.AuthorPage', verbose_name=_('author'), null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) publication_date = models.DateField( verbose_name=_('publication_date'), help_text=_("The publication date of the article"), default=timezone.now, blank=True, null=True, ) body = StreamField([ ('introduction', blocks.TextBlock(icon="italic", rows=3)), ('paragraph', blocks.RichTextBlock(icon="pilcrow")), # ('markdown_paragraph', MarkdownBlock(icon="code")), ('image', ImageChooserBlock(icon="image")), ('pullquote', PullQuoteBlock()), ]) class Meta: abstract = True verbose_name = _("Article") def __featured_item(self, block_type='featured_image'): for stream_child in self.featured: if stream_child.block_type == block_type: return stream_child return None @property def featured_image(self): return self.__featured_item('featured_image') @property def featured_audio(self): return self.__featured_item('featured_audio') @property def featured_video(self): return self.__featured_item('featured_video') @property def introduction(self): for stream_child in self.body: if stream_child.block_type == 'introduction': return stream_child.value return None
class Track(Page): page_body = RichTextField( blank=True, help_text= "The main content of the page, above the list of steps and form") form_submission_message = models.CharField( max_length=255, help_text= "The text that is shown to the user after they make a form submissiom", default="Based on your choices we suggest looking at the following:") question = models.CharField( max_length=255, blank=True, help_text="The question for the form on the page (optional)") choices = StreamField([ ('label', blocks.CharBlock(required=True)), ], blank=True, null=True) has_strict_rules = models.BooleanField( default=False, help_text= "If the rule definitions are strict it will ONLY display results that match the exact answers (instead of the union of the answers)" ) rules = StreamField( [('rule', blocks.StructBlock([ ('name', ChoiceRulesBlock()), ('pages', blocks.ListBlock(blocks.PageChooserBlock())), ('override', blocks.BooleanBlock(default=False, required=False)) ]))], default=[], blank=True) default_steps = StreamField( [('page', blocks.PageChooserBlock())], blank=True, default=[], help_text= "The steps to show if someone submits a form with answers that are not covered by the rules" ) content_panels = Page.content_panels + [ FieldPanel('page_body', classname='full'), MultiFieldPanel( [ MultiFieldPanel([ FieldPanel('question'), FieldPanel('form_submission_message'), StreamFieldPanel('choices') ]), FieldPanel('has_strict_rules'), StreamFieldPanel('rules'), StreamFieldPanel('default_steps'), ], heading="Options form for the page to help narrow down choices", classname="collapsible") ] template = 'roadmap/track/base.html' def steps(self): # Get list of all step pages that are descendants of this page events = Step.objects.live().descendant_of(self) return events # Directs people to the walk through or self service routes # Walk through path uses the choices model to filter steps to take def route(self, request, path_components): if len(request.GET): return RouteResult(self, kwargs={'template': 'roadmap/track/base.html'}) else: return super(Track, self).route(request, path_components) def serve(self, request, template=''): if template == '': template = self.template #Kind of hacky but intercept request if it has 'submit-choice' in the slug #Serve the rules for the selected choices if len(request.GET): #Get selected checkbox values from params in request selected_choices = list(request.GET.values()) #Sort the choices so we have them in the same order as the admin defined rules selected_choices.sort() pages = [] #list of pages that will be presented to the user default_pages = [ ] #default pages if there isn't a rule defined for the choices the user selected all_selected_choices = ','.join(selected_choices) #loop through each admin defined rule to see if we have a defined rule for the selected choices if self.has_strict_rules: #Find the one rule that matches the selected choices and only suggest those steps for rule in self.rules: if rule.value['override'] and re.search( rule.value['name'], all_selected_choices): pages = rule.value['pages'] break if rule.value['name'] == all_selected_choices: pages = rule.value['pages'] else: #Union all the pages that match with a rule for rule in self.rules: if rule.value['override'] and re.search( rule.value['name'], all_selected_choices): pages = rule.value['pages'] break if rule.value['name'] in selected_choices: for page in rule.value['pages']: if page not in pages: pages.append(page) for page in self.default_steps: #if the user defines default pages in the admin then create a list of pages #otherwise the default default_pages list is all the steps in the track default_pages.append(Page.objects.get(id=page.value.id)) if not pages: if not default_pages: default_pages = Step.objects.live().descendant_of(self) pages = default_pages request.path = '/'.join(request.path.split('/')[:3]) return render( request, template, { 'steps': list(map((lambda page: page.specific), pages)), 'page': self, 'selected_choices': ','.join(map(str, selected_choices)), 'default_pages': default_pages, 'showMessage': True }) #Otherwise just render the track page with the appropriate template return render(request, template, {'page': self, 'steps': self.steps()})
class CFGOVPage(Page): authors = ClusterTaggableManager(through=CFGOVAuthoredPages, blank=True, verbose_name='Authors', help_text='A comma separated list of ' + 'authors.', related_name='authored_pages') tags = ClusterTaggableManager(through=CFGOVTaggedPages, blank=True, related_name='tagged_pages') shared = models.BooleanField(default=False) has_unshared_changes = models.BooleanField(default=False) language = models.CharField(choices=ref.supported_languagues, default='en', max_length=2) social_sharing_image = models.ForeignKey( 'v1.CFGOVImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=( 'Optionally select a custom image to appear when users share this ' 'page on social media websites. Recommended size: 1200w x 630h. ' 'Maximum size: 4096w x 4096h.')) # This is used solely for subclassing pages we want to make at the CFPB. is_creatable = False objects = CFGOVPageManager() # These fields show up in either the sidebar or the footer of the page # depending on the page type. sidefoot = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('related_posts', organisms.RelatedPosts()), ('related_metadata', molecules.RelatedMetadata()), ('email_signup', organisms.EmailSignUp()), ('sidebar_contact', organisms.SidebarContactInfo()), ('rss_feed', molecules.RSSFeed()), ('social_media', molecules.SocialMedia()), ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)), ], blank=True) # Panels promote_panels = Page.promote_panels + [ ImageChooserPanel('social_sharing_image'), ] sidefoot_panels = [ StreamFieldPanel('sidefoot'), ] settings_panels = [ MultiFieldPanel(promote_panels, 'Settings'), InlinePanel('categories', label="Categories", max_num=2), FieldPanel('tags', 'Tags'), FieldPanel('authors', 'Authors'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), FieldPanel('language', 'language'), ] # Tab handler interface guide because it must be repeated for each subclass edit_handler = TabbedInterface([ ObjectList(Page.content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar/Footer'), ObjectList(settings_panels, heading='Configuration'), ]) def clean(self): super(CFGOVPage, self).clean() validate_social_sharing_image(self.social_sharing_image) def get_authors(self): """ Returns a sorted list of authors. Default is alphabetical """ return self.alphabetize_authors() def alphabetize_authors(self): """ Alphabetize authors of this page by last name, then first name if needed """ # First sort by first name author_names = self.authors.order_by('name') # Then sort by last name return sorted(author_names, key=lambda x: x.name.split()[-1]) def generate_view_more_url(self, request): 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 ProgrammePage(Page): ### Model Fields ###################################### short_description = models.CharField( blank=True, null=True, max_length=1000, help_text="Short Description to be displayed at the top of the page" ) body = StreamField( [ ('paragraph', RichTextBlock(features=[ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'bold', 'italic', 'link', 'ol', 'ul', 'hr'])), ('captioned_image', CaptionedImageBlock(label="Image")), ('authored_block_quote', AuthoredBlockQuoteBlock( label="Block Quote")), ('embed', EmbedBlock()), ('raw_html', RawHTMLBlock(label="Raw HTML")), ('clearfix', ClearfixBlock()) ], null=True ) host = models.CharField( blank=True, null=True, max_length=200, ) genre = models.CharField( blank=True, null=True, max_length=200, ) air_time = models.CharField( blank=True, null=True, max_length=200, help_text='Duration of Each Episode', ) tv_rating = models.CharField( blank=True, null=True, max_length=50, help_text='Malaysian Standard: U, P13, 18', ) banner = models.ForeignKey( 'wagtailimages.Image', blank=True, null=True, on_delete=models.SET_NULL, related_name='programme_banner' ) thumbnail = models.ForeignKey( 'wagtailimages.Image', blank=True, null=True, on_delete=models.SET_NULL, related_name='programme_thumbnail' ) ### Methods ########################################### def episodes(self): return [n.episode_ID for n in self.youtube_episode.all()] def thumbnail_url(self): if(self.thumbnail): return get_wagtail_image_url(self.thumbnail) def banner_url(self): if(self.banner): return get_wagtail_image_url(self.banner) # Render Stream object to sensible HTML data, then replace # <img> tags with the correct src location def body_rendered(self): text = self.body.render_as_block() text = html_replace_img(text) return text ### CMS and API Exposure ############################## # Content panels - What shows up on the CMS dashboard content_panels = Page.content_panels + [ FieldPanel('short_description'), StreamFieldPanel('body'), MultiFieldPanel( [ FieldPanel('genre'), FieldPanel('host'), FieldPanel('air_time'), FieldPanel('tv_rating') ], heading="Programme Info", ), InlinePanel('youtube_episode', label="Youtube Episodes"), ImageChooserPanel('banner'), ImageChooserPanel('thumbnail'), ] # API fields - What will be returned from API call api_fields = [ APIField('short_description'), APIField('body_rendered'), APIField('genre'), APIField('host'), APIField('air_time'), APIField('tv_rating'), APIField('episodes'), APIField('banner_url'), APIField('thumbnail_url'), ] subpage_types = [] parent_page_types = ['ProgrammeIndexPage']
class CustomPage(Page, index.Indexed): footer = ForeignKey('common.Footer', default=DEFAULT_FOOTER_ID, null=True, blank=True, on_delete=SET_NULL, related_name='+') content = StreamField([ ('appeal', StructBlock([ ('icon', ChoiceBlock(required=True, choices=[ ('none', 'none'), ('flask', 'flask'), ('group', 'group'), ('laptop', 'laptop'), ('sitemap', 'sitemap'), ('user', 'user'), ('book', 'book'), ('download', 'download'), ])), ('topic', CharBlock(required=True, max_length=35)), ('content', RichTextBlock(required=True)), ], classname='appeal', icon='tick', template='common/blocks/appeal.html')), ('heading', CharBlock(classname="full title")), ('statement', CharBlock()), ('paragraph', RichTextBlock()), ('imagechooser', ImageChooserBlock()), ('column', RowBlock()), ('tabs', TabsBlock()), ('image', ImageBlock()), ('customImage', CustomImageBlock()), ('rich_text', RichTextBlock()), ('raw_html', RawHTMLBlock( help_text= 'With great power comes great responsibility. This HTML is unescaped. Be careful!' )), ('people_block', PeopleBlock()), ('centered_text', CenteredTextBlock()), ('hero_block', HeroBlock()), ('spotlight_block', SpotlightBlock()), ('job_whole_block', JobsWholeBlock()), ('embed_block', EmbedBlock()), ('whitespaceblock', WhitespaceBlock()), ('clear_fixblock', ClearfixBlock()), ('code_block', CodeBlock()), ('table_block', CustomTableBlock()), ('calender_block', GoogleCalendarBlock()), ('journal_block', JournalsTabBlock()), ('render_file', MfrBlock()), ('sponsor_partner_block', SponsorPartnerBlock()), ('collapse_block', CollapseBoxBlock()), ('button', ButtonBlock()), ], null=True, blank=True) custom_url = CharField(max_length=256, default='', null=True, blank=True) menu_order = IntegerField( blank=True, default=1, help_text=( 'The order this page should appear in the menu. ' 'The lower the number, the more left the page will appear. ' 'This is required for all pages where "Show in menus" is checked.' )) search_fields = [ index.SearchField('content', partial_match=True), ] content_panels = Page.content_panels + [ StreamFieldPanel('content'), SnippetChooserPanel('footer'), ] promote_panels = Page.promote_panels + [ FieldPanel('custom_url'), FieldPanel('menu_order'), InlinePanel('versioned_redirects', label='URL Versioning'), ] def serve(self, request): return render( request, self.template, { 'page': self, 'people': Person.objects.all(), 'jobs': Job.objects.all(), 'journals': Journal.objects.all(), 'organizations': Organization.objects.all(), 'donations': Donation.objects.all(), 'inkinddonations': InkindDonation.objects.all(), }) @transaction.atomic # only commit when all descendants are properly updated def move(self, target, pos=None): """ Extension to the treebeard 'move' method to ensure that url_path is updated too. """ old_url_path = Page.objects.get(id=self.id).url_path super(Page, self).move(target, pos=pos) # treebeard's move method doesn't actually update the in-memory instance, so we need to work # with a freshly loaded one now new_self = Page.objects.get(id=self.id) new_url_path = new_self.set_url_path(new_self.get_parent()) new_self.save() new_self._update_descendant_url_paths(old_url_path, new_url_path) new_redirect = new_self.versioned_redirects.create() redirect_url = ('/' + '/'.join(old_url_path.split('/')[2:]))[:-1] new_redirect.old_path = redirect_url new_redirect.redirect_page = new_self new_redirect.site = new_self.get_site() new_redirect.save() new_self.redirect_set.add(new_redirect) # Log logger.info("Page moved: \"%s\" id=%d path=%s", self.title, self.id, new_url_path) @transaction.atomic # ensure that changes are only committed when we have updated all descendant URL paths, to preserve consistency def save(self, *args, **kwargs): self.full_clean() update_descendant_url_paths = False is_new = self.id is None if is_new: # we are creating a record. If we're doing things properly, this should happen # through a treebeard method like add_child, in which case the 'path' field # has been set and so we can safely call get_parent self.set_url_path(self.get_parent()) else: # Check that we are committing the slug to the database # Basically: If update_fields has been specified, and slug is not included, skip this step if not ('update_fields' in kwargs and 'slug' not in kwargs['update_fields']): # see if the slug has changed from the record in the db, in which case we need to # update url_path of self and all descendants old_record = Page.objects.get(id=self.id) if old_record.slug != self.slug: self.set_url_path(self.get_parent()) update_descendant_url_paths = True old_url_path = old_record.url_path new_url_path = self.url_path new_redirect = self.versioned_redirects.create() redirect_url = ('/' + '/'.join(old_url_path.split('/')[2:]))[:-1] new_redirect.old_path = redirect_url new_redirect.redirect_page = self new_redirect.site = self.get_site() new_redirect.save() self.redirect_set.add(new_redirect) for redirect in self.versioned_redirects.all(): redirect.versioned_redirect_page = self redirect.redirect_page = self redirect.save() result = super(Page, self).save(*args, **kwargs) if update_descendant_url_paths: self._update_descendant_url_paths(old_url_path, new_url_path) # Check if this is a root page of any sites and clear the 'wagtail_site_root_paths' key if so if Site.objects.filter(root_page=self).exists(): cache.delete('wagtail_site_root_paths') # Log if is_new: cls = type(self) logger.info( "Page created: \"%s\" id=%d content_type=%s.%s path=%s", self.title, self.id, cls._meta.app_label, cls.__name__, self.url_path) return result
class ResourcePage(AbstractForm): def process_form_submission(self, request_dict): return custom_form_submission(self, request_dict) def serve(self, request, *args, **kwargs): try: request_dict = request.POST.dict() id = request_dict['id'] self.process_form_submission(request_dict) try: cookie = request.COOKIES['ldmw_session'] except: cookie = uid.hex resource = get_resource(id, cookie) if request_dict['feedback'] == '': error = True else: error = False csrf = request.POST.get('csrfmiddlewaretoken') resource_result = render_to_string( 'resources/resource.html', { 'page': resource, 'like_feedback_submitted': True, 'error': error, 'csrf_token': csrf }) visited_result = render_to_string( 'resources/single_visited.html', { 'v': resource, 'like_feedback_submitted': True }) return JsonResponse({ 'result': resource_result, 'visited_result': visited_result, 'id': id, 'feedback': True }) except: request.is_preview = getattr(request, 'is_preview', False) return TemplateResponse( request, self.get_template(request, *args, **kwargs), self.get_context(request, *args, **kwargs)) form_fields = None heading = TextField(blank=True, help_text="The title of the resource being linked to") logo_background_color = RGBColorField( default='#16b28f', null=True, blank=True, help_text="The background colour of brand_logo") resource_url = URLField(blank=True, help_text="The url of the resource to link to") resource_url_text = TextField(blank=True, help_text="The text for the url link") tagline = RichTextField( blank=True, help_text="Bold text that displays on the resource list") body = StreamField([ ('rich_text', blocks.RichTextBlock()), ('rawhtml', blocks.RawHTMLBlock()), ('heading', blocks.RichTextBlock()), ('paragraph', blocks.RichTextBlock()), ('column_left', blocks.RichTextBlock()), ('column_right', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ]) pros = RichTextField(blank=True, help_text="A list of pros for the resource") cons = RichTextField(blank=True, help_text="A list of cons for the resource") topic_tags = ClusterTaggableManager( through=TopicTag, blank=True, verbose_name='Topic Tags', related_name='resource_topic_tags', help_text='Topic tags, eg: "sleep", "depression", "stress"') issue_tags = ClusterTaggableManager( through=IssueTag, blank=True, verbose_name='Issue Tags', related_name='resource_issue_tags', help_text='Issue tags, eg: "insomnia", "fatigue", "snoring"') reason_tags = ClusterTaggableManager( through=ReasonTag, blank=True, verbose_name='Reason Tags', related_name='resource_reason_tags', help_text='Reason tags, eg: "loneliness", "relationships"') content_tags = ClusterTaggableManager(through=ContentTag, blank=True, verbose_name='Content Tags', related_name='resource_content_tags', help_text=""" Content Type tags, eg: "videos", "blogs", "free", "subscription" """) hidden_tags = ClusterTaggableManager(through=HiddenTag, blank=True, verbose_name='Hidden Tags', related_name='resource_hidden_tags', help_text='Hidden tags for admin use') PRIORITY_CHOICES = ( (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), ) priority = IntegerField(choices=PRIORITY_CHOICES, default='5', help_text='Highest priority 1, lowest priority 5') background_color = RGBColorField( default='#ffffff', null=True, blank=True, help_text="The background colour to use if there is no hero image") hero_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=""" Max file size: 10MB. Choose from: GIF, JPEG, PNG (but pick PNG if you have the choice) """) brand_logo = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=""" Max file size: 10MB. Choose from: JPEG, PNG (Please upload 155x60 image) """) brand_text = RichTextField(blank=True) text_color = RGBColorField(default='#000000', null=True, blank=True, help_text=""" The colour of the brand text. It should contrast well with the background colour or image """) latitude = LatitudeField(blank=True, max_length=255, help_text=""" latitude. This should be a number between -90 and 90 """) longitude = LongitudeField(blank=True, max_length=255, help_text=""" longitude. This should be a number between -180 and 180 """) search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('pros'), index.SearchField('cons'), index.SearchField('heading'), index.SearchField('resource_url'), index.RelatedFields('issue_tags', [ index.SearchField('name'), ]), index.RelatedFields('content_tags', [ index.SearchField('name'), ]), index.RelatedFields('reason_tags', [ index.SearchField('name'), ]), ] content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('hero_image'), FieldPanel('background_color'), ImageChooserPanel('brand_logo'), FieldPanel('brand_text'), FieldPanel('text_color') ], heading="Branding"), InlinePanel('badges', label="Badge"), InlinePanel('latlong', label="Latitude and Longitude"), FieldPanel('heading', classname="full"), FieldRowPanel([ FieldPanel('logo_background_color', classname="col6"), ], classname="full"), FieldRowPanel([ FieldPanel('resource_url', classname="col6"), FieldPanel('resource_url_text', classname="col6"), ], classname="full"), FieldPanel('tagline', classname="full"), StreamFieldPanel('body'), InlinePanel('buttons', label="Buttons"), FieldPanel('pros', classname="full"), FieldPanel('cons', classname="full") ] promote_panels = Page.promote_panels + [ FieldPanel('topic_tags'), FieldPanel('issue_tags'), FieldPanel('reason_tags'), FieldPanel('content_tags'), FieldPanel('hidden_tags'), FieldPanel('priority'), ] def __init__(self, *args, **kwargs): self.__class__.objects.prefetch_related('tagged_items__tag') super(ResourcePage, self).__init__(*args, **kwargs) try: self.parent = self.get_parent().slug except: self.parent = None def get_context(self, request): context = super(ResourcePage, self).get_context(request) if (request.META.get('HTTP_REFERER') and request.session.get('results_page')): context['back'] = request.session.pop('results_page') if 'ldmw_session' in request.COOKIES: cookie = request.COOKIES['ldmw_session'] try: context['liked_value'] = Likes.objects\ .get(resource_id=self.id, user_hash=cookie)\ .like_value except: context['liked_value'] = 0 else: cookie = '' context['liked_value'] = 0 if 'ldmw_location_latlong' in request.COOKIES: try: location = request.COOKIES['ldmw_location_latlong'] [user_lat, user_long] = location.split(",") context['is_near'] = any( filter( lambda e: haversine_distance(float( user_lat), float(user_long), float( e.latitude), float(e.longitude)) / 1.6 < 1000, self.latlong.all())) # less than 1 mile except: print("Failed to get location") context['is_near'] = False else: context['is_near'] = False Home = apps.get_model('resources', 'home') combine_tags = create_tag_combiner(None) landing_pages = Home.objects.filter(~Q(slug="home")).live() context['landing_pages'] = landing_pages context['tags'] = combine_tags(self).specific.tags context['number_of_likes'] = Likes.objects\ .filter(resource_id=self.id, like_value=1)\ .count() context['number_of_dislikes'] = Likes.objects\ .filter(resource_id=self.id, like_value=-1)\ .count() context['badges'] = ResourcePageBadges.objects\ .filter(page_id=self.page_ptr_id) context['buttons'] = ResourcePageButtons.objects\ .filter(page_id=self.page_ptr_id) return base_context(context, self) def get_form_fields(self): return iter([]) def likes(self): return Likes.objects\ .filter(resource_id=self.id, like_value=1)\ .count() def dislikes(self): return Likes.objects\ .filter(resource_id=self.id, like_value=-1)\ .count() class Meta: verbose_name = "Resource"
class FaqsPage(Page): body = StreamField([ ('faq_question', blocks.CharBlock(classname="full title")), ('faq_answer', blocks.RichTextBlock()), ])
class HomePage(Page): """ The Home Page. This looks slightly more complicated than it is. You can see if you visit your site and edit the homepage that it is split between a: - Hero area - Body area - A promotional area - Moveable featured site sections """ # Hero section of HomePage image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Homepage image') hero_text = models.CharField( max_length=255, help_text='Write an introduction for the bakery') hero_cta = models.CharField(verbose_name='Hero CTA', max_length=255, help_text='Text to display on Call to Action') hero_cta_link = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Hero CTA link', help_text='Choose a page to link to for the Call to Action') # Body section of the HomePage body = StreamField(BaseStreamBlock(), verbose_name="Home content block", blank=True) # Promo section of the HomePage promo_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Promo image') promo_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') promo_text = RichTextField(null=True, blank=True, help_text='Write some promotional copy') # Featured sections on the HomePage # You will see on templates/base/home_page.html that these are treated # in different ways, and displayed in different areas of the page. # Each list their children items that we access via the children function # that we define on the individual Page models e.g. BlogIndexPage featured_section_1_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_1 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='First featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 1') featured_section_2_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_2 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Second featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 2') featured_section_3_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_3 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Third featured section for the homepage. Will display up to ' 'six child items.', verbose_name='Featured section 3') content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('image'), FieldPanel('hero_text', classname="full"), MultiFieldPanel([ FieldPanel('hero_cta'), PageChooserPanel('hero_cta_link'), ]) ], heading="Hero section"), MultiFieldPanel([ ImageChooserPanel('promo_image'), FieldPanel('promo_title'), FieldPanel('promo_text'), ], heading="Promo section"), StreamFieldPanel('body'), MultiFieldPanel([ MultiFieldPanel([ FieldPanel('featured_section_1_title'), PageChooserPanel('featured_section_1'), ]), MultiFieldPanel([ FieldPanel('featured_section_2_title'), PageChooserPanel('featured_section_2'), ]), MultiFieldPanel([ FieldPanel('featured_section_3_title'), PageChooserPanel('featured_section_3'), ]) ], heading="Featured homepage sections", classname="collapsible") ] def __str__(self): return self.title
class 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.ustream.tv/embed/video_id. It can be obtained by following the instructions listed here: " \ "https://support.ustream.tv/hc/en-us/articles/207851917-How-to-embed-a-stream-or-video-on-your-site", validators=[ RegexValidator(regex='^https?:\/\/www\.ustream\.tv\/embed\/.*$')]) live_stream_date = models.DateTimeField("Go Live Date", blank=True, null=True) # Venue content fields venue_name = models.CharField(max_length=100, blank=True) venue_street = models.CharField(max_length=100, blank=True) venue_suite = models.CharField(max_length=100, blank=True) venue_city = models.CharField(max_length=100, blank=True) venue_state = USStateField(blank=True) venue_zip = models.IntegerField(blank=True, null=True) agenda_items = StreamField([('item', AgendaItemBlock())], blank=True) objects = CFGOVPageManager() # General content tab content_panels = CFGOVPage.content_panels + [ FieldPanel('body', classname="full"), FieldRowPanel([ FieldPanel('start_dt', classname="col6"), FieldPanel('end_dt', classname="col6"), ]), MultiFieldPanel([ FieldPanel('archive_body', classname="full"), ImageChooserPanel('archive_image'), DocumentChooserPanel('video_transcript'), DocumentChooserPanel('speech_transcript'), FieldPanel('flickr_url'), FieldPanel('youtube_url'), ], heading='Archive Information'), FieldPanel('live_body', classname="full"), FieldPanel('future_body', classname="full"), MultiFieldPanel([ FieldPanel('live_stream_availability'), FieldPanel('live_stream_url'), FieldPanel('live_stream_date'), ], heading='Live Stream Information'), ] # Venue content tab venue_panels = [ FieldPanel('venue_name'), MultiFieldPanel([ FieldPanel('venue_street'), FieldPanel('venue_suite'), FieldPanel('venue_city'), FieldPanel('venue_state'), FieldPanel('venue_zip'), ], heading='Venue Address'), ] # Agenda content tab agenda_panels = [ StreamFieldPanel('agenda_items'), ] # Promotion panels promote_panels = [ MultiFieldPanel(AbstractFilterPage.promote_panels, "Page configuration"), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(venue_panels, heading='Venue Information'), ObjectList(agenda_panels, heading='Agenda Information'), ObjectList(AbstractFilterPage.sidefoot_panels, heading='Sidebar'), ObjectList(AbstractFilterPage.settings_panels, heading='Configuration'), ]) template = 'events/event.html'
class BlogPage(Page): intro = models.CharField(blank=True, max_length=1000) content = StreamField([ ('rich_text', RichTextBlock()), ('code_block', CodeBlock()), ('google_calendar', GoogleCalendarBlock()), ], null=True, blank=True) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date = models.DateField( _("Post date"), default=datetime.datetime.today, help_text=_("This date may be displayed on the blog post. It is not " "used to schedule posts to go live at a later date.")) header_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('Header image')) footer = models.ForeignKey('common.Footer', default=DEFAULT_FOOTER_ID, null=True, blank=True, on_delete=SET_NULL, related_name='+') blog_authors = models.ManyToManyField( Person, blank=True, through=BlogPagePerson, # blank=True, null=True, ) search_fields = Page.search_fields + [ index.SearchField('content'), ] blog_categories = models.ManyToManyField(BlogCategory, through=BlogCategoryBlogPage, blank=True) settings_panels = [ MultiFieldPanel([ FieldRowPanel([ FieldPanel('go_live_at'), FieldPanel('expire_at'), ], classname="label-above"), ], 'Scheduled publishing', classname="publishing"), FieldPanel('date'), # FieldPanel('authors', widget=forms.CheckboxSelectMultiple), InlinePanel('authors', label=_("Authors")), ] promote_panels = Page.promote_panels + [ FieldPanel('tags'), ] def get_author(self): blog_author_default = Person.objects.filter(user_id=self.owner.id) if not blog_author_default: return 'Center for Open Science' return blog_author_default[0].\ first_name + " " + blog_author_default[0].last_name def get_absolute_url(self): return self.url def get_blog_index(self): # Find closest ancestor which is a blog index return self.get_ancestors().type(BlogIndexPage).last() def get_context(self, request, *args, **kwargs): context = super(BlogPage, self).get_context(request, *args, **kwargs) context['blogs'] = self.get_blog_index().blogindexpage.blogs context = get_blog_context(context) context['COMMENTS_APP'] = COMMENTS_APP return context class Meta: verbose_name = _('Blog page') verbose_name_plural = _('Blog pages') parent_page_types = ['BlogIndexPage'] content_panels = Page.content_panels + [ InlinePanel('categories', label="Categories"), ImageChooserPanel('header_image'), FieldPanel('intro'), StreamFieldPanel('content'), SnippetChooserPanel('footer'), ]
class StreamModel(models.Model): body = StreamField([ ('text', CharBlock()), ('rich_text', RichTextBlock()), ('image', ImageChooserBlock()), ])
class HomePage(Page): body = StreamField(DemoStreamBlock()) search_fields = Page.search_fields + (index.SearchField('body'), ) class Meta: verbose_name = "Homepage"
class BlogPage(Page, LocalorePromoteFields): subtitle = models.CharField(max_length=255, blank=True) video_poster_image = models.ForeignKey('localore_admin.LocaloreImage', verbose_name="poster image", null=True, on_delete=models.SET_NULL, related_name='+') video_poster_image_mobile = models.ForeignKey( 'localore_admin.LocaloreImage', verbose_name="poster image (mobile)", null=True, on_delete=models.SET_NULL, related_name='+') video_mp4 = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_webm = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_ogv = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') video_credit_caption = RichTextField(verbose_name="credit caption", blank=True) video_youtube_id = models.CharField( verbose_name="YouTube video ID", max_length=12, blank=True, help_text=format_html( "The part in bold: " "https://www.youtube.com/watch?v=<b>j6IIjLK-8fU</b>"), ) video_is_360 = models.BooleanField( "360˚ video", default=False, help_text="This is a 360-degree video.", ) tile_image = models.ForeignKey( 'localore_admin.LocaloreImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=("Optional: " "The image to use on the connections index page. " "Will use poster image if not set.")) date = models.DateField("Post date", default=datetime.date.today) is_featured = models.BooleanField( "featured", default=False, help_text=("Makes this connection go to the top of the list " "on the connections index page."), ) intro = RichTextField(blank=True) body = StreamField(BlogBodyBlock) # search index config search_fields = Page.search_fields + [ index.SearchField('subtitle', partial_match=True), index.SearchField('intro', partial_match=True), index.SearchField('body', partial_match=True), ] # admin editor content panels config content_panels = Page.content_panels + [ FieldPanel('subtitle', classname='full'), MultiFieldPanel([ FieldPanel('date'), FieldPanel('is_featured'), ], "Index page order"), MultiFieldPanel([ ImageChooserPanel('video_poster_image'), ImageChooserPanel('video_poster_image_mobile'), ImageChooserPanel('tile_image'), DocumentChooserPanel('video_mp4'), DocumentChooserPanel('video_webm'), DocumentChooserPanel('video_ogv'), FieldPanel('video_credit_caption'), ], "Hero section"), MultiFieldPanel([ FieldPanel('video_youtube_id'), FieldPanel('video_is_360'), ], "Fullscreen video"), FieldPanel('intro', classname='full'), StreamFieldPanel('body'), InlinePanel('associated_productions', label="Associated Productions"), InlinePanel('related_posts', label="Related Connections"), ] # admin editor metadata panels config promote_panels = LocalorePromoteFields.promote_panels # parent page/subpage type rules parent_page_types = ['blog.BlogIndexPage'] subpage_types = [] @property def blog_index(self): return self.get_ancestors().type(BlogIndexPage).last() @property def prev_page(self): ordered_posts = (BlogPage.objects.live().sibling_of( self, inclusive=True).order_by('-is_featured', '-date', '-pk')) prev_item = None for item in ordered_posts: if item == self: return prev_item prev_item = item @property def next_page(self): ordered_posts = (BlogPage.objects.live().sibling_of( self, inclusive=True).order_by('is_featured', 'date', 'pk')) prev_item = None for item in ordered_posts: if item == self: return prev_item prev_item = item @property def video_poster_image_file_extension(self): return self.video_poster_image.file.url.split('.')[-1] @property def related_live_posts(self): return [ item.related_blog_page for item in (self.related_posts.all()) if item.related_blog_page.live ] @property def preview_modes(self): return super(BlogPage, self).preview_modes + [ ('no-video', 'Preview poster image'), ] def serve_preview(self, request, mode_name): if mode_name == 'no-video': self.video_mp4 = None return super(BlogPage, self).serve_preview(request, mode_name) class Meta: verbose_name = "Connection"
class DefaultRichBlockFieldPage(Page): body = StreamField([ ('rich_text', RichTextBlock()), ]) content_panels = Page.content_panels + [StreamFieldPanel('body')]
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'), FieldPanel('language', 'Language'), ] # This page class cannot be created. is_creatable = False objects = CFGOVPageManager() search_fields = CFGOVPage.search_fields + [index.SearchField('header')] @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 PartnerPage(Page): """ Detail view for a specific partner """ introduction = models.TextField( help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Landscape mode only; horizontal width between 1000px and 3000px.' ) body = StreamField( BaseStreamBlock(), verbose_name="Page body", blank=True ) origin = models.ForeignKey( Country, on_delete=models.SET_NULL, null=True, blank=True, ) # We include related_name='+' to avoid name collisions on relationships. # e.g. there are two FooPage models in two different apps, # and they both have a FK to bread_type, they'll both try to create a # relationship called `foopage_objects` that will throw a valueError on # collision. partner_type = models.ForeignKey( 'partners.PartnerType', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) tags = ClusterTaggableManager(through=PartnerPageTag, blank=True) content_panels = Page.content_panels + [ FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('origin'), FieldPanel('partner_type'), FieldPanel('tags'), ] @property def get_tags(self): """ Similar to the authors function above we're returning all the tags that are related to the blog post into a list we can access on the template. We're additionally adding a URL to access BlogPage objects with that tag """ tags = self.tags.all() for tag in tags: tag.url = '/'+'/'.join(s.strip('/') for s in [ self.get_parent().url, 'tags', tag.slug ]) return tags @property def get_people(self): people = self.people_covering.all() return people search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('tags'), ] parent_page_types = ['PartnersIndexPage']
class LocationPage(Page): """ Detail for a specific bakery location. """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) address = models.TextField() lat_long = models.CharField( max_length=36, help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \ Right click Google Maps and select 'What\'s Here'", validators=[ RegexValidator( regex='^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$', message= 'Lat Long must be a comma-separated numeric lat and long', code='invalid_lat_long'), ]) # Search index configuration search_fields = Page.search_fields + [ index.SearchField('address'), index.SearchField('body'), ] # Fields to show to the editor in the admin view content_panels = [ FieldPanel('title', classname="full"), FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('address', classname="full"), FieldPanel('lat_long'), InlinePanel('hours_of_operation', label="Hours of Operation"), ] def __str__(self): return self.title @property def operating_hours(self): hours = self.hours_of_operation.all() return hours # Determines if the location is currently open. It is timezone naive def is_open(self): now = datetime.now() current_time = now.time() current_day = now.strftime('%a').upper() try: self.operating_hours.get(day=current_day, opening_time__lte=current_time, closing_time__gte=current_time) return True except LocationOperatingHours.DoesNotExist: return False # Makes additional context available to the template so that we can access # the latitude, longitude and map API key to render the map def get_context(self, request): context = super(LocationPage, self).get_context(request) context['lat'] = self.lat_long.split(",")[0] context['long'] = self.lat_long.split(",")[1] context['google_map_api_key'] = settings.GOOGLE_MAP_API_KEY return context # Can only be placed under a LocationsIndexPage object parent_page_types = ['LocationsIndexPage']
class HomePage(Page): featured_content = StreamField([ ("featured_section", FeaturedSectionBlock()), ], blank=True, null=True) # TODO: Carousel is temporary and being phased out carousel_0 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_1 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_2 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_3 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_4 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_5 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_6 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_7 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_8 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") carousel_9 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") in_focus_title = models.TextField(blank=True, null=True, verbose_name="Title") in_focus_link = models.TextField(blank=True, null=True, verbose_name="Link") in_focus_link_text = models.TextField(blank=True, null=True, verbose_name="Link Text") in_focus_page1 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.PROTECT, related_name="+", verbose_name="Page one") in_focus_page2 = models.ForeignKey("wagtailcore.Page", null=True, blank=True, on_delete=models.PROTECT, related_name="+", verbose_name="Page two") talking_album = models.ForeignKey("album.Album", null=True, blank=True, related_name='+', on_delete=models.PROTECT) photo_album = models.ForeignKey("album.Album", null=True, blank=True, related_name='+', on_delete=models.PROTECT) video = models.ForeignKey("article.Article", null=True, blank=True, on_delete=models.PROTECT) language = models.CharField(max_length=7, choices=settings.LANGUAGES) content_panels = Page.content_panels + [ MultiFieldPanel([StreamFieldPanel('featured_content')], heading="Featured Content", classname="collapsible "), MultiFieldPanel([ PageChooserPanel('carousel_0'), PageChooserPanel('carousel_1'), PageChooserPanel('carousel_2'), PageChooserPanel('carousel_3'), PageChooserPanel('carousel_4'), PageChooserPanel('carousel_5'), PageChooserPanel('carousel_6'), PageChooserPanel('carousel_7'), PageChooserPanel('carousel_8'), PageChooserPanel('carousel_9'), ], "Carousel"), MultiFieldPanel([ FieldPanel('in_focus_title'), FieldPanel('in_focus_link'), FieldPanel('in_focus_link_text'), PageChooserPanel('in_focus_page1'), PageChooserPanel('in_focus_page2'), ], "In Focus"), FieldPanel('talking_album'), FieldPanel('photo_album'), FieldPanel('video'), FieldPanel('language'), ] base_form_class = HomePageAdminForm def __str__(self): return _("HomePage") def get_context(self, request, *args, **kwargs): category1 = Category.objects.get(slug="resource-conflicts") category2 = Category.objects.get(slug="adivasis") category3 = Category.objects.get(slug="dalits") category4 = Category.objects.get(slug="sports-games") return { 'talking_album': { 'image': self.talking_album.slides.first().image, 'count': self.talking_album.slides.count(), 'photographers': self.talking_album.photographers, 'section_model': self.talking_album, }, 'photo_album': { 'image': self.photo_album.slides.first().image, 'count': self.photo_album.slides.count(), 'photographers': self.photo_album.photographers, 'section_model': self.photo_album, }, 'video': { 'image': self.video.featured_image, 'photographers': self.video.authors.all(), 'section_model': self.video, }, 'page': self, 'categories': [category1, category2, category3, category4], 'translations': get_translations_for_page(self), 'translations_for_infocus_article1': get_translations_for_page(self.in_focus_page1.specific), 'translations_for_infocus_article2': get_translations_for_page(self.in_focus_page2.specific), 'current_page': 'home-page', 'request': request, } def carousel(self): items = [] for ii in range(10): item = getattr(self, "carousel_{0}".format(ii)) if item: items.append(item) return items def get_absolute_url(self): return reverse("home-page")
class RightSidebarPage(MenuPage): body = StreamField(MyStreamBlock(), blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ]
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) # 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()), ('contact', organisms.MainContactInfo()), ('sidebar_contact', organisms.SidebarContactInfo()), ('rss_feed', molecules.RSSFeed()), ('social_media', molecules.SocialMedia()), ], blank=True) # Panels sidefoot_panels = [ StreamFieldPanel('sidefoot'), ] settings_panels = [ MultiFieldPanel(Page.promote_panels, 'Settings'), FieldPanel('tags', 'Tags'), FieldPanel('authors', 'Authors'), FieldPanel('language', 'language'), InlinePanel('categories', label="Categories", max_num=2), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), ] # 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 generate_view_more_url(self, request): from ..forms import ActivityLogFilterForm activity_log = CFGOVPage.objects.get(slug='activity-log').specific form = ActivityLogFilterForm(parent=activity_log, hostname=request.site.hostname) available_tags = [ tag[0] for name, tags in form.fields['topics'].choices for tag in tags ] tags = [] index = util.get_form_id(activity_log) for tag in self.tags.names(): if tag in available_tags: tags.append('filter%s_topics=' % index + urllib.quote_plus(tag)) tags = '&'.join(tags) return get_protected_url({'request': request}, activity_log) + '?' + tags def related_posts(self, block, hostname): from . import AbstractFilterPage related = {} queries = {} query = models.Q(('tags__name__in', self.tags.names())) search_types = [ ('blog', 'posts', 'Blog', query), ('newsroom', 'newsroom', 'Newsroom', query), ('events', 'events', 'Events', query), ] for parent_slug, search_type, search_type_name, search_query in search_types: try: parent = Page.objects.get(slug=parent_slug) search_query &= Page.objects.child_of_q(parent) if 'specific_categories' in block.value: specific_categories = ref.related_posts_category_lookup( block.value['specific_categories']) choices = [ c[0] for c in ref.choices_for_page_type(parent_slug) ] categories = [ c for c in specific_categories if c in choices ] if categories: search_query &= Q(('categories__name__in', categories)) if parent_slug == 'events': try: parent_slug = 'archive-past-events' parent = Page.objects.get(slug=parent_slug) q = (Page.objects.child_of_q(parent) & query) if 'specific_categories' in block.value: specific_categories = ref.related_posts_category_lookup( block.value['specific_categories']) choices = [ c[0] for c in ref.choices_for_page_type(parent_slug) ] categories = [ c for c in specific_categories if c in choices ] if categories: q &= Q(('categories__name__in', categories)) search_query |= q except Page.DoesNotExist: print 'archive-past-events does not exist' queries[search_type_name] = search_query except Page.DoesNotExist: print parent_slug, 'does not exist' for parent_slug, search_type, search_type_name, search_query in search_types: if 'relate_%s' % search_type in block.value \ and block.value['relate_%s' % search_type]: related[search_type_name] = \ AbstractFilterPage.objects.live_shared(hostname).filter( queries[search_type_name]).distinct().exclude( id=self.id).order_by('-date_published')[:block.value['limit']] # Return a dictionary of lists of each type when there's at least one # hit for that type. return { search_type: queryset for search_type, queryset in related.items() if queryset } 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: return [ util.get_appropriate_page_version(request, ancestor) for ancestor in ancestors[i + 1:] ] return [] def get_appropriate_descendants(self, hostname, inclusive=True): return CFGOVPage.objects.live_shared(hostname).descendant_of( self, inclusive) def get_appropriate_siblings(self, hostname, inclusive=True): return CFGOVPage.objects.live_shared(hostname).sibling_of( self, inclusive) def get_next_appropriate_siblings(self, hostname, inclusive=False): return self.get_appropriate_siblings( hostname=hostname, inclusive=inclusive).filter(path__gte=self.path).order_by('path') def get_prev_appropriate_siblings(self, hostname, inclusive=False): return self.get_appropriate_siblings( hostname=hostname, inclusive=inclusive).filter(path__lte=self.path).order_by('-path') @property def status_string(self): page = CFGOVPage.objects.get(id=self.id) if page.expired: return _("expired") elif page.approved_schedule: return _("scheduled") elif page.live and page.shared: if page.has_unpublished_changes: if page.has_unshared_changes: for revision in page.revisions.order_by( '-created_at', '-id'): content = json.loads(revision.content_json) if content['shared']: if content['live']: return _('live + draft') else: return _('live + (shared + draft)') else: return _("live + shared") else: return _("live") elif page.shared: if page.has_unshared_changes: return _("shared + draft") else: return _("shared") else: return _("draft") def sharable_pages(self): """ Return a queryset of the pages that this user has permission to share. """ # Deal with the trivial cases first... if not self.user.is_active: return Page.objects.none() if self.user.is_superuser: return Page.objects.all() sharable_pages = Page.objects.none() for perm in self.permissions.filter(permission_type='share'): # User has share permission on any subpage of perm.page # (including perm.page itself). sharable_pages |= Page.objects.descendant_of(perm.page, inclusive=True) return sharable_pages def can_share_pages(self): """Return True if the user has permission to publish any pages""" return self.sharable_pages().exists() def route(self, request, path_components): if path_components: # Request is for a child of this page. child_slug = path_components[0] remaining_components = path_components[1:] try: subpage = self.get_children().get(slug=child_slug) except Page.DoesNotExist: raise Http404 return subpage.specific.route(request, remaining_components) else: # Request is for this very page. page = util.get_appropriate_page_version(request, self) if page: return RouteResult(page) raise Http404 def permissions_for_user(self, user): """ Return a CFGOVPagePermissionTester object defining what actions the user can perform on this page """ user_perms = CFGOVUserPagePermissionsProxy(user) return user_perms.for_page(self) 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 # 'template' is used as the key for front-end consistency def add_page_js(self, js): js['template'] = [] # Retrieves the stream values on a page from it's Streamfield def _get_streamfield_blocks(self): lst = [ value for key, value in vars(self).iteritems() if type(value) is StreamValue ] return list(chain(*lst)) # Gets the JS from the Streamfield data def _add_streamfield_js(self, js): # Create a dictionary with keys ordered organisms, molecules, then atoms for child in self._get_streamfield_blocks(): self._add_block_js(child.block, js) # Recursively search the blocks and classes for declared Media.js def _add_block_js(self, block, js): self._assign_js(block, js) if issubclass(type(block), blocks.StructBlock): for child in block.child_blocks.values(): self._add_block_js(child, js) elif issubclass(type(block), blocks.ListBlock): self._add_block_js(block.child_block, js) # Assign the Media js to the dictionary appropriately def _assign_js(self, obj, js): try: if hasattr(obj.Media, 'js'): for key in js.keys(): if obj.__module__.endswith(key): js[key] += obj.Media.js if not [ key for key in js.keys() if obj.__module__.endswith(key) ]: js.update({'other': obj.Media.js}) except: pass # Returns all the JS files specific to this page and it's current Streamfield's blocks @property def media(self): js = OrderedDict() for key in ['template', 'organisms', 'molecules', 'atoms']: js.update({key: []}) self.add_page_js(js) self._add_streamfield_js(js) for key, js_files in js.iteritems(): js[key] = OrderedDict.fromkeys(js_files).keys() return js