Example #1
0
class Report(RoutablePageMixin, Post):
    """
    Report class that inherits from the abstract
    Post model and creates pages for Policy Papers.
    """
    parent_page_types = ['ReportsHomepage']
    subpage_types = []

    sections = StreamField(
        [('section',
          ReportSectionBlock(template="components/report_section_body.html",
                             required=False))],
        null=True,
        blank=True)

    abstract = RichTextField(blank=True, null=True)

    acknowledgements = RichTextField(blank=True, null=True)

    source_word_doc = models.ForeignKey('wagtaildocs.Document',
                                        null=True,
                                        blank=True,
                                        on_delete=models.SET_NULL,
                                        related_name='+',
                                        verbose_name='Source Word Document')

    report_pdf = models.ForeignKey('wagtaildocs.Document',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+',
                                   verbose_name='Report PDF')

    dataviz_src = models.CharField(blank=True,
                                   null=True,
                                   max_length=300,
                                   help_text="")

    overwrite_sections_on_save = models.BooleanField(
        default=False,
        help_text=
        'If checked, sections and endnote fields ⚠ will be overwritten ⚠ with Word document source on save. Use with CAUTION!'
    )
    generate_pdf_on_publish = models.BooleanField(
        'Generate PDF on save',
        default=False,
        help_text=
        '⚠ Save latest content before checking this ⚠\nIf checked, the "Report PDF" field will be filled with a generated pdf. Otherwise, leave this unchecked and upload a pdf to the "Report PDF" field.'
    )
    revising = False

    featured_sections = StreamField([
        ('featured', FeaturedReportSectionBlock(required=False, null=True)),
    ],
                                    null=True,
                                    blank=True)

    endnotes = StreamField([
        ('endnote', EndnoteBlock(required=False, null=True)),
    ],
                           null=True,
                           blank=True)

    report_url = StreamField([
        ('report_url', URLBlock(required=False, null=True)),
    ],
                             null=True,
                             blank=True)

    attachment = StreamField([
        ('attachment', DocumentChooserBlock(required=False, null=True)),
    ],
                             null=True,
                             blank=True)

    partner_logo = models.ForeignKey('home.CustomImage',
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+')

    theme_full_bleed = models.BooleanField(
        default=False, help_text="Display bleed image on landing page")

    content_panels = [
        MultiFieldPanel([
            FieldPanel('title'),
            FieldPanel('subheading'),
            FieldPanel('date'),
            ImageChooserPanel('story_image'),
        ]),
        InlinePanel('authors', label=("Authors")),
        InlinePanel('programs', label=("Programs")),
        InlinePanel('subprograms', label=("Subprograms")),
        InlinePanel('topics', label=("Topics")),
        InlinePanel('location', label=("Locations")),
        MultiFieldPanel([
            FieldPanel('abstract'),
            StreamFieldPanel('featured_sections'),
            FieldPanel('acknowledgements'),
        ])
    ]

    sections_panels = [StreamFieldPanel('sections')]

    endnote_panels = [StreamFieldPanel('endnotes')]

    settings_panels = Post.settings_panels + [
        FieldPanel('theme_full_bleed'),
        FieldPanel('dataviz_src')
    ]

    promote_panels = Page.promote_panels + [
        FieldPanel('story_excerpt'),
    ]

    pdf_panels = [
        MultiFieldPanel([
            DocumentChooserPanel('source_word_doc'),
            FieldPanel('overwrite_sections_on_save'),
        ],
                        heading='Word Doc Import'),
        MultiFieldPanel([
            FieldPanel('generate_pdf_on_publish'),
            DocumentChooserPanel('report_pdf'),
            StreamFieldPanel('attachment')
        ],
                        heading='PDF Generation'),
        ImageChooserPanel('partner_logo')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Landing"),
        ObjectList(sections_panels, heading="Sections"),
        ObjectList(endnote_panels, heading="Endnotes"),
        ObjectList(promote_panels, heading="Promote"),
        ObjectList(settings_panels, heading='Settings', classname="settings"),
        ObjectList(pdf_panels, heading="PDF Publishing")
    ])

    search_fields = Post.search_fields + [index.SearchField('sections')]

    def get_context(self, request):
        context = super().get_context(request)

        if getattr(request, 'is_preview', False):
            import newamericadotorg.api.report
            revision = PageRevision.objects.filter(
                page=self).last().as_page_object()
            report_data = newamericadotorg.api.report.serializers.ReportDetailSerializer(
                revision).data
            context['initial_state'] = json.dumps(report_data)

        return context

    def save(self, *args, **kwargs):
        super(Report, self).save(*args, **kwargs)

        if not self.overwrite_sections_on_save and not self.generate_pdf_on_publish:
            self.revising = False

        if not self.revising and self.source_word_doc is not None and self.overwrite_sections_on_save:
            self.revising = True
            parse_pdf(self)
            self.overwrite_sections_on_save = False
            self.save_revision()

        if not self.revising and self.generate_pdf_on_publish:
            generate_pdf.apply_async(args=(self.id, ))

    # Extra views

    @route(r'pdf/$')
    def pdf(self, request):
        if not self.report_pdf:
            return self.pdf_render(request)
        url = 'https://s3.amazonaws.com/newamericadotorg/' + self.report_pdf.file.name
        return redirect(url)

    @route(r'pdf/render/$')
    def pdf_render(self, request):
        response = HttpResponse(content_type='application/pdf;')
        response[
            'Content-Disposition'] = 'inline; filename=%s.pdf' % self.title
        response['Content-Transfer-Encoding'] = 'binary'
        protocol = 'https://' if request.is_secure() else 'http://'
        base_url = protocol + request.get_host()

        contents = generate_report_contents(self)
        authors = get_report_authors(self)

        html = loader.get_template('report/pdf.html').render({
            'page': self,
            'contents': contents,
            'authors': authors
        })
        pdf = write_pdf(response, html, base_url)

        return response

    @route(r'print/$')
    def print(self, request):
        contents = generate_report_contents(self)
        authors = get_report_authors(self)

        return render(request,
                      'report/pdf.html',
                      context={
                          'page': self,
                          'contents': contents,
                          'authors': authors
                      })

    @route(r'[a-zA-Z0-9_\.\-]*/$')
    def section(self, request):
        # Serve the whole report, subsection routing is handled by React
        return self.serve(request)

    class Meta:
        verbose_name = 'Report'
Example #2
0
class ResourceItemPage(MethodsBasePage):
    subpage_types = []

    parent_page_type = ['contentPages.ResourcesPage']

    heading = TextField()
    description = RichTextField(blank=True, null=True, default='')
    preview_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    preview_image_screen_reader_text = TextField(blank=True, default='')

    link_document = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        related_name='+',
        on_delete=models.SET_NULL,
        verbose_name='Upload document'
    )

    document_type = models.ForeignKey(
        'contentPages.CreateNewResourceType',
        null=True,
        blank=False,
        related_name='+',
        on_delete=models.SET_NULL,
    )

    upload_link = TextField(blank=True, default='')

    product_code = CharField(max_length=256, blank=True, null=True, default='')
    overview = RichTextField(blank=True, default='')
    format = CharField(max_length=256, blank=True, null=True, default='')
    file_size = CharField(max_length=256, blank=True, null=True, default='')

    content_panels = MethodsBasePage.content_panels + [

        MultiFieldPanel([
            FieldPanel('heading'),
            FieldPanel('description'),
            FieldPanel('upload_link'),
            DocumentChooserPanel('link_document'),
            FieldPanel('document_type'),
            ImageChooserPanel('preview_image'),
            FieldPanel('preview_image_screen_reader_text'),
        ], heading='Header section'),
        MultiFieldPanel([
            FieldPanel('product_code'),
            FieldPanel('overview'),
            FieldPanel('format'),
            FieldPanel('file_size'),
        ], heading='Details'),
    ]

    @property
    def link_url(self):
        return settings.FINAL_SITE_DOMAIN + self.url

    @property
    def campaign_name(self):
        grandparent = self.get_parent().get_parent()
        return grandparent.specific.heading

    @property
    def campaign_live(self):
        try:
            grandparent = self.get_parent().get_parent()
            return grandparent.live
        except Exception:
            return False
Example #3
0
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

    def paginated_authors(self, info, **kwargs):
        return resolve_paginated_queryset(self.authors, info, **kwargs)

    graphql_fields = [
        GraphQLString("heading"),
        GraphQLString("date", required=True),
        GraphQLStreamfield("body"),
        GraphQLCollection(
            GraphQLForeignKey,
            "related_links",
            "home.blogpagerelatedlink",
            required=True,
            item_required=True,
        ),
        GraphQLCollection(GraphQLString, "related_urls", source="related_links.url"),
        GraphQLCollection(GraphQLString, "authors", source="authors.person.name"),
        GraphQLCollection(
            GraphQLForeignKey,
            "paginated_authors",
            "home.Author",
            is_paginated_queryset=True,
        ),
        GraphQLSnippet("advert", "home.Advert"),
        GraphQLImage("cover"),
        GraphQLDocument("book_file"),
        GraphQLMedia("featured_media"),
        GraphQLForeignKey("copy", "home.BlogPage"),
        GraphQLPage("author"),
    ]
Example #4
0
class Performances(index.Indexed, ClusterableModel):
    template = "performances/performances_page.html"

    #
    # essential_writing = models.ForeignKey(
    #     'assignments_types.EssentialWriting',
    #     on_delete=models.SET_NULL,
    #     related_name='+',
    #     null=True,
    #     help_text="Enter any lexis terms connected to this performance."
    # )

    # Create Fields //////////////////
    event_collection = models.ForeignKey(
        "events.CategoryEventCollection",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
        help_text="Enter collection to which this performance belongs.")

    # performance_type = models.CharField(
    #     max_length=100,
    #     blank=True,
    #     null=True,
    #     help_text="Enter performance category type for this item."
    # )

    performance_description = models.TextField(
        blank=True,
        null=True,
        help_text="Enter performance category type for this item.")
    performance_title = models.CharField(
        max_length=300,
        blank=True,
        null=True,
        help_text="Enter performance category type for this item.")

    performance_overview = RichTextField(
        features=['bold', 'italic', 'link'],
        blank=True,
        null=True,
        help_text="Enter a five-sentence overview of the performance.")

    performance_assignment = models.TextField(
        blank=True,
        null=True,
        help_text=
        "Enter a one sentence description that states how the learning outcome will be met in this performance."
    )

    # Create StreamFields //////////////////
    # performance_feats = StreamField(
    #     [("Performance_Feats", PerformanceFeats()), ],
    #     null=True,
    #     blank=True,
    #     help_text="Enter feats students should be able to perform."
    # )

    # document link//////////////////////////////
    resource_link = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text="Enter feats students should be able to perform.")

    # document link//////////////////////////////
    student_sample = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text=
        "Upload samples of student work that successfully demonstrate this performance."
    )

    # rating system
    # rating system
    RATING = (
        ('gold', 'Gold Star'),
        ('silver', 'Silver Star'),
        ('red', 'Red Star'),
        ('blue', 'Blue Star'),
        ('green', 'Green Star'),
    )
    star_value = models.CharField(
        max_length=20,
        blank=True,
        null=True,
        choices=RATING,
        help_text="Enter the rating value of the performance.")

    video_link = models.CharField(
        max_length=500,
        blank=True,
        null=True,
        help_text=
        "Provide a link that demonstrates how to teach this performance.")

    # Admin Display Panels
    panels = [
        FieldPanel("event_collection", widget=EventChooser),
        # FieldPanel("essential_writing"),
        MultiFieldPanel(
            [
                # FieldPanel("performance_type", classname="col12 line"),
                FieldPanel("performance_title", classname="col12 line"),
                FieldPanel("performance_description", classname="col12 line"),
                FieldPanel("performance_assignment", classname="col12 line"),
                FieldPanel("performance_overview", classname="col12 line"),

                # DocumentChooserPanel("resource_link", classname="col6"),
                # DocumentChooserPanel("student_sample", classname="col6"),
                # FieldPanel("video_link", classname="col12 line-top"),
            ],
            heading="Performance Info",
        ),

        # inlineLinked lexis links attempt working
        InlinePanel('performance_lexis_link', label='Linked Lexis'),
        InlinePanel('performance_feat_link', label='Performance Feats'),

        # StreamFieldPanel("performance_feats"),
        MultiFieldPanel(
            [
                DocumentChooserPanel("resource_link", classname="col12 line"),
                DocumentChooserPanel("student_sample", classname="col12 line"),
                FieldPanel("video_link", classname="col12"),
            ],
            heading="Resources & Samples",
        ),
        FieldPanel("star_value"),
    ]

    def __str__(self):
        return self.performance_title or ''

    search_fields = [
        index.SearchField('performance_title', partial_match=True),
    ]

    class Meta:
        verbose_name = "Performance"
        verbose_name_plural = "Performances"
