class HomePage(Page): intro = RichTextField(blank=True) content_panels = Page.content_panels + [ FieldPanel('intro', classname="full") ]
class AdvancedChartPage(InstructionsMixin, EChartOptionsMixin, D3OptionsMixin, PlotlyOptionsMixin, FallbackImageMixin, RoutablePageMixin, Page): """ A code based chart page for advanced users """ parent_page_types = [VisualisationsPage] subpage_types = [] subtitle = models.TextField( blank=True, null=True, help_text="Optional: subtitle to appear underneath the chart title.") html = AceEditorField(options={'mode': 'html'}, blank=True, default='{% load wagtailcore_tags %}') javascript = AceEditorField(options={'mode': 'javascript'}, blank=True, default='"use strict";') css = AceEditorField(options={'mode': 'css'}, blank=True, default='/* CSS goes here */') caption = RichTextField( null=True, blank=True, help_text='Optional: caption text and link(s) for the chart', features=INSTRUCTIONS_RICHTEXT_FEATURES + SIMPLE_RICHTEXT_FEATURES) content_panels = Page.content_panels + [ FieldPanel('subtitle'), PlotlyOptionsPanel(), D3OptionsPanel(), EChartOptionsPanel(), FieldPanel('html', classname='collapsible'), FieldPanel('javascript', classname='collapsible'), # FieldPanel('css', classname='collapsible'), TODO: add CSS support - may work best in an iFrame FallbackImagePanel(), InstructionsPanel(), FieldPanel('caption'), ] @cached_property def parent(self): return self.get_parent().specific @cached_property def instructions_text(self): if self.instructions: return self.instructions return '' def get_sitemap_urls(self, request): return [] @route(r'^$') def chart(self, request): if request.user.is_authenticated: return super().serve(request) raise Http404() class Meta: verbose_name = 'Advanced Chart Page'
class EventIndexPage(Page): UPCOMING = 'gte' PAST = 'lt' EVENT_LIST_CHOICES = ( (UPCOMING, 'Upcoming Events'), (PAST, 'Past Events'), ) show_events = models.CharField(max_length=3, choices=EVENT_LIST_CHOICES, default=UPCOMING) intro = RichTextField(blank=True) intro_homepage = models.TextField( help_text= 'Describe the events index page. This is displayed on the homepage.', blank=True) parent_page_types = ['home.HomePage'] subpage_types = ['events.EventPage'] search_fields = Page.search_fields + [index.SearchField('intro')] content_panels = Page.content_panels + [ FieldPanel('intro'), FieldPanel('intro_homepage') ] @property def events(self): events = EventPage.objects.child_of(self).live() kwargs = { '{0}__{1}'.format('date_from', self.show_events): date.today(), } events = events.filter(**kwargs) events = events.order_by('date_from') return events @property def past_events(self): events = EventPage.objects.child_of(self).live().filter( date_from__lt=date.today()) events = events.order_by('-date_from') events = events.all() # [:10] return events class Meta: verbose_name = "Event List" @property def past_event(self): event = EventPage.objects.child_of(self).live().filter( date_from__lt=date.today()) return event def get_context(self, request): past_events = self.past_events # Pagination page = request.GET.get('page', 1) paginator = Paginator(past_events, 20) try: past_events = paginator.page(page) except PageNotAnInteger: past_events = paginator.page(1) except EmptyPage: past_events = paginator.page(paginator.num_pages) context = super(EventIndexPage, self).get_context(request) # Hide upcoming events if past page 1 if int(page) == 1: context['events'] = self.events context['past_events'] = past_events return context
class DisclaimerPage(Page): template = "disclaimer/disclaimer_page.html" summary = RichTextField(default='Input here...') content_panels = Page.content_panels + [ FieldPanel("summary"), ]
class ParticipatePage2(PrimaryPage): parent_page_types = ['Homepage'] template = 'wagtailpages/static/participate_page2.html' ctaHero = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='primary_hero_participate', verbose_name='Primary Hero Image', ) ctaHeroHeader = models.TextField( blank=True, ) ctaHeroSubhead = RichTextField( features=[ 'bold', 'italic', 'link', ], blank=True, ) ctaCommitment = models.TextField( blank=True, ) ctaButtonTitle = models.CharField( verbose_name='Button Text', max_length=250, blank=True, ) ctaButtonURL = models.TextField( verbose_name='Button URL', blank=True, ) ctaHero2 = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='primary_hero_participate2', verbose_name='Primary Hero Image', ) ctaHeroHeader2 = models.TextField( blank=True, ) ctaHeroSubhead2 = RichTextField( features=[ 'bold', 'italic', 'link', ], blank=True, ) ctaCommitment2 = models.TextField( blank=True, ) ctaButtonTitle2 = models.CharField( verbose_name='Button Text', max_length=250, blank=True, ) ctaButtonURL2 = models.TextField( verbose_name='Button URL', blank=True, ) ctaHero3 = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='primary_hero_participate3', verbose_name='Primary Hero Image', ) ctaHeroHeader3 = models.TextField( blank=True, ) ctaHeroSubhead3 = RichTextField( features=[ 'bold', 'italic', 'link', ], blank=True, ) ctaCommitment3 = models.TextField( blank=True, ) ctaFacebook3 = models.TextField( blank=True, ) ctaTwitter3 = models.TextField( blank=True, ) ctaEmailShareBody3 = models.TextField( blank=True, ) ctaEmailShareSubject3 = models.TextField( blank=True, ) h2 = models.TextField( blank=True, ) h2Subheader = models.TextField( blank=True, verbose_name='H2 Subheader', ) content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('ctaHero'), FieldPanel('ctaHeroHeader'), FieldPanel('ctaHeroSubhead'), FieldPanel('ctaCommitment'), FieldPanel('ctaButtonTitle'), FieldPanel('ctaButtonURL'), ], heading="Primary CTA"), FieldPanel('h2'), FieldPanel('h2Subheader'), InlinePanel('featured_highlights', label='Highlights Group 1', max_num=3), MultiFieldPanel([ ImageChooserPanel('ctaHero2'), FieldPanel('ctaHeroHeader2'), FieldPanel('ctaHeroSubhead2'), FieldPanel('ctaCommitment2'), FieldPanel('ctaButtonTitle2'), FieldPanel('ctaButtonURL2'), ], heading="CTA 2"), InlinePanel('featured_highlights2', label='Highlights Group 2', max_num=6), MultiFieldPanel([ ImageChooserPanel('ctaHero3'), FieldPanel('ctaHeroHeader3'), FieldPanel('ctaHeroSubhead3'), FieldPanel('ctaCommitment3'), FieldPanel('ctaFacebook3'), FieldPanel('ctaTwitter3'), FieldPanel('ctaEmailShareSubject3'), FieldPanel('ctaEmailShareBody3'), ], heading="CTA 3"), InlinePanel('cta4', label='CTA Group 4', max_num=3), ]
class Highlight(TranslatableMixin, SortableMixin): """ An data type to highlight things like pulse projects, custom pages, etc Especially on the homepage under "Get Involved" """ title = models.CharField( max_length=300, help_text='Title of the higlight', ) description = models.TextField( max_length=5000, help_text='Description of the higlight', ) link_label = models.CharField( max_length=300, help_text='Text to show that links to this higlight\'s ' 'details page', ) link_url = models.URLField( max_length=2048, help_text='Link to this higlight\'s details page', ) image = models.ForeignKey( 'wagtailimages.Image', on_delete=models.SET_NULL, blank=False, null=True, related_name='+', ) footer = RichTextField( "footer", help_text="Content to appear after description (view more projects " "link or something similar)", null=True, ) publish_after = models.DateTimeField( help_text='Make this highlight visible only ' 'after this date and time (UTC)', null=True, ) expires = models.DateTimeField( help_text='Hide this highlight after this date and time (UTC)', default=None, null=True, blank=True, ) order = models.PositiveIntegerField( default=0, editable=False, db_index=True, ) panels = [ FieldPanel("title"), FieldPanel("description"), FieldPanel("link_label"), FieldPanel("link_url"), ImageChooserPanel("image"), FieldPanel("footer"), FieldPanel("publish_after"), FieldPanel("expires"), ] translatable_fields = [ TranslatableField('title'), TranslatableField('description'), TranslatableField('link_label'), TranslatableField('footer'), ] objects = HighlightQuerySet.as_manager() class Meta(TranslatableMixin.Meta): verbose_name_plural = 'highlights' ordering = ('order', ) def __str__(self): return str(self.title)
class SimpleGalleryIndex(RoutablePageMixin, Page): intro_title = models.CharField( verbose_name=_('Intro title'), max_length=250, blank=True, help_text=_('Optional H1 title for the gallery page.')) intro_text = RichTextField( blank=True, verbose_name=_('Intro text'), help_text=_('Optional text to go with the intro text.')) collection = models.ForeignKey( 'wagtailcore.Collection', verbose_name=_('Collection'), null=True, blank=False, on_delete=models.SET_NULL, related_name='+', help_text=_('Show images in this collection in the gallery view.')) images_per_page = models.IntegerField( default=8, verbose_name=_('Images per page'), help_text=_('How many images there should be on one page.')) use_lightbox = models.BooleanField( verbose_name=_('Use lightbox'), default=True, help_text=_( 'Use lightbox to view larger images when clicking the thumbnail.')) order_images_by = models.IntegerField(choices=IMAGE_ORDER_TYPES, default=1) content_panels = Page.content_panels + [ FieldPanel('intro_title', classname='full title'), FieldPanel('intro_text', classname='full title'), FieldPanel('collection'), FieldPanel('images_per_page', classname='full title'), FieldPanel('use_lightbox'), FieldPanel('order_images_by'), ] @property def images(self, tags=None): return get_gallery_images(self.collection.name, self) @property def tags(self): return self.get_gallery_tags() def get_context(self, request): images = self.images tags = self.tags context = super(SimpleGalleryIndex, self).get_context(request) page = request.GET.get('page') paginator = Paginator(images, self.images_per_page) try: images = paginator.page(page) except PageNotAnInteger: images = paginator.page(1) except EmptyPage: images = paginator.page(paginator.num_pages) context['gallery_images'] = images context['gallery_tags'] = tags return context @route('^tags/$', name='tag_archive') @route('^tags/([\w-]+)/$', name='tag_archive') def tag_archive(self, request, tag=None): try: tag = Tag.objects.get(slug=tag) except Tag.DoesNotExist: if tag: msg = 'There are no blog posts tagged with "{}"'.format(tag) messages.add_message(request, messages.INFO, msg) return redirect(self.url) try: taglist.append(tag) except NameError: taglist = [] taglist.append(tag) images = get_gallery_images(self.collection.name, self, tags=taglist) tags = self.get_gallery_tags(tags=taglist) paginator = Paginator(images, self.images_per_page) page = request.GET.get('page') try: images = paginator.page(page) except PageNotAnInteger: images = paginator.page(1) except EmptyPage: images = paginator.page(paginator.num_pages) context = self.get_context(request) context['gallery_images'] = images context['gallery_tags'] = tags context['current_tag'] = tag return render(request, 'wagtail_simple_gallery/simple_gallery_index.html', context) class Meta: verbose_name = _('Gallery index') verbose_name_plural = _('Gallery indices') template = getattr(settings, 'SIMPLE_GALLERY_TEMPLATE', 'wagtail_simple_gallery/simple_gallery_index.html') def get_gallery_tags(self, tags=[]): images = get_gallery_images(self.collection.name, self, tags=tags) for img in images: tags += img.tags.all() tags = sorted(set(tags)) return tags
class People(BasePage): RESOURCES_PER_PAGE = 8 parent_page_types = ["home.HomePage", "content.ContentPage"] subpage_types = ["Person"] template = "people.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, ) # Meta fields nav_description = TextField("Navigation description", max_length=400, blank=True, default="") keywords = ClusterTaggableManager(through=PeopleTag, blank=True) icon = FileField( upload_to="people/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], ) # Content panels content_panels = BasePage.content_panels + [FieldPanel("description")] # 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"), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) class Meta: verbose_name_plural = "People" @classmethod def can_create_at(cls, parent): # Allow only one instance of this page type return super().can_create_at(parent) and not cls.objects.exists() def get_context(self, request): context = super().get_context(request) context["filters"] = self.get_filters() context["people"] = self.get_people(request) return context def get_people(self, request): countries = request.GET.getlist(LOCATION_QUERYSTRING_KEY) roles = request.GET.getlist(ROLE_QUERYSTRING_KEY) topics = request.GET.getlist(TOPIC_QUERYSTRING_KEY) countries_q = Q(country__in=countries) if countries else Q() roles_q = Q(role__in=roles) if roles else Q() topics_q = Q(topics__topic__slug__in=topics) if topics else Q() combined_q = Q() if countries_q: combined_q.add(countries_q, Q.AND) if roles_q: combined_q.add(roles_q, Q.AND) if topics_q: combined_q.add(topics_q, Q.AND) people = Person.published_objects.filter(combined_q).order_by("title") people = paginate_resources( people, page_ref=request.GET.get(PAGINATION_QUERYSTRING_KEY), per_page=self.RESOURCES_PER_PAGE, ) return people def get_relevant_countries(self): # Relevant here means a country that a published person is in raw_countries = (person.country for person in Person.published_objects.distinct( "country").order_by("country") if person.country) return [{ "code": country.code, "name": country.name } for country in raw_countries] def get_filters(self): from ..topics.models import Topic return { "countries": self.get_relevant_countries(), "roles": ROLE_CHOICES, "topics": Topic.published_objects.order_by("title"), }
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") ] api_fields = [ APIField('image'), APIField('body'), ] def __str__(self): return self.title
class HomePage(Page): city = models.CharField(null=True, blank=False, max_length=255) zip_code = models.CharField(null=True, blank=False, max_length=255) address = models.CharField(null=True, blank=False, max_length=255) telephone = models.CharField(blank=True, max_length=255) vat_number = models.CharField(blank=True, max_length=255) email = models.CharField(blank=True, max_length=255) copyrightholder = models.CharField(blank=True, max_length=255) about = RichTextField(blank=True) privacy = RichTextField(blank=True) shipping = RichTextField(blank=True) gtc = RichTextField(blank=True) cancellation_policy = RichTextField(blank=True) sociallinks = StreamField([ ('link', blocks.URLBlock( help_text="Important! Format https://www.domain.tld/xyz")) ]) array = [] def sociallink_company(self): for link in self.sociallinks: self.array.append(str(link).split(".")[1]) return self.array headers = StreamField([ ('h_hero', _H_HeroBlock(icon='image')), # ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code')) ]) sections = StreamField( [('s_feature', _S_FeatureBlock(icon='fa-info')), ('s_maps', _S_MapsBlock(icon='fa-info')), ('s_partners', _S_PartnersBlock(icon='fa-info')), ('s_references', _S_ReferencesBlock(icon='fa-info')), ('s_about', _S_AboutBlock(icon='fa-info')), ('s_news', _S_NewsBlock(icon='fa-info')), ('s_projects', _S_ProjectsBlock(icon='fa-info')), ('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) # 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'), StreamFieldPanel('sections'), # StreamFieldPanel('footers') ] imprint_panels = [ MultiFieldPanel( [ FieldPanel('city'), FieldPanel('zip_code'), FieldPanel('address'), FieldPanel('telephone'), FieldPanel('email'), FieldPanel('copyrightholder') ], heading="contact", ), MultiFieldPanel( [FieldPanel('vat_number')], heading="legal", ), StreamFieldPanel('sociallinks'), MultiFieldPanel( [ FieldPanel('about'), FieldPanel('privacy'), FieldPanel('shipping'), FieldPanel('gtc'), FieldPanel('cancellation_policy'), ], heading="terms", ) ] token_panel = [FieldPanel('token')] edit_handler = TabbedInterface([ ObjectList(Page.content_panels + main_content_panels, heading='Main'), ObjectList(imprint_panels, heading='Imprint'), ObjectList(Page.promote_panels + token_panel + Page.settings_panels, heading='Settings', classname="settings") ]) preview_modes = []
class Person(BasePage): resource_type = "person" parent_page_types = ["People"] subpage_types = [] template = "person.html" # Content fields nickname = CharField(max_length=250, null=True, blank=True) job_title = CharField(max_length=250) role = CharField(max_length=250, choices=ROLE_CHOICES, default="staff") description = RichTextField( "About", blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional ‘About me’ section content, supports rich text", ) # 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", help_text="An image in 16:9 aspect ratio", ) # Meta city = CharField(max_length=250, blank=True, default="") country = CountryField(verbose_name="Country or Region", blank=True, default="") twitter = CharField(max_length=250, blank=True, default="") facebook = CharField(max_length=250, blank=True, default="") linkedin = CharField(max_length=250, blank=True, default="") github = CharField(max_length=250, blank=True, default="") email = CharField(max_length=250, blank=True, default="") websites = StreamField( StreamBlock([("website", PersonalWebsiteBlock())], max_num=3, required=False), null=True, blank=True, help_text="Optional links to any other personal websites", ) keywords = ClusterTaggableManager(through=PersonTag, blank=True) # Content panels content_panels = [ MultiFieldPanel( [ CustomLabelFieldPanel("title", label="Full name"), FieldPanel("nickname"), FieldPanel("job_title"), FieldPanel("role"), ], heading="Details", ), FieldPanel("description"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), 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."), ), ] # Meta panels meta_panels = [ MultiFieldPanel( [FieldPanel("city"), FieldPanel("country")], heading="Location", help_text=("Location fields. The country field is also filterable " "via the people directory page."), ), MultiFieldPanel([InlinePanel("topics")], heading="Topics this person specializes in"), MultiFieldPanel( [ FieldPanel("twitter"), FieldPanel("facebook"), FieldPanel("linkedin"), FieldPanel("github"), FieldPanel("email"), ], heading="Profiles", help_text="", ), StreamFieldPanel("websites"), 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")] # 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 display_title(self): """ Return the display title for profile pages. Adds a nickname to the person's full name when one is provided. """ return f'{self.title} aka "{self.nickname}"' if self.nickname else self.title @property def events(self): """ Return upcoming events where this person is a speaker, ordered by start date """ from ..events.models import Event upcoming_events = Event.published_objects.filter( start_date__gte=get_past_event_cutoff()) speaker_events = Event.published_objects.none() for event in upcoming_events.all(): # add the event to the list if the current person is a speaker if event.has_speaker(self): speaker_events = speaker_events | Event.published_objects.page( event) return speaker_events.order_by("start_date") @property def articles(self): """ Return articles and external articles where this person is (one of) the authors, ordered by article date, most recent first """ from ..articles.models import Article from ..externalcontent.models import ExternalArticle articles = Article.published_objects.none() external_articles = ExternalArticle.published_objects.none() all_articles = Article.published_objects.all() all_external_articles = ExternalArticle.published_objects.all() for article in all_articles: if article.has_author(self): articles = articles | Article.published_objects.page(article) for external_article in all_external_articles: if external_article.has_author(self): external_articles = external_articles | ( ExternalArticle.published_objects.page(external_article)) return sorted(chain(articles, external_articles), key=attrgetter("date"), reverse=True) @property def videos(self): """ Return the most recent videos and external videos where this person is (one of) the speakers. """ from ..videos.models import Video from ..externalcontent.models import ExternalVideo videos = Video.published_objects.none() external_videos = ExternalVideo.published_objects.none() all_videos = Video.published_objects.all() all_external_videos = ExternalVideo.published_objects.all() for video in all_videos: if video.has_speaker(self): videos = videos | Video.published_objects.page(video) for external_video in all_external_videos: if external_video.has_speaker(self): external_videos = external_videos | ( ExternalVideo.published_objects.page(external_video)) return sorted(chain(videos, external_videos), key=attrgetter("date"), reverse=True) @property def role_group(self): return { "slug": self.role, "title": dict(ROLE_CHOICES).get(self.role, "") } @property def country_group(self): return ({ "slug": self.country.code.lower(), "title": self.country.name } if self.country else { "slug": "" }) def get_topics(self) -> List: """Return the live/published Topic pages associated with this Person""" # Note that we do this in Python because django-modelcluster won't support # `filter(topic__live=True)` when _previewing_ pages (even tho it'll work # on saved ones) topics = [pt.topic for pt in self.topics.all()] return [t for t in topics if t.live]
class CoursePage(Page): parent_page_type = ["CourseIndexPage"] intro = models.CharField('one line summary', max_length=250) summary = RichTextField('full summary') # start_date = models.DateTimeField(blank=False) address_details = models.CharField('address details', max_length=250, blank=True) formatted_address = models.CharField(max_length=255) # latlng_address = models.CharField(max_length=255) course_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') course_flyer = StreamField([ ('course_flyer', DocumentChooserBlock()), ], blank=True) organiser_name = models.CharField('name', max_length=250, blank=True) organiser_email = models.EmailField('email', blank=True) organiser_number = models.CharField('number', max_length=250, blank=True) course_programme = StreamField([ ('table', TableBlock()), ], blank=True) class Meta: verbose_name = "Courses & Conferences Page" search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('summary'), ] content_panels = Page.content_panels + [ FieldPanel('intro', classname="full"), FieldPanel('summary', classname="full"), ImageChooserPanel('course_image'), InlinePanel('related_links', label="Course Links"), InlinePanel('related_dates', label="Course Dates"), StreamFieldPanel('course_programme'), StreamFieldPanel('course_flyer'), MultiFieldPanel( [ FieldPanel('address_details', classname="full"), MapFieldPanel('formatted_address'), ], heading="Location", # classname="collapsible collapsed" ), MultiFieldPanel( [ FieldPanel('organiser_name', classname="full"), FieldPanel('organiser_email', classname="full"), FieldPanel('organiser_number', classname="full"), ], heading="Organiser Details", # classname="collapsible collapsed" ), # MapFieldPanel('latlng_address', latlng=True), ] # parent_page_types = ['CourseIndexPage'] def get_context(self, request): context = super().get_context(request) relateddates = self.related_dates.order_by('date') #the [:1] returns the first result from the list e.g. the start date. context['relateddates'] = relateddates return context
class PublicationPage(FoundationMetadataPageMixin, Page): """ This is the root page of a publication. From here the user can browse to the various sections (called chapters). It will have information on the publication, its authors, and metadata from it's children Publications are collections of Articles Publications can also be broken down into Chapters, which are really just child publication pages Each of those Chapters may have several Articles An Article can only belong to one Chapter/Publication Page """ subpage_types = ['ArticlePage', 'PublicationPage'] toc_thumbnail_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='toc_thumbnail_image', verbose_name='Table of Content Thumbnail', help_text= 'Thumbnail image to show on table of content. Use square image of 320×320 pixels or larger.', ) hero_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='publication_hero_image', verbose_name='Publication Hero Image', ) subtitle = models.CharField( blank=True, max_length=250, ) secondary_subtitle = models.CharField( blank=True, max_length=250, ) publication_date = models.DateField("Publication date", null=True, blank=True) publication_file = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) additional_author_copy = models.CharField( help_text="Example: with contributing authors", max_length=100, blank=True, ) intro_notes = RichTextField(blank=True, features=['link', 'bold', 'italic', 'h4']) notes = RichTextField( blank=True, features=['link', 'bold', 'italic', 'h4', 'ol', 'ul']) contents_title = models.CharField( blank=True, default="Table of Contents", max_length=250, ) content_panels = Page.content_panels + [ MultiFieldPanel( [ FieldPanel('subtitle'), FieldPanel('secondary_subtitle'), FieldPanel('publication_date'), ImageChooserPanel('toc_thumbnail_image'), ImageChooserPanel('hero_image'), DocumentChooserPanel('publication_file'), InlinePanel('authors', label='Author'), FieldPanel('additional_author_copy'), ], heading='Hero', ), FieldPanel('intro_notes'), FieldPanel('contents_title'), FieldPanel('notes'), ] @property def is_chapter_page(self): """ A PublicationPage nested under a PublicationPage is considered to be a "ChapterPage". The templates used very similar logic and structure, and all the fields are the same. """ parent = self.get_parent().specific return parent.__class__ is PublicationPage @property def next_page(self): """ Only applies to Chapter Publication (sub-Publication Pages). Returns a Page object or None. """ next_page = self.get_parent() if self.is_chapter_page: sibling = self.get_siblings().filter(path__gt=self.path, live=True).first() if sibling: # If there is no more chapters. Return the parent page. next_page = sibling return next_page @property def prev_page(self): """ Only applies to Chapter Publication (sub-Publication Pages). Returns a Page object or None. """ prev_page = self.get_parent() if self.is_chapter_page: sibling = self.get_siblings().filter(path__lt=self.path, live=True).reverse().first() if sibling: # If there is no more chapters. Return the parent page. prev_page = sibling return prev_page @property def zen_nav(self): return True def breadcrumb_list(self): """ Get all the parent PublicationPages and return a QuerySet """ return Page.objects.ancestor_of(self).type(PublicationPage).live() def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) pages = [] for page in self.get_children(): if request.user.is_authenticated: # User is logged in, and can preview a page. Get all pages, even drafts. pages.append({ 'child': page, 'grandchildren': page.get_children() }) elif page.live: # User is not logged in AND this page is live. Only fetch live grandchild pages. pages.append({ 'child': page, 'grandchildren': page.get_children().live() }) context['child_pages'] = pages return set_main_site_nav_information(self, context, 'Homepage')
class CalendarPage(RoutablePageMixin, Page): """CalendarPage displays all the events which are in the same site.""" class Meta: verbose_name = _("calendar page") verbose_name_plural = _("calendar pages") EventsPerPage = getattr(settings, "JOYOUS_EVENTS_PER_PAGE", 25) holidays = Holidays() subpage_types = [ 'joyous.SimpleEventPage', 'joyous.MultidayEventPage', 'joyous.RecurringEventPage', 'joyous.MultidayRecurringEventPage' ] base_form_class = CalendarPageForm intro = RichTextField(_("intro"), blank=True, help_text=_("Introductory text.")) view_choices = MultipleSelectField(_("view choices"), blank=True, default=["L", "W", "M"], choices=EVENTS_VIEW_CHOICES) default_view = models.CharField(_("default view"), default="M", max_length=15, choices=EVENTS_VIEW_CHOICES) search_fields = Page.search_fields[:] content_panels = Page.content_panels + [ FieldPanel('intro', classname="full"), ] settings_panels = Page.settings_panels + [ MultiFieldPanel( [FieldPanel('view_choices'), FieldPanel('default_view')], heading=_("View Options")), ] @route(r"^$") @route(r"^{YYYY}/$".format(**DatePictures)) def routeDefault(self, request, year=None): """Route a request to the default calendar view.""" eventsView = request.GET.get('view', self.default_view) if eventsView in ("L", "list"): return self.serveUpcoming(request) elif eventsView in ("W", "weekly"): return self.serveWeek(request, year) else: return self.serveMonth(request, year) @route(r"^{YYYY}/{Mon}/$(?i)".format(**DatePictures)) def routeByMonthAbbr(self, request, year, monthAbbr): """Route a request with a month abbreviation to the monthly view.""" month = (DatePictures['Mon'].index(monthAbbr.lower()) // 4) + 1 return self.serveMonth(request, year, month) @route(r"^month/$") @route(r"^{YYYY}/{MM}/$".format(**DatePictures)) def serveMonth(self, request, year=None, month=None): """Monthly calendar view.""" myurl = self.get_url(request) def myUrl(urlYear, urlMonth): if 1900 <= urlYear <= 2099: return myurl + self.reverse_subpage('serveMonth', args=[urlYear, urlMonth]) today = timezone.localdate() if year is None: year = today.year if month is None: month = today.month year = int(year) month = int(month) lastDay = dt.date(year, month, calendar.monthrange(year, month)[1]) if year == today.year and month == today.month: weekNum = gregorian_to_week_date(today)[1] else: weekNum = gregorian_to_week_date(dt.date(year, month, 7))[1] weeklyUrl = myurl + self.reverse_subpage('serveWeek', args=[year, weekNum]) listUrl = myurl + self.reverse_subpage('serveUpcoming') prevMonth = month - 1 prevMonthYear = year if prevMonth == 0: prevMonth = 12 prevMonthYear -= 1 nextMonth = month + 1 nextMonthYear = year if nextMonth == 13: nextMonth = 1 nextMonthYear += 1 cxt = self._getCommonContext(request) cxt.update({ 'year': year, 'month': month, 'yesterday': today - dt.timedelta(1), 'lastweek': today - dt.timedelta(7), 'lastDay': lastDay, 'prevMonthUrl': myUrl(prevMonthYear, prevMonth), 'nextMonthUrl': myUrl(nextMonthYear, nextMonth), 'prevYearUrl': myUrl(year - 1, month), 'nextYearUrl': myUrl(year + 1, month), 'weeklyUrl': weeklyUrl, 'listUrl': listUrl, 'thisMonthUrl': myUrl(today.year, today.month), 'monthName': MONTH_NAMES[month], 'weekdayAbbr': weekday_abbr, 'events': self._getEventsByWeek(request, year, month) }) cxt.update(self._getExtraContext("month")) return TemplateResponse(request, "joyous/calendar_month.html", cxt) @route(r"^week/$") @route(r"^{YYYY}/W{WW}/$".format(**DatePictures)) def serveWeek(self, request, year=None, week=None): """Weekly calendar view.""" myurl = self.get_url(request) def myUrl(urlYear, urlWeek): if (urlYear < 1900 or urlYear > 2099 or urlYear == 2099 and urlWeek == 53): return None if urlWeek == 53 and num_weeks_in_year(urlYear) == 52: urlWeek = 52 return myurl + self.reverse_subpage('serveWeek', args=[urlYear, urlWeek]) today = timezone.localdate() thisYear, thisWeekNum, dow = gregorian_to_week_date(today) if year is None: year = thisYear if week is None: week = thisWeekNum year = int(year) week = int(week) firstDay, lastDay, prevYearNumWeeks, yearNumWeeks = week_info( year, week) if week == 53 and yearNumWeeks == 52: raise Http404("Only 52 weeks in {}".format(year)) eventsInWeek = self._getEventsByDay(request, firstDay, lastDay) if firstDay.year >= 1900: monthlyUrl = myurl + self.reverse_subpage( 'serveMonth', args=[firstDay.year, firstDay.month]) else: monthlyUrl = myurl + self.reverse_subpage('serveMonth', args=[1900, 1]) listUrl = myurl + self.reverse_subpage('serveUpcoming') prevWeek = week - 1 prevWeekYear = year if prevWeek == 0: prevWeek = prevYearNumWeeks prevWeekYear -= 1 nextWeek = week + 1 nextWeekYear = year if nextWeek > yearNumWeeks: nextWeek = 1 nextWeekYear += 1 cxt = self._getCommonContext(request) cxt.update({ 'year': year, 'week': week, 'yesterday': today - dt.timedelta(1), 'prevWeekUrl': myUrl(prevWeekYear, prevWeek), 'nextWeekUrl': myUrl(nextWeekYear, nextWeek), 'prevYearUrl': myUrl(year - 1, week), 'nextYearUrl': myUrl(year + 1, week), 'thisWeekUrl': myUrl(thisYear, thisWeekNum), 'monthlyUrl': monthlyUrl, 'listUrl': listUrl, 'weekName': _("Week {weekNum}").format(weekNum=week), 'weekdayAbbr': weekday_abbr, 'events': [eventsInWeek] }) cxt.update(self._getExtraContext("week")) return TemplateResponse(request, "joyous/calendar_week.html", cxt) @route(r"^day/$") @route(r"^{YYYY}/{MM}/{DD}/$".format(**DatePictures)) def serveDay(self, request, year=None, month=None, dom=None): """The events of the day list view.""" myurl = self.get_url(request) today = timezone.localdate() if year is None: year = today.year if month is None: month = today.month if dom is None: dom = today.day year = int(year) month = int(month) dom = int(dom) day = dt.date(year, month, dom) daysEvents = self._getEventsOnDay(request, day).all_events # FIXME: Does include Cancellations - should it? if len(daysEvents) == 1: event = daysEvents[0].page return redirect(event.get_url(request)) eventsPage = self._paginate(request, daysEvents) monthlyUrl = myurl + self.reverse_subpage('serveMonth', args=[year, month]) weekNum = gregorian_to_week_date(today)[1] weeklyUrl = myurl + self.reverse_subpage('serveWeek', args=[year, weekNum]) listUrl = myurl + self.reverse_subpage('serveUpcoming') cxt = self._getCommonContext(request) cxt.update({ 'year': year, 'month': month, 'dom': dom, 'day': day, 'monthlyUrl': monthlyUrl, 'weeklyUrl': weeklyUrl, 'listUrl': listUrl, 'monthName': MONTH_NAMES[month], 'weekdayName': WEEKDAY_NAMES[day.weekday()], 'events': eventsPage }) cxt.update(self._getExtraContext("day")) return TemplateResponse(request, "joyous/calendar_list_day.html", cxt) @route(r"^upcoming/$") def serveUpcoming(self, request): """Upcoming events list view.""" myurl = self.get_url(request) today = timezone.localdate() monthlyUrl = myurl + self.reverse_subpage( 'serveMonth', args=[today.year, today.month]) weekNum = gregorian_to_week_date(today)[1] weeklyUrl = myurl + self.reverse_subpage('serveWeek', args=[today.year, weekNum]) listUrl = myurl + self.reverse_subpage('servePast') upcomingEvents = self._getUpcomingEvents(request) # FIXME: Does not include Cancellations - should it? eventsPage = self._paginate(request, upcomingEvents) cxt = self._getCommonContext(request) cxt.update({ 'weeklyUrl': weeklyUrl, 'monthlyUrl': monthlyUrl, 'listUrl': listUrl, 'events': eventsPage }) cxt.update(self._getExtraContext("upcoming")) return TemplateResponse(request, "joyous/calendar_list_upcoming.html", cxt) @route(r"^past/$") def servePast(self, request): """Past events list view.""" myurl = self.get_url(request) today = timezone.localdate() monthlyUrl = myurl + self.reverse_subpage( 'serveMonth', args=[today.year, today.month]) weekNum = gregorian_to_week_date(today)[1] weeklyUrl = myurl + self.reverse_subpage('serveWeek', args=[today.year, weekNum]) listUrl = myurl + self.reverse_subpage('serveUpcoming') pastEvents = self._getPastEvents(request) # FIXME: Does not include Cancellations - should it? eventsPage = self._paginate(request, pastEvents) cxt = self._getCommonContext(request) cxt.update({ 'weeklyUrl': weeklyUrl, 'monthlyUrl': monthlyUrl, 'listUrl': listUrl, 'events': eventsPage }) cxt.update(self._getExtraContext("past")) return TemplateResponse(request, "joyous/calendar_list_past.html", cxt) @route(r"^mini/{YYYY}/{MM}/$".format(**DatePictures)) def serveMiniMonth(self, request, year=None, month=None): """Serve data for the MiniMonth template tag.""" if not request.is_ajax(): raise Http404("/mini/ is for ajax requests only") today = timezone.localdate() if year is None: year = today.year if month is None: month = today.month year = int(year) month = int(month) cxt = self._getCommonContext(request) cxt.update({ 'year': year, 'month': month, 'calendarUrl': self.get_url(request), 'monthName': MONTH_NAMES[month], 'weekdayInfo': zip(weekday_abbr, weekday_name), 'events': self._getEventsByWeek(request, year, month) }) cxt.update(self._getExtraContext("mini")) return TemplateResponse(request, "joyous/includes/minicalendar.html", cxt) @classmethod def can_create_at(cls, parent): return super().can_create_at(parent) and cls._allowAnotherAt(parent) @classmethod def _allowAnotherAt(cls, parent): """You can only create one of these pages per site.""" site = parent.get_site() if site is None: return False return not cls.peers().descendant_of(site.root_page).exists() @classmethod def peers(cls): """Return others of the same concrete type.""" contentType = ContentType.objects.get_for_model(cls) return cls.objects.filter(content_type=contentType) def _getCommonContext(self, request): cxt = self.get_context(request) cxt.update({ 'version': __version__, 'themeCSS': getattr(settings, "JOYOUS_THEME_CSS", ""), 'today': timezone.localdate() }) return cxt def _getExtraContext(self, route): return {} def _getEventsOnDay(self, request, day): """Return all the events in this site for a given day.""" return self._getEventsByDay(request, day, day)[0] def _getEventsByDay(self, request, firstDay, lastDay): """ Return the events in this site for the dates given, grouped by day. """ home = request.site.root_page return getAllEventsByDay(request, firstDay, lastDay, home=home, holidays=self.holidays) def _getEventsByWeek(self, request, year, month): """ Return the events in this site for the given month grouped by week. """ home = request.site.root_page return getAllEventsByWeek(request, year, month, home=home, holidays=self.holidays) def _getUpcomingEvents(self, request): """Return the upcoming events in this site.""" home = request.site.root_page return getAllUpcomingEvents(request, home=home) def _getPastEvents(self, request): """Return the past events in this site.""" home = request.site.root_page return getAllPastEvents(request, home=home) def _getEventFromUid(self, request, uid): """Try and find an event with the given UID in this site.""" event = getEventFromUid(request, uid) # might raise exception home = request.site.root_page if event.get_ancestors().filter(id=home.id).exists(): # only return event if it is in the same site return event def _getAllEvents(self, request): """Return all the events in this site.""" home = request.site.root_page return getAllEvents(request, home=home) def _paginate(self, request, events): paginator = Paginator(events, self.EventsPerPage) try: eventsPage = paginator.page(request.GET.get('page')) except PageNotAnInteger: eventsPage = paginator.page(1) except EmptyPage: eventsPage = paginator.page(paginator.num_pages) return eventsPage
class HomePage(Page): city = models.CharField(null=True, blank=False, max_length=255) zip_code = models.CharField(null=True, blank=False, max_length=255) address = models.CharField(null=True, blank=False, max_length=255) telephone = models.CharField(null=True, blank=False, max_length=255) telefax = models.CharField(null=True, blank=False, max_length=255) vat_number = models.CharField(null=True, blank=False, max_length=255) whatsapp_telephone = models.CharField(null=True, blank=True, max_length=255) whatsapp_contactline = models.CharField(null=True, blank=True, max_length=255) tax_id = models.CharField(null=True, blank=False, max_length=255) trade_register_number = models.CharField(null=True, blank=False, max_length=255) court_of_registry = models.CharField(null=True, blank=False, max_length=255) place_of_registry = models.CharField(null=True, blank=False, max_length=255) trade_register_number = models.CharField(null=True, blank=False, max_length=255) ownership = models.CharField(null=True, blank=False, max_length=255) email = models.CharField(null=True, blank=False, max_length=255) copyrightholder = models.CharField(null=True, blank=False, max_length=255) about = RichTextField(null=True, blank=False) privacy = RichTextField(null=True, blank=False) sociallinks = StreamField([ ('link', blocks.URLBlock(help_text="Important! Format https://www.domain.tld/xyz")) ]) array = [] def sociallink_company(self): for link in self.sociallinks: self.array.append(str(link).split(".")[1]) return self.array headers = StreamField([ ('h_hero', _H_HeroBlock(null=True, blank=False, icon='image')), ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code')) ], null=True, blank=False) sections = StreamField([ ('s_why', _S_WhyBlock(null=True, blank=False, icon='group')), ('s_about', _S_AboutBlock(null=True, blank=False, icon='fa-quote-left')), ('s_instagram', _S_InstagramBlock(null=True, blank=False, icon='fa-instagram')), ('s_steps', _S_StepsBlock(null=True, blank=False, icon='fa-list-ul')), ('s_shop', _S_ShopBlock(null=True, blank=False, icon='home')), ('s_trusted', _S_TrustedBlock(null=True, blank=False, icon='fa-list-ul')), ('s_wolf', _S_WolfBlock(null=True, blank=False, icon='fa-list-ul')), ('s_faq', _S_FAQBlock(null=True, blank=False, icon='home')), ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code')) ], 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'), StreamFieldPanel('sections') ] imprint_panels = [ MultiFieldPanel( [ FieldPanel('city'), FieldPanel('zip_code'), FieldPanel('address'), FieldPanel('telephone'), FieldPanel('telefax'), FieldPanel('whatsapp_telephone'), FieldPanel('whatsapp_contactline'), FieldPanel('email'), FieldPanel('copyrightholder') ], heading="contact", ), MultiFieldPanel( [ FieldPanel('vat_number'), FieldPanel('tax_id'), FieldPanel('trade_register_number'), FieldPanel('court_of_registry'), FieldPanel('place_of_registry'), FieldPanel('trade_register_number'), FieldPanel('ownership') ], heading="legal", ), StreamFieldPanel('sociallinks'), MultiFieldPanel( [ FieldPanel('about'), FieldPanel('privacy') ], heading="privacy", ) ] token_panel = [ FieldPanel('token') ] edit_handler = TabbedInterface([ ObjectList(Page.content_panels + main_content_panels, heading='Main'), ObjectList(imprint_panels, heading='Imprint'), ObjectList(Page.promote_panels + token_panel + Page.settings_panels, heading='Settings', classname="settings") ])
class SubscriptionPage(RoutablePageMixin, BannerPage): template = "subscription/subscribe.html" subpage_types = [] parent_page_type = [ "home.HomePage", ] max_count = 1 email_intro_text = RichTextField( blank=True, verbose_name="Intro Text", ) email_success_text = RichTextField( blank=True, verbose_name="Success Text", ) telegram_intro_text = RichTextField( blank=True, verbose_name="Intro Text", ) content_panels = BannerPage.content_panels + [ MultiFieldPanel( [ FieldPanel("email_intro_text"), FieldPanel("email_success_text"), ], heading="Email", ), MultiFieldPanel([FieldPanel("telegram_intro_text")], heading="Telegram"), ] def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) context["hide_newsletter_info"] = True return context @route(r"^$") @route(r"^subscribe/$", name="subscribe") def subscribe(self, request, *args, **kwargs): from subscription.forms import NewsletterSubscriptionForm context = self.get_context(request, *args, **kwargs) if request.method == "POST": form = NewsletterSubscriptionForm(request.POST) if form.is_valid(): subscriber = form.save(commit=False) if NewsletterSubscription.objects.filter( email=subscriber.email).exists(): context[ "error_message"] = """This email address is already subscribed. You will continue to receive updates.""" else: subscriber.save() return render(request, "subscription/subscribe_landing.html", context) else: form = NewsletterSubscriptionForm() context["form"] = form return render(request, "subscription/subscribe.html", context) @route(r"^unsubscribe/([0-9a-f]{32})/$", name="unsubscribe") def unsubscribe(self, request, uuid, *args, **kwargs): context = self.get_context(request, *args, **kwargs) subscription = NewsletterSubscription.objects.filter(uuid=uuid).first() if subscription: subscription.delete() context["email"] = subscription.email return render(request, "subscription/cancellation.html", context) def get_unsubscribe_url_for(self, subscription): return self.get_full_url() + self.reverse_subpage( "unsubscribe", args=(subscription.uuid, ))
class PublicationPage(BasePage, CanonicalMixin): # standard approach appears to be # (via https://docs.wagtail.io/en/latest/topics/streamfield.html) # author = models.Charfield(max_length=255) # which also gives subcategories for StreamField # next two lines would make the post less available but by commenting out # instead we make it available anywhere that doesn't exclude it # parent_page_types = ["PublicationIndexPage"] # subpage_types = [] updated_at = models.DateTimeField( editable=True, null=True, blank=True, verbose_name="Updated at (leave blank for initial publication)", ) history = RichTextField( blank=True, verbose_name="Version history (leave blank for initial publication)") content_panels = [ *Page.content_panels, FieldPanel("first_published_at"), FieldPanel("updated_at"), StreamFieldPanel("body"), FieldPanel("history"), ] settings_panels = CanonicalMixin.panels + BasePage.settings_panels def replace_headers(self, html): """Given a html string, return a html string where the header tags have id attributes so they can be used as anchors, and list the ids and text of those tags so we can make a table of contents.""" header_tags = ["h2"] toc = [] root = lxml.html.fromstring(html) for tag_name in header_tags: for tag in root.xpath(f"//{tag_name}"): # create a slugged name for the tag of the form tag-text-3 header_name = tag.text bare_slug = slugify(header_name, allow_unicode=True) self.slug_count[bare_slug] += 1 slug = bare_slug + slug_count_text(self.slug_count[bare_slug]) # modify the tag and record the details tag.set("id", slug) toc.append((header_name, slug)) return lxml.html.tostring(root).decode("utf-8"), toc def get_context(self, request): """Pass the html of each richtext node within the streamfield to replace_headers, creating a page-wide table of contents. We use self.slug_count to preserve the list of slugs seen so far across multiple rich_text blocks.""" context = super().get_context(request) context["toc"] = [] # table of contents self.slug_count = Counter({"contents": 1}) for i, block in enumerate(self.body._raw_data): if block["type"] == "rich_text": replacement_html, new_toc = self.replace_headers( block["value"]) context["toc"].extend(new_toc) self.body._raw_data[i]["value"] = replacement_html return context
class BlogPage(RoutablePageMixin, Page): subtitle = models.CharField(max_length=255, null=True, blank=True) main_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') date = models.DateField("Post date", null=True, blank=True) intro = models.CharField(max_length=250, null=True, blank=True) body = StreamField([ ('heading', CharBlock(classname="full title", icon='title')), ('paragraph', RichTextBlock(icon='pilcrow')), ('image', ImageChooserBlock(icon='image')), ('codeblock', TextBlock(icon='cogs')), ('markdown', MarkDownBlock()), ('real_codeblock', CodeBlock()), ], blank=True, null=True) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) listing_intro = RichTextField(null=True, blank=True) listing_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('subtitle'), ImageChooserPanel('main_image'), FieldPanel('date'), FieldPanel('intro'), StreamFieldPanel('body'), FieldPanel('tags'), ] promote_panels = Page.promote_panels + [ FieldPanel('listing_intro'), ImageChooserPanel('listing_image'), ] @property def home_page(self): return self.get_parent() @property def next_blog(self): blogs = BlogPage.objects.filter(live=True).order_by('-date') current_index = blogs.index(self) def get_absolute_url(self): return self.full_url @route(r'^$', name='normal_blog') def normal_blog(self, request): site_root = self.get_parent() return render(request, self.template, { 'self': self, }) @route(r'^amp/$', name='amp_blog') def amp_blog(self, request): context = self.get_context(request) context['is_amp'] = True context['base_template'] = 'base_amp.html' response = TemplateResponse(request, self.template, context) return response
class People(models.Model): STATUS_CHOICES = [ ('former', 'Ehemalig'), ('current', 'Aktuell'), ] ROLE_CHOICES = [ ('chairholder', 'Lehstuhlinhaber'), ('office-management', 'Office Management'), ('academic-staff-male', 'Wiss. Mitarbeiter'), ('academic-staff-female', 'Wiss. Mitarbeiterin'), ('academic-assistant', 'Wiss. Hilfskraft'), ('student-assistant', 'Stud. Hilfskraft'), ('jurcoach-law-team', 'Jurcoach Jura-Team'), ('jurcoach-evaluation-team', 'Jurcoach Evaluations-Team'), ('jurcoach-web-team', 'Jurcoach Informatik-Team'), ('webmaster', 'Webmaster'), ('associate-professor', 'Privatdozent'), ] user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') first_name = models.CharField("Vorname", max_length=255) last_name = models.CharField("Nachname", max_length=255) status = models.CharField( choices=STATUS_CHOICES, default='former', max_length=255, blank=True, ) role = models.CharField( choices=ROLE_CHOICES, max_length=255, blank=True, ) telephone = models.CharField("Telefonnummer", max_length=255, blank=True) email = models.CharField("Mailadresse", max_length=255, blank=True) room = models.CharField("Raumnummer", max_length=255, blank=True) description = RichTextField(blank=True) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') panels = [ MultiFieldPanel([ FieldRowPanel([ FieldPanel('user', classname="col-12"), ]), FieldRowPanel([ FieldPanel('first_name', classname="col-6"), FieldPanel('last_name', classname="col-6"), ], "Name"), FieldRowPanel([ FieldPanel('telephone', classname="col-4"), FieldPanel('email', classname="col-4"), ], "Contact"), FieldRowPanel([ FieldPanel('status', classname="col-6"), FieldPanel('role', classname="col-6"), ]), FieldRowPanel([ FieldPanel('room', classname="col-4"), ], "Room"), ], "Information"), MultiFieldPanel([ FieldRowPanel([ FieldPanel('description', classname="col-12"), ]), ], "Weitere Informationen"), ImageChooserPanel('image') ] search_fields = [ index.SearchField('first_name'), index.SearchField('last_name'), ] @property def thumb_image(self): try: return self.image.get_rendition('fill-120x120').img_tag() except: return '' def __str__(self): return '{} {}'.format(self.first_name, self.last_name) class Meta: verbose_name = 'Person' verbose_name_plural = 'Personen'
class BlogIndexPage(Page): intro = RichTextField(blank=True) content_panels = Page.content_panels + [ InlinePanel('related_links', label="Related links"), ]
class FormSection(SectionBase, SectionTitleBlock, ButtonAction, AbstractEmailForm): form_submit_button_text = models.CharField( blank=True, max_length=100, verbose_name='Submit button text', default='Submit', # help_text="Leave field empty to hide.", ) # intro = RichkTextField(blank=True) thank_you_text = RichTextField(blank=True) # basic tab panels basic_panels = AbstractEmailForm.content_panels + [ SectionBase.section_content_panels, SectionBase.section_layout_panels, SectionBase.section_design_panels, ] # advanced tab panels advanced_panels = (SectionTitleBlock.title_basic_panels, ) + ButtonAction.button_action_panels # form tab panels form_panels = [ InlinePanel('form_fields', label="Form fields"), FieldPanel('thank_you_text', classname="full"), MultiFieldPanel([ FieldRowPanel([ FieldPanel('from_address', classname="col6"), FieldPanel('to_address', classname="col6"), ]), FieldPanel('subject'), ], "Email"), ] # Register Tabs edit_handler = TabbedInterface([ ObjectList(basic_panels, heading="Basic"), ObjectList(form_panels, heading="Form"), ObjectList(advanced_panels, heading="Plus+"), ]) # Page settings template = 'sections/form_section_preview.html' parent_page_types = ['home.HomePage'] subpage_types = [] # Overriding Methods # def get_form(self, *args, **kwargs): # form = super().get_form(*args, **kwargs) # # Get form and update attributes # # https://stackoverflow.com/questions/48321770/how-to-modify-attributes-of-the-wagtail-form-input-fields # return form def serve(self, request): if request.is_ajax(): print('IS AXJAX') return super(FormSection, self).serve(request) def __str__(self): if self.title: return self.title + " (Form Section)" else: return super(AbstractEmailForm, self).__str__() class Meta: verbose_name = 'Form Section' verbose_name_plural = 'Form Sections'
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, max. 400 characters", max_length=400, ) 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=400, 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"), ])
class Homepage(FoundationMetadataPageMixin, Page): hero_headline = models.CharField( max_length=140, help_text='Hero story headline', blank=True, ) hero_story_description = RichTextField( features=[ 'bold', 'italic', 'link', ] ) hero_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='hero_image' ) hero_button_text = models.CharField( max_length=50, blank=True ) hero_button_url = models.URLField( blank=True ) content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('hero_headline'), FieldPanel('hero_story_description'), FieldRowPanel([ FieldPanel('hero_button_text'), FieldPanel('hero_button_url'), ]), ImageChooserPanel('hero_image'), ], heading='hero', classname='collapsible' ), InlinePanel('featured_highlights', label='Highlights', max_num=5), InlinePanel('featured_news', label='News', max_num=4), ] subpage_types = [ 'PrimaryPage', 'PeoplePage', 'InitiativesPage', 'Styleguide', 'NewsPage', 'ParticipatePage', 'ParticipatePage2', 'MiniSiteNameSpace', 'RedirectingPage', 'OpportunityPage', 'BanneredCampaignPage', ] def get_context(self, request): # We need to expose MEDIA_URL so that the s3 images will show up properly # due to our custom image upload approach pre-wagtail context = super(Homepage, self).get_context(request) context['MEDIA_URL'] = settings.MEDIA_URL return context
class JanisBasePage(Page): """ This is base page class made for our pages to inherit from. It is abstract, which for Django means that it isn't stored as it's own table in the DB. We use it to add functionality that we know will be desired by all other pages, such as setting the preview fields and urls for janis stuff to make our headless setup work smoothly """ parent_page_types = ['base.HomePage'] subpage_types = [] search_fields = Page.search_fields + [ index.RelatedFields('owner', [ index.SearchField('last_name', partial_match=True), index.FilterField('last_name'), ]) ] created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) author_notes = RichTextField( # max_length=DEFAULT_MAX_LENGTH, features=['ul', 'ol', 'link'], blank=True, verbose_name= 'Notes for authors (Not visible on the resident facing site)') notes_content_panel = [FieldPanel('author_notes')] coa_global = models.BooleanField(default=False, verbose_name='Make this a top level page') def janis_url(self): """ This function parses various attributes of related content types to construct the expected url structure for janis. For attributes with multiple relations, it ONLY takes the FIRST one. """ try: """ These use ternary operators with some appropriate conditionals the idea is: return this value in these cases or tell use you got nothing (see the privacy policy info page for example). 'None' responses get filtered out and removed from the URL path. TODO: make this more abstract(potentially not by content type) further check if the order of conditionals affects performance Better utilization of querysets may be possible for better performance """ page_slug = self.slug or None has_no_theme = [ 'service page', 'topic page', 'information page', 'department page', 'guide page', 'official document page', 'form container', 'location page' ] has_no_topic_collection = has_no_theme has_no_topic = [ 'topic page', 'topic collection page', 'department page', 'location page' ] theme_slug = (self.theme.slug if self.content_type.name not in has_no_theme else None) # https://docs.djangoproject.com/en/2.2/ref/models/querysets/#first topic_collection_slug = ( self.topiccollections.first().topiccollection.slug if (self.content_type.name not in has_no_topic_collection and # https://docs.djangoproject.com/en/2.2/ref/models/querysets/#exists self.topiccollections.exists()) else None) topic_slug = (self.topics.first().topic.slug if (self.content_type.name not in has_no_topic and self.topics.exists()) else None) # add hardcoded language path to base url base_url = f"{self.janis_url_base('publish_janis_branch')}/en" # Quick location page exception if self.content_type.name == 'location page': location_url = base_url + '/location/' + page_slug return location_url # attributes for the url are needed by not discovered yet lets fetch them # looking for missing elements, deducing content type from what works and what dosen't # this is pretty ugly and ought to be cleaned up if theme_slug is None and self.content_type.name != 'department page': try: theme_slug = self.topics.first( ).topic.topiccollections.first( ).topiccollection.theme.slug or None topic_collection_slug = self.topics.first( ).topic.topiccollections.first( ).topiccollection.slug or None except AttributeError as e: try: theme_slug = self.topiccollections.first( ).topiccollection.theme.slug or None topic_collection_slug = self.topiccollections.first( ).topiccollection.slug or None except AttributeError as e: # this is for pages just under departments theme_slug = self.related_departments.all( )[0].related_department.slug or None finally: paths_list = [ base_url, theme_slug, topic_collection_slug, topic_slug, page_slug ] janis_url = '/'.join(filter(None, (paths_list))) return janis_url # collect all our path elements paths_list = [ base_url, theme_slug, topic_collection_slug, topic_slug, page_slug ] # join them together, filtering out empty ones janis_url = '/'.join(filter(None, (paths_list))) return janis_url except Exception as e: # right now this is a catch-all, print("!janis url error!:", self.title, e) print(traceback.format_exc()) return "#" pass def janis_preview_url_end(self, revision=None): """ Optional "revision" parameter to get the janis_preview_url for a specific revision Otherwise, it will return the janis_preview_url for the latest revision """ if revision: url_page_type = revision.page.janis_url_page_type else: revision = self.get_latest_revision() url_page_type = self.janis_url_page_type try: global_id = graphene.Node.to_global_id('PageRevisionNode', revision.id) url_end = f"preview/{url_page_type}/{global_id}" except AttributeError: # TODO: make previews work for test fixture pages that may not have revisions/global_ids url_end = f"preview/{url_page_type}/" if settings.ISSTAGING or settings.ISPRODUCTION: return url_end else: # Pass address of CMS_API if we are not running on Staging or Production # Janis will query from its default CMS_API if a param is not provided return url_end + f"?CMS_API={settings.CMS_API}" def janis_url_base(self, janis_branch): """ returns a valid url of the base URL in janis: Use hardcoded JANIS_URL for staging and prod Otherwise, use configurable branch setting TODO: this and url_base in site settings could probably be revisited for semantics to be less confusing """ if settings.ISSTAGING or settings.ISPRODUCTION: return os.getenv("JANIS_URL") else: branch_settings = JanisBranchSettings.objects.first() return branch_settings.url_base(janis_branch) # alias for url base function janis_preview_url_start = janis_url_base # TODO this function and preview_url_data are pretty similar, we can probably consolidate them def janis_preview_url(self, revision=None, lang="en"): return f"{self.janis_preview_url_start('preview_janis_branch')}/{lang}/{self.janis_preview_url_end(revision=revision)}" # data needed to construct preview URLs for any language # [janis_preview_url_start]/[lang]/[janis_preview_url_end] # ex: http://localhost:3000/es/preview/information/UGFnZVJldmlzaW9uTm9kZToyMjg= def preview_url_data(self, revision=None): return { "janis_preview_url_start": self.janis_preview_url_start('preview_janis_branch'), "janis_preview_url_end": self.janis_preview_url_end(revision=revision), } @property def status_string(self): """ override wagtail default see https://github.com/wagtail/wagtail/blob/f44d27642b4a6932de73273d8320bbcb76330c21/wagtail/core/models.py#L1010 """ if not self.live: if self.expired: return ("Expired") elif self.approved_schedule: return ("Scheduled") else: return ("Draft") else: if self.approved_schedule: return ("Live + Scheduled") elif self.has_unpublished_changes: return ("Live + Draft") else: return ("Live") @cached_classmethod def get_edit_handler(cls): if hasattr(cls, 'edit_handler'): return cls.edit_handler.bind_to(model=cls) editor_panels = [ ObjectList(cls.content_panels + [ AdminOnlyFieldPanel('coa_global', classname="admin-only-field") ], heading='Content'), ObjectList(cls.notes_content_panel, heading='Notes') ] try: if flag_enabled('SHOW_EXTRA_PANELS'): editor_panels += (PermissionObjectList(cls.promote_panels, heading='SEO'), PermissionObjectList(cls.settings_panels, heading='Settings')) except ProgrammingError as e: print("some problem, maybe with flags") pass edit_handler = TabbedInterface(editor_panels) return edit_handler.bind_to(model=cls) class Meta: abstract = True # https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#custom-permissions permissions = [ ("view_extra_panels", "Can view extra panels"), ("view_snippets", "Can view snippets"), ("add_snippets", "Can add snippet"), ("delete_snippets", "Can delete snippet"), ]
class HomePage(Page): body = RichTextField(blank=True) content_panels = Page.content_panels + [ FieldPanel('body', classname="full"), ]
class HomePage(RoutablePageMixin, Page): banner_title = models.CharField(max_length=100, blank=False, null=True) banner_subtitle = RichTextField(features=['bold', 'italic']) banner_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=False, on_delete=models.SET_NULL, related_name='+', ) banner_cta = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) content = StreamField([ ('cta', blocks.CTABlock()), ], null=True, blank=True) # only one on site max_count = 1 # parent_page_type = ['wagtailcore.Page'] subpage_types = [ 'blog.BlogListingPage', 'contact.ContactPage', 'flex.FlexPage', 'behance.BehanceProjectListingPage' ] # search_fields = Page.search_fields + [ # ] api_fields = [ APIField('banner_title'), APIField('banner_subtitle'), APIField('banner_image'), APIField('banner_cta'), APIField('carousel_images'), APIField('content'), ] content_panels = Page.content_panels + [ MultiFieldPanel([ InlinePanel('carousel_images', max_num=5, min_num=1, label='Image'), ], heading='Carousel Images'), StreamFieldPanel('content'), ] # promote_panels = [] # settings_panels = [] banner_panels = [ MultiFieldPanel([ FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ImageChooserPanel('banner_image'), PageChooserPanel('banner_cta'), ], heading='Banners Options'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(banner_panels, heading='Banner'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings'), ]) class Meta: verbose_name = 'Home Page' verbose_name_plural = 'Home Pages' @route(r'^subscribe/$') def the_subscribe_page(self, request, *args, **kwargs): context = self.get_context(request, *args, **kwargs) context['a_special_text'] = 'Hello world 123123' return render(request, 'home/subscribe.html', context)
class ChartPage(ChartOptionsMixin, SpecificInstructionsMixin, FallbackImageMixin, RoutablePageMixin, Page): """ Individual chart page """ parent_page_types = [VisualisationsPage] subpage_types = [] class Meta: verbose_name = 'Plotly Studio Chart Page' subtitle = models.TextField( blank=True, null=True, help_text="Optional: subtitle to appear underneath the chart title.") chart_json = JSONField( verbose_name="Chart JSON", help_text= 'Paste exported Chart Studio JSON here. To preserve data integretity, the JSON data should not be edited in Wagtail' ) caption = RichTextField( null=True, blank=True, help_text='Optional: caption text and link(s) for the chart', features=MINIMAL_RICHTEXT_FEATURES) content_panels = Page.content_panels + [ FieldPanel('subtitle'), FieldPanel('chart_json'), FallbackImagePanel(), ChartOptionsPanel(), SpecificInstructionsPanel(), FieldPanel('caption') ] @cached_property def parent(self): return self.get_parent().specific @cached_property def instructions_text(self): if self.instructions: return self.instructions elif self.display_general_instructions and self.parent.instructions: return self.parent.instructions return '' def get_sitemap_urls(self, request): return [] @route(r'^$') def chart(self, request): if request.user.is_authenticated: return super().serve(request) raise Http404() @route(r'^data/$') def data(self, request): return JsonResponse(self.chart_json)
class SingleQuestion(models.Model): question = RichTextField() image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) ans1 = models.CharField( max_length=255, null=True, blank=True, ) ans2 = models.CharField( max_length=255, null=True, blank=True, ) ans3 = models.CharField( max_length=255, null=True, blank=True, ) ans4 = models.CharField( max_length=255, null=True, blank=True, ) ans5 = models.CharField( max_length=255, null=True, blank=True, ) POSSIBLE_CHOICES = ( (1, 'A'), (2, 'B'), (3, 'C'), (4, 'D'), (5, 'E') ) correct_ans = models.IntegerField( max_length=2, choices=POSSIBLE_CHOICES, ) QUESTION_CATEGORIES = ( ('logic', 'Logic'), ('math', 'Math'), ('pattern', 'Pattern'), ('spacial', 'Spacial'), ('language', 'Language'), ('reasoning', 'Reasoning'), ) category = models.CharField( max_length=25, choices=QUESTION_CATEGORIES, ) panels = [ FieldPanel('question'), ImageChooserPanel('image'), FieldPanel('ans1'), FieldPanel('ans2'), FieldPanel('ans3'), FieldPanel('ans4'), FieldPanel('ans5'), FieldPanel('correct_ans'), FieldPanel('category'), ] @property def question_text(self): return strip_tags(self.question)
class EventPage(Page): date_from = models.DateField( "Start date", help_text= "Required for us to know if the event is in the future or past") date_to = models.DateField( "End date", null=True, blank=True, help_text="Not required if event is on a single day") time_from = models.TimeField("Start time", null=True, blank=True) time_to = models.TimeField("End time", null=True, blank=True) meta_information = RichTextField( blank=True, help_text= "Meta information about the event e.g. Haverford College, Department of economics" ) description = RichTextField( blank=True, help_text="The description written as though in the future") description_past = RichTextField( blank=True, help_text="The description written as though in the past") parent_page_types = ['EventIndexPage'] subpage_types = [] search_fields = Page.search_fields + [index.SearchField('description')] content_panels = Page.content_panels + [ FieldRowPanel([ FieldPanel('date_from'), FieldPanel('date_to'), ], ), FieldPanel('meta_information'), MultiFieldPanel([ FieldPanel('description'), FieldPanel('description_past'), ], heading="Description of event"), ] @property def event_index(self): # Find closest ancestor which is an event index return self.get_ancestors().type(EventIndexPage).last() # This is a legacy from PromptWorks. It introduces an ical download on the event page def serve(self, request): if "format" in request.GET: if request.GET['format'] == 'ical': # Export to ical format response = HttpResponse( export_event(self, 'ical'), content_type='text/calendar', ) response[ 'Content-Disposition'] = 'attachment; filename=' + self.slug + '.ics' return response else: # Unrecognised format error message = 'Could not export event\n\nUnrecognised format: ' + request.GET[ 'format'] return HttpResponse(message, content_type='text/plain') else: # Display event page as usual return super(EventPage, self).serve(request) class Meta: verbose_name = "Event"
class ContactPage(Page): # ---- General Page information ------ title_sv = models.CharField(max_length=255) translated_title = TranslatedField('title', 'title_sv') contact_point = StreamField([('person', ContactCardBlock())], ) other_contacts = StreamField( [('contact', blocks.StructBlock([ ('person', ContactCardBlock()), ('english_groups', blocks.CharBlock( help_text=_('Comma separated list of English group names.'), required=False, )), ('swedish_groups', blocks.CharBlock( help_text=_('Comma separated list of Swedish group names.'), required=False, )), ], icon='user'))], ) map_location = models.CharField( max_length=255, verbose_name=_('Map Location'), help_text=_('Enter comma separated coordinates'), blank=True, ) location_description = RichTextField( verbose_name=_('Location Description'), help_text=_('Enter the text to show on the map'), blank=True, ) def get_context(self, request, *args, **kwargs): contacts = {} for contact in self.other_contacts: if request.LANGUAGE_CODE == 'sv': groups = contact.value.get('swedish_groups', '') else: groups = contact.value.get('english_groups', '') groups = groups.split(',') for group in groups: group = group.strip() list = contacts.get(group, []) list.append(contact.value['person']) contacts[group] = list context = super(ContactPage, self).get_context(request, *args, **kwargs) context['contacts'] = contacts return context content_panels = Page.content_panels + [ FieldPanel('title_sv', classname="full title"), FieldPanel('map_location'), FieldPanel('location_description'), StreamFieldPanel('contact_point'), StreamFieldPanel('other_contacts'), ]