class ContentPage(BasePage): parent_page_types = ["home.HomePage", "content.ContentPage", "topics.Topic"] subpage_types = ["people.People", "content.ContentPage"] template = "content.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text=( "Optional short text description, " f"max. {DESCRIPTION_MAX_LENGTH} characters" ), max_length=DESCRIPTION_MAX_LENGTH, ) body = CustomStreamField( help_text=( "Main page body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ) ) sidebar = CustomStreamField( null=True, blank=True, help_text=( "Sidebar page body content (narrower than main body). Rendered to the " "right of the main body content in desktop and below it in mobile." "Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ), ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=140, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) card_image_3_2 = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 3:2 aspect ratio", ) # Meta fields nav_description = TextField( "Navigation description", max_length=DESCRIPTION_MAX_LENGTH, blank=True, default="", ) icon = FileField( upload_to="contentpage/icons", blank=True, default="", help_text=( "MUST be a black-on-transparent SVG icon ONLY, " "with no bitmap embedded in it." ), validators=[check_for_svg_file], ) keywords = ClusterTaggableManager(through=ContentPageTag, blank=True) # Editor panel configuration content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("body"), StreamFieldPanel("sidebar"), ] # Card panels card_panels = [ FieldPanel( "card_title", help_text=( "Title displayed when this page is " "represented by a card in a list of items. " "If blank, the page's title is used." ), ), FieldPanel( "card_description", help_text=( "Summary text displayed when this page is " "represented by a card in a list of items. " "If blank, the page's description is used." ), ), MultiFieldPanel( [ImageChooserPanel("card_image")], heading="16:9 Image", help_text=( "Image used for representing this page as a Card. " "Should be 16:9 aspect ratio. " "If not specified a fallback will be used. " "This image is also shown when sharing this page via social " "media unless a social image is specified." ), ), MultiFieldPanel( [ImageChooserPanel("card_image_3_2")], heading="3:2 Image", help_text=( "Image used for representing this page as a Card. " "Should be 3:2 aspect ratio. " "If not specified a fallback will be used. " ), ), ] # Meta panels meta_panels = [ FieldPanel( "nav_description", help_text="Text to display in the navigation with the title for this page.", ), MultiFieldPanel( [FieldPanel("icon")], heading="Theme", help_text=( "This icon will be used if, for example, this page is shown in a Menu" ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and " "description for SEO purposes" ), ), ] # Settings panels settings_panels = BasePage.settings_panels + [ FieldPanel("slug"), FieldPanel("show_in_menus"), ] # Tabs edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) # Search config search_fields = BasePage.search_fields + [ # Inherit search_fields from Page # "title" is already specced in BasePage index.SearchField("description"), index.SearchField("body"), index.SearchField("sidebar"), # Add FilterFields for things we may be filtering on (eg topics) index.FilterField("slug"), ]
class DefaultRichBlockFieldPage(Page): body = StreamField([ ('rich_text', RichTextBlock()), ]) content_panels = Page.content_panels + [StreamFieldPanel('body')]
class BlogPage(RoutablePageMixin, Page): description = models.CharField( max_length=255, blank=True, ) herocarrousel = StreamField([ ('academic', HeroAcademic()), ('mini_photo', HeroWithMiniFoto()), ('solo_text', HeroSoloText()), ('photo_or_letters', HeroWithFotoOrLetter()), ], null=True, blank=True) herobanner = StreamField([ ('large_banner', Banner()), ('circle_banner', blocks.StaticBlock(label="Middle Circle Apply", icon="site")), ('circle_banner_half', blocks.StaticBlock(label="Middle Circle Apply Half", icon="site")), ('mini_banner', blocks.StaticBlock(label="Mini Circle Apply", icon="site")), ('only_text', HeroBannerCircText(label="Only Text Circle", icon="site")), ('circle_talk', blocks.StaticBlock(label="Middle Circle Talk", icon="site")), ('circle_talk_half', blocks.StaticBlock(label="Middle Circle Talk Half", icon="site")), ], null=True, blank=True) content_panels = Page.content_panels + [ FieldPanel('description', classname="full"), MultiFieldPanel([ StreamFieldPanel('herocarrousel'), StreamFieldPanel('herobanner'), ], heading="Hero", classname="collapsible collapsed"), ] def get_posts(self): return PostPage.objects.descendant_of(self).live().order_by('-date') def use_paginator(self, request, post_t): page_n = request.GET.get('page') if not page_n: page_n = "1" #post_t = PostPage.objects.descendant_of(self).live().order_by('-date') paginator = Paginator(post_t, 6) posts_page = paginator.get_page(page_n) return posts_page def get_context(self, request, *args, **kwargs): context = super(BlogPage, self).get_context(request, *args, **kwargs) context['posts'] = self.posts #context['posts'] = self.get_posts #context['posts'] = PostPage.objects.descendant_of(self).live() context['blog_page'] = self context['showing_all'] = self.showing_all return context @route(r'^view_all/') def view_all(self, request, *args, **kwargs): self.posts = self.get_posts() self.showing_all = True return Page.serve(self, request, *args, **kwargs) @route(r'^tag/(?P<tag>[-\w]+)/$') def post_by_tag(self, request, tag, *args, **kwargs): self.showing_all = False self.search_type = 'tag' self.search_term = tag posts_p = self.get_posts().filter(tags__slug=tag) self.posts = self.use_paginator(request, posts_p) return Page.serve(self, request, *args, **kwargs) @route(r'^category/(?P<category>[-\w]+)/$') def post_by_category(self, request, category, *args, **kwargs): self.showing_all = False self.search_type = 'category' self.search_term = category posts_p = self.get_posts().filter(categories__slug=category) self.posts = self.use_paginator(request, posts_p) return Page.serve(self, request, *args, **kwargs) @route(r'^author/(?P<author>[-\w]+)/$') def post_by_author(self, request, author, *args, **kwargs): self.showing_all = False self.search_type = 'author' self.search_term = author posts_p = self.get_posts().filter(author__slug=author) self.posts = self.use_paginator(request, posts_p) #self.posts = self.get_posts() return Page.serve(self, request, *args, **kwargs) @route(r'^(\d{4})/$') @route(r'^(\d{4})/(\d{2})/$') @route(r'^(\d{4})/(\d{2})/(\d{2})/$') def post_by_date(self, request, year, month=None, day=None, *args, **kwargs): self.showing_all = False self.posts = self.get_posts().filter(date__year=year) if month: self.posts = self.posts.filter(date__month=month) df = DateFormat(date(int(year), int(month), 1)) self.search_term = df.format('F Y') if day: self.posts = self.posts.filter(date__day=day) self.search_term = date_format( date(int(year), int(month), int(day))) return Page.serve(self, request, *args, **kwargs) @route(r'^(\d{4})/(\d{2})/(\d{2})/(.+)/$') def post_by_date_slug(self, request, year, month, day, slug, *args, **kwargs): post_page = self.get_posts().filter(slug=slug).first() self.showing_all = False if not post_page: raise Http404 return Page.serve(post_page, request, *args, **kwargs) @route(r'^$') def post_list(self, request, *args, **kwargs): #page_n = request.GET.get('page') #if not page_n: # page_n = "1" #post_t = self.get_posts() #paginator = Paginator(post_t, 3) #posts_page = paginator.get_page(page_n) #self.posts = posts_page #self.posts = self.get_posts() self.showing_all = False posts_p = self.get_posts() self.posts = self.use_paginator(request, posts_p) return Page.serve(self, request, *args, **kwargs)
class CallToActionSnippet(models.Model): title = models.CharField(max_length=255) summary = RichTextField(blank=True, max_length=255) image = models.ForeignKey('images.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') link = StreamField(blocks.StreamBlock([ ('external_link', blocks.StructBlock([ ('url', blocks.URLBlock()), ('title', blocks.CharBlock()), ], icon='link')), ( 'internal_link', blocks.StructBlock([ ('page', blocks.PageChooserBlock()), ('title', blocks.CharBlock(required=False)), ], icon='link'), ), ], max_num=1, required=True), blank=True) panels = [ FieldPanel('title'), FieldPanel('summary'), ImageChooserPanel('image'), StreamFieldPanel('link'), ] def get_link_text(self): # Link is required, so we should always have # an element with index 0 block = self.link[0] title = block.value['title'] if block.block_type == 'external_link': return title # Title is optional for internal_link # so fallback to page's title, if it's empty return title or block.value['page'].title def get_link_url(self): # Link is required, so we should always have # an element with index 0 block = self.link[0] if block.block_type == 'external_link': return block.value['url'] return block.value['page'].get_url() def __str__(self): return self.title
class Video(BasePage): resource_type = "video" parent_page_types = ["Videos"] subpage_types = [] template = "video.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) body = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES, help_text=( "Optional body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets"), ) related_links_mdn = StreamField( StreamBlock([("link", ExternalLinkBlock())], required=False), null=True, blank=True, help_text="Optional links to MDN Web Docs for further reading", verbose_name="Related MDN links", ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) types = CharField(max_length=14, choices=VIDEO_TYPE, default="conference") duration = CharField( max_length=30, blank=True, null=True, help_text= ("Optional video duration in MM:SS format e.g. “12:34”. Shown when the " "video is displayed as a card"), ) transcript = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES, help_text="Optional text transcript of the video, supports rich text", ) video_url = StreamField( StreamBlock([("embed", EmbedBlock())], min_num=1, max_num=1, required=True), help_text= ("Embed URL for the video e.g. https://www.youtube.com/watch?v=kmk43_2dtn0" ), ) speakers = StreamField( StreamBlock( [("speaker", PageChooserBlock(target_model="people.Person"))], required=False, ), blank=True, null=True, help_text= "Optional list of people associated with or starring in the video", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta fields date = DateField("Upload date", default=datetime.date.today) keywords = ClusterTaggableManager(through=VideoTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), ImageChooserPanel("image"), StreamFieldPanel("video_url"), FieldPanel("body"), StreamFieldPanel("related_links_mdn"), FieldPanel("transcript"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ FieldPanel("date"), StreamFieldPanel("speakers"), MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text= ("These are the topic pages the video will appear on. The first topic " "in the list will be treated as the primary topic and will be shown " "in the page’s related content."), ), FieldPanel("duration"), MultiFieldPanel( [FieldPanel("types")], heading="Type", help_text="Choose a video type to help people search for the video", ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and description " "for SEO purposes"), ), ] settings_panels = [FieldPanel("slug")] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) @property def primary_topic(self): """Return the first (primary) topic specified for the video.""" video_topic = self.topics.first() return video_topic.topic if video_topic else None @property def read_time(self): return str(readtime.of_html(str(self.body))) @property def related_resources(self): """Returns resources that are related to the current resource, i.e. live, public articles and videos which have the same topics.""" topic_pks = [topic.topic.pk for topic in self.topics.all()] return get_combined_articles_and_videos( self, topics__topic__pk__in=topic_pks) def has_speaker(self, person): for speaker in self.speakers: # pylint: disable=not-an-iterable if str(speaker.value) == str(person.title): return True return False
class ProjectsPage(Page): headers = StreamField([ ('h_hero', _H_HeroBlock(icon='image')), # ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code')) ]) price_min = models.IntegerField( verbose_name="Preis der billigsten Einheit", null=True, blank=False) price_max = models.IntegerField( verbose_name= "Preis der teuersten Einheit (leer lassen wenn nur eine Einheit vorhanden ist)", null=True, blank=True) buy_available = models.BooleanField(verbose_name="Kaufmöglichkeit") rent_available = models.BooleanField(verbose_name="Mietmöglichkeit") location_name = models.CharField( verbose_name="Ortsname (z.B. Villach-Landskron)", null=True, blank=True, max_length=255) coordinates = models.CharField( verbose_name= "Standpunkt (Koordinaten, z.B. Beispiel: 46.6120061,13.916085)", null=True, blank=True, max_length=255) sections = StreamField( [ ('s_info', _S_InfoBlock(icon='fa-info')), ('s_contentcenter', _S_ContentCenter(icon='fa-info')), ('s_contentright', _S_ContentRight(icon='fa-info')), ('s_contentleft', _S_ContentLeft(icon='fa-info')), # ('s_imagegallery', _S_ImageGallery(icon='fa-info')) ], null=True, blank=True) gallery = StreamField([('g_gallery', _G_GalleryBlock(icon='fa-info'))], null=True, blank=True) flats = StreamField([('f_flats', _F_FlatsBlock(icon='fa-info'))], verbose_name="Wohneinheiten-Pages", null=True, blank=True) # footers = StreamField([ # ], null=True, blank=False) token = models.CharField(null=True, blank=True, max_length=255) # graphql_fields = [ # GraphQLStreamfield("headers"), # GraphQLStreamfield("sections"), # ] main_content_panels = [ StreamFieldPanel('headers'), FieldPanel('price_min'), FieldPanel('price_max'), FieldPanel('buy_available'), FieldPanel('rent_available'), FieldPanel('location_name'), FieldPanel('coordinates'), StreamFieldPanel('sections'), StreamFieldPanel('gallery'), StreamFieldPanel('flats') # StreamFieldPanel('footers') ] edit_handler = TabbedInterface([ ObjectList(Page.content_panels + main_content_panels, heading='Main'), ObjectList(Page.promote_panels, heading='Settings', classname="settings") ]) preview_modes = []
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') 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.')) schema_json = JSONField( null=True, blank=True, verbose_name='Schema JSON', help_text=mark_safe( 'Enter structured data for this page in JSON-LD format, ' 'for use by search engines in providing rich search results. ' '<a href="https://developers.google.com/search/docs/guides/' 'intro-structured-data">Learn more.</a> ' 'JSON entered here will be output in the ' '<code><head></code> of the page between ' '<code><script type="application/ld+json"></code> and ' '<code></script></code> tags.'), ) force_breadcrumbs = models.BooleanField( "Force breadcrumbs on child pages", default=False, blank=True, help_text=( "Normally breadcrumbs don't appear on pages one or two levels " "below the homepage. Check this option to force breadcrumbs to " "appear on all children of this page no matter how many levels " "below the homepage they are (for example, if you want " "breadcrumbs to appear on all children of a top-level campaign " "page)."), ) # This is used solely for subclassing pages we want to make at the CFPB. is_creatable = False objects = CFGOVPageManager() search_fields = Page.search_fields + [ index.SearchField('sidefoot'), ] # 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'), FieldPanel('force_breadcrumbs', 'Breadcrumbs'), ] sidefoot_panels = [ StreamFieldPanel('sidefoot'), ] settings_panels = [ MultiFieldPanel(promote_panels, 'Settings'), InlinePanel('categories', label="Categories", max_num=2), FieldPanel('tags', 'Tags'), FieldPanel('authors', 'Authors'), FieldPanel('schema_json', 'Structured Data'), 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 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().specific() for i, ancestor in enumerate(ancestors): if ancestor.is_child_of(request.site.root_page): if ancestor.specific.force_breadcrumbs: return ancestors[i:] return 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) # Add any banners that are enabled and match the current request path # to a context variable. context['banners'] = Banner.objects \ .filter(enabled=True) \ .annotate( # This annotation creates a path field in the QuerySet # that we can use in the filter below to compare with # the url_pattern defined on each enabled banner. path=Value(request.path, output_field=models.CharField())) \ .filter(path__regex=F('url_pattern')) if self.schema_json: context['schema_json'] = self.schema_json 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, or if form_id is not a block that implements get_result() to process the POST, 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 if streamfield_index is not None: try: form_module = streamfield[streamfield_index] except IndexError: form_module = None try: result = form_module.block.get_result(self, request, form_module.value, True) except AttributeError: return self._return_bad_post_response(request) 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 LivePageMixin(models.Model): """ Base class for pages using Wagtail Live. Attributes: channel_id (str): Id of the corresponding channel in a messaging app. last_updated_at (DateTime): Date and time of the last update of this page. live_posts (StreamField): StreamField containing all the posts/messages published respectively on this page/channel. """ channel_id = models.CharField( help_text="Channel ID", max_length=255, blank=True, unique=True, ) last_updated_at = models.DateTimeField( help_text="Last update of this page", blank=True, default=timezone.now, ) live_posts = StreamField( [ ("live_post", LivePostBlock()), ], blank=True, ) panels = [ FieldPanel("channel_id"), StreamFieldPanel("live_posts"), ] @property def last_update_timestamp(self): """Timestamp of the last update of this page.""" # Live posts are saved using a json format. # We strip the microseconds here to follow that format. microsecond = (self.last_updated_at.microsecond // 1000) * 1000 return self.last_updated_at.replace( microsecond=microsecond).timestamp() def save(self, sync=True, *args, **kwargs): """Update live page on save depending on the `WAGTAIL_LIVE_SYNC_WITH_ADMIN` setting.""" sync_changes = sync and getattr(settings, "WAGTAIL_LIVE_SYNC_WITH_ADMIN", True) has_changed = False if sync_changes and self.id: renders, seen = [], set() previous_posts = { live_post.id: live_post for live_post in self.__class__.objects.get( id=self.id).live_posts } now = timezone.now() for i, post in enumerate(self.live_posts): # New posts post_id = post.id if post_id in previous_posts: seen.add(post_id) # Check if the post has been modified. previous_post = previous_posts[post_id] identic = compare_live_posts_values( post.value, previous_post.value) if not identic: post.value["modified"] = now renders.append(i) else: # This is a new post. # Force the value of `created` here to keep it synchronized with the # `last_updated_at` property. # This is mostly to avoid missing new updates with the polling publishers. post.value["created"] = now renders.append(i) removals = list(set(previous_posts.keys()).difference(seen)) has_changed = bool(renders or removals) if has_changed: self.last_updated_at = now result = super().save(*args, **kwargs) if sync_changes and has_changed: # Reverse renders so the latest posts, which are in the start of the list, # are processed later in the front end. renders.reverse() renders = list(map(self.get_live_post_by_index, renders)) # Send signal. live_page_update.send( sender=self.__class__, channel_id=self.channel_id, renders=renders, removals=removals, ) return result def _get_live_post_index(self, message_id): for i, post in enumerate(self.live_posts): if post.value["message_id"] == message_id: return i def get_live_post_index(self, message_id): """ Retrieves the index of a live post. Args: message_id (str): ID of the message corresponding to a live post. Returns: int: Index of the live post if found else -1 """ return self._get_live_post_index(message_id=message_id) def get_live_post_by_index(self, live_post_index): """ Retrieves a live post by its index. Args: live_post_index (int): Index of the live post to look for. Returns: LivePostBlock: The live post instance Raises: IndexError: if a live post with the given index doesn't exist. """ return self.live_posts[live_post_index] def get_live_post_by_message_id(self, message_id): """ Retrieves a live post by its ID. Args: message_id (str): ID of the message corresponding to a live post. Returns: LivePostBlock: The live post instance Raises: KeyError: if a live post with the given ID doesn't exist. """ live_post_index = self.get_live_post_index(message_id=message_id) if live_post_index is None: raise KeyError return self.get_live_post_by_index(live_post_index) def add_live_post(self, live_post): """ Adds a new live post to live page. Args: live_post (LivePostBlock): live post to add """ posts = self.live_posts lp_index = 0 post_created_at = live_post["created"] while lp_index < len(posts): if posts[lp_index].value["created"] < post_created_at: break lp_index += 1 # Insert to keep posts sorted by time self.live_posts.insert(lp_index, ("live_post", live_post)) self.last_updated_at = post_created_at self.save(sync=False) live_post = self.get_live_post_by_index(lp_index) live_page_update.send( sender=self.__class__, channel_id=self.channel_id, renders=[live_post], removals=[], ) def update_live_post(self, live_post): """ Updates a live post when it has been edited. Args: live_post (livePostBlock): Live post to update. """ live_post.value["modified"] = self.last_updated_at = timezone.now() self.save(sync=False) live_page_update.send( sender=self.__class__, channel_id=self.channel_id, renders=[live_post], removals=[], ) def delete_live_post(self, message_id): """ Deletes the live post corresponding to message_id. Args: message_id (str): ID of the message corresponding to a live post. Raises: KeyError: if live post containing message with message_id doesn't exist. """ live_post_index = self.get_live_post_index(message_id=message_id) if live_post_index is None: raise KeyError live_post_id = self.live_posts[live_post_index].id del self.live_posts[live_post_index] self.last_updated_at = timezone.now() self.save(sync=False) live_page_update.send( sender=self.__class__, channel_id=self.channel_id, renders={}, removals=[live_post_id], ) def get_updates_since(self, last_update_ts): """ Retrieves new updates since a given timestamp value. Args: last_update_ts (DateTime): Timestamp of the last update. Returns: (list, dict): a tuple containing the current live posts and the updated posts since `last_update_ts`. """ # Reverse posts list so that latest updates are processed later by the client side. posts = reversed(self.live_posts) current_posts, updated_posts = [], {} for post in posts: post_id = post.id current_posts.append(post_id) created = post.value["created"] if created >= last_update_ts: # This is a new post updated_posts[post_id] = { "show": post.value["show"], "content": post.render(context={"block_id": post.id}), } continue last_modified = post.value["modified"] if last_modified and last_modified >= last_update_ts: # This is an edited post updated_posts[post_id] = { "show": post.value["show"], "content": post.render(context={"block_id": post.id}), } return (updated_posts, current_posts) class Meta: abstract = True
class LabPage(BasePage): subpage_types = ['RFPPage'] parent_page_types = ['LabIndex'] introduction = models.TextField(blank=True) icon = models.ForeignKey( 'images.CustomImage', null=True, blank=True, related_name='+', on_delete=models.SET_NULL ) lab_type = models.ForeignKey( 'wagtailcore.Page', blank=True, null=True, on_delete=models.SET_NULL, related_name='lab_public', ) lab_link = models.CharField(blank=True, max_length=255, verbose_name=_('External link'), validators=[MailToAndURLValidator()]) link_text = models.CharField(max_length=255, help_text=_('Text to display on the button for external links'), blank=True) body = StreamField(LabBlock()) search_fields = BasePage.search_fields + [ index.SearchField('introduction'), index.SearchField('body') ] content_panels = BasePage.content_panels + [ ImageChooserPanel('icon'), FieldPanel('introduction'), MultiFieldPanel([ PageChooserPanel('lab_type', 'funds.LabType'), FieldRowPanel([ FieldPanel('lab_link'), FieldPanel('link_text'), ]), ], heading=_('Link for lab application')), StreamFieldPanel('body'), InlinePanel('related_pages', label=_('Related pages')), ] def get_context(self, request): context = super().get_context(request) context['rfps'] = self.get_children().live().public() return context can_open = True @property def is_open(self): try: return bool(self.lab_type.specific.open_round) except AttributeError: return bool(self.lab_link) def clean(self): if self.lab_type and self.lab_link: raise ValidationError({ 'lab_type': _('Cannot link to both a Lab page and external link'), 'lab_link': _('Cannot link to both a Lab page and external link'), }) if not self.lab_type and not self.lab_link: raise ValidationError({ 'lab_type': _('Please provide a way for applicants to apply'), 'lab_link': _('Please provide a way for applicants to apply'), }) if self.lab_type and self.link_text: raise ValidationError({ 'link_text': _('Cannot customise the text for internal lab pages, leave blank'), }) if self.lab_link and not self.link_text: raise ValidationError({ 'link_text': _('Please provide some text for the link button'), })
class StreamFieldEntryPage(EntryPage): streamfield = StreamField(MyStreamBlock(), blank=True) content_panels = EntryPage.content_panels + [ StreamFieldPanel('streamfield') ]
class AboutPage(AbstractEmailForm): max_count = 1 page_heading = models.CharField(max_length=100, blank=False, null=True) header_image = models.ForeignKey("wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+") brand_name = models.CharField(max_length=100, blank=False, null=True) parent_company = models.CharField(max_length=100, blank=False, null=True) company_details = RichTextField(blank=False, null=True) team_heading = models.CharField(max_length=40, blank=True, null=True) team_cards = StreamField( [("emp_cards", EmployeeCardBlock(label="Employee"))], null=True, blank=True) form_heading = models.CharField(max_length=40, blank=True, null=True) form_success_text = models.CharField( max_length=200, blank=False, default= 'Thank you for getting in touch! We will reply to you as soon as possible.' ) content_panels = AbstractEmailForm.content_panels + [ MultiFieldPanel([ FieldPanel("page_heading"), ImageChooserPanel("header_image"), FieldPanel("brand_name"), FieldPanel("parent_company"), FieldPanel("company_details"), ], heading="Company Summary"), MultiFieldPanel( [FieldPanel("team_heading"), StreamFieldPanel("team_cards")], heading="Team Details"), MultiFieldPanel([ FieldPanel('form_heading'), InlinePanel('form_fields', label='Form Fields'), FieldPanel('form_success_text'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('from_address', classname='col6'), FieldPanel('to_address', classname='col6'), ]), FieldPanel("subject"), ], heading="Email Settings") ], heading="Contact Form") ] def get_context(self, request): context = super().get_context(request) context["addresses"] = BusinessAddress.objects.all() context["numbers"] = BusinessContact.objects.all() return context def save(self, *args, **kwargs): key = make_template_fragment_key("about") cache.delete(key) return super().save(*args, **kwargs)
class LeftSidebarPage(MenuPage): body = StreamField(MyStreamBlock(), blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ]
class FullWidthPage(MenuPage): body = StreamField(MyStreamBlock(), blank=True) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ]
class MoloSurveyPage(TranslatablePageMixinNotRoutable, surveys_models.AbstractSurvey): parent_page_types = [ 'surveys.SurveysIndexPage', 'core.SectionPage', 'core.ArticlePage' ] subpage_types = [] language = models.ForeignKey( 'core.SiteLanguage', blank=True, null=True, on_delete=models.SET_NULL, ) translated_pages = models.ManyToManyField("self", blank=True) form_builder = SurveysFormBuilder base_form_class = MoloSurveyForm introduction = TextField(blank=True) homepage_introduction = TextField(blank=True) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') description = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('paragraph', MarkDownBlock()), ('image', ImageChooserBlock()), ('list', blocks.ListBlock(blocks.CharBlock(label="Item"))), ('numbered_list', blocks.ListBlock(blocks.CharBlock(label="Item"))), ('page', blocks.PageChooserBlock()), ], null=True, blank=True) thank_you_text = TextField(blank=True) submit_text = TextField(blank=True) homepage_button_text = TextField(blank=True) allow_anonymous_submissions = BooleanField( default=False, help_text='Check this to allow users who are NOT logged in to complete' ' surveys.') allow_multiple_submissions_per_user = BooleanField( default=False, help_text='Check this to allow users to complete a survey more than' ' once.') show_results = BooleanField( default=False, help_text='Whether to show the survey results to the user after they' ' have submitted their answer(s).') show_results_as_percentage = BooleanField( default=False, help_text='Whether to show the survey results to the user after they' ' have submitted their answer(s) as a percentage or as' ' a number.') multi_step = BooleanField( default=False, verbose_name='Multi-step', help_text='Whether to display the survey questions to the user one at' ' a time, instead of all at once.') display_survey_directly = BooleanField( default=False, verbose_name='Display Question Directly', help_text='This is similar to polls, in which the questions are ' 'displayed directly on the page, instead of displaying ' 'a link to another page to complete the survey.') your_words_competition = BooleanField( default=False, verbose_name='Is YourWords Competition', help_text='This will display the correct template for yourwords') extra_style_hints = models.TextField( default='', null=True, blank=True, help_text=_("Styling options that can be applied to this page " "and all its descendants")) content_panels = surveys_models.AbstractSurvey.content_panels + [ ImageChooserPanel('image'), FieldPanel('introduction', classname='full'), FieldPanel('homepage_introduction', classname='full'), FieldPanel('homepage_button_text', classname='full'), StreamFieldPanel('description'), InlinePanel('survey_form_fields', label='Form fields'), FieldPanel('submit_text', classname='full'), FieldPanel('thank_you_text', classname='full'), InlinePanel('terms_and_conditions', label="Terms and Conditions"), ] settings_panels = surveys_models.AbstractSurvey.settings_panels + [ MultiFieldPanel([ FieldPanel('allow_anonymous_submissions'), FieldPanel('allow_multiple_submissions_per_user'), FieldPanel('show_results'), FieldPanel('show_results_as_percentage'), FieldPanel('multi_step'), FieldPanel('display_survey_directly'), FieldPanel('your_words_competition'), ], heading='Survey Settings'), MultiFieldPanel([ FieldRowPanel([FieldPanel('extra_style_hints')], classname="label-above") ], "Meta") ] def get_effective_extra_style_hints(self): return self.extra_style_hints def get_effective_image(self): return self.image def get_data_fields(self): data_fields = [ ('username', _('Username')), ('created_at', _('Submission Date')), ] data_fields += [(field.clean_name, field.admin_label) for field in self.get_form_fields()] return data_fields def get_submission_class(self): return MoloSurveySubmission def get_parent_section(self): return SectionPage.objects.all().ancestor_of(self).last() def process_form_submission(self, form): user = form.user if not form.user.is_anonymous() else None self.get_submission_class().objects.create(form_data=json.dumps( form.cleaned_data, cls=DjangoJSONEncoder), page=self, user=user) def has_user_submitted_survey(self, request, survey_page_id): if 'completed_surveys' not in request.session: request.session['completed_surveys'] = [] if request.user.pk is not None \ and self.get_submission_class().objects.filter( page=self, user__pk=request.user.pk ).exists() \ or survey_page_id in request.session['completed_surveys']: return True return False def set_survey_as_submitted_for_session(self, request): if 'completed_surveys' not in request.session: request.session['completed_surveys'] = [] request.session['completed_surveys'].append(self.id) request.session.modified = True def get_form(self, *args, **kwargs): prevent_required = kwargs.pop('prevent_required', False) form = super(MoloSurveyPage, self).get_form(*args, **kwargs) if prevent_required: for field in form.fields.values(): field.required = False return form def get_form_class_for_step(self, step): return self.form_builder(step.object_list).get_form_class() @property def session_key_data(self): return 'survey_data-{}'.format(self.pk) def load_data(self, request): return json.loads(request.session.get(self.session_key_data, '{}')) def save_data(self, request, data): request.session[self.session_key_data] = json.dumps( data, cls=DjangoJSONEncoder) def serve_questions(self, request): """ Implements a simple multi-step form. Stores each step in the session. When the last step is submitted correctly, the whole form is saved in the DB. """ context = self.get_context(request) # this will only return a page if there is a translation page = get_translation_for([context['page']], locale=request.LANGUAGE_CODE, site=request.site) if page: page = page[0] if not page.language.is_main_language: # if there is a translation, redirect to the translated page return redirect(page.url) survey_data = self.load_data(request) paginator = SkipLogicPaginator( self.get_form_fields(), request.POST, survey_data, ) is_last_step = False step_number = request.GET.get('p', 1) try: step = paginator.page(step_number) except PageNotAnInteger: step = paginator.page(1) except EmptyPage: step = paginator.page(paginator.num_pages) is_last_step = True if request.method == 'POST': # The first step will be submitted with step_number == 2, # so we need to get a from from previous step # Edge case - submission of the last step prev_step = step if is_last_step else paginator.page( int(step_number) - 1) # Create a form only for submitted step prev_form_class = self.get_form_class_for_step(prev_step) prev_form = prev_form_class( paginator.new_answers, page=self, user=request.user, ) if prev_form.is_valid(): # If data for step is valid, update the session survey_data.update(prev_form.cleaned_data) self.save_data(request, survey_data) if prev_step.has_next(): # Create a new form for a following step, if the following # step is present form_class = self.get_form_class_for_step(step) form = form_class(page=self, user=request.user) else: # If there is no more steps, create form for all fields data = self.load_data(request) form = self.get_form(data, page=self, user=request.user, prevent_required=True) if form.is_valid(): # Perform validation again for whole form. # After successful validation, save data into DB, # and remove from the session. self.set_survey_as_submitted_for_session(request) # We fill in the missing fields which were skipped with # a default value for question in self.get_form_fields(): if question.clean_name not in data: form.cleaned_data[question.clean_name] = SKIP self.process_form_submission(form) del request.session[self.session_key_data] return prev_step.success(self.slug) else: # If data for step is invalid # we will need to display form again with errors, # so restore previous state. step = prev_step form = prev_form else: # Create empty form for non-POST requests form_class = self.get_form_class_for_step(step) form = form_class(page=self, user=request.user) context['form'] = form context['fields_step'] = step context['is_intermediate_step'] = step.possibly_has_next() return render(request, self.template, context) @cached_property def has_page_breaks(self): return any(field.has_skipping or field.page_break for field in self.get_form_fields()) def serve(self, request, *args, **kwargs): if (not self.allow_multiple_submissions_per_user and self.has_user_submitted_survey(request, self.id)): if not (request.method == 'POST' and 'ajax' in request.POST): return render(request, self.template, self.get_context(request)) if ((self.has_page_breaks or self.multi_step) and not self.display_survey_directly): return self.serve_questions(request) if request.method == 'POST': form = self.get_form(request.POST, page=self, user=request.user) if form.is_valid(): # check if the post is made via ajax call if 'ajax' in request.POST and \ request.POST['ajax'] == 'True': # check if a submission exists for this question and user submission = self.get_submission_class().objects.filter( page=self, user__pk=request.user.pk) if submission.exists(): # currently for submissions via ajax calls # user should be able to update their submission submission.delete() self.set_survey_as_submitted_for_session(request) self.process_form_submission(form) # render the landing_page return redirect( reverse('molo.surveys:success', args=(self.slug, ))) return super(MoloSurveyPage, self).serve(request, *args, **kwargs)
class ArticlePage(Page): parent_page_types = [ "ArticleIndexPage", ] subpage_types = [] author = models.ForeignKey('AuthorPage', null=True, on_delete=models.SET_NULL, related_name='+') main_image = models.ForeignKey('images.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') date = models.DateField("Post date") intro = models.TextField( max_length=480, help_text= 'This will only appear in article previews, not with the full article. ' + md_format_help) body = StreamField([ ('text', blocks.RichTextBlock(features=DEFAULT_RICHTEXT_FEATURES)), ('captioned_image', CaptionedImageBlock()), ('embed', EmbedBlock(icon='media')), ('block_quote', QuoteBlock()), ('table', TableBlock(template='blog/table_block.html')), ('media', OptionsMediaBlock()), ('code', CodeBlock()), ]) notes = models.TextField( null=True, blank=True, help_text="This text will not appear on the page.") tags = ClusterTaggableManager(through=ArticlePageTag, blank=True) content_panels = Page.content_panels + [ PageChooserPanel('author', ), FieldPanel('date'), ImageChooserPanel('main_image'), InlinePanel('article_audiences', label='Audiences'), FieldPanel('tags'), FieldPanel('intro'), StreamFieldPanel('body'), FieldPanel('notes'), ] search_fields = Page.search_fields + [ index.SearchField('body'), # index.SearchField('tags__name'), ] class Meta: verbose_name = "Article" def __unicode__(self): return self.title def subject(self): # Find closest ancestor which is article index page return self.get_ancestors().type(ArticleIndexPage).last()
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 BlogPage(HeadlessPreviewMixin, Page): date = models.DateField("Post date") advert = models.ForeignKey( "home.Advert", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) cover = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) book_file = models.ForeignKey( "wagtaildocs.Document", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) featured_media = models.ForeignKey( "wagtailmedia.Media", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) author = models.ForeignKey( AuthorPage, null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) body = StreamField(StreamFieldBlock()) content_panels = Page.content_panels + [ FieldPanel("date"), ImageChooserPanel("cover"), StreamFieldPanel("body"), InlinePanel("related_links", label="Related links"), InlinePanel("authors", label="Authors"), FieldPanel("author"), SnippetChooserPanel("advert"), DocumentChooserPanel("book_file"), MediaChooserPanel("featured_media"), ] @property def copy(self): return self graphql_fields = [ GraphQLString("heading"), GraphQLString("date"), GraphQLStreamfield("body"), GraphQLCollection( GraphQLForeignKey, "related_links", "home.blogpagerelatedlink" ), GraphQLCollection(GraphQLString, "related_urls", source="related_links.url"), GraphQLCollection(GraphQLString, "authors", source="authors.person.name"), GraphQLSnippet("advert", "home.Advert"), GraphQLImage("cover"), GraphQLDocument("book_file"), GraphQLMedia("featured_media"), GraphQLForeignKey("copy", "home.BlogPage"), GraphQLPage("author"), ]
class ContentPage(Page): article_date = models.DateField("Article date (only for blog)", null=True, blank=True) article_image = models.ForeignKey( "cms.CustomImage", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) article_image_caption = models.TextField( "Article image caption (only for blog)", null=True, blank=True) article_teaser = models.TextField("Article teaser (only for blog)", null=True, blank=True) layout = models.CharField( max_length=2, choices=PageLayout.choices, default=PageLayout.CENTERED_MIDDLE, ) body = StreamField([ ("heading", blocks.CharBlock(classname="full title")), ("paragraph", blocks.RichTextBlock()), ("quote", QuoteBlock()), ("image", ImageChooserBlock()), ("two_columns", TwoColumnBlock()), ("centered_column", CenteredColumnBlock()), ("raw_html", blocks.RawHTMLBlock()), ("list_child_pages", blocks.PageChooserBlock(can_choose_root=False)), ]) content_panels = Page.content_panels + [ StreamFieldPanel("body"), FieldPanel("article_date"), FieldPanel("article_teaser"), FieldPanel("article_image_caption"), FieldPanel("layout"), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ImageChooserPanel("article_image", ), ] # Export fields over the API api_fields = [ APIField("body"), APIField("article_date"), APIField("article_teaser"), APIField("article_image"), APIField("article_image_caption"), APIField("layout"), APIField( "article_image_thumbnail", serializer=ImageRenditionField("fill-1200x600", source="article_image"), ), ]
class FlatPage(Page): headers = StreamField([ ('h_hero', _H_HeroBlock(icon='image')), # ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code')) ]) price = models.DecimalField(verbose_name="Preis", max_digits=11, decimal_places=2, null=True, blank=False) br_choices = ( ('RE', 'Miete'), ('BU', 'Kauf'), ) buy_or_rent = models.CharField(verbose_name="Mieten oder Kaufen?", max_length=4, choices=br_choices, default='RE') lead = models.CharField(null=True, blank=True, max_length=512) available = models.BooleanField(verbose_name="Verfügbar") # ground_plan = ImageChooserBlock( # required=True, blank=False, help_text="Raumplan") sections = StreamField( [('s_contentcenter', _S_ContentCenter(icon='fa-info')), ('s_contentright', _S_ContentRight(icon='fa-info')), ('s_contentleft', _S_ContentLeft(icon='fa-info'))], null=True, blank=True) gallery = StreamField([('g_gallery', _G_GalleryBlock(icon='fa-info'))], null=True, blank=True) ground_plan = StreamField( [('p_groundplan', _P_GroundPlanBlock(icon='fa-info'))], null=True, blank=True, verbose_name="Grundrissplan") main_content_panels = [ StreamFieldPanel('headers'), FieldPanel('price'), FieldPanel('buy_or_rent'), FieldPanel('lead'), FieldPanel('available'), # FieldPanel('ground_plan'), StreamFieldPanel('ground_plan'), StreamFieldPanel('sections'), StreamFieldPanel('gallery'), # StreamFieldPanel('footers') ] edit_handler = TabbedInterface([ ObjectList(Page.content_panels + main_content_panels, heading='Main'), ObjectList(Page.promote_panels, heading='Settings', classname="settings") ]) preview_modes = []
class ContactFormPage(AbstractEmailForm): """ Contact form page that uses wagtail fields. Note that fields are locked to first_names, last_names, email, phone & message. Users can still build custom forms by using the FormPage model. """ image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') body = StreamField(BaseStreamBlock()) thank_you_text = RichTextField(blank=True) form_builder = CaptchaFormBuilder content_panels = AbstractEmailForm.content_panels + [ ImageChooserPanel('image'), StreamFieldPanel('body'), # InlinePanel('form_fields', label="Form fields"), FieldPanel('thank_you_text', classname="full"), MultiFieldPanel([ FieldRowPanel([ FieldPanel('from_address', classname="col6"), FieldPanel('to_address', classname="col6"), ]), ], "Email"), ] def get_submission_class(self): return ContactMessage def process_bilingual_form_data(self, form): """ Returns a cleaned_data object with all english keys. wagtail-modeltranslation is delivering a cleaned_data object with translated-keys. """ keys = list(form.cleaned_data.keys()) # Original keys fields = [ ['name', keys[0]], ['email', keys[1]], ['phone', keys[2]], ['subject', keys[3]], ['message', keys[4]], ['captcha', keys[5]], ] res = [] for f in fields: try: res.append( form.cleaned_data[f[0]]) # Attempt english retrieval first except KeyError: res.append(form.cleaned_data[f[1]]) # Translated language return {f[0]: r for f, r in zip(fields, res)} # Return dict with english keys def process_form_submission(self, form): # import pdb; pdb.set_trace() cleaned_data = self.process_bilingual_form_data(form) try: contact = Contact.objects.get(email=cleaned_data['email']) except Contact.DoesNotExist: contact = Contact.objects.create( names=cleaned_data['name'], email=cleaned_data['email'], phone=cleaned_data['phone'], ) submission = self.get_submission_class().objects.create( form_data=json.dumps(cleaned_data, cls=DjangoJSONEncoder), page=self, contact=contact, ) if self.to_address: self.send_mail(form) return submission def send_mail(self, form): cleaned_data = self.process_bilingual_form_data(form) addresses = [x.strip() for x in self.to_address.split(',')] content = [] for field in form: value = field.value() if isinstance(value, list): value = ', '.join(value) content.append('{}: {}'.format(field.label, value)) content = '\n'.join(content) subject = "Message from: %(name)s <%(email)s>" % { 'name': cleaned_data['name'], 'email': cleaned_data['email'], } send_mail( subject, content, addresses, self.from_address, ) def save(self, *args, **kwargs): super().save() field_list = list(self.form_fields.all().values_list('label', flat=True)) if CONTACT_CONST['name']['label'] not in field_list: ContactCaptchaFormField.objects.create( label=CONTACT_CONST['name']['label'], label_es=CONTACT_CONST['name']['label_es'], placeholder=CONTACT_CONST['name']['placeholder'], placeholder_es=CONTACT_CONST['name']['placeholder_es'], field_type='singleline', additional_attrs=CONTACT_CONST['name']['attrs'], required=True, page=self) if CONTACT_CONST['email']['label'] not in field_list: ContactCaptchaFormField.objects.create( label=CONTACT_CONST['email']['label'], label_es=CONTACT_CONST['email']['label_es'], placeholder=CONTACT_CONST['email']['placeholder'], placeholder_es=CONTACT_CONST['email']['placeholder_es'], additional_attrs=CONTACT_CONST['email']['attrs'], field_type='email', required=True, page=self, ) if CONTACT_CONST['phone']['label'] not in field_list: ContactCaptchaFormField.objects.create( label=CONTACT_CONST['phone']['label'], label_es=CONTACT_CONST['phone']['label_es'], placeholder=CONTACT_CONST['phone']['placeholder'], placeholder_es=CONTACT_CONST['phone']['placeholder_es'], additional_attrs=CONTACT_CONST['phone']['attrs'], field_type='singleline', required=False, page=self, ) if CONTACT_CONST['sub']['label'] not in field_list: ContactCaptchaFormField.objects.create( label=CONTACT_CONST['sub']['label'], label_es=CONTACT_CONST['sub']['label_es'], placeholder=CONTACT_CONST['sub']['placeholder'], placeholder_es=CONTACT_CONST['sub']['placeholder_es'], additional_attrs=CONTACT_CONST['sub']['attrs'], field_type='singleline', required=True, page=self, ) if CONTACT_CONST['msg']['label'] not in field_list: ContactCaptchaFormField.objects.create( label=CONTACT_CONST['msg']['label'], label_es=CONTACT_CONST['msg']['label_es'], placeholder=CONTACT_CONST['msg']['placeholder'], placeholder_es=CONTACT_CONST['msg']['placeholder_es'], additional_attrs=CONTACT_CONST['msg']['attrs'], field_type='multiline', required=True, page=self, ) if 'recaptchav2' not in list(self.form_fields.all().values_list( 'field_type', flat=True)): ContactCaptchaFormField.objects.create( label='captcha', label_es='captcha', field_type='recaptchav2', required=True, page=self, ) super().save(*args, **kwargs)
class ArticlePage(Page): preview_modes = [('', 'English'), ('Spanish', 'Spanish')] def get_sitemap_urls(self, request = None): pages = [] for language in translation.trans_real.settings.LANGUAGES: translation.activate(language[0]) if self.is_published: pages.append(super().get_sitemap_urls()[0]) return pages def serve_preview(self, request, mode_name): if (mode_name == 'Spanish'): translation.activate("es") print(translation.get_language()) return super().serve(request) def serve(self, request): if ( not self.is_published ): raise Http404("Not implemented yet!", translation.get_language()) return super().serve(request) translated_title = TranslatedField() title_en, title_es = translated_title.init( models.CharField, ('title_en', 'title_es'), max_length=255, blank=True) def get_title(): return str(translated_title) is_published = TranslatedField() is_published_en, is_published_es = is_published.init( models.BooleanField, ('is_published_en', 'is_published_es'), blank=True, default=False) sub_title = TranslatedField() sub_title_en, sub_title_es = sub_title.init( models.CharField, ('sub_title_en', 'sub_title_es'), max_length=255, blank=True) blurb = TranslatedField() blurb_en, blurb_es = blurb.init( RichTextField, ('blurb_en', 'blurb_es'), blank=True) intro = TranslatedField() intro_en, intro_es = intro.init( RichTextField, ('intro_en', 'intro_es'), blank=True) colour = ColorField(default='#6c6c1c') cover_image = models.ForeignKey( CustomImage, null=True, # blank=True, on_delete=models.SET_NULL, related_name='+' ) translated_seo_title = TranslatedField() seo_title_en, seo_title_es = translated_seo_title.init( models.TextField, ('seo_title_en', 'seo_title_es'), blank=True) translated_seo_description = TranslatedField() seo_description_en, seo_description_es = translated_seo_description.init( models.TextField, ('seo_description_en', 'seo_description_es'), blank=True) intro_extras = StreamField([ ('code_snippet', CodeSnippetBlock("code_snippets.CodeSnippet")) ], blank=True) content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel("seo_title_en"), FieldPanel("seo_description_en"), FieldPanel("blurb_en"), FieldPanel("is_published_en"), ], heading="Promote"), MultiFieldPanel([ FieldPanel('title_en'), FieldPanel('sub_title_en'), FieldPanel('colour'), ImageChooserPanel('cover_image'), ], heading="Cover"), FieldPanel('intro_en'), InlinePanel('things_to_take_en', label="Things to take"), StreamFieldPanel('intro_extras'), InlinePanel('sections_en', label="Section") ] es_content_panels = [ MultiFieldPanel([ FieldPanel("seo_title_es"), FieldPanel("seo_description_es"), FieldPanel("blurb_es"), FieldPanel("is_published_es"), ], heading="Promote"), FieldPanel('title_es'), FieldPanel('sub_title_es'), FieldPanel('intro_es'), InlinePanel('things_to_take_es'), InlinePanel('sections_es', label="Sección"), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='EN Content'), ObjectList(es_content_panels, heading='ES content'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ]) search_fields = Page.search_fields + [ index.SearchField('title_en'), index.SearchField('title_es'), index.SearchField('sub_title_en'), index.SearchField('sub_title_es'), ] def __str__(self): return self.title sections = TranslatedField() sections.init(None, ('sections_en', 'sections_es')) things_to_take = TranslatedField() things_to_take.init(None, ('things_to_take_en', 'things_to_take_es')) @property def related_names(self): return self.sub_title.split(", ") @property def things_to_take_en(self): tips = self.things_to_take_en.all() return tips @property def sections_en(self): article = self.sections_en.all() return article @property def things_to_take_es(self): tips = self.things_to_take_es.all() return tips @property def sections_es(self): article = self.sections_es.all() return article
class BaseResumePage(MetadataMixin, Page): page_ptr = models.OneToOneField( Page, parent_link=True, related_name="+", on_delete=models.CASCADE ) is_creatable = False font = models.CharField(max_length=100, null=True, blank=True) background_color = models.CharField(max_length=100, null=True, blank=True) full_name = models.CharField(max_length=100, null=True, blank=True) role = models.CharField(max_length=100, null=True, blank=True) about = MarkdownField(max_length=2500, null=True, blank=True) photo = models.ForeignKey( Image, null=True, blank=True, on_delete=models.SET_NULL, related_name="+" ) social_links = fields.StreamField( [ ( "social_link", blocks.StructBlock( [ ("text", blocks.TextBlock()), ("url", blocks.URLBlock()), ("logo", ImageChooserBlock()), ], icon="group", ), ), ], null=True, blank=True, ) resume = fields.StreamField( [ ("work_experience", WorkExperienceBlock()), ("contributions", ContributionsBlock()), ("writing", WritingsBlock()), ("education", EducationBlock()), ], null=True, blank=True, ) content_panels = Page.content_panels + [ MultiFieldPanel( [ FieldPanel("font"), FieldPanel("background_color"), ], heading="Customization", ), MultiFieldPanel( [ FieldPanel("full_name"), FieldPanel("role"), MarkdownPanel("about"), ImageChooserPanel("photo"), StreamFieldPanel("social_links"), ], heading="Personal details", ), StreamFieldPanel("resume"), ] def get_template(self, request): # pylint: disable=arguments-differ return "wagtail_resume/resume_page.html" def get_meta_title(self): return self.full_name def get_meta_description(self): about = Truncator(self.about).words(35) return f"{self.full_name} - {self.role}. {about}" def get_meta_image(self): return self.photo def get_meta_url(self): return self.get_full_url() def get_meta_twitter_card_type(self): return self.photo
class NewsletterPage(Page): body = StreamField( [ ('advertisement_block', AdvertisementBlock()), ('content_block', ContentBlock()), ('featured_content_block', FeaturedContentBlock()), ('social_block', SocialBlock()), ('text_block', TextBlock()), ], blank=True, ) html_file = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) # Reference field for the Drupal-Wagtail migrator. Can be removed after. drupal_node_id = models.IntegerField(blank=True, null=True) content_panels = Page.content_panels + [ MultiFieldPanel( [ StreamFieldPanel('body'), ], heading='Body', classname='collapsible collapsed', ), MultiFieldPanel( [ DocumentChooserPanel('html_file'), ], heading='HTML File', classname='collapsible collapsed', ), ] def html_string(self): def in_line_tracking(href, title): tracking = f'utm_source=cigi_newsletter&utm_medium=email&utm_campaign={slugify(title)}' if '?' in href: return f'{href}&{tracking}' else: return f'{href}?{tracking}' text_soup = BeautifulSoup( render_to_string('newsletters/newsletter_html.html', { 'self': self, 'page': self, 'is_html_string': True }), 'html.parser') for link in text_soup.findAll('a'): try: link['href'] = in_line_tracking(link['href'], self.title) except KeyError: pass return str(text_soup) parent_page_types = ['newsletters.NewsletterListPage'] subpage_types = [] templates = 'newsletters/newsletter_page.html' class Meta: verbose_name = 'Newsletter' verbose_name_plural = 'Newsletters'
class ProjectPage(Page): title_de = models.CharField(max_length=255, blank=True) subtitle = models.CharField(max_length=255, blank=True) subtitle_de = models.CharField(max_length=255, blank=True) teaser = RichTextField(blank=True) teaser_de = RichTextField(blank=True) BODY_BLOCKS = [ ('richtext', RichTextBlock()), ('html', RawHTMLBlock()), ('image', ImageChooserBlock()), ] body = StreamField(BODY_BLOCKS, blank=True) body_de = StreamField(BODY_BLOCKS, blank=True) collaboration = RichTextField(blank=True) collaboration_de = RichTextField(blank=True) publications = RichTextField(blank=True) publications_de = RichTextField(blank=True) references = RichTextField(blank=True) references_de = RichTextField(blank=True) start_date = models.DateField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) STATUS_IN_PROGRESS = 'IN_PROGRESS' STATUS_COMPLETED = 'COMPLETED' STATUS_CHOICES = ((STATUS_IN_PROGRESS, _('in progress')), (STATUS_COMPLETED, _('completed'))) status = models.CharField(max_length=32, choices=STATUS_CHOICES, default=STATUS_IN_PROGRESS) trans_title = TranslatedTextField('title') trans_subtitle = TranslatedTextField('subtitle') trans_teaser = TranslatedTextField('teaser') trans_body = TranslatedStreamField('body') trans_collaboration = TranslatedTextField('collaboration') trans_publications = TranslatedTextField('publications') trans_references = TranslatedTextField('references') search_fields = Page.search_fields + [ index.SearchField('title_de'), index.SearchField('subtitle'), index.SearchField('subtitle_de'), index.SearchField('teaser'), index.SearchField('teaser_de'), index.SearchField('body'), index.SearchField('body_de'), ] content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('subtitle', classname="full title"), FieldPanel('teaser', classname='full'), StreamFieldPanel('body', classname='full'), FieldPanel('collaboration', classname='full'), FieldPanel('publications', classname='full'), FieldPanel('references', classname='full'), ] content_de_panels = [ FieldPanel('title_de', classname="full title"), FieldPanel('subtitle_de', classname="full title"), FieldPanel('teaser_de', classname='full'), StreamFieldPanel('body_de', classname='full'), FieldPanel('collaboration_de', classname='full'), FieldPanel('publications_de', classname='full'), FieldPanel('references_de', classname='full'), ] misc_panels = [ FieldPanel('status'), FieldPanel('start_date'), FieldPanel('end_date'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(content_de_panels, heading='Content DE'), ObjectList(misc_panels, heading='Misc'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname='settings'), ]) parent_page_types = ['projects.ProjectIndexPage'] @property def color(self): return self.get_parent().specific.color
class LandingPage( BasePost ): # a special type of post page (I intend to use it for game devlog) Page = TranslatablePage project_overview = models.BooleanField(default=False) banner = models.ForeignKey('wagtailimages.Image', on_delete=models.SET_NULL, related_name='+', blank=True, null=True) title_color = ColorField(default='#FFFFFF') tilable_banner = models.BooleanField(default=False) intro = models.CharField( max_length=255, blank=True, ) body = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('code', blocks.TextBlock()), ('code_output', blocks.TextBlock()), ('image', ImageChooserBlock(icon="image")), ('two_columns', TwoColumnBlock()), ('embedded_video', EmbedBlock(icon="media")), ('custom_html', blocks.TextBlock(icon='plus-inverse')), ], null=True, blank=True) content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('banner'), FieldPanel('tilable_banner'), FieldPanel('title_color') ], heading='Banner'), FieldPanel('categories', widget=forms.CheckboxSelectMultiple), FieldPanel('intro'), StreamFieldPanel('body'), PageChooserPanel( 'related_page1', ['blog.PostPage', 'blog.LandingPage', 'blog.LandingPost']), PageChooserPanel( 'related_page2', ['blog.PostPage', 'blog.LandingPage', 'blog.LandingPost']), # InlinePanel('related_links', label="Related Links"), ] settings_panels = Page.settings_panels + [ FieldPanel('date'), FieldPanel('project_overview'), ImageChooserPanel('thumbnail'), MultiFieldPanel([ FieldPanel('is_series'), FieldPanel('series_name'), FieldPanel('series_id') ], heading='SeriesSetting', classname="collapsible collapsed"), ] def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) return context
class EnforcementActionPage(AbstractFilterPage): public_enforcement_action = models.CharField(max_length=150, blank=True) initial_filing_date = models.DateField(null=True, blank=True) settled_or_contested_at_filing = models.CharField(max_length=10, choices=[('Settled', 'Settled'), ('Contested', 'Contested')], blank=True) court = models.CharField(default='', max_length=150, blank=True) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('expandable', organisms.Expandable()), ('expandable_group', organisms.ExpandableGroup()), ('notification', molecules.Notification()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = [StreamFieldPanel('header'), StreamFieldPanel('content')] metadata_panels = [ FieldPanel('public_enforcement_action'), FieldPanel('initial_filing_date'), InlinePanel('defendant_types', label='Defendant/Respondent Type'), InlinePanel('categories', label="Forum", min_num=1, max_num=2), FieldPanel('court'), InlinePanel('docket_numbers', label="Docket Number", min_num=1), FieldPanel('settled_or_contested_at_filing'), InlinePanel('statuses', label="Status", min_num=1), InlinePanel('products', label="Products"), InlinePanel('at_risk_groups', label="At Risk Groups"), InlinePanel('statutes', label="Statutes/Regulations"), InlinePanel('enforcement_dispositions', label='Final Disposition'), ] settings_panels = [ MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'), MultiFieldPanel([ FieldPanel('preview_title'), FieldPanel('preview_subheading'), FieldPanel('preview_description'), FieldPanel('secondary_link_url'), FieldPanel('secondary_link_text'), ImageChooserPanel('preview_image'), ], heading='Page Preview Fields', classname='collapsible'), FieldPanel('authors', 'Authors'), MultiFieldPanel([ FieldPanel('date_published'), FieldPanel('comments_close_by'), ], 'Relevant Dates', classname='collapsible'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), FieldPanel('language', 'Language'), MultiFieldPanel(CFGOVPage.archive_panels, 'Archive'), ] edit_handler = TabbedInterface([ ObjectList(AbstractFilterPage.content_panels + content_panels, heading='General Content'), ObjectList(metadata_panels, heading='Metadata'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(settings_panels, heading='Configuration') ]) template = 'enforcement-action/index.html' objects = PageManager() search_fields = AbstractFilterPage.search_fields + [ index.SearchField('content') ] @classmethod def all_actions(cls): # Return the collection of all Enforcement Action Pages. # Exclude any pages in the Trash or otherwise not a child of the # EnforcementActionsFilterPage. try: # TODO: find a less hacky way to get only the pages in the # correct part of the page tree pg_id = 1327 parent_page = Page.objects.get(id=pg_id) query = cls.objects.child_of(parent_page) except (Page.DoesNotExist): query = cls.objects query = query.filter(initial_filing_date__isnull=False) query = query.live().order_by('-initial_filing_date') return query def get_context(self, request): context = super(EnforcementActionPage, self).get_context(request) dispositions = self.enforcement_dispositions.all() context.update({ 'total_consumer_relief': sum(disp.final_order_consumer_redress + disp.final_order_other_consumer_relief for disp in dispositions), 'total_cmp': sum(disp.final_order_civil_money_penalty for disp in dispositions), 'defendant_types': [ d.get_defendant_type_display() for d in self.defendant_types.all() ], 'statutes': [s.statute for s in self.statutes.all()], 'products': [p.product for p in self.products.all()], 'at_risk_groups': [g.at_risk_group for g in self.at_risk_groups.all()] }) return context
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=r'^(\-?\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 UseCasePage(Page): category = models.CharField(max_length=2, choices=CATEGORY_CHOICES) image = models.ForeignKey( 'a4_candy_cms_images.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name="Use Case Header Image", help_text="The Image that is shown on the use case item page " + "and the use case index page") title_de = models.CharField(max_length=250, blank=True, verbose_name="German Title") title_en = models.CharField(max_length=250, blank=True, verbose_name="English Title") body_streamfield_de = fields.StreamField( [('paragraph', blocks.RichTextBlock()), ('html', blocks.RawHTMLBlock()), ('examples', ExampleBlock())], blank=True) body_streamfield_en = fields.StreamField( [('paragraph', blocks.RichTextBlock()), ('html', blocks.RawHTMLBlock()), ('examples', ExampleBlock())], blank=True) subtitle = TranslatedField('title_de', 'title_en') teaser = TranslatedField('teaser_de', 'teaser_en') body = TranslatedField('body_streamfield_de', 'body_streamfield_en') def get_context(self, request): category = self.category if category: try: use_cases = UseCasePage.objects\ .filter(category=category)\ .exclude(id=self.id) except ValueError: use_cases = [] context = super().get_context(request) context['other_use_cases'] = use_cases return context en_content_panels = [ FieldPanel('title_en'), StreamFieldPanel('body_streamfield_en') ] de_content_panels = [ FieldPanel('title_de'), StreamFieldPanel('body_streamfield_de') ] common_panels = [ FieldPanel('title'), ImageChooserPanel('image'), FieldPanel('slug'), FieldPanel('category') ] edit_handler = TabbedInterface([ ObjectList(common_panels, heading='Common'), ObjectList(en_content_panels, heading='English'), ObjectList(de_content_panels, heading='German') ]) subpage_types = []
class TableBlockStreamPage(Page): table = StreamField([('table', TableBlock())]) content_panels = [StreamFieldPanel('table')]
class CybergatewayHomePage(Page): """ The Cybergateway themed template Page """ # Hero section of HomePage site_logo = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Site Logo Image' ) site_link = models.CharField( max_length=255, default="#", help_text='Give a site redirect link', ) site_text = models.CharField( max_length=50, default="#", help_text='Give a Site Name', ) site_header = models.CharField( max_length=70, default="#", help_text='Give a Site Header Name', ) site_link1 = models.CharField( max_length=70, default="#", help_text='Give a Site Nav Link [1]', ) site_link_text1 = models.CharField( max_length=70, help_text='Give a Site Nav Link Text [1]', ) site_link2 = models.CharField( max_length=70, default='#', help_text='Give a Site Nav Link [2]', ) site_link_text2 = models.CharField( max_length=70, help_text='Give a Site Nav Link Text [2]', ) site_link3 = models.CharField( max_length=70, default="#", help_text='Give a Site Nav Link [3]', ) site_link_text3 = models.CharField( max_length=70, help_text='Give a Site Nav Link Text [3]', ) contact = StreamField( BaseStreamBlock(), verbose_name="Contact Info Block", blank=True, null=True) footer = StreamField( BaseStreamBlock(), verbose_name="Footer Content Block", blank=True, null=True) boolean_choices = ( ("yes", "Yes"), ("no", "No") ) show_navbar = models.CharField( choices=boolean_choices, max_length=5, help_text="Choose yes if you want to display the navbar on home page and no if you don't want to.", default="yes") show_nav_extra = models.CharField( choices=boolean_choices, max_length=5, help_text="Choose yes if you want the secondary navbar to show on home page or no if you don't want to", default="yes") show_footer = models.CharField( choices=boolean_choices, max_length=5, help_text="Choose yes if you want the Footer to show on home page or no if you don't want to", default="yes") content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('site_logo'), FieldPanel('site_link'), FieldPanel('site_text'), FieldPanel('site_header'), FieldPanel('site_link1'), FieldPanel('site_link_text1'), FieldPanel('site_link2'), FieldPanel('site_link_text2'), FieldPanel('site_link3'), FieldPanel('site_link_text3'), ], heading="Navbar Section"), InlinePanel("row", label="row"), StreamFieldPanel('contact'), StreamFieldPanel('footer'), ] customization_panels = [ FieldPanel('show_navbar'), FieldPanel('show_nav_extra'), FieldPanel('show_footer'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(customization_panels, heading='Customization'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ]) def __str__(self): return self.title