Example #5
0
class PressIndex(Page):
    press_kit = models.ForeignKey('wagtaildocs.Document',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL,
                                  related_name='+')

    def get_press_kit(self):
        return build_image_url(self.press_kit)

    press_kit_url = property(get_press_kit)

    press_inquiry_name = models.CharField(max_length=255,
                                          blank=True,
                                          null=True)
    press_inquiry_phone = models.CharField(max_length=255)
    press_inquiry_email = models.EmailField()
    experts_heading = models.CharField(max_length=255)
    experts_blurb = models.TextField()
    mentions = StreamField([
        ('mention', NewsMentionBlock(icon='document')),
    ],
                           null=True)
    promote_image = models.ForeignKey('wagtailimages.Image',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')

    @property
    def releases(self):
        releases = PressRelease.objects.live().child_of(self)
        releases_data = {}
        for release in releases:
            releases_data['press/{}'.format(release.slug)] = {
                'detail_url': '/apps/cms/api/v2/pages/{}/'.format(release.pk),
                'date': release.date,
                'heading': release.heading,
                'excerpt': release.excerpt,
                'author': release.author,
            }
        return releases_data

    content_panels = Page.content_panels + [
        DocumentChooserPanel('press_kit'),
        FieldPanel('press_inquiry_name'),
        FieldPanel('press_inquiry_phone'),
        FieldPanel('press_inquiry_email'),
        FieldPanel('experts_heading'),
        FieldPanel('experts_blurb'),
        InlinePanel('experts_bios', label="Experts"),
        StreamFieldPanel('mentions'),
        InlinePanel('mission_statements', label="Mission Statement"),
    ]

    promote_panels = [
        FieldPanel('slug'),
        FieldPanel('seo_title'),
        FieldPanel('search_description'),
        ImageChooserPanel('promote_image')
    ]

    api_fields = [
        APIField('press_kit'),
        APIField('press_kit_url'),
        APIField('releases'),
        APIField('slug'),
        APIField('seo_title'),
        APIField('search_description'),
        APIField('promote_image'),
        APIField('experts_heading'),
        APIField('experts_blurb'),
        APIField('experts_bios'),
        APIField('mentions'),
        APIField('mission_statements'),
        APIField('press_inquiry_name'),
        APIField('press_inquiry_phone'),
        APIField('press_inquiry_email')
    ]

    subpage_types = ['news.PressRelease']
    parent_page_types = ['pages.HomePage']
    max_count = 1
Example #6
0
class SeriesPage(ThemeablePage, FeatureStyleFields, Promotable,
                 ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    subtitle = RichTextField(blank=True, default="")
    short_description = RichTextField(blank=True, default="")
    body = article_fields.BodyField(blank=True, default="")

    main_image = models.ForeignKey('images.AttributedImage',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    feature_image = models.ForeignKey('images.AttributedImage',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    primary_topic = models.ForeignKey('articles.Topic',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='series')
    project = models.ForeignKey(
        "projects.ProjectPage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    search_fields = Page.search_fields + [
        index.SearchField('subtitle', partial_match=True),
        index.SearchField('body', partial_match=True),
        index.SearchField('get_primary_topic_name', partial_match=True),
        index.SearchField('get_topic_names', partial_match=True),
    ]

    number_of_related_articles = models.PositiveSmallIntegerField(
        default=6, verbose_name="Number of Related Articles to Show")

    def get_primary_topic_name(self):
        if self.primary_topic:
            return self.primary_topic.name
        else:
            ""

    def get_topic_names(self):
        return '\n'.join(
            [topic.name if topic else "" for topic in self.topics])

    def get_author_names(self):
        return '\n'.join(
            [author.full_name if author else "" for author in self.authors])

    @property
    def articles(self):
        article_list = []
        for article_link in self.related_article_links.all():
            if article_link.article:
                article_link.article.override_text = article_link.override_text
                article_link.article.override_image = article_link.override_image
                article_list.append(article_link.article)
        return article_list

    @property
    def authors(self):
        author_list = []
        for article_link in self.related_article_links.all():
            if article_link.article:
                if article_link.article:
                    for author_link in article_link.article.author_links.all():
                        if author_link.author:
                            if author_link.author not in author_list:
                                author_list.append(author_link.author)
        author_list.sort(key=attrgetter('last_name'))
        return author_list

    @property
    def topics(self):
        all_topics = []
        if self.primary_topic:
            all_topics.append(self.primary_topic)
        for article_link in self.related_article_links.all():
            if article_link.article:
                all_topics.extend(article_link.article.topics)

        all_topics = list(set(all_topics))
        if all_topics:
            all_topics.sort(key=attrgetter('name'))
        return all_topics

    @property
    def related_series(self):
        related_series_list = []
        if self.project:
            related_series_list = self.project.get_related_series(self)
        return related_series_list

    def get_content(self):
        '''
        A generic and generative interface for getting all the content block for an article,
        including advanced content such as chapters.
        '''
        for block in self.body:
            yield block

    def related_articles(self, number):
        articles = []
        if self.primary_topic:
            articles = list(ArticlePage.objects.live().filter(
                primary_topic=self.primary_topic).distinct().order_by(
                    '-first_published_at')[:number])

        current_total = len(articles)
        if current_total < number:
            for article in self.articles:
                articles.extend(list(article.related_articles(number)))
                articles = list(set(articles))[:number]
                current_total = len(articles)

                if current_total >= number:
                    return articles

        return articles

    content_panels = Page.content_panels + [
        FieldPanel('subtitle'),
        FieldPanel('short_description'),
        PageChooserPanel('project'),
        ImageChooserPanel('main_image'),
        ImageChooserPanel('feature_image'),
        DocumentChooserPanel('video_document'),
        StreamFieldPanel('body'),
        InlinePanel('related_article_links', label="Articles"),
        SnippetChooserPanel('primary_topic'),
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('sticky'),
            FieldPanel('sticky_for_type_section'),
            FieldPanel('slippery'),
            FieldPanel('slippery_for_type_section'),
            FieldPanel('editors_pick'),
            FieldPanel('feature_style'),
            FieldPanel('title_size'),
            FieldPanel('fullbleed_feature'),
            FieldPanel('image_overlay_opacity'),
        ],
                        heading="Featuring Settings")
    ]

    style_panels = ThemeablePage.style_panels + [
        MultiFieldPanel([
            FieldPanel('include_main_image'),
            FieldPanel('include_main_image_overlay'),
            FieldPanel('full_bleed_image_size'),
            FieldPanel('include_caption_in_footer'),
        ],
                        heading="Main Image"),
        MultiFieldPanel([
            FieldPanel('number_of_related_articles'),
        ],
                        heading="Sections")
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Example #7
0
class FacultyResources(models.Model):
    resource = models.ForeignKey(
        FacultyResource,
        null=True,
        help_text="Manage resources through snippets.",
        related_name='+',
        on_delete=models.SET_NULL
    )

    def get_resource_heading(self):
        return self.resource.heading
    resource_heading = property(get_resource_heading)

    def get_resource_description(self):
        return self.resource.description
    resource_description = property(get_resource_description)

    def get_resource_unlocked(self):
        return self.resource.unlocked_resource
    resource_unlocked = property(get_resource_unlocked)

    def get_resource_creator_fest_resource(self):
        return self.resource.creator_fest_resource
    creator_fest_resource = property(get_resource_creator_fest_resource)

    link_external = models.URLField("External link", blank=True, help_text="Provide an external URL starting with http:// (or fill out either one of the following two).")
    link_page = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        related_name='+',
        on_delete=models.SET_NULL,
        help_text="Or select an existing page to attach."
    )
    link_document = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        related_name='+',
        on_delete=models.SET_NULL,
        help_text="Or select a document for viewers to download."
    )

    def get_link_document(self):
        return build_document_url(self.link_document.url)
    link_document_url = property(get_link_document)

    def get_document_title(self):
        return self.link_document.title
    link_document_title = property(get_document_title)

    link_text = models.CharField(max_length=255, help_text="Call to Action Text")
    coming_soon_text = models.CharField(max_length=255, null=True, blank=True, help_text="If there is text in this field a coming soon banner will be added with this description.")
    updated = models.DateTimeField(blank=True, null=True, help_text='Late date resource was updated')
    featured = models.BooleanField(default=False, help_text="Add to featured bar on resource page")

    api_fields = [
        APIField('resource_heading'),
        APIField('resource_description'),
        APIField('resource_unlocked'),
        APIField('creator_fest_resource'),
        APIField('link_external'),
        APIField('link_page'),
        APIField('link_document_url'),
        APIField('link_document_title'),
        APIField('link_text'),
        APIField('coming_soon_text'),
        APIField('updated'),
        APIField('featured')
    ]

    panels = [
        SnippetChooserPanel('resource'),
        FieldPanel('link_external'),
        PageChooserPanel('link_page'),
        DocumentChooserPanel('link_document'),
        FieldPanel('link_text'),
        FieldPanel('coming_soon_text'),
        FieldPanel('updated'),
        FieldPanel('featured')
    ]
class ArticlePage(FoundationMetadataPageMixin, Page):

    """

    Articles can belong to any page in the Wagtail Tree.
    An ArticlePage can have no children
    If not a child of a Publication Page, page nav at bottom of page
    and breadcrumbs will not render.
    """
    subpage_types = []
    body = StreamField(article_fields)

    toc_thumbnail_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        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='+',
        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)

    article_file = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    show_side_share_buttons = models.BooleanField(
        default=True,
        help_text="Show social share buttons on the side"
    )

    content_panels = [
        FieldPanel(
            "title",
            classname="full title",
            widget=TitleWidget(attrs={"class": "max-length-warning", "data-max-length": 60})
        ),
        MultiFieldPanel([
            InlinePanel("authors", label="Author", min_num=0)
        ], heading="Author(s)"),
        MultiFieldPanel([
            ImageChooserPanel("toc_thumbnail_image"),
        ], heading="Table of Content Thumbnail"),
        MultiFieldPanel([
            ImageChooserPanel("hero_image"),
            FieldPanel('subtitle'),
            FieldPanel('secondary_subtitle'),
            FieldPanel('publication_date'),
            DocumentChooserPanel('article_file'),
        ], heading="Hero"),
        FieldPanel('show_side_share_buttons'),
        StreamFieldPanel('body'),
        InlinePanel("footnotes", label="Footnotes"),
    ]

    translatable_fields = [
        # Promote tab fields
        SynchronizedField('slug'),
        TranslatableField('seo_title'),
        SynchronizedField('show_in_menus'),
        TranslatableField('search_description'),
        SynchronizedField('search_image'),
        # Content tab fields
        TranslatableField('title'),
        SynchronizedField('toc_thumbnail_image'),
        SynchronizedField('hero_image'),
        TranslatableField('subtitle'),
        SynchronizedField('article_file'),
        TranslatableField('body'),
        TranslatableField('footnotes'),
    ]

    @property
    def is_publication_article(self):
        parent = self.get_parent().specific
        return parent.__class__ is PublicationPage

    @property
    def next_page(self):
        """
        Get the next page for a publication. Details below:

        Check the parent page type. If the parent page type is a "Chapter Page",
        then look for siblings of `this` page. If no next sibling can be found
        look for the parent page next sibling. And if that cannot be found,
        return the Chapter Page's parent (Publication Page).
        Otherwise if the parent page is a Publication page: look for the next sibling,
        if there is no next sibling page, return this pages' parent.
        """

        parent = self.get_parent().specific
        next_page = self.get_siblings().filter(path__gt=self.path, live=True).first()
        if parent.is_chapter_page:
            # if there is no next page look for the next chapter
            if not next_page:
                next_page = parent.get_siblings().filter(path__gt=self.path, live=True).first()
                # if there is no next chapter return to the parent.get_parent()
                if not next_page:
                    next_page = parent.get_parent()
        else:
            # Parent is a PublicationPage, not a chapter page
            # if there is no next page, return the parent
            if not next_page:
                next_page = parent

        return next_page

    @property
    def prev_page(self):
        """
        Get the previous page for a publication. Details below:

        Check the parent page type. If the parent page type is a "Chapter Page",
        then look for siblings of `this` page. If no previous sibling can be found
        look for the parent page previous sibling. And if that cannot be found,
        return the Chapter Page's parent (Publication Page).
        Otherwise if the parent page is a Publication page: look for the previous sibling,
        if there is no previous sibling page, return this pages' parent.
        """

        parent = self.get_parent().specific
        prev_page = self.get_siblings().filter(path__lt=self.path, live=True).reverse().first()
        if parent.is_chapter_page:
            # look for the previous page in this chapter
            # if there is no previous page look for the previous chapter
            if not prev_page:
                prev_page = parent.get_siblings().filter(path__lt=self.path, live=True).reverse().first()
                # if there is no previous chapter return to the parent.get_parent()
                if not prev_page:
                    prev_page = parent.get_parent()
        else:
            # Parent is a PublicationPage, not a chapter page
            # look for the previous page in this publication
            # if there is no previous page, return the parent
            if not prev_page:
                prev_page = parent

        return prev_page

    def breadcrumb_list(self):
        """
        Get all the parent PublicationPages and return a QuerySet
        """
        return Page.objects.ancestor_of(self).type(PublicationPage).live()

    @property
    def zen_nav(self):
        return True

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        # Add get_titles to the page context. This is in get_context() because
        # we need access to the `request` object
        # menu_items is required for zen_nav in the templates
        context['get_titles'] = get_plaintext_titles(request, self.body, "content")
        return set_main_site_nav_information(self, context, 'Homepage')
Example #9
0
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'
Example #10
0
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']

    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,
    )
    notes = RichTextField(blank=True, features=['link', 'bold', 'italic'])
    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('hero_image'),
                DocumentChooserPanel('publication_file'),
                InlinePanel('authors', label='Author'),
                FieldPanel('additional_author_copy'),
            ],
            heading='Hero',
        ),
        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.
        """
        if self.is_chapter_page:
            next_page = self.get_siblings().filter(path__gt=self.path,
                                                   live=True).first()
            if not next_page:
                # If there is no more chapters. Return the parent page.
                next_page = self.get_parent()
        return next_page

    @property
    def prev_page(self):
        """
        Only applies to Chapter Publication (sub-Publication Pages).
        Returns a Page object or None.
        """
        if self.is_chapter_page:
            prev_page = self.get_siblings().filter(
                path__lt=self.path, live=True).reverse().first()
            if not prev_page:
                # If there is no more chapters. Return the parent page.
                prev_page = self.get_parent()
        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')
Example #11
0
class ProjectPage(ContactFieldsMixin, BasePage):
    template = "patterns/pages/project/project_detail.html"

    hero_image = models.ForeignKey(
        "images.CustomImage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    introduction = models.CharField(max_length=500, blank=True)
    introduction_image = models.ForeignKey(
        get_image_model_string(),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    video_caption = models.CharField(
        blank=True,
        max_length=80,
        help_text=_("The text displayed next to the video play button"),
    )
    video = models.URLField(blank=True)
    body = StreamField(
        [
            ("quote_block", QuoteBlock()),
            (
                "rich_text_block",
                RichTextBlock(features=[
                    "h2", "h3", "bold", "italic", "image", "ul", "ol", "link"
                ]),
            ),
            ("link_block", LinkBlock()),
        ],
        blank=True,
        verbose_name=_("Body copy"),
    )
    start_date = models.DateField(blank=True, null=True)
    end_date = models.DateField(blank=True, null=True)
    funding = models.CharField(max_length=250, blank=True)
    specification_document = models.ForeignKey(
        "documents.CustomDocument",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.SET_NULL,
        verbose_name=_("Project PDF"),
    )
    specification_document_link_text = models.CharField(
        max_length=80,
        blank=True,
        null=True,
        verbose_name=_("Project PDF link text"),
        help_text=
        _("You must enter link text if you add a Project PDF, e.g. 'Download project PDF'"
          ),
    )

    gallery = StreamField([("slide", GalleryBlock())],
                          blank=True,
                          verbose_name=_("Gallery"))
    more_information_title = models.CharField(max_length=80,
                                              default="More information")
    more_information = StreamField(
        [("accordion_block", AccordionBlockWithTitle())],
        blank=True,
        verbose_name=_("More information"),
    )
    partners = StreamField([("link", LinkBlock())],
                           blank=True,
                           verbose_name=_("Links to partners"))
    funders = StreamField([("link", LinkBlock())],
                          blank=True,
                          verbose_name=_("Links to funders"))
    quote_carousel = StreamField([("quote", QuoteBlock())],
                                 blank=True,
                                 verbose_name=_("Quote carousel"))
    external_links = StreamField([("link", LinkBlock())],
                                 blank=True,
                                 verbose_name="External Links")
    content_panels = BasePage.content_panels + [
        MultiFieldPanel(
            [ImageChooserPanel("hero_image")],
            heading=_("Hero"),
        ),
        MultiFieldPanel(
            [
                FieldPanel("introduction"),
                ImageChooserPanel("introduction_image"),
                FieldPanel("video"),
                FieldPanel("video_caption"),
            ],
            heading=_("Introduction"),
        ),
        StreamFieldPanel("body"),
        StreamFieldPanel("gallery"),
        MultiFieldPanel(
            [
                FieldPanel("more_information_title"),
                StreamFieldPanel("more_information"),
            ],
            heading=_("More information"),
        ),
        MultiFieldPanel(
            [
                InlinePanel(
                    "project_lead", label="Project team lead", max_num=1),
                InlinePanel("related_staff", label="Project team"),
            ],
            "Project team and staff",
        ),
        InlinePanel("related_student_pages", label="Project students"),
        StreamFieldPanel("partners"),
        StreamFieldPanel("funders"),
        StreamFieldPanel("quote_carousel"),
        StreamFieldPanel("external_links"),
        MultiFieldPanel([*ContactFieldsMixin.panels],
                        heading="Contact information"),
    ]

    key_details_panels = [
        InlinePanel("related_sectors", label=_("Innovation RCA sectors")),
        InlinePanel("related_research_themes", label=_("Research themes")),
        InlinePanel("expertise", label=_("RCA Expertise")),
        InlinePanel("related_school_pages", label=_("Related schools")),
        InlinePanel("related_research_pages",
                    label=_("Related research centres")),
        InlinePanel("research_types", label=_("Research types")),
        FieldPanel("start_date"),
        FieldPanel("end_date"),
        FieldPanel("funding"),
        MultiFieldPanel(
            [
                DocumentChooserPanel("specification_document"),
                FieldPanel("specification_document_link_text"),
            ],
            heading="PDF download",
        ),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(key_details_panels, heading="Key details"),
        ObjectList(BasePage.promote_panels, heading="Promote"),
        ObjectList(BasePage.settings_panels, heading="Settings"),
    ])

    def get_related_projects(self):
        """
        Displays latest projects from the parent School/Centre  the project belongs to.
        IF there are no projects with the same theme School/Centre latest projects with a
        matching research_type will be displayed.
        IF there are no projects with a matching research_type, the latest projects with
        matching expertise tags will be displayed.

        Returns:
            List -- of filtered and formatted ProjectPages
        """

        all_projects = ProjectPage.objects.live().public().not_page(self)

        schools = self.related_school_pages.values_list("page_id")
        projects = all_projects.filter(
            related_school_pages__page_id__in=schools).distinct()
        if projects:
            return format_projects_for_gallery(projects)

        research_centres = self.related_research_pages.values_list("page_id")
        projects = all_projects.filter(
            related_research_pages__page_id__in=research_centres).distinct()
        if projects:
            return format_projects_for_gallery(projects)

        research_types = self.research_types.values_list("research_type_id")
        projects = all_projects.filter(
            research_types__research_type_id__in=research_types).distinct()
        if projects:
            return format_projects_for_gallery(projects)

        expertise = self.expertise.values_list("area_of_expertise_id")
        projects = all_projects.filter(
            expertise__area_of_expertise_id__in=expertise).distinct()

        if projects:
            return format_projects_for_gallery(projects)

    def clean(self):
        super().clean()
        errors = defaultdict(list)

        if self.end_date and self.end_date < self.start_date:
            errors["end_date"].append(
                _("Events involving time travel are not supported"))
        if self.specification_document and not self.specification_document_link_text:
            errors["specification_document_link_text"].append(
                "You must provide link text for the document")

        if errors:
            raise ValidationError(errors)

    def get_related_school_or_centre(self):
        # returns the first related schools page, if none, return the related research
        # centre page
        related_school = self.related_school_pages.first()
        related_research_page = self.related_research_pages.first()
        if related_school:
            return related_school.page
        elif related_research_page:
            return related_research_page.page

    def get_expertise_linked_filters(self):
        """ For the expertise taxonomy thats listed out in key details,
        they need to link to the parent project picker page with a filter pre
        selected"""
        # Get parent page
        parent_picker = ProjectPickerPage.objects.parent_of(
            self).live().first()
        expertise = []
        for i in self.expertise.all().select_related("area_of_expertise"):
            if parent_picker:
                expertise.append({
                    "title":
                    i.area_of_expertise.title,
                    "link":
                    f"{parent_picker.url}?expertise={i.area_of_expertise.slug}",
                })
            else:
                expertise.append({"title": i.area_of_expertise.title})
        return expertise

    def get_sector_linked_filters(self):
        """ For the sector taxonomy thats listed out in key details,
        they need to link to the parent project picker page with a filter pre
        selected"""

        parent_picker = ProjectPickerPage.objects.parent_of(
            self).live().first()
        sectors = []
        for i in self.related_sectors.all().select_related("sector"):
            if parent_picker:
                sectors.append({
                    "title":
                    i.sector.title,
                    "link":
                    f"{parent_picker.url}?sector={i.sector.slug}",
                })
            else:
                sectors.append({"title": i.sector.title})
        return sectors

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        taxonomy_tags = []
        if self.related_school_pages:
            for i in self.related_school_pages.all():
                taxonomy_tags.append({"title": i.page.title})
        if self.related_research_pages:
            for i in self.related_research_pages.all():
                taxonomy_tags.append({"title": i.page.title})
        if self.research_types:
            for i in self.research_types.all():
                taxonomy_tags.append({"title": i.research_type.title})

        context["expertise"] = self.get_expertise_linked_filters()
        context["sectors"] = self.get_sector_linked_filters()
        context["project_lead"] = self.project_lead.select_related("image")
        context["related_staff"] = self.related_staff.select_related("image")
        context["taxonomy_tags"] = taxonomy_tags
        context["related_projects"] = self.get_related_projects()

        return context

    @cached_property
    def is_startup_project(self):
        return len(
            self.research_types.filter(research_type__title="Start-up")) > 0
Example #12
0
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

    TODO: this poem is beautiful, but it may not belong here
    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']

    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,
    )
    notes = RichTextField(blank=True, )
    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('hero_image'),
            DocumentChooserPanel('publication_file'),
            InlinePanel("authors", label="Author"),
            FieldPanel("additional_author_copy"),
        ],
                        heading="Hero"),
        FieldPanel('contents_title'),
        FieldPanel('notes')
    ]
Example #13
0
class PublicationPage(
    BasicPageAbstract,
    ContentPage,
    FeatureablePageAbstract,
    FromTheArchivesPageAbstract,
    ShareablePageAbstract,
):
    """View publication page"""

    class BookFormats(models.TextChoices):
        HARDCOVER = ('HC', 'Hardcover')
        PAPERBACK = ('PB', 'Paperback')
        TRADE_PB = ('TP', 'Trade PB')

    class PublicationTypes(models.TextChoices):
        BOOKS = ('books', 'Books')
        CIGI_COMMENTARIES = ('cigi_commentaries', 'CIGI Commentaries')
        CIGI_PAPERS = ('cigi_papers', 'CIGI Papers')
        COLLECTED_SERIES = ('collected_series', 'Collected Series')
        CONFERENCE_REPORTS = ('conference_reports', 'Conference Reports')
        ESSAY_SERIES = ('essay_series', 'Essay Series')
        POLICY_BRIEFS = ('policy_briefs', 'Policy Briefs')
        POLICY_MEMOS = ('policy_memos', 'Policy Memos')
        SPECIAL_REPORTS = ('special_reports', 'Special Reports')
        SPEECHES = ('speeches', 'Speeches')
        STUDENT_ESSAY = ('student_essay', 'Student Essay')

    book_excerpt = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link'],
        verbose_name='Excerpt',
    )
    book_excerpt_download = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Excerpt Download',
    )
    book_format = models.CharField(
        blank=True,
        max_length=2,
        choices=BookFormats.choices,
        verbose_name='Format',
        help_text='Select the formation of this book/publication.',
    )
    book_pages = models.IntegerField(
        blank=True,
        null=True,
        verbose_name='Pages',
        help_text='Enter the number of pages in the book.',
    )
    book_publisher = models.CharField(blank=True, max_length=255)
    book_publisher_url = models.URLField(blank=True)
    book_purchase_links = StreamField(
        [
            ('purchase_link', BookPurchaseLinkBlock())
        ],
        blank=True,
    )
    ctas = StreamField(
        [
            ('ctas', CTABlock())
        ],
        blank=True,
        verbose_name='Call to Action Buttons',
    )
    editorial_reviews = StreamField(
        [
            ('editorial_review', RichTextBlock(
                features=['bold', 'italic', 'link'],
            )),
        ],
        blank=True,
    )
    embed_issuu = models.URLField(
        blank=True,
        verbose_name='Issuu Embed',
        help_text='Enter the Issuu URL (https://issuu.com/cigi/docs/modern_conflict_and_ai_web) to add an embedded Issuu document.',
    )
    embed_youtube = models.URLField(
        blank=True,
        verbose_name='YouTube Embed',
        help_text='Enter the YouTube URL (https://www.youtube.com/watch?v=4-Xkn1U1DkA) or short URL (https://youtu.be/o5acQ2GxKbQ) to add an embedded video.',
    )
    image_cover = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Cover image',
        help_text='An image of the cover of the publication.',
    )
    image_poster = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Poster image',
        help_text='A poster image which will be used in the highlights section of the homepage.',
    )
    isbn = models.CharField(
        blank=True,
        max_length=32,
        verbose_name='ISBN',
        help_text='Enter the print ISBN for this book.',
    )
    isbn_ebook = models.CharField(
        blank=True,
        max_length=32,
        verbose_name='eBook ISBN',
        help_text='Enter the ISBN for the eBook version of this publication.',
    )
    isbn_hardcover = models.CharField(
        blank=True,
        max_length=32,
        verbose_name='Hardcover ISBN',
        help_text='Enter the ISBN for the hardcover version of this publication.',
    )
    pdf_downloads = StreamField(
        [
            ('pdf_download', PDFDownloadBlock())
        ],
        blank=True,
        verbose_name='PDF Downloads',
    )
    publication_series = models.ForeignKey(
        'publications.PublicationSeriesPage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    publication_type = models.ForeignKey(
        'publications.PublicationTypePage',
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='publications',
    )
    short_description = RichTextField(
        blank=True,
        null=False,
        features=['bold', 'italic', 'link'],
    )

    # Reference field for the Drupal-Wagtail migrator. Can be removed after.
    drupal_node_id = models.IntegerField(blank=True, null=True)

    def featured_person_list(self):
        """
        For featured publications, only display the first 3 authors/editors.
        """

        # @todo test
        person_list = list(self.authors.all()) + list(self.editors.all())
        del person_list[3:]
        result = []
        for person in person_list:
            if person.author:
                result.append(person.author)
            elif person.editor:
                result.append(person.editor)
        return result

    def featured_person_list_has_more(self):
        """
        If there are more than 3 authors/editors for featured publications,
        display "and more".
        """

        # @todo test
        return (self.author_count + self.editor_count) > 3

    def has_book_metadata(self):
        return (
            self.publication_type and
            self.publication_type.title == 'Books' and
            (self.book_format or self.book_pages or self.book_publisher or self.isbn or self.isbn_ebook or self.isbn_hardcover)
        )

    content_panels = [
        BasicPageAbstract.title_panel,
        MultiFieldPanel(
            [
                FieldPanel('short_description'),
                StreamFieldPanel('body'),
            ],
            heading='Body',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                PageChooserPanel(
                    'publication_type',
                    ['publications.PublicationTypePage'],
                ),
                FieldPanel('publishing_date'),
            ],
            heading='General Information',
            classname='collapsible collapsed',
        ),
        ContentPage.authors_panel,
        ContentPage.editors_panel,
        MultiFieldPanel(
            [
                FieldPanel('book_publisher'),
                FieldPanel('book_publisher_url'),
                FieldPanel('book_format'),
                FieldPanel('isbn'),
                FieldPanel('isbn_hardcover'),
                FieldPanel('isbn_ebook'),
                FieldPanel('book_pages'),
                StreamFieldPanel('book_purchase_links'),
                DocumentChooserPanel('book_excerpt_download'),
                FieldPanel('book_excerpt'),
            ],
            heading='Book Info',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                StreamFieldPanel('editorial_reviews'),
            ],
            heading='Editorial Reviews',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                ImageChooserPanel('image_cover'),
                ImageChooserPanel('image_poster'),
            ],
            heading='Images',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('embed_issuu'),
                StreamFieldPanel('pdf_downloads'),
                StreamFieldPanel('ctas'),
                FieldPanel('embed_youtube'),
            ],
            heading='Media',
            classname='collapsible collapsed',
        ),
        ContentPage.recommended_panel,
        MultiFieldPanel(
            [
                FieldPanel('topics'),
                PageChooserPanel(
                    'publication_series',
                    ['publications.PublicationSeriesPage'],
                ),
                FieldPanel('projects'),
            ],
            heading='Related',
            classname='collapsible collapsed',
        ),
        FromTheArchivesPageAbstract.from_the_archives_panel,
    ]
    promote_panels = Page.promote_panels + [
        FeatureablePageAbstract.feature_panel,
        ShareablePageAbstract.social_panel,
        SearchablePageAbstract.search_panel,
    ]

    search_fields = BasicPageAbstract.search_fields \
        + ContentPage.search_fields \
        + [
            index.FilterField('publication_series'),
            index.FilterField('publication_type'),
            index.FilterField('publishing_date'),
        ]

    parent_page_types = ['publications.PublicationListPage']
    subpage_types = []
    templates = 'publications/publication_page.html'

    class Meta:
        verbose_name = 'Publication'
        verbose_name_plural = 'Publications'
Example #14
0
class NewsletterPage(Page):
    class CallToActionChoices(models.TextChoices):
        EXPLORE = ('explore', 'Explore')
        LEARN_MORE = ('learn_more', 'Learn More')
        LISTEN = ('listen', 'Listen')
        NO_CTA = ('no_cta', 'No CTA')
        PDF = ('pdf', 'PDF')
        READ = ('read', 'Read')
        RSVP = ('rsvp', 'RSVP')
        SHARE_FACEBOOK = ('share_facebook', 'Share (Facebook)')
        SHARE_LINKEDIN = ('share_linkedin', 'Share (LinkedIn)')
        SHARE_TWITTER = ('share_twitter', 'Share (Twitter)')
        SUBSCRIBE = ('subscribe', 'Subscribe')
        WATCH = ('watch', 'Watch')

    body = StreamField(
        [
            ('advertisement_block',
             StructBlock([
                 ('title', CharBlock(required=False)),
                 ('text', RichTextBlock(required=False)),
                 ('url', URLBlock(required=True)),
                 ('image', ImageChooserBlock(required=False)),
                 ('cta',
                  ChoiceBlock(
                      choices=CallToActionChoices.choices,
                      verbose_name='CTA',
                      required=True,
                  )),
             ])),
            ('content_block',
             StructBlock([
                 ('content', PageChooserBlock(required=False)),
                 ('url', URLBlock(required=False)),
                 ('title_override', CharBlock(required=False)),
                 ('text_override', RichTextBlock(required=False)),
                 ('cta',
                  ChoiceBlock(
                      choices=CallToActionChoices.choices,
                      verbose_name='CTA',
                      required=True,
                  )),
                 ('line_separator_above',
                  BooleanBlock(
                      verbose_name='Add line separator above block', )),
             ])),
            ('featured_content_block',
             StructBlock([
                 ('content', PageChooserBlock(required=False)),
                 ('url', URLBlock(required=False)),
                 ('title_override', CharBlock(required=False)),
                 ('text_override', RichTextBlock(required=False)),
                 ('image_override', ImageChooserBlock(required=False)),
                 ('cta',
                  ChoiceBlock(
                      choices=CallToActionChoices.choices,
                      verbose_name='CTA',
                      required=True,
                  )),
             ])),
            ('social_block',
             StructBlock([
                 ('title', CharBlock(required=False)),
                 ('text', RichTextBlock(required=False)),
             ])),
            ('text_block',
             StructBlock([
                 ('title', CharBlock(required=False)),
                 ('text', RichTextBlock(required=False)),
             ])),
        ],
        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',
        ),
        MultiFieldPanel(
            [
                DocumentChooserPanel('html_file'),
            ],
            heading='HTML File',
            classname='collapsible collapsed',
        ),
    ]

    parent_page_types = ['newsletters.NewsletterListPage']
    subpage_types = []
    templates = 'newsletters/newsletter_page.html'

    class Meta:
        verbose_name = 'Newsletter'
        verbose_name_plural = 'Newsletters'
Example #15
0
class Concert(MetadataPageMixin, Page):
    base_form_class = ConcertAdminForm
    promo_copy = RichTextField(
        blank=True,
        features=['bold', 'italic'],
    )
    description = StreamField([
        ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()),
        ('button',
         blocks.StructBlock([('button_text', blocks.CharBlock(required=True)),
                             ('button_link', blocks.URLBlock(required=True))],
                            template='main/blocks/button_block.html'))
    ])
    venue = RichTextField(
        default='St. Paul\'s Church, 315 West 22nd Street',
        features=['bold'],
    )
    concert_image = models.ForeignKey('wagtailimages.Image',
                                      null=True,
                                      blank=True,
                                      on_delete=models.PROTECT,
                                      related_name='concert_image')
    program_notes = models.ForeignKey('wagtaildocs.Document',
                                      null=True,
                                      blank=True,
                                      on_delete=models.PROTECT)
    roster = ParentalManyToManyField('ActiveRosterMusician', blank=True)
    season = models.CharField(max_length=9, null=True, blank=True)
    legacy_id = models.IntegerField(null=True, blank=True, unique=True)

    @staticmethod
    def calculate_season(date):
        # The first day of the concert season is Aug 1
        season_first_day = make_aware(datetime(date.year, 8, 1))
        if date >= season_first_day:
            return "{}-{}".format(date.year, date.year + 1)
        else:
            return "{}-{}".format(date.year - 1, date.year)

    def get_meta_description(self):
        return self.search_description or self.promo_copy

    def get_meta_image(self):
        return self.search_image or self.concert_image

    def get_context(self, request):
        context = super().get_context(request)
        performances = self.get_descendants().live().public().select_related(
            'performance__conductor', 'performance__composition')

        # Conductors
        # TODO: test that conductor exists, is live, and is public
        conductors = dict()
        for p in performances:
            conductor = p.specific.conductor
            if (conductor and conductor.is_live_public()):
                name = conductor.title
                conductors[name] = {
                    'name': name,
                    'last_name': p.specific.conductor.last_name,
                    'url': p.specific.conductor.url,
                    'headshot': p.specific.conductor.headshot,
                    'bio': p.specific.conductor.biography,
                }

        context['conductors'] = sorted(conductors.values(),
                                       key=lambda x: x['last_name'])

        # Program
        program = list()
        for p in performances:
            # First make a set of all performers for a given performance
            performers = list()
            # TODO: test that performer is live, is public()
            perfs = (p for p in p.specific.performer.all()
                     if p.person.is_live_public())
            for performer in perfs:
                performers.append({
                    'name': performer.person.title,
                    'url': performer.person.url,
                    'instrument': performer.instrument.instrument
                })

            # Then assemble with composer and composition
            cmpsr = p.specific.composition.composer
            composer = None
            if cmpsr.is_live_public():
                composer = cmpsr

            program.append({
                'composer': composer.title if composer else 'Anon.',
                'composition': p.specific.composition.title,
                'supplemental_text': p.specific.supplemental_text,
                'performers':
                performers  # TODO: what happens if performers is null?
            })
        context['program'] = program

        # Performer
        # This needs to display the performer, the work they are performing,
        # and the date they are performing it.
        performers = list()
        for soloist in (p.person
                        for p in self.performer.all().order_by('sort_order')
                        if p.person.is_live_public()):
            # Select performances that have this soloist as a performer
            perfs = Performance.objects.descendant_of(self).\
                filter(performer__person__id=soloist.id).live().public()

            # Build up an aggregation of solo performances
            solo_perfs = list()
            solo_instrument = set()
            for p in perfs:
                cmpsr = p.composition.composer
                composer = None
                if cmpsr.is_live_public():
                    composer = cmpsr

                solo_perfs.append({
                    'composer':
                    composer.title if composer else 'Anon.',
                    'work':
                    p.composition.title,
                    'dates': [d.date for d in p.performance_date.all()]
                })
                s = p.performer.get(person__id=soloist.id)
                solo_instrument.add(s.instrument)

            performers.append({
                'name': soloist.title,
                'url': soloist.url,
                'headshot': soloist.headshot,
                'instrument': list(solo_instrument),
                'performances': solo_perfs,
                'bio': soloist.biography
            })

        context['performers'] = performers
        return context

    def performances_by_date(self):
        """
        Performances here means something different from the class
        Performance. It's a single concert and the performances that make
        up a given day's concert.

        This will be used on the Concert Index pages

        This method returns a dict that looks like:
            date
            program:
              * composer
              * composition
              * performers:
                * name
                * url
                * instrument
        """
        concert_dates = self.concert_date.all()
        performances_by_date = list()
        for cd in concert_dates:
            performances = list()
            ps = cd.performance_set.all().live().public()
            for p in ps:
                # First make a set of all performers for a given performance
                performers = list()
                live_public_performers = (
                    perf for perf in p.specific.performer.all()
                    if perf.person.is_live_public())
                for performer in live_public_performers:
                    performers.append({
                        'name':
                        performer.person.title,
                        'url':
                        performer.person.url,
                        'instrument':
                        performer.instrument.instrument
                    })

                # Then assemble with composer and composition
                cmpsr = p.specific.composition.composer
                composer = cmpsr if cmpsr.is_live_public() else 'Anon.'
                performances.append({
                    'composer': composer,
                    'composition': p.specific.composition.title,
                    'supplemental_text': p.specific.supplemental_text,
                    'performers': performers
                })

            # Finally assemble with the date
            performances_by_date.append({
                'date': cd.date,
                'program': performances
            })

        return performances_by_date

    def get_url_parts(self, *args, **kwargs):
        site_id, root_url, page_path = super().get_url_parts(*args, **kwargs)

        # Insert season in second to last position in path
        # Note: empty strings occupy the first and last positions
        # after spliting
        path = page_path.split('/')
        path.insert(-2, self.season)
        page_path = '/'.join(path)

        return (site_id, root_url, page_path)

    def full_clean(self, *args, **kwargs):
        if self.concert_date.exists():
            first_concert_date = self.concert_date.first().date
            self.season = self.calculate_season(first_concert_date)

        try:
            super().full_clean(*args, **kwargs)
        except ValidationError as err:
            # Only raise a validation error if concert slugs share the
            # same season
            if 'This slug is already in use' in err.messages:
                qs = Concert.objects.filter(season=self.season,
                                            slug=self.slug).exclude(id=self.id)
                if qs:
                    raise err

    content_panels = Page.content_panels + [
        FieldPanel('promo_copy'),
        StreamFieldPanel('description'),
        FieldPanel('venue'),
        ImageChooserPanel('concert_image'),
        DocumentChooserPanel('program_notes'),
        InlinePanel('concert_date', label="Concert Dates", min_num=1),
        InlinePanel(
            'performer',
            label='Concert Performers',
            help_text=('Performers listed on the concert can be managed here. '
                       'Create some performances to populate the drop down '
                       'menus with names.')),
        # Save for future use
        # FieldPanel('roster', widget=forms.CheckboxSelectMultiple)
    ]

    promote_panels = [
        MultiFieldPanel([
            FieldPanel('slug'),
            FieldPanel('seo_title'),
            FieldPanel('show_in_menus'),
            FieldPanel(
                'search_description',
                help_text=(
                    'Use this field for short descriptions on social media '
                    'sites. Otherwise the promo text will be used.')),
            ImageChooserPanel('search_image'),
        ], ugettext_lazy('Common page configuration')),
    ]

    objects = ConcertManager()
    parent_page_types = ['ConcertIndex']
    subpage_types = ['Performance']
Example #16
0
class SamplePage(Page):
    parent_page_types = ['samples.SamplesIndexPage']
    subpage_types = []

    raw_subtitle = RichTextField('Подзаголовок', blank=True, null=True)
    tags = ClusterTaggableManager(through='samples.SamplePageTag', blank=True)
    # поле для ввода текста
    raw_content = RichTextField('Содержание', blank=True)
    doc = models.ForeignKey('base.CustomDocument',
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            related_name='+')

    @property
    def subtitle(self):
        return richtext(self.raw_subtitle)

    @property  # тут получаем текст с валидными тегами
    def text(self):
        return richtext(self.raw_content)

    @property
    def section_title(self):
        return self.get_parent().title

    @property
    def section_url(self):
        return self.get_parent().url

    @property
    def breadcrumbs(self):
        breadcrumbs = []
        for page in self.get_ancestors()[2:]:
            breadcrumbs.append({'title': page.title, 'url': page.url})
        return breadcrumbs

    @property
    def tags_slugs(self):
        return '\n'.join(self.tags.all().values_list('slug', flat=True))

    @property
    def clean_preview(self):
        h = html2text.HTML2Text()
        h.ignore_links = True
        h.ignore_emphasis = True
        h.ignore_images = True
        if self.subtitle:
            raw_text = h.handle(self.subtitle)
        else:
            raw_text = h.handle(self.raw_content)
        if len(raw_text) > 310:
            raw_text = raw_text[:310]
        space_index = raw_text.rfind(' ')
        raw_text = raw_text[:space_index] + '...'
        return raw_text.replace('\n', ' ').strip()

    def clean(self):
        super().clean()
        # автоматически создаем слаг
        if self.slug == 'default-blank-slug':
            if len(self.title) > 30:
                self.slug = slugify(self.title[:30])
            else:
                self.slug = slugify(self.title)
            if '--' in self.slug:
                self.slug = self.slug.replace('--', '-')
            if self.slug.endswith('-'):
                self.slug = self.slug[:-1]

    content_panels = Page.content_panels + [
        FieldPanel('raw_subtitle'),
        FieldPanel('raw_content', heading='Содержание'),
        DocumentChooserPanel('doc', heading='Документ'),
        MultiFieldPanel([FieldPanel("tags")], heading="Теги"),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('subtitle'),
        index.SearchField('text'),
        index.RelatedFields('tags', [
            index.SearchField('slug'),
            index.FilterField('slug'),
        ]),
        index.SearchField('tags_slugs', partial_match=True),
    ]

    api_fields = [
        APIField('title'),
        APIField('breadcrumbs'),
        APIField('section_title'),
        APIField('section_url'),
        APIField('subtitle'),
        APIField('text'),
        APIField('tags', serializer=serializers.TagSerializer()),
        APIField('doc', serializer=serializers.DocSerializer()),
        APIField('tags_slugs'),
        APIField('clean_preview')
    ]

    def get_sitemap_urls(self, request):
        return [{
            'location': self.full_url[:-1],
            'lastmod': self.last_published_at,
        }]

    class Meta:
        verbose_name = 'Образец документа'
        verbose_name_plural = 'Образец документа'
Example #17
0
class ArticlePage(ThemeablePage, FeatureStyleFields, Promotable,
                  ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    excerpt = RichTextField(blank=True, default="")
    body = article_fields.BodyField()
    chapters = article_fields.ChapterField(blank=True, null=True)
    table_of_contents_heading = models.TextField(blank=True,
                                                 default="Table of Contents")
    citations_heading = models.TextField(blank=True, default="Works Cited")
    endnotes_heading = models.TextField(blank=True, default="End Notes")
    endnote_identifier_style = models.CharField(
        max_length=20,
        default="roman-lower",
        choices=(('roman-lower', 'Roman Numerals - Lowercase'),
                 ('roman-upper', 'Roman Numerals - Uppercase'), ('numbers',
                                                                 'Numbers')))

    main_image = models.ForeignKey('images.AttributedImage',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    feature_image = models.ForeignKey('images.AttributedImage',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    primary_topic = models.ForeignKey('articles.Topic',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='articles')
    category = models.ForeignKey('articles.ArticleCategory',
                                 related_name='%(class)s',
                                 on_delete=models.SET_NULL,
                                 null=True,
                                 default=1)

    include_author_block = models.BooleanField(default=True)

    visualization = models.BooleanField(default=False)
    interview = models.BooleanField(default=False)
    video = models.BooleanField(default=False)
    number_of_related_articles = models.PositiveSmallIntegerField(
        default=6, verbose_name="Number of Related Articles to Show")
    json_file = article_fields.WagtailFileField(
        max_length=255,
        blank=True,
        null=True,
        verbose_name='JSON file',
        help_text=
        "Only provide if you know your template will be filled with the contents of a JSON data file."
    )

    project = models.ForeignKey(
        "projects.ProjectPage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )

    _response_to = False

    search_fields = Page.search_fields + [
        index.SearchField('excerpt', partial_match=True),
        index.SearchField('body', partial_match=True),
        index.SearchField('chapters', partial_match=True),
        index.SearchField('get_primary_topic_name', partial_match=True),
        index.SearchField('get_category_name', partial_match=True),
        index.SearchField('get_topic_names', partial_match=True),
        index.SearchField('get_author_names', partial_match=True),
    ]

    def get_primary_topic_name(self):
        if self.primary_topic:
            return self.primary_topic.name
        return ""

    def get_category_name(self):
        if self.category:
            return self.category.name
        return ""

    def get_topic_names(self):
        return '\n'.join([
            link.topic.name if link.topic else ""
            for link in self.topic_links.all()
        ])

    def get_author_names(self):
        return '\n'.join([
            author_link.author.full_name if author_link.author else ""
            for author_link in self.author_links.all()
        ])

    @property
    def authors(self):
        author_list = []
        for link in self.author_links.all():
            if link.author:
                author_list.append((link.author))
        return author_list

    @property
    def series_articles(self):
        related_series_data = []
        for link in self.series_links.all():
            series_page = link.series
            series_articles = series_page.articles
            series_articles.remove(self)
            related_series_data.append((series_page, series_articles))
        return related_series_data

    @property
    def topics(self):
        primary_topic = self.primary_topic
        all_topics = [link.topic for link in self.topic_links.all()]
        if primary_topic:
            all_topics.append(primary_topic)
        all_topics = list(set(all_topics))
        if len(all_topics) > 0:
            all_topics.sort(key=attrgetter('name'))
        return all_topics

    @property
    def response_to(self):
        if self._response_to is False:
            response_to_count = self.response_to_links.count()
            if response_to_count > 1:
                logger.warning(
                    'ArticlePage(pk={0}) appears to be a response to multiple articles. Only the first one is being returned.'
                    .format(self.pk))
            if response_to_count != 0:
                self._response_to = self.response_to_links.first().response_to
            else:
                self._response_to = None

        return self._response_to

    @property
    def is_response(self):
        return self.response_to is not None

    def responses(self):
        return [link.response for link in self.response_links.all()]

    def get_content(self):
        '''
        A generic and generative interface for getting all the content block for an article,
        including advanced content such as chapters.
        '''
        for block in self.body:
            yield block
        for chapter in self.chapters:
            for block in chapter.value['body']:
                yield block

    def related_articles(self, number):
        included = [self.id]
        article_list = []
        if self.primary_topic:
            articles = ArticlePage.objects.live().filter(
                primary_topic=self.primary_topic).exclude(id=self.id).distinct(
                ).order_by('-first_published_at')[:number]
            article_list.extend(articles.all())
            included.extend([article.id for article in articles.all()])

        current_total = len(article_list)

        if current_total < number:
            # still don't have enough, so pick using secondary topics
            topics = Topic.objects.filter(article_links__article=self)
            if topics:
                additional_articles = ArticlePage.objects.live().filter(
                    primary_topic__in=topics).exclude(
                        id__in=included).distinct().order_by(
                            '-first_published_at')[:number - current_total]
                article_list.extend(additional_articles.all())
                current_total = len(article_list)
                included.extend(
                    [article.id for article in additional_articles.all()])

        if current_total < number:
            authors = ContributorPage.objects.live().filter(
                article_links__article=self)
            if authors:
                additional_articles = ArticlePage.objects.live().filter(
                    author_links__author__in=authors).exclude(
                        id__in=included).distinct().order_by(
                            '-first_published_at')[:number - current_total]
                article_list.extend(additional_articles.all())
                current_total = len(article_list)
                included.extend(
                    [article.id for article in additional_articles.all()])

        if current_total < number:
            # still don't have enough, so just pick the most recent
            additional_articles = ArticlePage.objects.live().exclude(
                id__in=included).order_by('-first_published_at')[:number -
                                                                 current_total]
            article_list.extend(additional_articles.all())

        return article_list

    content_panels = Page.content_panels + [
        FieldPanel('excerpt'),
        InlinePanel('author_links', label="Authors"),
        PageChooserPanel('project'),
        ImageChooserPanel('main_image'),
        ImageChooserPanel('feature_image'),
        DocumentChooserPanel('video_document'),
        StreamFieldPanel('body'),
        SnippetChooserPanel('primary_topic'),
        InlinePanel('topic_links', label="Secondary Topics"),
        InlinePanel('response_links', label="Responses"),
    ]

    advanced_content_panels = [
        FieldPanel('json_file'),
        MultiFieldPanel([
            FieldPanel('table_of_contents_heading'),
            StreamFieldPanel('chapters'),
        ],
                        heading="Chapters Section"),
        MultiFieldPanel([
            FieldPanel('endnotes_heading'),
            FieldPanel('endnote_identifier_style'),
            InlinePanel('endnote_links', label="End Notes"),
        ],
                        heading="End Notes Section"),
        MultiFieldPanel([
            FieldPanel('citations_heading'),
            InlinePanel('citation_links', label="Citations"),
        ],
                        heading="Citations Section"),
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('sticky'),
            FieldPanel('sticky_for_type_section'),
            FieldPanel('slippery'),
            FieldPanel('slippery_for_type_section'),
            FieldPanel('editors_pick'),
            FieldPanel('feature_style'),
            FieldPanel('title_size'),
            FieldPanel('fullbleed_feature'),
            FieldPanel('image_overlay_opacity'),
        ],
                        heading="Featuring Settings"),
    ]

    style_panels = ThemeablePage.style_panels + [
        MultiFieldPanel([
            FieldPanel('include_main_image'),
            FieldPanel('include_main_image_overlay'),
            FieldPanel('full_bleed_image_size'),
            FieldPanel('include_caption_in_footer'),
        ],
                        heading="Main Image"),
        MultiFieldPanel([
            InlinePanel('background_image_links', label="Background Images"),
        ],
                        heading="Background Images"),
        MultiFieldPanel([
            FieldPanel('include_author_block'),
            FieldPanel('number_of_related_articles')
        ],
                        heading="Sections"),
        MultiFieldPanel([
            FieldPanel('interview'),
            FieldPanel('video'),
            FieldPanel('visualization'),
        ],
                        heading="Categorization")
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(advanced_content_panels, heading='Advanced Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Example #18
0
        on_delete=models.SET_NULL,
        related_name='+'
    )
    document_link_title = models.CharField(max_length=32, blank=True, null=True)

    def __str__(self):
        return self.title

PressArticle.panels = [
    MultiFieldPanel([
        FieldRowPanel([
            FieldPanel('title', classname='col8'),
            FieldPanel('description', classname='col8'),
            FieldPanel('link', classname='col6'),
            FieldPanel('link_title', classname='col6'),
            DocumentChooserPanel('document', classname='col6'),
            FieldPanel('document_link_title', classname='col6')
        ]),
    ],
    heading='Press article'
    )
]

@register_snippet
class Directions(ClusterableModel, models.Model):

    transportation_means = models.CharField(choices=TRANSPORTATION_CHOICES, max_length=32)

    def __str__(self):
        return self.transportation_means
Example #19
0
class Book(Page):
    created = models.DateTimeField(auto_now_add=True)
    book_state = models.CharField(max_length=255, choices=BOOK_STATES, default='live', help_text='The state of the book.')
    cnx_id = models.CharField(
        max_length=255, help_text="This is used to pull relevant information from CNX.",
        blank=True, null=True)
    salesforce_abbreviation = models.CharField(max_length=255, blank=True, null=True)
    salesforce_name = models.CharField(max_length=255, blank=True, null=True)
    updated = models.DateTimeField(blank=True, null=True, help_text='Late date web content was updated')
    is_ap = models.BooleanField(default=False, help_text='Whether this book is an AP (Advanced Placement) book.')
    description = RichTextField(
        blank=True, help_text="Description shown on Book Detail page.")

    cover = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='The book cover to be shown on the website.'
    )
    def get_cover_url(self):
        return build_document_url(self.cover.url)
    cover_url = property(get_cover_url)

    title_image = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='The svg for title image to be shown on the website.'
    )
    def get_title_image_url(self):
        return build_document_url(self.title_image.url)
    title_image_url = property(get_title_image_url)

    cover_color = models.CharField(max_length=255, choices=COVER_COLORS, default='blue', help_text='The color of the cover.')
    book_cover_text_color = models.CharField(max_length=255, choices=BOOK_COVER_TEXT_COLOR, default='yellow', help_text="Use by the Unified team - this will not change the text color on the book cover.")
    reverse_gradient = models.BooleanField(default=False)
    publish_date = models.DateField(null=True, help_text='Date the book is published on.')
    authors = StreamField([
        ('author', AuthorBlock()),
    ], null=True)

    print_isbn_10 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 10 for print version (hardcover).')
    print_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for print version (hardcover).')
    print_softcover_isbn_10 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 10 for print version (softcover).')
    print_softcover_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for print version (softcover).')
    digital_isbn_10 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 10 for digital version.')
    digital_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for digital version.')
    ibook_isbn_10 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 10 for iBook version.')
    ibook_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for iBook version.')
    ibook_volume_2_isbn_10 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 10 for iBook v2 version.')
    ibook_volume_2_isbn_13 = models.CharField(max_length=255, blank=True, null=True, help_text='ISBN 13 for iBook v2 version.')
    license_text = models.TextField(
        blank=True, null=True, help_text="Overrides default license text.")
    license_name = models.CharField(
        max_length=255, blank=True, null=True, editable=False, help_text="Name of the license.")
    license_version = models.CharField(
        max_length=255, blank=True, null=True, editable=False, help_text="Version of the license.")
    license_url = models.CharField(
        max_length=255, blank=True, null=True, editable=False, help_text="External URL of the license.")

    high_resolution_pdf = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text="High quality PDF document of the book."
    )
    def get_high_res_pdf_url(self):
        if self.high_resolution_pdf:
            return build_document_url(self.high_resolution_pdf.url)
        else:
            return None
    high_resolution_pdf_url = property(get_high_res_pdf_url)

    low_resolution_pdf = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text="Low quality PDF document of the book."
    )
    def get_low_res_pdf_url(self):
        if self.low_resolution_pdf:
            return build_document_url(self.low_resolution_pdf.url)
        else:
            return None
    low_resolution_pdf_url = property(get_low_res_pdf_url)

    free_stuff_instructor = StreamField(SharedContentBlock(), null=True, blank=True, help_text="Snippet to show texts for free instructor resources.")
    free_stuff_student = StreamField(SharedContentBlock(), null=True, blank=True, help_text="Snipped to show texts for free student resources.")
    community_resource_heading = models.CharField(max_length=255, blank=True, null=True, help_text="Snipped to show texts for community resources.")
    community_resource_logo = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text="Logo for community resources."
    )

    def get_community_resource_logo_url(self):
        if self.community_resource_logo:
            return build_document_url(self.community_resource_logo.url)
        else:
            return None

    community_resource_logo_url = property(get_community_resource_logo_url)
    community_resource_cta = models.CharField(max_length=255, blank=True, null=True, help_text='Call the action text.')
    community_resource_url = models.URLField(blank=True, help_text='URL of the external source.')
    community_resource_blurb = models.TextField(blank=True, help_text='Blurb.')
    community_resource_feature_link = models.ForeignKey(
        'wagtaildocs.Document',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='+',
        help_text='Document of the community resource feature.'
    )
    def get_community_resource_feature_link_url(self):
        return build_document_url(self.community_resource_feature_link.url)

    community_resource_feature_link_url = property(get_community_resource_feature_link_url)
    community_resource_feature_text = models.TextField(blank=True, help_text='Text of the community resource feature.')


    webinar_content = StreamField(SharedContentBlock(), null=True, blank=True)
    ally_content = StreamField(SharedContentBlock(), null=True, blank=True)
    coming_soon = models.BooleanField(default=False) #TODO: Remove after FE implements book_states
    ibook_link = models.URLField(blank=True, help_text="Link to iBook")
    ibook_link_volume_2 = models.URLField(blank=True, help_text="Link to secondary iBook")
    webview_link = models.URLField(blank=True, help_text="Link to CNX Webview book")
    webview_rex_link = models.URLField(blank=True, help_text="Link to REX Webview book")
    rex_callout_title = models.CharField(max_length=255, blank=True, null=True, help_text='Title of the REX callout', default="Recommended")
    rex_callout_blurb = models.CharField(max_length=255, blank=True, null=True, help_text='Additional text for the REX callout.')
    enable_study_edge = models.BooleanField(default=False, help_text="This will cause the link to the Study Edge app appear on the book details page.")
    bookshare_link = models.URLField(blank=True, help_text="Link to Bookshare resources")
    amazon_coming_soon = models.BooleanField(default=False, help_text='Whether this book is coming to Amazon bookstore.')
    amazon_link = models.URLField(blank=True, help_text="Link to Amazon")
    kindle_link = models.URLField(blank=True, help_text="Link to Kindle version")
    chegg_link = models.URLField(blank=True, null=True, help_text="Link to Chegg e-reader")
    chegg_link_text = models.CharField(max_length=255, blank=True, null=True, help_text='Text for Chegg link.')
    bookstore_coming_soon = models.BooleanField(default=False, help_text='Whether this book is coming to bookstore soon.')
    bookstore_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Bookstore content.')
    comp_copy_available = models.BooleanField(default=True, help_text='Whether free compy available for teachers.')
    comp_copy_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Content of the free copy.')
    errata_content = StreamField(SharedContentBlock(), null=True, blank=True, help_text='Errata content.')
    table_of_contents = JSONField(editable=False, blank=True, null=True, help_text='TOC.')
    tutor_marketing_book = models.BooleanField(default=False, help_text='Whether this is a Tutor marketing book.')
    partner_list_label = models.CharField(max_length=255, null=True, blank=True, help_text="Controls the heading text on the book detail page for partners. This will update ALL books to use this value!")
    partner_page_link_text = models.CharField(max_length=255, null=True, blank=True, help_text="Link to partners page on top right of list.")
    featured_resources_header = models.CharField(max_length=255, null=True, blank=True, help_text="Featured resource header on instructor resources tab.")

    videos = StreamField([
        ('video', blocks.ListBlock(blocks.StructBlock([
            ('title', blocks.CharBlock()),
            ('description', blocks.RichTextBlock()),
            ('embed', blocks.RawHTMLBlock()),
        ])))
    ], null=True, blank=True)
    promote_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Promote image.'
    )

    last_updated_pdf = models.DateTimeField(blank=True, null=True, help_text="Last time PDF was revised.", verbose_name='PDF Content Revision Date')

    book_detail_panel = Page.content_panels + [
        FieldPanel('book_state'),
        FieldPanel('cnx_id'),
        FieldPanel('salesforce_abbreviation'),
        FieldPanel('salesforce_name'),
        FieldPanel('updated'),
        FieldPanel('publish_date'),
        InlinePanel('book_subjects', label='Subjects'),
        FieldPanel('is_ap'),
        FieldPanel('description', classname="full"),
        DocumentChooserPanel('cover'),
        DocumentChooserPanel('title_image'),
        FieldPanel('cover_color'),
        FieldPanel('book_cover_text_color'),
        FieldPanel('reverse_gradient'),
        FieldPanel('print_isbn_10'),
        FieldPanel('print_isbn_13'),
        FieldPanel('print_softcover_isbn_10'),
        FieldPanel('print_softcover_isbn_13'),
        FieldPanel('digital_isbn_10'),
        FieldPanel('digital_isbn_13'),
        FieldPanel('ibook_isbn_10'),
        FieldPanel('ibook_isbn_13'),
        FieldPanel('ibook_volume_2_isbn_10'),
        FieldPanel('ibook_volume_2_isbn_13'),
        FieldPanel('license_text'),
        FieldPanel('webview_rex_link'),
        FieldPanel('rex_callout_title'),
        FieldPanel('rex_callout_blurb'),
        FieldPanel('enable_study_edge'),
        DocumentChooserPanel('high_resolution_pdf'),
        DocumentChooserPanel('low_resolution_pdf'),
        StreamFieldPanel('free_stuff_instructor'),
        StreamFieldPanel('free_stuff_student'),
        FieldPanel('community_resource_heading'),
        DocumentChooserPanel('community_resource_logo'),
        FieldPanel('community_resource_url'),
        FieldPanel('community_resource_cta'),
        FieldPanel('community_resource_blurb'),
        DocumentChooserPanel('community_resource_feature_link'),
        FieldPanel('community_resource_feature_text'),
        StreamFieldPanel('webinar_content'),
        StreamFieldPanel('ally_content'),
        FieldPanel('coming_soon'),
        FieldPanel('ibook_link'),
        FieldPanel('ibook_link_volume_2'),
        FieldPanel('bookshare_link'),
        FieldPanel('amazon_coming_soon'),
        FieldPanel('amazon_link'),
        FieldPanel('kindle_link'),
        FieldPanel('chegg_link'),
        FieldPanel('chegg_link_text'),
        FieldPanel('bookstore_coming_soon'),
        StreamFieldPanel('bookstore_content'),
        FieldPanel('comp_copy_available'),
        StreamFieldPanel('comp_copy_content'),
        StreamFieldPanel('errata_content'),
        FieldPanel('tutor_marketing_book'),
        FieldPanel('partner_list_label'),
        FieldPanel('partner_page_link_text'),
        FieldPanel('last_updated_pdf'),
        StreamFieldPanel('videos')
    ]
    instructor_resources_panel = [
        FieldPanel('featured_resources_header'),
        InlinePanel('book_faculty_resources', label="Instructor Resources"),
    ]
    student_resources_panel = [
        InlinePanel('book_student_resources', label="Student Resources"),
    ]
    author_panel = [
        StreamFieldPanel('authors')
    ]

    edit_handler = TabbedInterface([
        ObjectList(book_detail_panel, heading='Book Details'),
        ObjectList(instructor_resources_panel, heading='Instructor Resources'),
        ObjectList(student_resources_panel, heading='Student Resources'),
        ObjectList(author_panel, heading='Authors'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
    ])

    api_fields = [
        APIField('created'),
        APIField('updated'),
        APIField('slug'),
        APIField('title'),
        APIField('book_state'),
        APIField('cnx_id'),
        APIField('salesforce_abbreviation'),
        APIField('salesforce_name'),
        APIField('book_subjects'),
        APIField('is_ap'),
        APIField('description'),
        APIField('cover_url'),
        APIField('title_image_url'),
        APIField('cover_color'),
        APIField('book_cover_text_color'),
        APIField('reverse_gradient'),
        APIField('book_student_resources'),
        APIField('book_faculty_resources'),
        APIField('publish_date'),
        APIField('authors'),
        APIField('print_isbn_10'),
        APIField('print_isbn_13'),
        APIField('print_softcover_isbn_10'),
        APIField('print_softcover_isbn_13'),
        APIField('digital_isbn_10'),
        APIField('digital_isbn_13'),
        APIField('ibook_isbn_10'),
        APIField('ibook_isbn_13'),
        APIField('ibook_volume_2_isbn_10'),
        APIField('ibook_volume_2_isbn_13'),
        APIField('license_text'),
        APIField('license_name'),
        APIField('license_version'),
        APIField('license_url'),
        APIField('high_resolution_pdf_url'),
        APIField('low_resolution_pdf_url'),
        APIField('free_stuff_instructor'),
        APIField('free_stuff_student'),
        APIField('community_resource_heading'),
        APIField('community_resource_logo_url'),
        APIField('community_resource_url'),
        APIField('community_resource_cta'),
        APIField('community_resource_blurb'),
        APIField('community_resource_feature_link_url'),
        APIField('community_resource_feature_text'),
        APIField('webinar_content'),
        APIField('ally_content'),
        APIField('coming_soon'),
        APIField('ibook_link'),
        APIField('ibook_link_volume_2'),
        APIField('webview_link'),
        APIField('webview_rex_link'),
        APIField('rex_callout_title'),
        APIField('rex_callout_blurb'),
        APIField('enable_study_edge'),
        APIField('bookshare_link'),
        APIField('amazon_coming_soon'),
        APIField('amazon_link'),
        APIField('kindle_link'),
        APIField('chegg_link'),
        APIField('chegg_link_text'),
        APIField('bookstore_coming_soon'),
        APIField('bookstore_content'),
        APIField('comp_copy_available'),
        APIField('comp_copy_content'),
        APIField('errata_content'),
        APIField('table_of_contents'),
        APIField('tutor_marketing_book'),
        APIField('partner_list_label'),
        APIField('partner_page_link_text'),
        APIField('videos'),
        APIField('seo_title'),
        APIField('search_description'),
        APIField('promote_image'),
        APIField('last_updated_pdf'),
        APIField('featured_resources_header')
    ]

    template = 'page.html'

    parent_page_types = ['books.BookIndex']

    promote_panels = [
        FieldPanel('slug'),
        FieldPanel('seo_title'),
        FieldPanel('search_description'),
        ImageChooserPanel('promote_image')
    ]

    @property
    def book_title(self):
        return format_html(
            '{}',
            mark_safe(self.book.title),
        )

    def subjects(self):
        subject_list = []
        for subject in self.book_subjects.all():
            subject_list.append(subject.subject_name)
        return subject_list

    def get_slug(self):
        return 'books/{}'.format(self.slug)

    def book_urls(self):
        book_urls = []
        for field in self.api_fields:
            try:
                url = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', getattr(self, field))
                if url:
                    book_urls.append(url)
            except(TypeError, AttributeError):
                pass
        return book_urls

    def clean(self):
        errors = {}

        if self.cnx_id:
            try:
                url = '{}/contents/{}.json'.format(
                    settings.CNX_ARCHIVE_URL, self.cnx_id)
                context = ssl._create_unverified_context()
                response = urllib.request.urlopen(url, context=context).read()
                result = json.loads(response.decode('utf-8'))

                self.license_name = result['license']['name']
                self.license_version = result['license']['version']
                self.license_url = result['license']['url']

                if result['collated']:
                    htmlless_toc = cleanhtml(json.dumps(result['tree']))
                    self.table_of_contents = json.loads(htmlless_toc)
                else:
                    self.table_of_contents = result['tree']

            except urllib.error.HTTPError as err:
                errors.setdefault('cnx_id', []).append(err)

        if errors:
            raise ValidationError(errors)

    def save(self, *args, **kwargs):
        if self.cnx_id:
            self.webview_link = '{}contents/{}'.format(settings.CNX_URL, self.cnx_id)

        if self.partner_list_label:
            Book.objects.all().update(partner_list_label=self.partner_list_label)

        if self.partner_page_link_text:
            Book.objects.all().update(partner_page_link_text=self.partner_page_link_text)

        return super(Book, self).save(*args, **kwargs)

    def __str__(self):
        return self.book_title
Example #20
0
class LibraryItemPage(Page):
    parent_page_types = ['library.LibraryIndexPage']
    subpage_types = []

    raw_subtitle = RichTextField(verbose_name='Подзаголовок',
                                 blank=True,
                                 null=True)
    content = StreamField([('full_richtext', base_blocks.RichTextBlock()),
                           ('table', TableBlock(label='Таблица')),
                           ('raw_html', RawHTMLBlock(label='HTML'))],
                          null=True,
                          blank=True,
                          verbose_name='Содержание')
    doc = models.ForeignKey('base.CustomDocument',
                            blank=True,
                            null=True,
                            on_delete=models.SET_NULL,
                            related_name='+',
                            verbose_name="Документ")
    publish_date = models.DateField("Дата публикации", blank=True, null=True)
    tags = ClusterTaggableManager(through='library.LibraryPageTag', blank=True)

    @property
    def subtitle(self):
        return richtext(self.raw_subtitle)

    @property
    def breadcrumbs(self):
        breadcrumbs = []
        for page in self.get_ancestors()[2:]:
            breadcrumbs.append({'title': page.title, 'url': page.url})
        return breadcrumbs

    @property
    def section_title(self):
        return self.get_parent().title

    @property
    def section_url(self):
        return self.get_parent().url

    @property
    def tags_slugs(self):
        return '\n'.join(self.tags.all().values_list('slug', flat=True))

    content_panels = Page.content_panels + [
        FieldPanel('publish_date'),
        FieldPanel('raw_subtitle'),
        StreamFieldPanel('content'),
        MultiFieldPanel([
            InlinePanel("library_authors", label="Автор", min_num=0, max_num=5)
        ],
                        heading="Автор(ы)"),
        FieldPanel('tags'),
        DocumentChooserPanel('doc'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('subtitle'),
        index.SearchField('content'),
        index.SearchField('tags'),
        index.RelatedFields('tags', [
            index.SearchField('slug'),
            index.FilterField('slug'),
            index.FilterField('name'),
            index.SearchField('name')
        ]),
        index.SearchField('tags_slugs', partial_match=True),
        index.SearchField('publish_date'),
        index.FilterField('publish_date'),
    ]

    api_fields = [
        APIField('breadcrumbs'),
        APIField('section_title'),
        APIField('section_url'),
        APIField('publish_date', serializer=serializers.DateSerializer()),
        APIField('tags', serializer=serializers.TagSerializer()),
        APIField('subtitle'),
        APIField('content'),
        APIField(
            'authors',
            serializer=serializers.AuthorSerializer(source='library_authors')),
        APIField('doc', serializer=serializers.DocSerializer()),
        APIField('tags_slugs'),
    ]

    def get_sitemap_urls(self, request):
        return [{
            'location': self.full_url[:-1],
            'lastmod': self.last_published_at,
        }]

    def clean(self):
        super().clean()
        # автоматически создаем слаг
        if self.slug == 'default-blank-slug':
            if len(self.title) > 40:
                self.slug = slugify(self.title[:40])
            else:
                self.slug = slugify(self.title)
            if '--' in self.slug:
                self.slug = self.slug.replace('--', '-')
            if self.slug.endswith('-'):
                self.slug = self.slug[:-1]

    class Meta:
        verbose_name = 'Материал библиотеки'
        verbose_name_plural = 'Материалы библиотеки'
Example #21
0
class ActivityPage(CFGOVPage):
    """A model for the Activity Detail page."""

    # Allow Activity pages to exist under the ActivityIndexPage or the Trash
    parent_page_types = ["ActivityIndexPage", "v1.HomePage"]
    subpage_types = []
    objects = CFGOVPageManager()

    date = models.DateField('Updated', default=timezone.now)
    summary = models.TextField('Summary', blank=False)
    big_idea = RichTextField('Big idea', blank=False)
    essential_questions = RichTextField('Essential questions', blank=False)
    objectives = RichTextField('Objectives', blank=False)
    what_students_will_do = RichTextField('What students will do',
                                          blank=False)  # noqa: E501
    activity_file = models.ForeignKey('wagtaildocs.Document',
                                      null=True,
                                      blank=False,
                                      on_delete=models.SET_NULL,
                                      related_name='+',
                                      verbose_name='Teacher guide')
    # TODO: to figure out how to use Document choosers on ManyToMany fields
    handout_file = models.ForeignKey('wagtaildocs.Document',
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+',
                                     verbose_name='Student file 1')
    handout_file_2 = models.ForeignKey('wagtaildocs.Document',
                                       null=True,
                                       blank=True,
                                       on_delete=models.SET_NULL,
                                       related_name='+',
                                       verbose_name='Student file 2')
    handout_file_3 = models.ForeignKey('wagtaildocs.Document',
                                       null=True,
                                       blank=True,
                                       on_delete=models.SET_NULL,
                                       related_name='+',
                                       verbose_name='Student file 3')
    building_block = ParentalManyToManyField(
        'teachers_digital_platform.ActivityBuildingBlock',
        blank=False)  # noqa: E501
    school_subject = ParentalManyToManyField(
        'teachers_digital_platform.ActivitySchoolSubject',
        blank=False)  # noqa: E501
    topic = ParentalTreeManyToManyField(
        'teachers_digital_platform.ActivityTopic', blank=False)  # noqa: E501
    # Audience
    grade_level = ParentalManyToManyField(
        'teachers_digital_platform.ActivityGradeLevel',
        blank=False)  # noqa: E501
    age_range = ParentalManyToManyField(
        'teachers_digital_platform.ActivityAgeRange',
        blank=False)  # noqa: E501
    student_characteristics = ParentalManyToManyField(
        'teachers_digital_platform.ActivityStudentCharacteristics',
        blank=True)  # noqa: E501
    # Activity Characteristics
    activity_type = ParentalManyToManyField(
        'teachers_digital_platform.ActivityType', blank=False)  # noqa: E501
    teaching_strategy = ParentalManyToManyField(
        'teachers_digital_platform.ActivityTeachingStrategy',
        blank=False)  # noqa: E501
    blooms_taxonomy_level = ParentalManyToManyField(
        'teachers_digital_platform.ActivityBloomsTaxonomyLevel',
        blank=False)  # noqa: E501
    activity_duration = models.ForeignKey(
        'teachers_digital_platform.ActivityDuration',
        blank=False,
        on_delete=models.PROTECT)  # noqa: E501
    # Standards taught
    jump_start_coalition = ParentalManyToManyField(
        'teachers_digital_platform.ActivityJumpStartCoalition',
        blank=True,
        verbose_name='Jump$tart Coalition',
    )
    council_for_economic_education = ParentalManyToManyField(
        'teachers_digital_platform.ActivityCouncilForEconEd',
        blank=True,
        verbose_name='Council for Economic Education',
    )
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('date'),
        FieldPanel('summary'),
        FieldPanel('big_idea'),
        FieldPanel('essential_questions'),
        FieldPanel('objectives'),
        FieldPanel('what_students_will_do'),
        MultiFieldPanel(
            [
                DocumentChooserPanel('activity_file'),
                DocumentChooserPanel('handout_file'),
                DocumentChooserPanel('handout_file_2'),
                DocumentChooserPanel('handout_file_3'),
            ],
            heading="Download activity",
        ),
        FieldPanel('building_block', widget=forms.CheckboxSelectMultiple),
        FieldPanel('school_subject', widget=forms.CheckboxSelectMultiple),
        FieldPanel('topic', widget=forms.CheckboxSelectMultiple),
        MultiFieldPanel(
            [
                FieldPanel('grade_level',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('age_range', widget=forms.CheckboxSelectMultiple),
                FieldPanel('student_characteristics',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
            ],
            heading="Audience",
        ),
        MultiFieldPanel(
            [
                FieldPanel('activity_type',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('teaching_strategy',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('blooms_taxonomy_level',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('activity_duration'),
            ],
            heading="Activity characteristics",
        ),
        MultiFieldPanel(
            [
                FieldPanel('council_for_economic_education',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
                FieldPanel('jump_start_coalition',
                           widget=forms.CheckboxSelectMultiple),  # noqa: E501
            ],
            heading="National standards",
        ),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar/Footer'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    # admin use only
    search_fields = Page.search_fields + [
        index.SearchField('summary'),
        index.SearchField('big_idea'),
        index.SearchField('essential_questions'),
        index.SearchField('objectives'),
        index.SearchField('what_students_will_do'),
        index.FilterField('date'),
        index.FilterField('building_block'),
        index.FilterField('school_subject'),
        index.FilterField('topic'),
        index.FilterField('grade_level'),
        index.FilterField('age_range'),
        index.FilterField('student_characteristics'),
        index.FilterField('activity_type'),
        index.FilterField('teaching_strategy'),
        index.FilterField('blooms_taxonomy_level'),
        index.FilterField('activity_duration'),
        index.FilterField('jump_start_coalition'),
        index.FilterField('council_for_economic_education'),
    ]

    def get_subtopic_ids(self):
        """Get a list of this activity's subtopic ids."""
        topic_ids = [topic.id for topic in self.topic.all()]
        root_ids = ActivityTopic.objects \
            .filter(id__in=topic_ids, parent=None) \
            .values_list('id', flat=True)
        return set(topic_ids) - set(root_ids)

    def get_grade_level_ids(self):
        """Get a list of this activity's grade_level ids."""
        grade_level_ids = [
            grade_level.id for grade_level in self.grade_level.all()
        ]
        return grade_level_ids

    def get_related_activities_url(self):
        """Generate a search url for related Activities."""
        parent_page = self.get_parent()
        subtopic_ids = [str(x) for x in self.get_subtopic_ids()]
        grade_level_ids = [str(y) for y in self.get_grade_level_ids()]

        url = parent_page.get_url() + '?q='
        if subtopic_ids:
            subtopics = '&topic=' + \
                        '&topic='.join(subtopic_ids)
            url += subtopics
        if grade_level_ids:
            grade_levels = '&grade_level=' + \
                           '&grade_level='.join(grade_level_ids)
            url += grade_levels
        return url

    def get_topics_list(self, parent=None):
        """
        Get a hierarchical list of this activity's topics.

        parent: ActivityTopic
        """
        if parent:
            descendants = set(parent.get_descendants()) & set(self.topic.all())
            children = parent.get_children()
            children_list = []
            # If this parent has descendants in self.topic, add its children.
            if descendants:
                for child in children:
                    if child in self.topic.all():
                        children_list.append(child.title)

                if children_list:
                    return parent.title + " (" + ', '.join(children_list) + ")"
            # Otherwise, just add the parent.
            else:
                return parent.title
        else:
            # Build root list of topics and recurse their children.
            topic_list = []
            topic_ids = [topic.id for topic in self.topic.all()]
            ancestors = ActivityTopic.objects.filter(
                id__in=topic_ids).get_ancestors(True)
            roots = ActivityTopic.objects.filter(parent=None) & ancestors
            for root_topic in roots:
                topic_list.append(self.get_topics_list(root_topic))

            if topic_list:
                return ', '.join(topic_list)

    class Meta:
        verbose_name = "TDP Activity page"
Example #22
0
class ProgrammePage(ContactFieldsMixin, BasePage):
    parent_page_types = ["ProgrammeIndexPage"]
    subpage_types = ["guides.GuidePage"]
    template = "patterns/pages/programmes/programme_detail.html"

    # Comments resemble tabbed panels in the editor
    # Content
    degree_level = models.ForeignKey(DegreeLevel,
                                     on_delete=models.SET_NULL,
                                     blank=False,
                                     null=True,
                                     related_name="+")
    programme_type = models.ForeignKey(
        ProgrammeType,
        on_delete=models.SET_NULL,
        blank=False,
        null=True,
        related_name="+",
    )
    hero_image = models.ForeignKey(
        "images.CustomImage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    hero_image_credit = models.CharField(
        max_length=255,
        blank=True,
        help_text="Adding specific credit text here will \
        override the images meta data fields.",
    )
    hero_video = models.URLField(blank=True)
    hero_video_preview_image = models.ForeignKey(
        "images.CustomImage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    # Key Details
    programme_details_credits = models.CharField(max_length=25, blank=True)
    programme_details_credits_suffix = models.CharField(
        max_length=1,
        choices=(("1", "credits"), ("2", "credits at FHEQ Level 6")),
        blank=True,
    )
    programme_details_time = models.CharField(max_length=25, blank=True)
    programme_details_time_suffix = models.CharField(
        max_length=1,
        choices=(
            ("1", "year programme"),
            ("2", "month programme"),
            ("3", "week programme"),
        ),
        blank=True,
    )
    programme_details_duration = models.CharField(
        max_length=1,
        choices=(
            ("1", "Full-time study"),
            ("2", "Full-time study with part-time option"),
            ("3", "Part-time study"),
        ),
        blank=True,
    )

    next_open_day_date = models.DateField(blank=True, null=True)
    link_to_open_days = models.URLField(blank=True)
    application_deadline = models.DateField(blank=True, null=True)
    application_deadline_options = models.CharField(
        max_length=1,
        choices=(
            ("1", "Applications closed. Please check back soon."),
            ("2", "Still accepting applications"),
        ),
        blank=True,
    )

    programme_specification = models.ForeignKey(
        "documents.CustomDocument",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    # Programme Overview
    programme_description_title = models.CharField(max_length=125, blank=True)
    programme_description_subtitle = models.CharField(max_length=500,
                                                      blank=True)
    programme_image = models.ForeignKey(
        get_image_model_string(),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    programme_video_caption = models.CharField(
        blank=True,
        max_length=80,
        help_text="The text dipsplayed next to the video play button",
    )
    programme_video = models.URLField(blank=True)
    programme_description_copy = RichTextField(blank=True)

    programme_gallery = StreamField([("slide", GalleryBlock())],
                                    blank=True,
                                    verbose_name="Programme gallery")

    # Staff
    staff_link = models.URLField(blank=True)
    staff_link_text = models.CharField(
        max_length=125, blank=True, help_text="E.g. 'See all programme staff'")

    facilities_snippet = models.ForeignKey(
        "utils.FacilitiesSnippet",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    facilities_gallery = StreamField(
        [(
            "slide",
            StructBlock([("title", CharBlock()),
                         ("image", ImageChooserBlock())]),
        )],
        blank=True,
    )

    notable_alumni_links = StreamField(
        [(
            "Link_to_person",
            StructBlock(
                [("name", CharBlock()), ("link", URLBlock(required=False))],
                icon="link",
            ),
        )],
        blank=True,
    )

    # TODO
    # Alumni Stories Carousel (api fetch)
    # Related Content (news and events api fetch)

    # Programme Curriculumm
    curriculum_image = models.ForeignKey(
        get_image_model_string(),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    curriculum_subtitle = models.CharField(blank=True, max_length=100)
    curriculum_video_caption = models.CharField(
        blank=True,
        max_length=80,
        help_text="The text dipsplayed next to the video play button",
    )
    curriculum_video = models.URLField(blank=True)
    curriculum_text = models.TextField(blank=True, max_length=250)

    # Pathways
    pathway_blocks = StreamField(
        [("accordion_block", AccordionBlockWithTitle())],
        blank=True,
        verbose_name="Accordion blocks",
    )
    what_you_will_cover_blocks = StreamField(
        [
            ("accordion_block", AccordionBlockWithTitle()),
            ("accordion_snippet",
             SnippetChooserBlock("utils.AccordionSnippet")),
        ],
        blank=True,
        verbose_name="Accordion blocks",
    )

    # Requirements
    requirements_text = RichTextField(blank=True)
    requirements_blocks = StreamField(
        [
            ("accordion_block", AccordionBlockWithTitle()),
            ("accordion_snippet",
             SnippetChooserBlock("utils.AccordionSnippet")),
        ],
        blank=True,
        verbose_name="Accordion blocks",
    )

    # fees
    fees_disclaimer = models.ForeignKey(
        "utils.FeeDisclaimerSnippet",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    # Scholarships
    scholarships_title = models.CharField(max_length=120)
    scholarships_information = models.CharField(max_length=250)
    scholarship_accordion_items = StreamField(
        [("accordion", AccordionBlockWithTitle())], blank=True)
    scholarship_information_blocks = StreamField(
        [("information_block", InfoBlock())], blank=True)
    # More information
    more_information_blocks = StreamField([("information_block", InfoBlock())],
                                          blank=True)

    # Apply
    disable_apply_tab = models.BooleanField(
        default=0,
        help_text=(
            "This setting will remove the apply tab from this programme. "
            "This setting is ignored if the feature has already been disabled"
            " at the global level in Settings > Programme settings."),
    )
    apply_image = models.ForeignKey(
        get_image_model_string(),
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    steps = StreamField(
        [
            ("step", StepBlock()),
            ("step_snippet", SnippetChooserBlock("utils.StepSnippet")),
        ],
        blank=True,
    )

    content_panels = BasePage.content_panels + [
        # Taxonomy, relationships etc
        FieldPanel("degree_level"),
        InlinePanel("subjects", label="Subjects"),
        FieldPanel(
            "programme_type",
            help_text="Used to show content related to this programme page",
        ),
        MultiFieldPanel(
            [
                ImageChooserPanel("hero_image"),
                FieldPanel("hero_image_credit"),
                FieldPanel("hero_video"),
                ImageChooserPanel("hero_video_preview_image"),
            ],
            heading="Hero",
        ),
        MultiFieldPanel(
            [InlinePanel("related_programmes", label="Related programmes")],
            heading="Related Programmes",
        ),
        MultiFieldPanel(
            [InlinePanel("related_schools_and_research_pages")],
            heading="Related Schools and Research Centres",
        ),
    ]
    key_details_panels = [
        MultiFieldPanel(
            [
                FieldPanel("programme_details_credits"),
                FieldPanel("programme_details_credits_suffix"),
                FieldPanel("programme_details_time"),
                FieldPanel("programme_details_time_suffix"),
                FieldPanel("programme_details_duration"),
            ],
            heading="Details",
        ),
        FieldPanel("next_open_day_date"),
        FieldPanel("link_to_open_days"),
        FieldPanel("application_deadline"),
        FieldPanel(
            "application_deadline_options",
            help_text="Optionally display information about the deadline",
        ),
        InlinePanel("career_opportunities", label="Career Opportunities"),
        DocumentChooserPanel("programme_specification"),
    ]
    programme_overview_pannels = [
        MultiFieldPanel(
            [
                FieldPanel("programme_description_title"),
                FieldPanel("programme_description_subtitle"),
                ImageChooserPanel("programme_image"),
                FieldPanel("programme_video_caption"),
                FieldPanel("programme_video"),
                FieldPanel("programme_description_copy"),
            ],
            heading="Programme Description",
        ),
        MultiFieldPanel([StreamFieldPanel("programme_gallery")],
                        heading="Programme gallery"),
        MultiFieldPanel(
            [
                InlinePanel("related_staff", max_num=2),
                FieldPanel("staff_link"),
                FieldPanel("staff_link_text"),
            ],
            heading="Staff",
        ),
        MultiFieldPanel(
            [
                SnippetChooserPanel("facilities_snippet"),
                StreamFieldPanel("facilities_gallery"),
            ],
            heading="Facilities",
        ),
        MultiFieldPanel([StreamFieldPanel("notable_alumni_links")],
                        heading="Alumni"),
        MultiFieldPanel(
            [
                ImageChooserPanel("contact_model_image"),
                FieldPanel("contact_model_url"),
                FieldPanel("contact_model_email"),
                PageChooserPanel("contact_model_form"),
            ],
            heading="Contact information",
        ),
    ]
    programme_curriculum_pannels = [
        MultiFieldPanel(
            [
                ImageChooserPanel("curriculum_image"),
                FieldPanel("curriculum_subtitle"),
                FieldPanel("curriculum_video"),
                FieldPanel("curriculum_video_caption"),
                FieldPanel("curriculum_text"),
            ],
            heading="Curriculum introduction",
        ),
        MultiFieldPanel([StreamFieldPanel("pathway_blocks")],
                        heading="Pathways"),
        MultiFieldPanel(
            [StreamFieldPanel("what_you_will_cover_blocks")],
            heading="What you'll cover",
        ),
    ]

    programme_requirements_pannels = [
        FieldPanel("requirements_text"),
        StreamFieldPanel("requirements_blocks"),
    ]
    programme_fees_and_funding_panels = [
        SnippetChooserPanel("fees_disclaimer"),
        MultiFieldPanel([InlinePanel("fee_items", label="Fee items")],
                        heading="For this program"),
        MultiFieldPanel(
            [
                FieldPanel("scholarships_title"),
                FieldPanel("scholarships_information"),
                StreamFieldPanel("scholarship_accordion_items"),
                StreamFieldPanel("scholarship_information_blocks"),
            ],
            heading="Scholarships",
        ),
        MultiFieldPanel([StreamFieldPanel("more_information_blocks")],
                        heading="More information"),
    ]
    programme_apply_pannels = [
        MultiFieldPanel([FieldPanel("disable_apply_tab")],
                        heading="Apply tab settings"),
        MultiFieldPanel([ImageChooserPanel("apply_image")],
                        heading="Introduction image"),
        MultiFieldPanel([StreamFieldPanel("steps")],
                        heading="Before you begin"),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(key_details_panels, heading="Key details"),
        ObjectList(programme_overview_pannels, heading="Overview"),
        ObjectList(programme_curriculum_pannels, heading="Curriculum"),
        ObjectList(programme_requirements_pannels, heading="Requirements"),
        ObjectList(programme_fees_and_funding_panels, heading="Fees"),
        ObjectList(programme_apply_pannels, heading="Apply"),
        ObjectList(BasePage.promote_panels, heading="Promote"),
        ObjectList(BasePage.settings_panels, heading="Settings"),
    ])

    search_fields = BasePage.search_fields + [
        index.SearchField("programme_description_subtitle",
                          partial_match=True),
        index.AutocompleteField("programme_description_subtitle",
                                partial_match=True),
        index.SearchField("pathway_blocks", partial_match=True),
        index.AutocompleteField("pathway_blocks", partial_match=True),
        index.RelatedFields(
            "programme_type",
            [
                index.SearchField("display_name", partial_match=True),
                index.AutocompleteField("display_name", partial_match=True),
            ],
        ),
        index.RelatedFields(
            "degree_level",
            [
                index.SearchField("title", partial_match=True),
                index.AutocompleteField("title", partial_match=True),
            ],
        ),
        index.RelatedFields(
            "subjects",
            [
                index.RelatedFields(
                    "subject",
                    [
                        index.SearchField("title", partial_match=True),
                        index.AutocompleteField("title", partial_match=True),
                    ],
                )
            ],
        ),
    ]
    api_fields = [
        # Fields for filtering and display, shared with shortcourses.ShortCoursePage.
        APIField("subjects"),
        APIField("programme_type"),
        APIField("related_schools_and_research_pages"),
        APIField(
            "summary",
            serializer=CharFieldSerializer(
                source="programme_description_subtitle"),
        ),
        APIField(
            name="hero_image_square",
            serializer=ImageRenditionField("fill-580x580",
                                           source="hero_image"),
        ),
        # Displayed fields, specific to programmes.
        APIField("degree_level", serializer=degree_level_serializer()),
        APIField("pathway_blocks"),
    ]

    def __str__(self):
        bits = [self.title]
        if self.degree_level:
            bits.append(str(self.degree_level))
        return " ".join(bits)

    def get_admin_display_title(self):
        bits = [self.draft_title]
        if self.degree_level:
            bits.append(str(self.degree_level))
        return " ".join(bits)

    def get_school(self):
        related = self.related_schools_and_research_pages.select_related(
            "page").first()
        if related:
            return related.page

    def clean(self):
        super().clean()
        errors = defaultdict(list)
        if self.hero_video and not self.hero_video_preview_image:
            errors["hero_video_preview_image"].append(
                "Please add a preview image for the video.")
        if self.programme_details_credits and not self.programme_details_credits_suffix:
            errors["programme_details_credits_suffix"].append(
                "Please add a suffix")
        if self.programme_details_credits_suffix and not self.programme_details_credits:
            errors["programme_details_credits"].append(
                "Please add a credit value")
        if self.programme_details_time and not self.programme_details_time_suffix:
            errors["programme_details_time_suffix"].append(
                "Please add a suffix")
        if self.programme_details_time_suffix and not self.programme_details_time:
            errors["programme_details_time"].append("Please add a time value")
        if self.curriculum_video:
            try:
                embed = embeds.get_embed(self.curriculum_video)
            except EmbedException:
                errors["curriculum_video"].append("invalid embed URL")
            else:
                if embed.provider_name.lower() != "youtube":
                    errors["curriculum_video"].append(
                        "Only YouTube videos are supported for this field ")
        if self.staff_link and not self.staff_link_text:
            errors["staff_link_text"].append(
                "Please the text to be used for the link")
        if self.staff_link_text and not self.staff_link:
            errors["staff_link_text"].append(
                "Please add a URL value for the link")
        if not self.search_description:
            errors["search_description"].append(
                "Please add a search description for the page.")
        if errors:
            raise ValidationError(errors)

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context["related_sections"] = [{
            "title":
            "Related programmes",
            "related_items": [
                rel.page.specific
                for rel in self.related_programmes.select_related("page")
            ],
        }]
        context["related_staff"] = self.related_staff.select_related("image")

        # If one of the slides in the the programme_gallery contains author information
        # we need to set a modifier
        for block in self.programme_gallery:
            if block.value["author"]:
                context[
                    "programme_slideshow_modifier"] = "slideshow--author-info"

        # Set the page tab titles
        context["tabs"] = [
            {
                "title": "Overview"
            },
            {
                "title": "Curriculum"
            },
            {
                "title": "Requirements"
            },
            {
                "title": "Fees & funding"
            },
        ]
        # Only add the 'apply tab' depending global settings or specific programme page settings
        site = Site.find_for_request(request)
        programme_settings = ProgrammeSettings.for_site(site)
        if not programme_settings.disable_apply_tab and not self.disable_apply_tab:
            context["tabs"].append({"title": "Apply"})

        # Global fields from ProgrammePageGlobalFieldsSettings
        programme_page_global_fields = ProgrammePageGlobalFieldsSettings.for_site(
            site)
        context["programme_page_global_fields"] = programme_page_global_fields

        # School
        context["programme_school"] = self.get_school()

        return context
Example #23
0
class PortfolioPage(Page):
    # Title Page
    name = models.CharField(
        max_length=150,
        blank=True,
    )
    phoneno = models.CharField(
        max_length=150,
        blank=True,
    )
    profile_image = models.ForeignKey('wagtailimages.Image',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    designation = models.CharField(
        max_length=150,
        blank=True,
    )
    age = models.DateField(
        verbose_name="Date of Birth",
        blank=True,
    )
    email = models.EmailField(max_length=70, blank=True, unique=True)
    location = models.CharField(
        max_length=150,
        blank=True,
    )
    # Header Page
    header_title = models.CharField(
        max_length=150,
        blank=True,
    )
    header_content = MarkdownField(
        verbose_name='Header Content',
        blank=True,
    )
    resume_csv = models.ForeignKey('wagtaildocs.Document',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')
    # Education Page
    resume_title = models.CharField(
        max_length=150,
        blank=True,
    )
    resume_content = MarkdownField(
        verbose_name='Resume Content',
        blank=True,
    )

    education_title = models.CharField(
        max_length=150,
        blank=True,
    )

    class EducationBlock(blocks.StructBlock):
        university = blocks.CharBlock(classname="full title")
        year_passing = blocks.CharBlock(classname="full title")
        education_content = blocks.CharBlock(classname="full title")

    education = StreamField([
        ('education', EducationBlock()),
    ],
                            null=True,
                            blank=True)

    #Employment Page
    employment_title = models.CharField(
        max_length=150,
        blank=True,
    )

    class EmploymentBlock(blocks.StructBlock):
        company_name = blocks.CharBlock(classname="full title")
        experience = blocks.CharBlock(classname="full title")
        designation = blocks.CharBlock(classname="full title")

    employment = StreamField([
        ('employment', EmploymentBlock()),
    ],
                             null=True,
                             blank=True)

    #Skills Page
    skills_title = models.CharField(
        max_length=150,
        blank=True,
    )

    class SkillsBlock(blocks.StructBlock):
        skills = blocks.CharBlock(classname="full title")
        percentage = blocks.CharBlock(classname="full title")

    skills = StreamField([
        ('skills', SkillsBlock()),
    ], null=True, blank=True)

    #Testimonials
    class TestimonialBlock(blocks.StructBlock):
        title = blocks.CharBlock(classname="full title")
        sub_title = blocks.CharBlock(classname="full title")
        content = blocks.RichTextBlock(blank=True)

    testimonial = StreamField([
        ('testimonial', TestimonialBlock()),
    ],
                              null=True,
                              blank=True)

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('name'),
            ImageChooserPanel('profile_image'),
            FieldPanel('phoneno'),
            FieldPanel('designation'),
            FieldPanel('age'),
            FieldPanel('email'),
            FieldPanel('location')
        ], "Heading"),
        MultiFieldPanel([
            FieldPanel('header_title'),
            MarkdownPanel('header_content'),
            DocumentChooserPanel('resume_csv'),
        ], "Header"),
        MultiFieldPanel([
            FieldPanel('resume_title'),
            MarkdownPanel('resume_content'),
            FieldPanel('education_title'),
            StreamFieldPanel('education'),
            FieldPanel('employment_title'),
            StreamFieldPanel('employment'),
            FieldPanel('skills_title'),
            StreamFieldPanel('skills'),
        ], "Resume"),
        MultiFieldPanel([
            StreamFieldPanel('testimonial'),
        ], "Testimonial"),
    ]

    def get_context(self, request, *args, **kwargs):
        context = super(PortfolioPage,
                        self).get_context(request, *args, **kwargs)
        context['portfolio'] = self
        context['posts'] = self.get_posts
        context['blog_page'] = self.get_blogs
        context['projects'] = self.get_projects
        context['parent_projects'] = self.get_parent_project
        context['gits'] = self.get_gits
        context['parent_gits'] = self.get_parent_git
        return context

    def get_posts(self):
        return PostPage.objects.live().order_by('date')[:6]

    def get_blogs(self):
        return BlogPage.objects.live().first()

    def get_projects(self):
        return ProjectPage.objects.live().order_by('?')[:3]

    def get_parent_project(self):
        return ProjectParentPage.objects.live().first()

    def get_gits(self):
        return GitPage.objects.live().order_by('?')[:3]

    def get_parent_git(self):
        return GitParentPage.objects.live().first()
Example #24
0
class ResetNetworkHomePage(ResetNetworkBasePage):
    class Meta:
        verbose_name = "Reset Network Home Page"

    parent_page_types = ['wagtailcore.Page']
    subpage_types = [
        'reset_network_about.ResetNetworkAboutPage',
        'reset_network_open_calls.ResetNetworkOpenCallsPage',
        'reset_network_people.ResetNetworkPeoplePage',
        'reset_network_resources.ResetNetworkResourcesPage',
        'reset_network_work.ResetNetworkWorkPage',
        'reset_network_basic_page.ResetNetworkBasicPage',
        'reset_network_contact_us.ResetNetworkContactUsPage',
    ]

    # Content elements
    content_heading = models.CharField(verbose_name='Heading', max_length=255, blank=False)
    content_heading_gif_desktop = models.ForeignKey('wagtaildocs.Document', verbose_name='Heading image desktop', null=True, blank=True,
                                                    related_name='+', on_delete=models.SET_NULL)
    content_heading_gif_mobile = models.ForeignKey('wagtaildocs.Document', verbose_name='Heading image mobile', null=True, blank=True,
                                                   related_name='+', on_delete=models.SET_NULL)
    content_text = models.TextField(verbose_name='Text', blank=True)
    content_link = models.ForeignKey('wagtailcore.Page', verbose_name='Link', null=True, blank=True, related_name='+',
                                     on_delete=models.PROTECT)
    content_link_text = models.CharField(verbose_name='Link text', max_length=255, blank=True)

    # Section 1 elements
    section_1_heading = models.CharField(verbose_name='Heading', max_length=255, blank=True)
    section_1_text = RichTextField(verbose_name='Text', blank=True)
    section_1_link = models.ForeignKey('wagtailcore.Page', verbose_name='Link', null=True, blank=True, related_name='+',
                                       on_delete=models.PROTECT)
    section_1_link_text = models.CharField(verbose_name='Link text', max_length=255, blank=True)
    section_1_image = models.ForeignKey('images.CustomImage', null=True, blank=True, related_name='+',
                                        on_delete=models.SET_NULL)

    # Section 2 (open calls) elements
    section_2_heading = models.CharField(verbose_name='Heading', max_length=255, blank=True)
    section_2_text = models.TextField(verbose_name='Text', blank=True)
    section_2_link = models.ForeignKey('wagtailcore.Page', verbose_name='Link', null=True, blank=True, related_name='+',
                                       on_delete=models.PROTECT)
    section_2_link_text = models.CharField(verbose_name='Link text', max_length=255, blank=True)
    section_2_image = models.ForeignKey('images.CustomImage', null=True, blank=True, related_name='+',
                                        on_delete=models.SET_NULL)

    # Section 3 (resources) elements
    section_3_heading = models.CharField(verbose_name='Heading', max_length=255, blank=True)
    section_3_link = models.ForeignKey('wagtailcore.Page', verbose_name='Link', null=True, blank=True, related_name='+',
                                       on_delete=models.PROTECT)
    section_3_link_text = models.CharField(verbose_name='Link text', max_length=255, blank=True)

    content_panels = Page.content_panels + [
        InlinePanel('reset_network_home_page_hero_images', label='Hero Image', heading='Hero Images'),
        MultiFieldPanel([
            FieldPanel('content_heading'),
            DocumentChooserPanel('content_heading_gif_desktop'),
            DocumentChooserPanel('content_heading_gif_mobile'),
            FieldPanel('content_text'),
            PageChooserPanel('content_link'),
            FieldPanel('content_link_text'),
        ], heading='Content'),
        MultiFieldPanel([
            FieldPanel('section_1_heading'),
            FieldPanel('section_1_text'),
            PageChooserPanel('section_1_link'),
            FieldPanel('section_1_link_text'),
            ImageChooserPanel('section_1_image'),
        ], heading='Content - Section 1'),
        MultiFieldPanel([
            FieldPanel('section_2_heading'),
            FieldPanel('section_2_text'),
            PageChooserPanel('section_2_link'),
            FieldPanel('section_2_link_text'),
            ImageChooserPanel('section_2_image'),
            InlinePanel('reset_network_home_page_open_calls', label='Open Call Link', heading='Open Call Links'),
        ], heading='Content - Section 2 (Open Calls)'),
        MultiFieldPanel([
            FieldPanel('section_3_heading'),
            PageChooserPanel('section_3_link'),
            FieldPanel('section_3_link_text'),
            InlinePanel('reset_network_home_page_featured', label='Featured Card', heading='Featured'),
        ], heading='Content - Section 3 (Resources)'),
    ]
Example #25
0
class AiLabResourceMixin(models.Model):
    parent_page_types = [
        "AiLabUnderstandIndexPage",
        "AiLabDevelopIndexPage",
        "AiLabAdoptIndexPage",
    ]

    summary = models.CharField(max_length=255)
    featured_image = models.ForeignKey(
        settings.WAGTAILIMAGES_IMAGE_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    download = models.ForeignKey(
        settings.WAGTAILDOCS_DOCUMENT_MODEL,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    topics = ParentalManyToManyField("AiLabTopic", blank=False)
    featured_resources = fields.StreamField(resource_link_blocks, blank=True)

    content_panels = [
        FieldPanel("title"),
        FieldPanel("summary", widget=forms.Textarea),
        ImageChooserPanel("featured_image"),
        DocumentChooserPanel("download"),
        FieldPanel("first_published_at"),
        FieldPanel("topics", widget=forms.CheckboxSelectMultiple),
        StreamFieldPanel("body"),
    ]

    @cached_classmethod
    def get_admin_tabs(cls):
        tabs = super().get_admin_tabs()
        del tabs[1]
        tabs.insert(1, ([StreamFieldPanel("featured_resources")], "Featured"))
        return tabs

    def get_template(self, request):
        return "ai_lab/resource.html"

    def get_context(self, value, parent_context=None):
        context = super().get_context(value, parent_context=parent_context)
        topics = self.topics.all()

        if len(self.featured_resources) == 0:
            context["featured_resources"] = self._get_featured_resources(
                topics)
        else:
            context["featured_resources"] = [
                resource.value for resource in self.featured_resources
            ]
        return context

    def _get_featured_resources(self, topics):
        from modules.ai_lab.models import AiLabResourceIndexPage

        root_page = AiLabResourceIndexPage.objects.all()[0]

        child_resources = list(root_page._get_resources().live())

        if self in child_resources:
            child_resources.remove(self)

        featured_resources = []

        # Try and find resources with the same topic(s) first
        if len(child_resources) > 0:
            for resource in child_resources:
                if len(featured_resources) == 3:
                    break
                for topic in topics:
                    if topic in resource.topics.all():
                        if resource not in featured_resources:
                            featured_resources.append(resource)

            # If there are not enough resources with a topic, then pad
            # the remainder with random resource(s)
            if len(featured_resources) < 3:
                remainder = 3 - len(featured_resources)
                if len(child_resources) >= remainder:
                    featured_resources.extend(
                        random.sample(child_resources, remainder))

        return featured_resources

    class Meta:
        abstract = True
Example #26
0
class PersonPage(ArchiveablePageAbstract, Page):
    """View person page"""
    class ExternalPublicationTypes(models.TextChoices):
        GENERIC = 'Generic'
        BOOK = 'Book'
        BOOK_SECTION = 'Book Section'
        EDITED_BOOK = 'Edited Book'
        ELECTRONIC_ARTICLE = 'Electronic Article'
        ELECTRONIC_BOOK = 'Electronic Book'
        JOURNAL_ARTICLE = 'Journal Article'
        NEWSPAPER_ARTICLE = 'Newspaper Article'
        REPORT = 'Report'
        THESIS = 'Thesis'
        WEB_PAGE = 'Web Page'

    address_city = models.CharField(blank=True, max_length=255)
    address_country = models.CharField(blank=True, max_length=255)
    address_line1 = models.CharField(blank=True, max_length=255)
    address_line2 = models.CharField(blank=True, max_length=255)
    address_postal_code = models.CharField(blank=True, max_length=32)
    address_province = models.CharField(blank=True, max_length=255)
    board_position = models.CharField(blank=True, max_length=255)
    body = StreamField([('paragraph', ParagraphBlock())],
                       blank=True,
                       verbose_name='Full Biography')
    curriculum_vitae = models.ForeignKey('wagtaildocs.Document',
                                         null=True,
                                         blank=True,
                                         on_delete=models.SET_NULL,
                                         related_name='+')
    education = StreamField(
        [('education',
          blocks.StructBlock(
              [('degree', blocks.CharBlock(required=True)),
               ('school', blocks.CharBlock(required=True)),
               ('school_website', blocks.URLBlock(required=False)),
               ('year', blocks.IntegerBlock(required=False))]))],
        blank=True)
    email = models.EmailField(blank=True)
    expertise = StreamField([('expertise', blocks.CharBlock(required=True))],
                            blank=True)
    first_name = models.CharField(blank=True, max_length=255)
    image_media = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Media photo',
        help_text=
        'A high resolution image that is downloadable from the expert\'s page.'
    )
    image_square = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Square image',
        help_text=
        'For circular profile images that are used throughout the website.')
    languages = StreamField([('language', blocks.CharBlock(required=True))],
                            blank=True)
    last_name = models.CharField(blank=True, max_length=255)
    linkedin_url = models.URLField(blank=True)
    person_types = ParentalManyToManyField('people.PersonType', blank=True)
    phone_number = models.CharField(blank=True, max_length=32)
    position = models.CharField(blank=True, max_length=255)
    projects = ParentalManyToManyField('research.ProjectPage', blank=True)
    short_bio = RichTextField(blank=True, verbose_name='Short Biography')
    external_publications = StreamField(
        [('external_publication',
          blocks.StructBlock(
              [('author', blocks.CharBlock(required=True)),
               ('location_in_work', blocks.CharBlock(required=False)),
               ('publisher_info', blocks.CharBlock(required=False)),
               ('publication_type',
                blocks.ChoiceBlock(
                    required=True,
                    choices=ExternalPublicationTypes.choices,
                )), ('secondary_author', blocks.CharBlock(required=False)),
               ('secondary_title', blocks.CharBlock(required=False)),
               ('title', blocks.CharBlock(required=False)),
               ('url', blocks.URLBlock(required=False)),
               ('url_title', blocks.CharBlock(required=False)),
               ('year', blocks.IntegerBlock(required=False))]))],
        blank=True)
    topics = ParentalManyToManyField('research.TopicPage', blank=True)
    twitter_username = models.CharField(blank=True, max_length=255)
    website = models.URLField(blank=True)

    # 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([
            FieldPanel('first_name'),
            FieldPanel('last_name'),
            FieldPanel('position'),
            FieldPanel('board_position')
        ],
                        heading='General Information',
                        classname='collapsible'),
        MultiFieldPanel([FieldPanel('short_bio'),
                         StreamFieldPanel('body')],
                        heading='Biography',
                        classname='collapsible collapsed'),
        MultiFieldPanel([
            FieldPanel('address_line1'),
            FieldPanel('address_line2'),
            FieldPanel('address_city'),
            FieldPanel('address_province'),
            FieldPanel('address_postal_code'),
            FieldPanel('address_country')
        ],
                        heading='Address',
                        classname='collapsible collapsed'),
        MultiFieldPanel([
            FieldPanel('email'),
            FieldPanel('phone_number'),
            FieldPanel('twitter_username'),
            FieldPanel('linkedin_url'),
            FieldPanel('website')
        ],
                        heading='Contact Information',
                        classname='collapsible collapsed'),
        MultiFieldPanel([
            FieldPanel('person_types'),
            StreamFieldPanel('languages'),
            DocumentChooserPanel('curriculum_vitae')
        ],
                        heading='Additional Information',
                        classname='collapsible collapsed'),
        MultiFieldPanel([StreamFieldPanel('education')],
                        heading='Education',
                        classname='collapsible collapsed'),
        MultiFieldPanel([
            StreamFieldPanel('expertise'),
            FieldPanel('projects'),
        ],
                        heading='Expertise',
                        classname='collapsible collapsed'),
        MultiFieldPanel([
            ImageChooserPanel('image_square'),
            ImageChooserPanel('image_media')
        ],
                        heading='Images',
                        classname='collapsible collapsed'),
        MultiFieldPanel([FieldPanel('topics')],
                        heading='Related',
                        classname='collapsible collapsed'),
        MultiFieldPanel([StreamFieldPanel('external_publications')],
                        heading='External Publications',
                        classname='collapsible collapsed'),
    ]
    settings_panels = Page.settings_panels + [
        ArchiveablePageAbstract.archive_panel,
    ]

    parent_page_types = ['people.PeoplePage']
    subpage_types = []
    templates = 'people/person_page.html'

    class Meta:
        verbose_name = 'Person Page'
        verbose_name_plural = 'Person Pages'
Example #27
0
class EventPage(AbstractFilterPage):
    # General content fields
    body = RichTextField('Subheading', blank=True)
    archive_body = RichTextField(blank=True)
    live_body = RichTextField(blank=True)
    future_body = RichTextField(blank=True)
    persistent_body = StreamField([
        ('content', blocks.RichTextBlock(icon='edit')),
        ('content_with_anchor', molecules.ContentWithAnchor()),
        ('heading', v1_blocks.HeadingBlock(required=False)),
        ('image', molecules.ContentImage()),
        ('table_block', organisms.AtomicTableBlock(
            table_options={'renderer': 'html'}
        )),
        ('reusable_text', v1_blocks.ReusableTextChooserBlock(
            'v1.ReusableText'
        )),
    ], blank=True)
    start_dt = models.DateTimeField("Start")
    end_dt = models.DateTimeField("End", blank=True, null=True)
    future_body = RichTextField(blank=True)
    archive_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    video_transcript = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    speech_transcript = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    flickr_url = models.URLField("Flickr URL", blank=True)
    archive_video_id = models.CharField(
        'YouTube video ID (archive)',
        null=True,
        blank=True,
        max_length=11,
        # This is a reasonable but not official regex for YouTube video IDs.
        # https://webapps.stackexchange.com/a/54448
        validators=[RegexValidator(regex=r'^[\w-]{11}$')],
        help_text=organisms.VideoPlayer.YOUTUBE_ID_HELP_TEXT
    )
    live_stream_availability = models.BooleanField(
        "Streaming?",
        default=False,
        blank=True,
        help_text='Check if this event will be streamed live. This causes the '
                  'event page to show the parts necessary for live streaming.'
    )
    live_video_id = models.CharField(
        'YouTube video ID (live)',
        null=True,
        blank=True,
        max_length=11,
        # This is a reasonable but not official regex for YouTube video IDs.
        # https://webapps.stackexchange.com/a/54448
        validators=[RegexValidator(regex=r'^[\w-]{11}$')],
        help_text=organisms.VideoPlayer.YOUTUBE_ID_HELP_TEXT
    )
    live_stream_date = models.DateTimeField(
        "Go Live Date",
        blank=True,
        null=True,
        help_text='Enter the date and time that the page should switch from '
                  'showing the venue image to showing the live video feed. '
                  'This is typically 15 minutes prior to the event start time.'
    )

    # Venue content fields
    venue_coords = models.CharField(max_length=100, blank=True)
    venue_name = models.CharField(max_length=100, blank=True)
    venue_street = models.CharField(max_length=100, blank=True)
    venue_suite = models.CharField(max_length=100, blank=True)
    venue_city = models.CharField(max_length=100, blank=True)
    venue_state = USStateField(blank=True)
    venue_zipcode = models.CharField(max_length=12, blank=True)
    venue_image_type = models.CharField(
        max_length=8,
        choices=(
            ('map', 'Map'),
            ('image', 'Image (selected below)'),
            ('none', 'No map or image'),
        ),
        default='map',
        help_text='If "Image" is chosen here, you must select the image you '
                  'want below. It should be sized to 1416x796.',
    )
    venue_image = models.ForeignKey(
        'v1.CFGOVImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    post_event_image_type = models.CharField(
        max_length=16,
        choices=(
            ('placeholder', 'Placeholder image'),
            ('image', 'Unique image (selected below)'),
        ),
        default='placeholder',
        verbose_name='Post-event image type',
        help_text='Choose what to display after an event concludes. This will '
                  'be overridden by embedded video if the "YouTube video ID '
                  '(archive)" field on the previous tab is populated. If '
                  '"Unique image" is chosen here, you must select the image '
                  'you want below. It should be sized to 1416x796.',
    )
    post_event_image = models.ForeignKey(
        'v1.CFGOVImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    # Agenda content fields
    agenda_items = StreamField([('item', AgendaItemBlock())], blank=True)

    objects = CFGOVPageManager()

    search_fields = AbstractFilterPage.search_fields + [
        index.SearchField('body'),
        index.SearchField('archive_body'),
        index.SearchField('live_video_id'),
        index.SearchField('flickr_url'),
        index.SearchField('archive_video_id'),
        index.SearchField('future_body'),
        index.SearchField('agenda_items')
    ]

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('body'),
        FieldRowPanel([
            FieldPanel('start_dt', classname="col6"),
            FieldPanel('end_dt', classname="col6"),
        ]),
        MultiFieldPanel([
            FieldPanel('archive_body'),
            ImageChooserPanel('archive_image'),
            DocumentChooserPanel('video_transcript'),
            DocumentChooserPanel('speech_transcript'),
            FieldPanel('flickr_url'),
            FieldPanel('archive_video_id'),
        ], heading='Archive Information'),
        FieldPanel('live_body'),
        FieldPanel('future_body'),
        StreamFieldPanel('persistent_body'),
        MultiFieldPanel([
            FieldPanel('live_stream_availability'),
            FieldPanel('live_video_id'),
            FieldPanel('live_stream_date'),
        ], heading='Live Stream Information'),
    ]
    # Venue content tab
    venue_panels = [
        FieldPanel('venue_name'),
        MultiFieldPanel([
            FieldPanel('venue_street'),
            FieldPanel('venue_suite'),
            FieldPanel('venue_city'),
            FieldPanel('venue_state'),
            FieldPanel('venue_zipcode'),
        ], heading='Venue Address'),
        MultiFieldPanel([
            FieldPanel('venue_image_type'),
            ImageChooserPanel('venue_image'),
        ], heading='Venue Image'),
        MultiFieldPanel([
            FieldPanel('post_event_image_type'),
            ImageChooserPanel('post_event_image'),
        ], heading='Post-event Image')
    ]
    # Agenda content tab
    agenda_panels = [
        StreamFieldPanel('agenda_items'),
    ]
    # Promotion panels
    promote_panels = [
        MultiFieldPanel(AbstractFilterPage.promote_panels,
                        "Page configuration"),
    ]
    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(venue_panels, heading='Venue Information'),
        ObjectList(agenda_panels, heading='Agenda Information'),
        ObjectList(AbstractFilterPage.sidefoot_panels,
                   heading='Sidebar'),
        ObjectList(AbstractFilterPage.settings_panels,
                   heading='Configuration'),
    ])

    template = 'events/event.html'

    @property
    def event_state(self):
        if self.end_dt:
            end = convert_date(self.end_dt, 'America/New_York')
            if end < datetime.now(timezone('America/New_York')):
                return 'past'

        if self.live_stream_date:
            start = convert_date(
                self.live_stream_date,
                'America/New_York'
            )
        else:
            start = convert_date(self.start_dt, 'America/New_York')

        if datetime.now(timezone('America/New_York')) > start:
            return 'present'

        return 'future'

    @property
    def page_js(self):
        if (
            (self.live_stream_date and self.event_state == 'present')
            or (self.archive_video_id and self.event_state == 'past')
        ):
            return super(EventPage, self).page_js + ['video-player.js']

        return super(EventPage, self).page_js

    def location_image_url(self, scale='2', size='276x155', zoom='12'):
        if not self.venue_coords:
            self.venue_coords = get_venue_coords(
                self.venue_city, self.venue_state
            )
        api_url = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static'
        static_map_image_url = '{}/{},{}/{}?access_token={}'.format(
            api_url,
            self.venue_coords,
            zoom,
            size,
            settings.MAPBOX_ACCESS_TOKEN
        )

        return static_map_image_url

    def clean(self):
        super(EventPage, self).clean()
        if self.venue_image_type == 'image' and not self.venue_image:
            raise ValidationError({
                'venue_image': 'Required if "Venue image type" is "Image".'
            })
        if self.post_event_image_type == 'image' and not self.post_event_image:
            raise ValidationError({
                'post_event_image': 'Required if "Post-event image type" is '
                                    '"Image".'
            })

    def save(self, *args, **kwargs):
        self.venue_coords = get_venue_coords(self.venue_city, self.venue_state)
        return super(EventPage, self).save(*args, **kwargs)

    def get_context(self, request):
        context = super(EventPage, self).get_context(request)
        context['event_state'] = self.event_state
        return context
Example #28
0
class SignUpFormPage(HeadlessPreviewMixin, Page):
    formatted_title = models.CharField(
        max_length=255,
        blank=True,
        help_text="This is the title displayed on the page, not the document "
        "title tag. HTML is permitted. Be careful.")
    intro = RichTextField()
    call_to_action_text = models.CharField(
        max_length=255, help_text="Displayed above the email submission form.")
    call_to_action_image = models.ForeignKey('torchbox.TorchboxImage',
                                             null=True,
                                             blank=True,
                                             on_delete=models.SET_NULL,
                                             related_name='+')
    form_button_text = models.CharField(max_length=255)
    thank_you_text = models.CharField(
        max_length=255, help_text="Displayed on successful form submission.")
    email_subject = models.CharField(max_length=100, verbose_name='subject')
    email_body = models.TextField(verbose_name='body')
    email_attachment = models.ForeignKey(
        'wagtaildocs.Document',
        null=True,
        related_name='+',
        on_delete=models.SET_NULL,
        verbose_name='attachment',
    )
    email_from_address = models.EmailField(
        verbose_name='from address',
        help_text="Anything ending in @torchbox.com is good.")

    sign_up_form_class = SignUpFormPageForm

    content_panels = [
        MultiFieldPanel([
            FieldPanel('title', classname="title"),
            FieldPanel('formatted_title'),
        ], 'Title'),
        FieldPanel('intro', classname="full"),
        InlinePanel('bullet_points', label="Bullet points"),
        InlinePanel('logos', label="Logos"),
        InlinePanel('quotes', label="Quotes"),
        MultiFieldPanel([
            FieldPanel('call_to_action_text'),
            ImageChooserPanel('call_to_action_image'),
            FieldPanel('form_button_text'),
            FieldPanel('thank_you_text'),
        ], 'Form'),
        MultiFieldPanel([
            FieldPanel('email_subject'),
            FieldPanel('email_body'),
            DocumentChooserPanel('email_attachment'),
            FieldPanel('email_from_address'),
        ], 'Email'),
    ]

    def get_context(self, request, *args, **kwargs):
        context = super(SignUpFormPage,
                        self).get_context(request, *args, **kwargs)
        context['form'] = self.sign_up_form_class()
        return context

    @vary_on_headers('X-Requested-With')
    def serve(self, request, *args, **kwargs):
        if request.is_ajax() and request.method == "POST":
            form = self.sign_up_form_class(request.POST)

            if form.is_valid():
                form.save()
                self.send_email_response(form.cleaned_data['email'])
                return render(
                    request,
                    'sign_up_form/includes/sign_up_form_page_landing.html', {
                        'page': self,
                        'form': form,
                        'legend': self.call_to_action_text
                    })
            else:
                return render(
                    request,
                    'sign_up_form/includes/sign_up_form_page_form.html', {
                        'page': self,
                        'form': form,
                        'legend': self.call_to_action_text
                    })
        response = super(SignUpFormPage, self).serve(request)
        try:
            del response['cache-control']
        except KeyError:
            pass
        return response

    def send_email_response(self, to_address):
        email_message = EmailMessage(
            subject=self.email_subject,
            body=self.email_body,
            from_email=self.email_from_address,
            to=[to_address],
        )
        email_message.attach(
            self.email_attachment.file.name.split('/')[-1],
            self.email_attachment.file.read())
        email_message.send()
Example #29
0
class StaffPage(BasePageWithoutStaffPageForeignKeys):
    """
    Staff profile content type.
    """

    subpage_types = ['base.IntranetPlainPage']
    # editable by HR.
    cnetid = CharField(
        blank=False,
        help_text=
        'Campus-wide unique identifier which links this record to the campus directory.',
        max_length=255)
    chicago_id = CharField(blank=True,
                           help_text='Campus-wide unique identifier',
                           max_length=9)
    display_name = CharField(
        blank=True,
        help_text='Version of this staff person\'s name to display.',
        max_length=255,
        null=True)
    official_name = CharField(
        blank=True,
        help_text='Official version of this staff person\'s name.',
        max_length=255,
        null=True)
    first_name = CharField(blank=True,
                           help_text='First name, for sorting.',
                           max_length=255,
                           null=True)
    middle_name = CharField(blank=True,
                            help_text='Middle name, for sorting.',
                            max_length=255,
                            null=True)
    last_name = CharField(blank=True,
                          help_text='Last name, for sorting.',
                          max_length=255,
                          null=True)
    position_title = CharField(blank=True,
                               help_text='Position title.',
                               max_length=255,
                               null=True)
    employee_type = IntegerField(
        choices=EMPLOYEE_TYPES,
        default=1,
        help_text='Clerical, exempt, non-exempt, IT or Librarian.')
    position_eliminated = BooleanField(
        default=False, help_text='Position will not be refilled.')
    supervisor_override = models.ForeignKey(
        'staff.StaffPage',
        blank=True,
        help_text=
        'If supervisor cannot be determined by the staff person\'s unit, specify supervisor here.',
        null=True,
        on_delete=models.SET_NULL,
        related_name='supervisor_override_for')
    supervises_students = BooleanField(default=False,
                                       help_text='For HR reporting.')
    profile_picture = models.ForeignKey(
        'wagtailimages.Image',
        blank=True,
        help_text=
        'Profile pictures should be frontal headshots, preferrably on a gray background.',
        null=True,
        on_delete=models.SET_NULL,
        related_name='+')
    pronouns = CharField(
        blank=True,
        help_text='Your pronouns, example: (they/them/theirs)',
        max_length=255,
        null=True)
    libguide_url = models.URLField(
        blank=True,
        help_text='Your profile page on guides.lib.uchicago.edu.',
        max_length=255,
        null=True)
    bio = StreamField(
        DefaultBodyFields(),
        blank=True,
        help_text='A brief bio highlighting what you offer to Library users.',
        null=True)
    cv = models.ForeignKey('wagtaildocs.Document',
                           blank=True,
                           help_text='Your CV or resume.',
                           null=True,
                           on_delete=models.SET_NULL,
                           related_name='+')
    is_public_persona = BooleanField(
        default=False, help_text='(display changes not yet implemented)')
    orcid_regex = RegexValidator(regex=ORCID_FORMAT, message=ORCID_ERROR_MSG)
    orcid = CharField(blank=True,
                      help_text='See https://orcid.org for more information.',
                      max_length=255,
                      null=True,
                      validators=[orcid_regex])

    objects = StaffPageManager()

    def get_employee_type(self):
        """
        Get the serialized employee type for display
        in the api. Converts the integer representation
        from the database to a human readable string.

        Returns:
            String
        """
        try:
            return EMPLOYEE_TYPES[0][self.employee_type]
        except (IndexError):
            return ''

    def get_serialized_units(self):
        """
        Return a serialized list of library units assigned to
        the staff member.

        Returns:
            List
        """
        return [
            str(Page.objects.get(id=u['library_unit_id']))
            for u in self.staff_page_units.values()
        ]

    api_fields = [
        APIField('cnetid'),
        APIField('employee_type',
                 serializer=serializers.CharField(source='get_employee_type')),
        APIField('position_title'),
        APIField('position_eliminated'),
        APIField('supervises_students'),
        APIField(
            'library_units',
            serializer=serializers.ListField(source='get_serialized_units')),
    ]

    @property
    def get_staff_subjects(self):
        """
        Get the subjects beloning to the staff member - UNTESTED
        """
        # MT note: get_public_profile is an undefined value; this
        # should be fixed
        pass
        # return get_public_profile('elong')

    @property
    def is_subject_specialist(self):
        """
        See if the staff member is a subject
        specialist - PLACEHOLDER
        """
        return self.get_subjects() != ''

    @property
    def public_page(self):
        """
        Get a public staff profile page for the
        library expert if one exists.
        """
        from public.models import StaffPublicPage  # Should try to do better
        try:
            return StaffPublicPage.objects.get(cnetid=self.cnetid)
        except (IndexError):
            return None

    @property
    def get_supervisors(self):
        if self.supervisor_override:
            return [self.supervisor_override]
        else:
            supervisors = []
            for u in self.staff_page_units.all():
                try:
                    if u.library_unit.department_head.cnetid == self.cnetid:
                        p = u.library_unit.get_parent().specific
                        if p.department_head:
                            supervisors.append(p.department_head)
                    else:
                        supervisors.append(u.library_unit.department_head)
                except AttributeError:
                    continue
            return supervisors

    def get_subjects(self):
        """
        Get all the subjects for a staff member.
        Must return a string for elasticsearch.

        Returns:
            String, concatenated list of subjects.
        """
        subject_list = self.staff_subject_placements.values_list('subject',
                                                                 flat=True)
        return '\n'.join(
            Subject.objects.get(id=subject).name for subject in subject_list)

    def get_subject_objects(self):
        """
        Get all the subject objects for a staff member.

        Returns:
            Set of subjects for a staff member.
        """
        subject_ids = (Subject.objects.get(id=sid)
                       for sid in self.staff_subject_placements.values_list(
                           'subject_id', flat=True))
        return set(subject_ids)

    def get_staff(self):
        """
        Get a queryset of the staff members this
        person supervises.

        TO DO: include a parameter that controls whether this is
        recursive or not. If it's recursive it should look into the
        heirarchy of UnitPages to get sub-staff.

        Returns:
            a queryset of StaffPage objects.
        """

        cnetids = set()
        for s in StaffPage.objects.all():
            if not s == self:
                if s.supervisor_override:
                    cnetids.add(s.cnetid)
                else:
                    for u in s.staff_page_units.filter(
                            library_unit__department_head=self):
                        try:
                            cnetids.add(u.page.cnetid)
                        except:
                            continue

        return StaffPage.objects.filter(cnetid__in=list(cnetids))

    @staticmethod
    def get_staff_by_building(building_str):
        building = 0
        for b in BUILDINGS:
            if b[1] == building_str:
                building = b[0]
        if building > 0:
            return StaffPage.objects.live().filter(
                staff_page_units__library_unit__building=building).distinct()
        else:
            return StaffPage.objects.none()

    content_panels = Page.content_panels + [
        ImageChooserPanel('profile_picture'),
        FieldPanel('pronouns'),
        StreamFieldPanel('bio'),
        DocumentChooserPanel('cv'),
        FieldPanel('libguide_url'),
        FieldPanel('is_public_persona'),
        InlinePanel('staff_subject_placements', label='Subject Specialties'),
        InlinePanel('expertise_placements', label='Expertise'),
        FieldPanel('orcid')
    ] + BasePageWithoutStaffPageForeignKeys.content_panels

    # for a thread about upcoming support for read-only fields,
    # see: https://github.com/wagtail/wagtail/issues/2893
    human_resources_panels = [
        FieldPanel('cnetid'),
        MultiFieldPanel(
            [
                FieldPanel('display_name'),
                FieldPanel('official_name'),
                FieldPanel('first_name'),
                FieldPanel('middle_name'),
                FieldPanel('last_name'),
                FieldPanel('position_title'),
                InlinePanel('staff_page_email', label='Email Addresses'),
                InlinePanel('staff_page_phone_faculty_exchange',
                            label='Phone Number and Faculty Exchange'),
                InlinePanel('staff_page_units', label='Library Units'),
                FieldPanel('employee_type'),
                FieldPanel('position_eliminated'),
                FieldPanel('supervises_students'),
                PageChooserPanel('supervisor_override'),
            ],
            heading=
            'Human-resources editable fields. These fields will push to the campus directory (where appropriate).'
        ),
        MultiFieldPanel(
            [
                FieldPanel('chicago_id'),
            ],
            heading=
            'Read-only fields. These values are pulled from the campus directory.'
        )
    ]

    search_fields = BasePageWithoutStaffPageForeignKeys.search_fields + [
        index.SearchField('profile_picture'),
        index.SearchField('cv'),
        index.SearchField('libguide_url'),
        index.SearchField('orcid'),
        index.SearchField('position_title')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
        ObjectList(human_resources_panels, heading='Human Resources Info'),
    ])

    class Meta:
        ordering = ['last_name', 'first_name']

    def get_context(self, request):
        position_title = self.position_title
        emails = self.staff_page_email.all().values_list('email', flat=True)

        units = set()
        for staff_page_unit in self.staff_page_units.all():
            try:
                unit_title = staff_page_unit.library_unit.get_full_name()
            except:
                unit_title = None
            try:
                unit_url = staff_page_unit.library_unit.intranet_unit_page.first(
                ).url
            except:
                unit_url = None
            units.add(json.dumps({'title': unit_title, 'url': unit_url}))
        units = list(map(json.loads, list(units)))

        subjects = []
        for subject in self.staff_subject_placements.all():
            subjects.append({'name': subject.subject.name, 'url': ''})

        group_memberships = []
        for group_membership in self.member.all():
            if group_membership.parent.is_active:
                group_memberships.append({
                    'group': {
                        'title': group_membership.parent.title,
                        'url': group_membership.parent.url
                    },
                    'role': group_membership.role
                })

        context = super(StaffPage, self).get_context(request)
        context['position_title'] = position_title
        context['emails'] = emails
        context['units'] = units
        context['subjects'] = subjects
        context['group_memberships'] = group_memberships
        return context
class LongformPage(BasePage):
    template = "patterns/pages/longform/longform_page.html"
    subpage_types = ["LongformChapterPage"]

    class Meta:
        verbose_name = "Long-form content page"

    is_chapter_page = False
    is_numbered = models.BooleanField(
        default=False,
        help_text='Adds numbers to each chapter, e.g. "1. Introduction"',
    )

    last_updated = models.DateField()
    version_number = models.CharField(blank=True, max_length=100)

    hero_image = models.ForeignKey(
        "images.CustomImage",
        blank=True,
        null=True,
        related_name="+",
        on_delete=models.SET_NULL,
    )
    chapter_heading = models.CharField(
        blank=True,
        default="Introduction",
        help_text=
        ('Optional, e.g. "Introduction", chapter heading that will appear before the '
         "body. Is the same level as a main heading"),
        max_length=255,
    )
    body = StreamField(LongformStoryBlock())

    document = models.ForeignKey(
        settings.WAGTAILDOCS_DOCUMENT_MODEL,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    document_link_text = models.CharField(
        blank=True,
        help_text=
        'Optional, e.g. "Download the policy", defaults to the linked document\'s own title',
        max_length=255,
    )

    search_fields = BasePage.search_fields + [index.SearchField("body")]

    content_panels = BasePage.content_panels + [
        FieldPanel("last_updated"),
        FieldPanel("version_number"),
        MultiFieldPanel(
            [
                DocumentChooserPanel("document"),
                FieldPanel("document_link_text")
            ],
            heading="Documents",
        ),
        ImageChooserPanel("hero_image"),
        FieldPanel("is_numbered", heading="Enable chapter numbers"),
        FieldPanel("chapter_heading"),
        StreamFieldPanel("body"),
    ]

    @cached_property
    def previous_chapter(self):
        return None

    @cached_property
    def next_chapter(self):
        return self.get_children().live().specific().first()

    @cached_property
    def chapter_number(self):
        return 1 if self.is_numbered else None

    def get_index(self):
        return [self] + list(self.get_children().specific())