Exemple #1
0
class Resource(Page):
    base_form_class = ResourcePageForm

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

    thumbnail = models.ForeignKey('core.AffixImage',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL,
                                  max_length=500,
                                  related_name='+')

    date = models.DateField(blank=True, null=True)
    show_day = models.BooleanField(default=True)
    show_month = models.BooleanField(default=True)
    show_year = models.BooleanField(default=True)

    content = StreamField([
        ("authors", blocks.RichTextBlock(blank=True)),
        ("copyright", blocks.RichTextBlock(blank=True)),
        ("focus", blocks.RichTextBlock(blank=True)),
        ("factoids", blocks.RichTextBlock(blank=True)),
    ])

    rooms = ParentalManyToManyField('resources.Room',
                                    blank=True,
                                    related_name='resources')
    racks = ParentalManyToManyField('resources.Rack',
                                    blank=True,
                                    related_name='resources')

    subjects = ParentalManyToManyField('resources.Subject',
                                       related_name='resources',
                                       blank=True)

    type = models.ForeignKey('resources.ResourceType',
                             related_name='resources',
                             null=True,
                             blank=True,
                             on_delete=models.SET_NULL)

    categories = ParentalManyToManyField("category.Category",
                                         related_name="resources_by_category",
                                         blank=True)
    language = models.CharField(max_length=7, choices=settings.LANGUAGES)

    tags = ClusterTaggableManager(through=ResourceTag, blank=True)
    promote_panels = Page.promote_panels + [
        FieldPanel('tags'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('title', partial_match=True,
                          boost=SearchBoost.TITLE),
        index.SearchField('language'),
        index.SearchField(
            'content', partial_match=True, boost=SearchBoost.CONTENT),
        index.FilterField('date'),
        index.FilterField('get_categories'),
        index.FilterField('language'),
        index.FilterField('get_search_type'),
    ]

    def get_search_type(self):
        return self.__class__.__name__.lower()

    def get_categories(self):
        return [category.name for category in self.categories.all()]

    def get_absolute_url(self):
        return reverse("resource-detail", kwargs={"slug": self.slug})

    def get_template(self, request, *args, **kwargs):
        self.template = "resources/resource_detail.html"
        return super(Resource, self).get_template(request, *args, **kwargs)

    def get_context(self, request, *args, **kwargs):
        return {'resource': self, 'request': request}

    content_panels = Page.content_panels + [
        DocumentChooserPanel('document'),
        ImageChooserPanel('thumbnail'),
        FieldPanel('language'),
        StreamFieldPanel('content'),
        FieldPanel('rooms', widget=forms.SelectMultiple(attrs={'size': 10})),
        FieldPanel('racks', widget=forms.SelectMultiple(attrs={'size': 10})),
        # FieldPanel('subjects'),
        # FieldPanel('type'),
        FieldPanel('categories',
                   widget=forms.SelectMultiple(attrs={'size': 10})),
        MultiFieldPanel([
            FieldPanel('date'),
            FieldRowPanel([
                FieldPanel('show_day', classname="col4"),
                FieldPanel('show_month', classname="col4"),
                FieldPanel('show_year', classname="col4")
            ])
        ], 'Date'),
    ]

    @property
    def featured_image(self):
        return None

    def __str__(self):
        return self.title
Exemple #2
0
class BlogPage(Page):
    subtitle = models.CharField(blank=True, max_length=255)
    date_published = models.DateField("Date article published",
                                      blank=True,
                                      null=True)
    introduction = models.CharField(max_length=250)
    body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True)
    tags = ClusterTaggableManager(
        verbose_name='Tags',
        through=BlogPageTag,
        blank=True,
        related_name='posts_en',
        help_text='A comma-separated list of tags for the english'\
                  ' version.'
    )
    tags_es = ClusterTaggableManager(
        verbose_name='Tags [ES] (Etiquetas)',
        through=BlogPageTagES,
        blank=True,
        related_name='posts_es',
        help_text='Listado separado por comas de etiquetas para la'\
                  ' versión en español.',
    )

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    search_fields = Page.search_fields + [
        index.SearchField('title'),
        index.SearchField('subtitle'),
        index.SearchField('introduction'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('subtitle', classname='full'),
        FieldPanel('introduction', classname='full'),
        StreamFieldPanel('body'),
        MultiFieldPanel([
            FieldPanel('date_published'),
            FieldPanel('tags'),
            FieldPanel('tags_es'),
        ],
                        heading='Blog information'),
        InlinePanel('blog_person_relationship',
                    label=_("Author(s)"),
                    panels=None,
                    min_num=1),
        InlinePanel('gallery_images', label='Gallery images'),
    ]

    def authors(self):
        """
        Returns the BlogPage's related People. Again note that we are using
        the ParentalKey's related_name from the BlogPeopleRelationship model
        to access these objects. This allows us to access the People objects
        with a loop on the template. If we tried to access the blog_person_
        relationship directly we'd print `blog.BlogPeopleRelationship.None`
        """
        authors = [n.people for n in self.blog_person_relationship.all()]
        return authors

    @property
    def get_tags(self):
        """
        Similar to the authors function above we're returning all the tags that
        are related to the blog post into a list we can access on the template.
        We're additionally adding a URL to access BlogPage objects with that tag
        """
        tags = getattr(self, get_suffixed_string('tags')).all()
        for tag in tags:
            tag.url = '/' + '/'.join(
                s.strip('/')
                for s in [self.get_parent().url, 'tags', tag.slug])
        return tags

    # Specifies parent to BlogPage as being BlogIndexPages
    parent_page_types = ['BlogIndexPage']

    # Specifies what content types can exist as children of BlogPage.
    # Empty list means that no child content types are allowed.
    subpage_types = []

    def get_year_month_hierarchy(self):
        qset = BlogPage.objects.descendant_of(
            self.get_parent()).live().order_by('-date_published').values_list(
                'date_published', flat=True)
        pairs = list(set(map(lambda x: (x.year, x.month), qset)))
        result = []
        for pair in pairs:
            if not result:
                result.append({'year': pair[0], 'months': []})
            if not [x for x in result if x['year'] == pair[0]]:
                result.append({'year': pair[0], 'months': []})
            idx = [result.index(x) for x in result if x['year'] == pair[0]][0]
            result[idx]['months'].append(pair[1])
            result[idx]['months'] = list(set(result[idx]['months']))
        return result

    def get_context(self, request):
        context = super(BlogPage, self).get_context(request)
        context['dates'] = self.get_year_month_hierarchy()
        return context

    class Meta:
        ordering = ('-date_published', )
Exemple #3
0
class RestaurantPage(Page):
    tags = ClusterTaggableManager(through='tests.TaggedRestaurant', blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('tags'),
    ]
class Article(BasePage):
    # IMPORTANT: EACH ARTICLE is NOW LABELLED "POST" IN THE FRONT END

    resource_type = "article"  # If you change this, CSS will need updating, too
    parent_page_types = ["Articles"]
    subpage_types = []
    template = "article.html"

    class Meta:
        verbose_name = "post"  # NB
        verbose_name_plural = "posts"  # NB

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
    )
    body = CustomStreamField(help_text=(
        "The main post content. Supports rich text, images, embed via URL, "
        "embed via HTML, and inline code snippets"))
    related_links = StreamField(
        StreamBlock([("link", ExternalLinkBlock())], required=False),
        blank=True,
        null=True,
        help_text="Optional links further reading",
        verbose_name="Related links",
    )

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField("Description",
                                 max_length=400,
                                 blank=True,
                                 default="")
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
    )

    # Meta fields
    date = DateField(
        "Post date",
        default=datetime.date.today,
        help_text="The date the post was published",
    )
    authors = StreamField(
        StreamBlock(
            [
                ("author", PageChooserBlock(target_model="people.Person")),
                ("external_author", ExternalAuthorBlock()),
            ],
            required=False,
        ),
        blank=True,
        null=True,
        help_text=(
            "Optional list of the post's authors. Use ‘External author’ to add "
            "guest authors without creating a profile on the system"),
    )
    keywords = ClusterTaggableManager(through=ArticleTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        MultiFieldPanel(
            [ImageChooserPanel("image")],
            heading="Image",
            help_text=
            ("Optional header image. If not specified a fallback will be used. "
             "This image is also shown when sharing this page via social media"
             ),
        ),
        StreamFieldPanel("body"),
        StreamFieldPanel("related_links"),
    ]

    # Card panels
    card_panels = [
        FieldPanel("card_title"),
        FieldPanel("card_description"),
        ImageChooserPanel("card_image"),
    ]

    # Meta panels
    meta_panels = [
        FieldPanel("date"),
        StreamFieldPanel("authors"),
        MultiFieldPanel(
            [InlinePanel("topics")],
            heading="Topics",
            help_text=
            ("The topic pages this post will appear on. The first topic in the "
             "list will be treated as the primary topic and will be shown in the "
             "page’s related content."),
        ),
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=(
                "Optional fields to override the default title and description "
                "for SEO purposes"),
        ),
    ]

    # Settings panels
    settings_panels = BasePage.settings_panels + [FieldPanel("slug")]

    # Tabs
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(card_panels, heading="Card"),
        ObjectList(meta_panels, heading="Meta"),
        ObjectList(settings_panels, heading="Settings", classname="settings"),
    ])

    def get_absolute_url(self):
        # For the RSS feed
        return self.full_url

    @property
    def primary_topic(self):
        """Return the first (primary) topic specified for the Article."""
        article_topic = self.topics.first()
        return article_topic.topic if article_topic else None

    @property
    def read_time(self):
        return str(readtime.of_html(str(self.body)))

    @property
    def related_resources(self):
        """Returns resources that are related to the current resource, i.e.
        live, public Articles and Videos which have the same Topics."""
        topic_pks = [topic.topic.pk for topic in self.topics.all()]
        return get_combined_articles_and_videos(
            self, topics__topic__pk__in=topic_pks)

    @property
    def month_group(self):
        return self.date.replace(day=1)

    def has_author(self, person):
        for author in self.authors:  # pylint: disable=not-an-iterable
            if author.block_type == "author" and str(author.value) == str(
                    person.title):
                return True
        return False
Exemple #5
0
class HomePage(BasePage):
    subpage_types = [
        "articles.Articles",
        "content.ContentPage",
        "events.Events",
        "people.People",
        "topics.Topics",
        "videos.Videos",
    ]
    template = "home.html"

    # Content fields
    subtitle = TextField(max_length=250, blank=True, default="")
    button_text = CharField(max_length=30, blank=True, default="")
    button_url = CharField(max_length=2048, blank=True, default="")
    image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
    )
    featured = StreamField(
        StreamBlock(
            [
                (
                    "post",
                    PageChooserBlock(
                        target_model=(
                            "articles.Article",
                            "externalcontent.ExternalArticle",
                        )
                    ),
                ),
                (
                    "content_page",
                    PageChooserBlock(target_model=("content.ContentPage",)),
                ),
                ("external_page", FeaturedExternalBlock()),
                (
                    "video",
                    PageChooserBlock(
                        target_model=(
                            "videos.Video",
                            # NB: ExternalVideo is NOT allowed on the homepage
                            # "externalcontent.ExternalVideo"
                        )
                    ),
                ),
            ],
            min_num=2,
            max_num=5,
            required=False,
        ),
        null=True,
        blank=True,
        help_text=(
            "Optional space for featured posts, videos or links, min. 2, max. 5. "
            "Note that External Video is NOT allowed here."
        ),
    )

    featured_people = StreamField(
        StreamBlock(
            [("person", PageChooserBlock(target_model="people.Person"))],
            max_num=3,
            required=False,
        ),
        null=True,
        blank=True,
        help_text="Optional featured people, max. 3",
    )

    about_title = TextField(max_length=250, blank=True, default="")
    about_subtitle = TextField(max_length=250, blank=True, default="")
    about_button_text = CharField(max_length=30, blank=True, default="")
    about_button_url = URLField(max_length=140, blank=True, default="")

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField(
        "Description", max_length=DESCRIPTION_MAX_LENGTH, blank=True, default=""
    )
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
    )

    # Meta fields
    keywords = ClusterTaggableManager(through=HomePageTag, blank=True)

    # Editor panel configuration
    content_panels = BasePage.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("subtitle"),
                # DISABLED - header needs a design tweak to fit in a button robustly
                # FieldPanel("button_text"),
                # FieldPanel("button_url"),
            ],
            heading="Header section",
            help_text="Optional fields for the header section",
        ),
        MultiFieldPanel(
            [ImageChooserPanel("image")],
            heading="Image",
            help_text=(
                "Optional image shown when sharing this page through social media"
            ),
        ),
        StreamFieldPanel("featured"),
        StreamFieldPanel("featured_people"),
        MultiFieldPanel(
            [
                FieldPanel("about_title"),
                FieldPanel("about_subtitle"),
                FieldPanel("about_button_text"),
                FieldPanel("about_button_url"),
            ],
            heading="About section",
            help_text="Optional section to explain more about Mozilla",
        ),
    ]

    # Card panels
    card_panels = [
        MultiFieldPanel(
            [
                FieldPanel("card_title"),
                FieldPanel("card_description"),
                ImageChooserPanel("card_image"),
            ],
            heading="Card overrides",
            help_text=(
                (
                    "Optional fields to override the default title, "
                    "description and image when this page is shown as a card"
                )
            ),
        )
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=(
                "Optional fields to override the default "
                "title and description for SEO purposes"
            ),
        )
    ]

    # Settings panels
    settings_panels = [FieldPanel("slug")]

    # Tabs
    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Content"),
            ObjectList(card_panels, heading="Card"),
            ObjectList(meta_panels, heading="Meta"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )

    @classmethod
    def can_create_at(cls, parent):
        # Allow only one instance of this page type
        return super().can_create_at(parent) and not cls.objects.exists()

    @property
    def primary_topics(self):
        """The site’s top-level topics, i.e. topics without a parent topic."""
        from ..topics.models import Topic

        return Topic.published_objects.filter(parent_topics__isnull=True)
class Place(ClusterableModel):
    name = models.CharField(max_length=255)
    tags = ClusterTaggableManager(through=TaggedPlace, blank=True)

    def __str__(self):
        return self.name
Exemple #7
0
class BlogPage(MetadataPageMixin, Page):
    date = models.DateField("Post date", default=models.fields.datetime.date.today)
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    show_in_homepage_slider = models.BooleanField(
        verbose_name='show in homepage slider',
        default=False
    )
    author = models.ForeignKey(
         'blog.AuthorPage', null=True, blank=True,
         on_delete=models.SET_NULL, related_name='+'
     )
    video = models.ForeignKey(
        'wagtail_embed_videos.EmbedVideo',
        verbose_name="Video",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    def get_context(self, request):
        # Update context to include only published posts, ordered by reverse-chron
        context = super().get_context(request)
        blogpages = BlogPage.objects.live().order_by('-first_published_at')[:3]
        context['blogpages'] = blogpages

        return context

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def greek_date(self):
        return format_date(self.date, locale='el_GR')

    def get_meta_image(self):
        """A relevant Wagtail Image to show. Optional."""
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def get_meta_description(self):
        """
        A short text description of this object.
        This should be plain text, not HTML.
        """
        return self.intro

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
        index.SearchField('author'),
        index.SearchField('tags'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('tags'),
            FieldPanel('show_in_homepage_slider'),
            PageChooserPanel('author'),
        ], heading="Post information"),
        FieldPanel('intro'),
        FieldPanel('body'),
        EmbedVideoChooserPanel('video'),
        InlinePanel('gallery_images', label="Gallery images"),
    ]

    class Meta:
        verbose_name = 'Post'
        verbose_name_plural = 'Posts'
Exemple #8
0
class BanneredCampaignPage(PrimaryPage):
    """
    title, header, intro, and body are inherited from PrimaryPage
    """

    # Note that this is a different related_name, as the `page`
    # name is already taken as back-referenced to CampaignPage.
    cta = models.ForeignKey(
        'Petition',
        related_name='bcpage',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        help_text='Choose an existing, or create a new, pettition form')

    signup = models.ForeignKey(
        'Signup',
        related_name='bcpage',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        help_text='Choose an existing, or create a new, sign-up form')

    tags = ClusterTaggableManager(through=BanneredCampaignTag, blank=True)

    panel_count = len(PrimaryPage.content_panels)
    n = panel_count - 1

    content_panels = PrimaryPage.content_panels[:n] + [
        SnippetChooserPanel('cta'),
        SnippetChooserPanel('signup'),
    ] + PrimaryPage.content_panels[n:]

    promote_panels = FoundationMetadataPageMixin.promote_panels + [
        FieldPanel('tags'),
    ]

    translatable_fields = [
        # Promote tab fields
        SynchronizedField('slug'),
        TranslatableField('seo_title'),
        SynchronizedField('show_in_menus'),
        TranslatableField('search_description'),
        SynchronizedField('search_image'),
        # Content tab fields
        TranslatableField('header'),
        TranslatableField('intro'),
        TranslatableField('body'),
        TranslatableField("title"),
        SynchronizedField("banner"),
        SynchronizedField("narrowed_page_content"),
        SynchronizedField("zen_nav"),
        TranslatableField("cta"),
        TranslatableField("signup"),
    ]

    subpage_types = [
        'BanneredCampaignPage', 'RedirectingPage', 'PublicationPage',
        'OpportunityPage', 'ArticlePage'
    ]

    show_in_menus_default = True

    def get_context(self, request):
        context = super().get_context(request)
        context['related_posts'] = get_content_related_by_tag(self)
        return get_page_tree_information(self, context)

    class Meta:
        verbose_name = "Banner Page"
        verbose_name_plural = "Banner pages"
Exemple #9
0
class ArticlePage(RoutablePageMixin, Page):

    sum_deck = models.CharField(max_length=1000)
    content = RichTextField(blank=True)

    yes_no = {
            ('y', 'Yes'),
            ('n', 'No'),
        }

    draft = models.CharField(max_length=2, choices=yes_no, blank=True, default='n')

    section_list = (
                ('unews', 'University News'),
                ('metro', 'Metro'),
                ('sr', 'Science & Research'),
                ('ac', 'Arts & Culture'),
                ('sports', 'Sports'),
                ('sportscol', 'Sports Columns'),
                ('opinion', 'Opinion'),
                ('col', 'Columns'),
                ('edit', 'Editorials'),
                ('letter', 'Letters to the Editor'),
                ('notes', "Editors' Note"),
                ('mult', 'Multimedia'),
                ('vid', 'Video'),
                ('gal', 'Photo Gallery'),
                ('igraph', 'Interactive Graphic'),
                ('graph', 'Graphics'),
                ('ill', 'Illustrations'),
                ('op', 'Op-eds'),
                ('data', 'Data Science'),
                ('com', 'Comics'),
                # add more to this list
            )

    featured_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    tags = ClusterTaggableManager(through=ArticleTag)
    section = models.CharField(max_length=8, choices=section_list, blank=True, default='h')

    nums = {
        ('1', 'Primary'),
        ('2', 'Secondary'),
        ('3', 'Tertiary'),
    }

    featured_on_main = models.CharField(max_length=2, choices=yes_no, blank=True, default='n')
    position_on_main = models.CharField(max_length=1, choices=nums, blank=True)

    api_fields = [
        APIField('sum_deck'),
        APIField('content'),
        APIField('draft'),
        APIField('section'),
        APIField('featured_on_main'),
        APIField('position_on_main'),
        APIField('tags'),
        APIField('authors'),
        APIField('featured_image'),
        APIField('gallery_images')
    ]

    content_panels = Page.content_panels + [
        FieldPanel('section', classname='class'),
        FieldPanel('sum_deck', classname='class'),
        FieldPanel('draft', classname='class'),
        FieldPanel('featured_on_main', classname='class'),
        FieldPanel('position_on_main', classname='class'),
        FieldPanel('tags'),
        InlinePanel('authors', heading='authors', help_text='Add contributing authors'),
        FieldPanel('content', classname='class'),
        ImageChooserPanel('featured_image', classname='image'),
        InlinePanel('gallery_images', label="Photo Gallery Images"),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('content'),
        index.SearchField('section'),
        index.SearchField('sum_deck'),
        index.SearchField('tags'),
        index.SearchField('authors'),
    ]
Exemple #10
0
class CoursePage(Page):

    #database fields
    date = models.DateField("Post Date")
    name = models.CharField(max_length=200)
    intro = models.CharField(max_length=250)
    students = models.ManyToManyField(User)
    body = RichTextField(blank=True)
    feed_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+')

    tags = ClusterTaggableManager(through=CoursePageTag, blank=True)
    #body = RichTextField(blank=True)

    # search index configuration

    search_fields = Page.search_fields + [
        index.SearchField('name'),
        index.SearchField('intro'),
        index.SearchField('body'),
        index.FilterField('date'),
    ]

    # editor panels configuration

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('tags'),
        ],
                        heading='Course information'),
        FieldPanel('name'),
        FieldPanel('intro'),
        FieldPanel('body', classname="full"),
        InlinePanel('gallery_images', label="Gallery images"),
        InlinePanel('related_links', label="Related links"),
    ]

    promote_panels = [
        MultiFieldPanel(Page.promote_panels, "Common page configuration"),
        ImageChooserPanel('feed_image'),
    ]

    # parent page/subpage type rules

    parent_page_types = ['courses.CourseIndexPage']
    subpage_types = ['courses.SectionPage']

    def get_absolute_url(self):
        return reverse('course_detail', args=(self.id, ))

    def __str__(self):
        return self.name

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    def get_context(self, request):
        # Update context to include only published posts, ordered by reverse-chron = .order_by('-first_published_at')
        context = super(CoursePage, self).get_context(request)
        sections = self.get_children().live().order_by('first_published_at')
        context['sections'] = sections
        units = []
        #print sections
        for s in sections:
            units.append(s.specific.number)
        units = list(set(units))
        context['units'] = units
        return context
Exemple #11
0
class People(Page):
    parent_page_types = ['home.HomePage', 'content.ContentPage']
    subpage_types = ['Person']
    template = 'people.html'

    # Content fields
    description = TextField(
        blank=True,
        default='',
        help_text='Optional short text description, max. 400 characters',
        max_length=400,
    )

    # Meta fields
    keywords = ClusterTaggableManager(through=PeopleTag, blank=True)

    # Content panels
    content_panels = Page.content_panels + [
        FieldPanel('description'),
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                FieldPanel('seo_title'),
                FieldPanel('search_description'),
                FieldPanel('keywords'),
            ],
            heading='SEO',
            help_text=
            'Optional fields to override the default title and description for SEO purposes'
        ),
    ]

    # Settings panels
    settings_panels = [
        FieldPanel('slug'),
        FieldPanel('show_in_menus'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(meta_panels, heading='Meta'),
        ObjectList(settings_panels, heading='Settings', classname='settings'),
    ])

    class Meta:
        verbose_name_plural = 'People'

    @classmethod
    def can_create_at(cls, parent):
        # Allow only one instance of this page type
        return super().can_create_at(parent) and not cls.objects.exists()

    def get_context(self, request):
        context = super().get_context(request)
        context['filters'] = self.get_filters()
        return context

    @property
    def people(self):
        return Person.objects.all().public().live().order_by('title')

    def get_filters(self):
        from ..topics.models import Topic
        return {
            'roles': True,
            'topics': Topic.objects.live().public().order_by('title'),
        }
Exemple #12
0
class Person(Page):
    resource_type = 'person'
    parent_page_types = ['People']
    subpage_types = []
    template = 'person.html'

    # Content fields
    job_title = CharField(max_length=250)
    role = CharField(max_length=250, choices=ROLE_CHOICES, default='staff')
    description = RichTextField(
        'About',
        blank=True,
        default='',
        features=RICH_TEXT_FEATURES,
        help_text='Optional ‘About me’ section content, supports rich text',
    )
    image = ForeignKey(
        'mozimages.MozImage',
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name='+',
    )

    # Card fields
    card_title = CharField('Title', max_length=140, blank=True, default='')
    card_description = TextField('Description',
                                 max_length=400,
                                 blank=True,
                                 default='')
    card_image = ForeignKey(
        'mozimages.MozImage',
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name='+',
        verbose_name='Image',
    )

    # Meta
    twitter = CharField(max_length=250, blank=True, default='')
    facebook = CharField(max_length=250, blank=True, default='')
    linkedin = CharField(max_length=250, blank=True, default='')
    github = CharField(max_length=250, blank=True, default='')
    email = CharField(max_length=250, blank=True, default='')
    websites = StreamField(
        StreamBlock([('website', PersonalWebsiteBlock())],
                    max_num=3,
                    required=False),
        null=True,
        blank=True,
        help_text='Optional links to any other personal websites',
    )
    keywords = ClusterTaggableManager(through=PersonTag, blank=True)

    # Content panels
    content_panels = [
        MultiFieldPanel([
            CustomLabelFieldPanel('title', label='Full name'),
            FieldPanel('job_title'),
            FieldPanel('role'),
        ],
                        heading='Details'),
        FieldPanel('description'),
        MultiFieldPanel(
            [
                ImageChooserPanel('image'),
            ],
            heading='Image',
            help_text=
            ('Optional header image. If not specified a fallback will be used. This image is also shown when sharing '
             'this page via social media')),
    ]

    # Card panels
    card_panels = [
        FieldPanel('card_title'),
        FieldPanel('card_description'),
        ImageChooserPanel('card_image'),
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel([
            InlinePanel('topics'),
        ],
                        heading='Topics interested in'),
        MultiFieldPanel([
            FieldPanel('twitter'),
            FieldPanel('facebook'),
            FieldPanel('linkedin'),
            FieldPanel('github'),
            FieldPanel('email'),
        ],
                        heading='Profiles',
                        help_text=''),
        StreamFieldPanel('websites'),
        MultiFieldPanel(
            [
                FieldPanel('seo_title'),
                FieldPanel('search_description'),
                FieldPanel('keywords'),
            ],
            heading='SEO',
            help_text=
            'Optional fields to override the default title and description for SEO purposes'
        ),
    ]

    # Settings panels
    settings_panels = [
        FieldPanel('slug'),
    ]

    # Tabs
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(card_panels, heading='Card'),
        ObjectList(meta_panels, heading='Meta'),
        ObjectList(settings_panels, heading='Settings', classname='settings'),
    ])

    @property
    def events(self):
        '''
        Return upcoming events where this person is a speaker,
        ordered by start date
        '''
        from ..events.models import Event

        upcoming_events = (Event.objects.filter(
            start_date__gte=datetime.datetime.now()).live().public())

        speaker_events = Event.objects.none()

        for event in upcoming_events.all():
            # add the event to the list if the current person is a speaker
            if event.has_speaker(self):
                speaker_events = speaker_events | Event.objects.page(event)

        return speaker_events.order_by('start_date')

    @property
    def articles(self):
        '''
        Return articles and external articles where this person is (one of) the authors,
        ordered by article date, most recent first
        '''
        from ..articles.models import Article
        from ..externalcontent.models import ExternalArticle

        articles = Article.objects.none()
        external_articles = ExternalArticle.objects.none()

        all_articles = Article.objects.live().public().all()
        all_external_articles = ExternalArticle.objects.live().public().all()

        for article in all_articles:
            if article.has_author(self):
                articles = articles | Article.objects.page(article)

        for external_article in all_external_articles:
            if external_article.has_author(self):
                external_articles = external_articles | ExternalArticle.objects.page(
                    external_article)

        return sorted(chain(articles, external_articles),
                      key=attrgetter('date'),
                      reverse=True)

    @property
    def videos(self):
        '''
        Return the most recent videos and external videos where this person is (one of)
        the speakers.
        '''
        from ..videos.models import Video
        from ..externalcontent.models import ExternalVideo

        videos = Video.objects.none()
        external_videos = ExternalVideo.objects.none()

        all_videos = Video.objects.live().public().all()
        all_external_videos = ExternalVideo.objects.live().public().all()

        for video in all_videos:
            if video.has_speaker(self):
                videos = videos | Video.objects.page(video)

        for external_video in all_external_videos:
            if external_video.has_speaker(self):
                external_videos = external_videos | ExternalVideo.objects.page(
                    external_video)

        return sorted(chain(videos, external_videos),
                      key=attrgetter('date'),
                      reverse=True)

    @property
    def role_group(self):
        return {
            'slug': self.role,
            'title': dict(ROLE_CHOICES).get(self.role, ''),
        }
Exemple #13
0
class ContentPage(BasePage):
    parent_page_types = ["home.HomePage", "content.ContentPage", "topics.Topic"]
    subpage_types = ["people.People", "content.ContentPage"]
    template = "content.html"

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    body = CustomStreamField(
        help_text=(
            "Main page body content. Supports rich text, images, embed via URL, "
            "embed via HTML, and inline code snippets"
        )
    )

    sidebar = CustomStreamField(
        null=True,
        blank=True,
        help_text=(
            "Sidebar page body content (narrower than main body). Rendered to the "
            "right of the main body content in desktop and below it in mobile."
            "Supports rich text, images, embed via URL, "
            "embed via HTML, and inline code snippets"
        ),
    )

    # Card fields
    card_title = CharField("Title", max_length=140, blank=True, default="")
    card_description = TextField("Description", max_length=140, blank=True, default="")
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
        help_text="An image in 16:9 aspect ratio",
    )

    # Meta fields
    nav_description = TextField(
        "Navigation description", max_length=400, blank=True, default=""
    )
    icon = FileField(
        upload_to="contentpage/icons",
        blank=True,
        default="",
        help_text=(
            "MUST be a black-on-transparent SVG icon ONLY, "
            "with no bitmap embedded in it."
        ),
        validators=[check_for_svg_file],
    )
    keywords = ClusterTaggableManager(through=ContentPageTag, blank=True)

    # Editor panel configuration
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        StreamFieldPanel("body"),
        StreamFieldPanel("sidebar"),
    ]

    # Card panels
    card_panels = [
        FieldPanel(
            "card_title",
            help_text=(
                "Title displayed when this page is "
                "represented by a card in a list of items. "
                "If blank, the page's title is used."
            ),
        ),
        FieldPanel(
            "card_description",
            help_text=(
                "Summary text displayed when this page is "
                "represented by a card in a list of items. "
                "If blank, the page's description is used."
            ),
        ),
        MultiFieldPanel(
            [ImageChooserPanel("card_image")],
            heading="16:9 Image",
            help_text=(
                "Image used for representing this page as a Card. "
                "Should be 16:9 aspect ratio. "
                "If not specified a fallback will be used. "
                "This image is also shown when sharing this page via social "
                "media unless a social image is specified."
            ),
        ),
    ]

    # Meta panels
    meta_panels = [
        FieldPanel(
            "nav_description",
            help_text="Text to display in the navigation with the title for this page.",
        ),
        MultiFieldPanel(
            [FieldPanel("icon")],
            heading="Theme",
            help_text=(
                "This icon will be used if, for example, this page is shown in a Menu"
            ),
        ),
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=(
                "Optional fields to override the default title and "
                "description for SEO purposes"
            ),
        ),
    ]

    # Settings panels
    settings_panels = BasePage.settings_panels + [
        FieldPanel("slug"),
        FieldPanel("show_in_menus"),
    ]

    # Tabs
    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading="Content"),
            ObjectList(card_panels, heading="Card"),
            ObjectList(meta_panels, heading="Meta"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )
Exemple #14
0
class GenericPage(Page):
    """
    GenericPage uses a Streamfield with a raw HTML block so is flexible.
    Used for leaf nodes where we don't want to show links to siblings,
    such as tool pages and standalone articles.
    """

    date = models.DateField("Post date", null=True, blank=True)
    short_description = RichTextField(blank=True)
    github_link = models.URLField("Github link", blank=True)
    include_mathjax = models.BooleanField("Include mathjax")
    extra_js_code = models.TextField("Additional JS code", blank=True)
    show_siblings = models.BooleanField(default=False)
    tags = ClusterTaggableManager(through=GenericPageTag, blank=True)

    featured_image = models.ForeignKey('wagtailimages.Image',
                                       null=True,
                                       blank=True,
                                       on_delete=models.SET_NULL,
                                       related_name='+')

    body = StreamField([('heading', blocks.CharBlock(classname="full title")),
                        ('paragraph', blocks.RichTextBlock()),
                        ('html', blocks.RawHTMLBlock()),
                        ('image', ImageChooserBlock()),
                        ('code_block', CodeBlock()),
                        ('two_columns', TwoColumnBlock()),
                        ('link_block', PageLinksBlock())])

    # Inherit search_fields from Page and add more
    search_fields = Page.search_fields + [
        index.SearchField('short_description', boost="1.5"),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('show_siblings'),
        MultiFieldPanel([
            FieldPanel('github_link'),
            FieldPanel('include_mathjax'),
            InlinePanel('css_links', label="CSS links"),
            InlinePanel('js_links', label="JS links"),
            FieldPanel('extra_js_code'),
        ],
                        heading="Additional resources",
                        classname="collapsible collapsed"),
        StreamFieldPanel('body'),
    ]

    promote_panels = [
        MultiFieldPanel(
            [
                ImageChooserPanel('featured_image'),
                FieldPanel('short_description'),
            ],
            heading="Featured content information",
        )
    ] + Page.promote_panels + [FieldPanel('tags')]

    def get_context(self, request):
        context = super(GenericPage, self).get_context(request)
        if self.show_siblings:
            context["previous_page"] = self.get_prev_siblings().live().first()
            context["next_page"] = self.get_next_siblings().live().first()
        return context
Exemple #15
0
class CommunityAsset(ClusterableModel):
    name = models.CharField(blank=False, null=True, max_length=200)
    parent_organisation = models.CharField(blank=True, null=True, max_length=200, help_text="The parent organisation delivering this service, if applicable")
    description = models.TextField(blank=False, null=True, max_length=500, help_text="Describe the service in a short paragraph")
    price = models.CharField(blank=True, null=True, max_length=100, help_text="Give a cost per session/activity. If a simple price cannot be given, leave the field blank.", verbose_name="Cost (£)")
    
    category = models.ForeignKey(Categories, on_delete=models.CASCADE, null=True, blank=False, default="")
    keywords = ClusterTaggableManager(through=Keywords, blank=True)
    age_groups = models.ManyToManyField(AgeGroups, blank=True)
    suitability = models.ManyToManyField(Suitabilities, blank=True)
    accessibility = models.ManyToManyField(Accessibilities, blank=True)

    venue = models.CharField(blank=True, null=True, max_length=150)
    area = models.CharField(blank=True, null=True, max_length=100)
    postcode = models.CharField(blank=True, null=True, max_length=100)

    # Wagtailgmaps expects a `CharField` (or any other field that renders as a text input)
    lat_lng = models.CharField(max_length=255, null=True)

    days = models.ManyToManyField(Days, blank=True)
    frequency = models.CharField(blank=True, null=True, max_length=150, help_text="Describe the frequency of this event if applicable. For example 'daily' or 'fortnightly'")
    daytime = models.BooleanField(blank=True, null=True, help_text="Does this service happen during the daytime (between 9-5)?", default=False, verbose_name="During daytime?")

    contact_name = models.CharField(blank=True, null=True, max_length=150, help_text="Give the name of a person involved with this service")
    email = models.EmailField(blank=True, null=True, max_length=100, help_text="Give a contact email address")
    phone = models.CharField(blank=True, null=True, max_length=100, help_text="Give a contact telephone number, with no spaces")
    url = models.URLField(blank=True, null=True, help_text="The website or webpage where this service can be booked, or where more info can be found about it", verbose_name="Website URL")
    
    review_notes = models.TextField(blank=True, null=True, max_length=500)
    assigned_to = models.TextField(blank=True, null=True, max_length=500)
    review_number = models.CharField(blank=True, null=True, max_length=100)
    review_status = models.ForeignKey(ReviewStatus, on_delete=models.CASCADE, null=True, blank=False, default="")

    last_updated = models.CharField(blank=True, null=True, max_length=100)
    review_date = models.CharField(blank=True, null=True, max_length=100)

    laf_areas = models.ForeignKey(LAFAreas, on_delete=models.CASCADE, null=True, blank=True, default="", verbose_name="LAF Area")
    ccg_locality = models.ForeignKey(CCGLocalities, on_delete=models.CASCADE, null=True, blank=True, default="", verbose_name="CCG Locality")

    vol_dbs_check = models.TextField(blank=True, null=True, max_length=500, verbose_name="Volunteer DBS check")
    safeguarding = models.TextField(blank=True, null=True, max_length=500)
    health_safety = models.TextField(blank=True, null=True, max_length=500)
    insurance = models.TextField(blank=True, null=True, max_length=500)

    clo_notes = models.TextField(blank=True, null=True, max_length=500, verbose_name="CLO notes")

    legacy_categories = models.ManyToManyField(LegacyCategories, blank=True)

    panels = [

        MultiFieldPanel([
            FieldPanel('name'),
            FieldPanel('parent_organisation'),
            FieldPanel('description'),
            FieldPanel('price'),
        ]),

        MultiFieldPanel([
            FieldPanel('category', widget=Select),
            FieldPanel('keywords'),
            FieldPanel('age_groups', widget=CheckboxSelectMultiple),
            FieldPanel('suitability', widget=CheckboxSelectMultiple),
            FieldPanel('accessibility', widget=CheckboxSelectMultiple),
        ], heading="Discovery"),

        MultiFieldPanel([
            FieldPanel('days', widget=CheckboxSelectMultiple),
            FieldRowPanel([
                FieldPanel('daytime', widget=CheckboxInput),
                FieldPanel('frequency')
            ]),
        ], heading="When?"),

        MultiFieldPanel([
            FieldPanel('venue'),
            FieldRowPanel([
                FieldPanel('area'),
                FieldPanel('postcode')
            ]),

            MapFieldPanel('lat_lng', latlng=True),

        ], heading="Where?"),

        MultiFieldPanel([
            FieldPanel('contact_name'),
            FieldRowPanel([
                FieldPanel('phone'),
                FieldPanel('email')
            ]),
            FieldPanel('url'),
        ], heading="Contact details"),

        MultiFieldPanel([
                FieldPanel('review_notes'),
                FieldRowPanel([
                    FieldPanel('assigned_to'),
                    FieldPanel('review_number'),
                ]),
                FieldRowPanel([
                    FieldPanel('last_updated'),
                    FieldPanel('review_date'),
                ]),
                FieldRowPanel([
                    FieldPanel('vol_dbs_check'),
                    FieldPanel('safeguarding'),
                ]),
                FieldRowPanel([
                    FieldPanel('health_safety'),
                    FieldPanel('insurance'),
                ]),
                FieldRowPanel([
                    FieldPanel('laf_areas', widget=Select),
                    FieldPanel('ccg_locality', widget=Select),
                ]),
                FieldPanel('clo_notes'),
                FieldPanel('legacy_categories', widget=CheckboxSelectMultiple),
        ], heading="Internal")
    ]

    def __str__(self):
        return self.name
class ThesisPage(Page):
    description = RichTextField()
    why_important = RichTextField()
    sources = RichTextField()
    tags = ClusterTaggableManager(through=ThesisPageTag, blank=True)
    provider = models.ForeignKey(ThesisProvider, default=1, on_delete=models.SET_NULL, null=True)

    search_fields = Page.search_fields + [
        index.SearchField('description'),
        index.SearchField('why_important'),
        index.SearchField('sources'),
    ]

    content_panels = Page.content_panels + [
        FieldPanel('description'),
        FieldPanel('why_important'),
        FieldPanel('tags'),
        FieldPanel('sources'),
        SnippetChooserPanel('provider'),
    ]

    promote_panels = [
        MultiFieldPanel(Page.promote_panels, "Common page configuration"),
    ]

    parent_page_types = ['theses.ThesisIndexPage']

    def serve(self, request):
        if request.method == 'POST':
            from theses.forms import InterestsForm
            form = InterestsForm(request.POST)
            if form.is_valid():
                form.clean()
                mail_content = self.build_mail_content(request.build_absolute_uri(), form.cleaned_data)
                send_mail('Thesis interest: {}'.format(request.POST['thesis_title']),
                          mail_content,
                          THESES_MAILS,  # recipient email
                          form.cleaned_data['contact_email']
                          )

                return JsonResponse({'message': 'Thank you for your interest! '
                                                'We will let get back to you soon!'})
            else:
                logger.error('The submitted form was invalid.')
                return JsonResponse({'message': 'Sorry, submitting your form was not '
                                                'successful. Please use our contact page.'})
        else:
            return super(ThesisPage, self).serve(request)

    def get_context(self, request):
        from theses.forms import InterestsForm
        context = super(ThesisPage, self).get_context(request)
        context["contactForm"] = InterestsForm
        return context

    @staticmethod
    def build_mail_content(uri, data):
        return dedent("""
        Thesis: {thesis_uri}
        Name: {contact_name},
        Contact email: {contact_email},
        Course and University: {course_and_university},
        Deadline: {deadline}
        
        --------Message--------
        {content}
        """.format(thesis_uri=uri, **data))
Exemple #17
0
class PaperPage(Page):
    is_featured = models.BooleanField()
    cover_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    date = models.DateField("Published date")
    date_fmt = models.CharField("Date format",
                                max_length=8,
                                choices=DATEFMT,
                                default='YMD')
    abstract = RichTextField(blank=True, null=True)
    extra = RichTextField(blank=True, null=True)
    tags = ClusterTaggableManager(through=PaperPageTag, blank=True)
    authors = models.TextField()
    journal = models.ForeignKey(
        'publication.Journal',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    volume = models.CharField(max_length=64, blank=True, null=True)
    issue = models.CharField(max_length=64, blank=True, null=True)
    page = models.CharField(max_length=64, blank=True, null=True)
    doi = models.CharField(max_length=250, blank=True, null=True)
    permalink = models.URLField("Permanent link", blank=True, null=True)
    bibtex = models.TextField(blank=True, null=True)
    category = models.ForeignKey(
        'publication.Category',
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='+',
    )

    content_panels = Page.content_panels + [
        FieldPanel('is_featured'),
        ImageChooserPanel('cover_image'),
        FieldPanel('authors'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('journal'),
            ]),
            FieldRowPanel([
                FieldPanel('volume'),
                FieldPanel('issue'),
                FieldPanel('page'),
            ]),
        ], "Reference information"),
        FieldPanel('date_fmt'),
        FieldPanel('date'),
        FieldPanel('category'),
        FieldPanel('tags'),
        FieldPanel('doi'),
        FieldPanel('permalink'),
        FieldPanel('bibtex'),
        FieldPanel('abstract', classname="full"),
        FieldPanel('extra', classname="full"),
    ]

    subpage_types = []
    parent_page_types = [
        'PapersIndexPage',
    ]

    def print_authors(self):
        return "{}, et al.".format(self.authors.split(';')[0])
Exemple #18
0
class CaseStudy(ClusterableModel):
    """Dedicated snippet for use as a case study. Supports personalised
    selection via its tags.

    The decision about the appropriate Case Study block to show will happen
    when the page attempts to render the relevant CaseStudyBlock.

    Note that this is rendered via Wagtail's ModelAdmin, so appears in the sidebar,
    but we have to keep it registered as a Snippet to be able to transfer it
    with Wagtail-Transfer
    """

    title = models.CharField(
        max_length=255,
        blank=False,
        verbose_name='Internal case study title',
    )

    # old name company_name
    summary_context = models.CharField(max_length=255,
                                       blank=False,
                                       default='How we did it')
    # old name summary
    lead_title = models.TextField(
        blank=False)  # Deliberately not rich-text / no formatting
    body = StreamField(
        [
            (
                'media',
                blocks.StreamBlock(
                    [
                        ('video',
                         core_blocks.SimpleVideoBlock(
                             template='core/includes/_case_study_video.html')),
                        ('image', core_blocks.ImageBlock()),
                    ],
                    min_num=1,
                    max_num=2,
                ),
            ),
            (
                'text',
                blocks.RichTextBlock(features=RICHTEXT_FEATURES__MINIMAL, ),
            ),
            (
                'quote',
                core_blocks.CaseStudyQuoteBlock(),
            ),
        ],
        validators=[case_study_body_validation],
        help_text=
        ('This block must contain one Media section (with one or two items in it) '
         'and/or Quote sections, then one Text section.'),
    )

    # We are keeping the personalisation-relevant tags in separate
    # fields to aid lookup and make the UX easier for editors
    hs_code_tags = ClusterTaggableManager(through='core.HSCodeTaggedCaseStudy',
                                          blank=True,
                                          verbose_name='HS-code tags')

    country_code_tags = ClusterTaggableManager(
        through='core.CountryTaggedCaseStudy',
        blank=True,
        verbose_name='Country tags')
    region_code_tags = ClusterTaggableManager(
        through='core.RegionTaggedCaseStudy',
        blank=True,
        verbose_name='Region tags')
    trading_bloc_code_tags = ClusterTaggableManager(
        through='core.TradingBlocTaggedCaseStudy',
        blank=True,
        verbose_name='Trading bloc tags')

    created = CreationDateTimeField('created', null=True)
    modified = ModificationDateTimeField('modified', null=True)

    panels = [
        MultiFieldPanel(
            [
                FieldPanel('title'),
                FieldPanel('lead_title'),
                FieldPanel('summary_context'),
                StreamFieldPanel('body'),
            ],
            heading='Case Study content',
        ),
        MultiFieldPanel(
            [
                FieldPanel('hs_code_tags'),
                FieldPanel('country_code_tags'),
                FieldPanel('region_code_tags'),
                FieldPanel('trading_bloc_code_tags'),
            ],
            heading='Case Study tags for Personalisation',
        ),
        MultiFieldPanel(
            [
                InlinePanel('related_pages', label='Related pages'),
            ],
            heading=
            'Related Lesson, Topic & Module, also used for Personalisation',
        ),
    ]

    def __str__(self):
        display_name = self.title if self.title else self.summary_context
        return f'{display_name}'

    def save(self, **kwargs):
        self.update_modified = kwargs.pop(
            'update_modified', getattr(self, 'update_modified', True))
        super().save(**kwargs)

    def get_cms_standalone_view_url(self):
        return reverse('cms_extras:case-study-view', args=[self.id])

    class Meta:
        verbose_name_plural = 'Case studies'
        get_latest_by = 'modified'
        ordering = (
            '-modified',
            '-created',
        )
Exemple #19
0
class PresentationPage(Page):
    is_featured = models.BooleanField()
    cover_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    date = models.DateField("Presentation date")
    date_fmt = models.CharField("Date format",
                                max_length=8,
                                choices=DATEFMT,
                                default='YMD')
    abstract = RichTextField(blank=True, null=True)
    extra = RichTextField(blank=True, null=True)
    tags = ClusterTaggableManager(through=PresentationPageTag, blank=True)
    presentor = models.ForeignKey(
        'home.Person',
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='+',
    )
    meeting = models.CharField(max_length=512)
    country = models.CharField(max_length=256, blank=True, null=True)
    location = models.CharField(max_length=256, blank=True, null=True)
    presentation_type = models.CharField(max_length=64,
                                         choices=PRESENTATIONTYPE,
                                         blank=True,
                                         null=True)
    category = models.ForeignKey(
        'publication.Category',
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name='+',
    )

    content_panels = Page.content_panels + [
        FieldPanel('is_featured'),
        ImageChooserPanel('cover_image'),
        SnippetChooserPanel('presentor'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('meeting', classname="col10"),
            ]),
            FieldRowPanel([
                FieldPanel('country', classname="col6"),
                FieldPanel('location', classname="col6"),
            ]),
            FieldRowPanel([
                FieldPanel('presentation_type', classname="col6"),
                FieldPanel('category', classname="col6"),
            ])
        ], "Meeting information"),
        FieldPanel('date_fmt'),
        FieldPanel('date'),
        FieldPanel('tags'),
        FieldPanel('abstract', classname="full"),
        FieldPanel('extra', classname="full"),
    ]

    subpage_types = []
    parent_page_types = [
        'PresentationsIndexPage',
    ]
Exemple #20
0
class ArticlePage(Page):
    tags = ClusterTaggableManager(through=ArticlePageTag, blank=True)
    author_image = models.ForeignKey('wagtailimages.Image',
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+')
    author_name = models.CharField(max_length=10,
                                   validators=[MinLengthValidator(2)])
    article_datetime = models.DateTimeField()
    article_cover = models.ForeignKey('wagtailimages.Image',
                                      null=True,
                                      blank=True,
                                      on_delete=models.SET_NULL,
                                      related_name='+')
    article_cover_app = models.ForeignKey('wagtailimages.Image',
                                          null=True,
                                          blank=True,
                                          on_delete=models.SET_NULL,
                                          related_name='+')
    description = models.TextField(blank=True)
    liked_count = models.IntegerField(default=0)

    body = StreamField([
        ('Paragraph', blocks.RichTextBlock()),
        ('RawHTML', blocks.RawHTMLBlock()),
        ('DocumentChooser', DocumentChooserBlock()),
    ])

    content_panels = Page.content_panels + [
        HelpPanel('建议文章标题不超过30个字'),
        FieldPanel('tags'),
        HelpPanel('建议标签数量最多2个,标签字数1~5个'),
        FieldPanel('description'),
        ImageChooserPanel('author_image'),
        FieldPanel('author_name'),
        FieldPanel('article_datetime'),
        ImageChooserPanel('article_cover'),
        ImageChooserPanel('article_cover_app'),
        StreamFieldPanel('body'),
    ]

    api_fields = [
        APIField('tags'),
        APIField('author_image'),
        APIField('author_name'),
        APIField('article_datetime',
                 serializer=DateTimeField(format="%Y-%m-%d %H:%M")),
        APIField('article_cover'),
        APIField('article_cover_app'),
        APIField('description'),
        APIField('liked_count'),
        # This will nest the relevant BlogPageAuthor objects in the API response
    ]

    parent_page_types = ['ArticleListPage']
    subpage_types = []

    def get_datetime_format(self):

        return self.article_datetime.strftime("%Y-%m-%d %H:%I")

    @property
    def first_tag(self):
        if self.tags:
            return self.tags.first()
        else:
            return None
class Topic(Page):
    resource_type = 'topic'
    parent_page_types = ['Topics']
    subpage_types = ['Topic']
    template = 'topic.html'

    # Content fields
    description = TextField(max_length=250, blank=True, default='')
    featured = StreamField(
        StreamBlock([
            ('article',
             PageChooserBlock(
                 required=False,
                 target_model=[
                     'articles.Article',
                     'externalcontent.ExternalArticle',
                 ],
             )),
            ('external_page', FeaturedExternalBlock()),
        ],
                    min_num=0,
                    max_num=4,
                    required=False),
        null=True,
        blank=True,
    )
    get_started = StreamField(
        StreamBlock([('panel', GetStartedBlock())],
                    min_num=0,
                    max_num=3,
                    required=False),
        null=True,
        blank=True,
    )

    # Card fields
    card_title = CharField('Title', max_length=140, blank=True, default='')
    card_description = TextField('Description',
                                 max_length=140,
                                 blank=True,
                                 default='')
    card_image = ForeignKey(
        'mozimages.MozImage',
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name='+',
        verbose_name='Image',
    )

    # Meta
    icon = FileField(upload_to='topics/icons', blank=True, default='')
    color = CharField(max_length=14, choices=COLOR_CHOICES, default='blue-40')
    keywords = ClusterTaggableManager(through=TopicTag, blank=True)

    # Content panels
    content_panels = Page.content_panels + [
        FieldPanel('description'),
        StreamFieldPanel('featured'),
        StreamFieldPanel('get_started'),
        MultiFieldPanel([
            InlinePanel('people'),
        ], heading='People'),
    ]

    # Card panels
    card_panels = [
        FieldPanel('card_title'),
        FieldPanel('card_description'),
        ImageChooserPanel('card_image'),
    ]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                InlinePanel('parent_topics', label='Parent topic(s)'),
                InlinePanel('child_topics', label='Child topic(s)'),
            ],
            heading='Parent/child topic(s)',
            classname='collapsible collapsed',
            help_text=(
                'Topics with no parent (i.e. top-level topics) will be listed '
                'on the home page. Child topics are listed on the parent '
                'topic’s page.')),
        MultiFieldPanel([
            FieldPanel('icon'),
            FieldPanel('color'),
        ],
                        heading='Theme'),
        MultiFieldPanel([
            FieldPanel('seo_title'),
            FieldPanel('search_description'),
            FieldPanel('keywords'),
        ],
                        heading='SEO'),
    ]

    # Settings panels
    settings_panels = [
        FieldPanel('slug'),
        FieldPanel('show_in_menus'),
    ]

    # Tabs
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(card_panels, heading='Card'),
        ObjectList(meta_panels, heading='Meta'),
        ObjectList(settings_panels, heading='Settings', classname='settings'),
    ])

    @property
    def articles(self):
        return get_combined_articles(self, topics__topic__pk=self.pk)

    @property
    def events(self):
        """Return upcoming events for this topic,
        ignoring events in the past, ordered by start date"""
        return get_combined_events(self,
                                   topics__topic__pk=self.pk,
                                   start_date__gte=datetime.datetime.now())

    @property
    def color_value(self):
        return dict(COLOR_VALUES)[self.color]

    @property
    def subtopics(self):
        return [topic.child for topic in self.child_topics.all()]
Exemple #22
0
class PracticePage(Page):
    parent_page_types = ['practice.PracticeIndexPage']
    subpage_types = []
    full_title = models.TextField("Полное название", max_length=800, null=True)
    raw_subtitle = RichTextField(verbose_name='Подзаголовок', blank=True)
    content = RichTextField(verbose_name='Содержание', blank=True)
    doc = models.ForeignKey(
        'base.CustomDocument', blank=True, null=True,
         on_delete=models.SET_NULL, related_name='+',
         verbose_name= "Документ")
    publish_date = models.DateField("Дата обнародования", null=True)
    category = ForeignKey('practice.CaseCategory', blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Категория дел")
    level = ForeignKey('practice.CourtLevel', blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Уровень суда")
    tags = ClusterTaggableManager(through='practice.PracticePageTag', blank=True)

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

    @property
    def text(self):
        return richtext(self.content)

    @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))
    
    def get_sitemap_urls(self, request):
        return [{
            'location': self.full_url[:-1],
            'lastmod': self.last_published_at,
        }]


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

    @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()

    search_fields = Page.search_fields + [
    index.SearchField('full_title'),
    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('category'),
    index.FilterField('level'),
    index.FilterField('category'),
    index.RelatedFields('category', [
        index.FilterField('name'),
    ]),
]   

    api_fields = [
        APIField('full_title'),
        APIField('subtitle'),
        APIField('text'),
        APIField('doc', serializer=base_serializers.DocSerializer()),
        APIField('breadcrumbs'),
        APIField('section_title'),
        APIField('section_url'),
        APIField('publish_date', serializer=base_serializers.DateSerializer()),
        APIField('category', serializer=serializers.CategorySerializer()),
        APIField('level', serializer=serializers.LevelSerializer()),
        APIField('tags', serializer=base_serializers.TagSerializer()),
        APIField('related_docs', serializer=serializers.RelatedDocsSerializer()),
        APIField('clean_preview'),
    ]

    content_panels =  [
        FieldPanel('full_title'),
        FieldPanel('raw_subtitle'),
        FieldPanel('publish_date'),
        DocumentChooserPanel('doc'),
        FieldPanel('category', widget=forms.RadioSelect),
        FieldPanel('level', widget=forms.RadioSelect),
        FieldPanel('tags'),
        MultiFieldPanel([InlinePanel("related_docs", label='Документ')
            ], heading='Связанные документы'
            ),
        FieldPanel('content'),
        ]

    class Meta:
        verbose_name = 'Страница судебной практики'
        verbose_name_plural = 'Страницы судебной практики'
Exemple #23
0
class BlogPage(Page):
    """
    A Blog Page

    We access the People object with an inline panel that references the
    ParentalKey's related_name in BlogPeopleRelationship. More docs:
    http://docs.wagtail.io/en/latest/topics/pages.html#inline-models
    """
    introduction = models.TextField(
        help_text='Text to describe the page',
        blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Landscape mode only; horizontal width between 1000px and 3000px.'
    )
    body = RichTextField(verbose_name="Page body", blank=True)
    subtitle = models.CharField(blank=True, max_length=255)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    date_published = models.DateField(
        "Date article published", blank=True, null=True
    )

    content_panels = Page.content_panels + [
        FieldPanel('subtitle', classname="full"),
        FieldPanel('introduction', classname="full"),
        ImageChooserPanel('image'),
        FieldPanel('body', classname="full"),
        FieldPanel('date_published'),
        FieldPanel('tags'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    def authors(self):
        """
        Returns the BlogPage's related People. Again note that we are using
        the ParentalKey's related_name from the BlogPeopleRelationship model
        to access these objects. This allows us to access the People objects
        with a loop on the template. If we tried to access the blog_person_
        relationship directly we'd print `blog.BlogPeopleRelationship.None`
        """
        authors = [
            n.people for n in self.blog_person_relationship.all()
        ]

        return authors

    @property
    def get_tags(self):
        """
        Similar to the authors function above we're returning all the tags that
        are related to the blog post into a list we can access on the template.
        We're additionally adding a URL to access BlogPage objects with that tag
        """
        tags = self.tags.all()
        for tag in tags:
            tag.url = '/' + '/'.join(s.strip('/') for s in [
                self.get_parent().url,
                'tags',
                tag.slug
            ])
        return tags

    # Specifies parent to BlogPage as being BlogIndexPages
    parent_page_types = ['BlogIndexPage']

    # Specifies what content types can exist as children of BlogPage.
    # Empty list means that no child content types are allowed.
    subpage_types = []
Exemple #24
0
class TaggedPage(Page):
    tags = ClusterTaggableManager(through=TaggedPageTag, blank=True)
Exemple #25
0
class BlogPage(Page):
    author = models.ForeignKey(
        'core.AuthorPage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    publication_date = models.DateField(
        help_text="Past or future date of publication")
    summary = models.TextField(help_text="Intro or short summary of post")
    body = StreamField([
        (
            'rich_text',
            blocks.RichTextBlock(
                icon='doc-full',
                label='Rich Text',
                features=[
                    # Default features
                    'h2',
                    'h3',
                    'h4',
                    'hr',
                    'link',
                    'bold',
                    'italic',
                    'ol',
                    'ul',
                    'document-link',
                    'embed',
                    'image',

                    # Extra features
                    'code',
                ],
            )),
        ('code', CodeBlock(icon='code')),
        ('quote', QuoteBlock(icon='openquote')),
        ('markdown', MarkdownBlock()),
        ('html', blocks.RawHTMLBlock(icon='site', label='HTML')),
        ('image', CaptionedImageBlock()),
    ])
    banner_image = models.ForeignKey("wagtailimages.Image",
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    teaser_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        help_text="Image to display on the blog index page",
        related_name="+")

    content_panels = Page.content_panels + [
        PageChooserPanel('author'),
        FieldPanel('publication_date'),
        FieldPanel('summary'),
        StreamFieldPanel('body'),
        ImageChooserPanel('banner_image'),
    ]
    parent_page_types = ['BlogIndexPage']
    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            ImageChooserPanel('teaser_image'),
        ], "Teaser Details"),
        FieldPanel('tags'),
    ]

    def get_context(self, request):
        context = super(BlogPage, self).get_context(request)
        context['blog_index'] = BlogIndexPage.objects.first()
        return context
class Articles(BasePage):

    RESOURCES_PER_PAGE = 6

    # IMPORTANT: ARTICLES ARE NOW LABELLED "POSTS" IN THE FRONT END
    parent_page_types = ["home.HomePage"]
    subpage_types = ["Article"]
    template = "articles.html"

    class Meta:
        verbose_name = "posts"
        verbose_name_plural = "posts"

    # Content fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )

    # Meta fields
    keywords = ClusterTaggableManager(through=ArticlesTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [FieldPanel("description")]

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [
                FieldPanel("seo_title"),
                FieldPanel("search_description"),
                ImageChooserPanel("social_image"),
                FieldPanel("keywords"),
            ],
            heading="SEO",
            help_text=("Optional fields to override the default title "
                       "and description for SEO purposes"),
        )
    ]

    # Settings panels
    settings_panels = BasePage.settings_panels + [
        FieldPanel("slug"),
        FieldPanel("show_in_menus"),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Content"),
        ObjectList(meta_panels, heading="Meta"),
        ObjectList(settings_panels, heading="Settings", classname="settings"),
    ])

    @classmethod
    def can_create_at(cls, parent):
        # Allow only one instance of this page type
        return super().can_create_at(parent) and not cls.objects.exists()

    def get_context(self, request):
        context = super().get_context(request)
        context["filters"] = self.get_filters()
        context["resources"] = self.get_resources(request)
        return context

    def get_resources(self, request):
        # This Page class will show both Articles/Posts and Videos in its listing

        # We can't use __in in this deeply related query, so we have to make
        # a custom Q object instead and pass is in as a filter, then deal with
        # it later
        topics = request.GET.getlist(TOPIC_QUERYSTRING_KEY)
        topics_q = Q(topics__topic__slug__in=topics) if topics else Q()
        resources = get_combined_articles_and_videos(self, q_object=topics_q)
        resources = paginate_resources(
            resources,
            page_ref=request.GET.get(PAGINATION_QUERYSTRING_KEY),
            per_page=self.RESOURCES_PER_PAGE,
        )

        return resources

    def get_filters(self):
        from ..topics.models import Topic

        return {"topics": Topic.published_objects.order_by("title")}
Exemple #27
0
class RecordPage(ContentPage):
    formatted_title = models.CharField(max_length=255, null=True, blank=True, default='',
                                        help_text="Use if you need italics in the title. e.g. <em>Italicized words</em>")
    date = models.DateField(default=datetime.date.today)
    category = models.CharField(
        max_length=255,
        choices=constants.record_page_categories.items()
    )
    read_next = models.ForeignKey(
        'RecordPage',
        blank=True,
        null=True,
        default=get_previous_record_page,
        on_delete=models.SET_NULL
    )
    related_section_title = models.CharField(
        max_length=255,
        blank=True,
        default='Explore campaign finance data'
    )
    related_section_url = models.CharField(
        max_length=255,
        blank=True,
        default='/data/'
    )
    monthly_issue = models.CharField(
        max_length=255,
        blank=True,
        default=''
    )
    monthly_issue_url = models.CharField(
        max_length=255,
        blank=True,
        default=''
    )

    keywords = ClusterTaggableManager(through=RecordPageTag, blank=True)

    homepage_pin = models.BooleanField(default=False)
    homepage_pin_expiration = models.DateField(blank=True, null=True)
    homepage_pin_start = models.DateField(blank=True, null=True)
    homepage_hide = models.BooleanField(default=False)
    template = 'home/updates/record_page.html'
    content_panels = ContentPage.content_panels + [
        FieldPanel('formatted_title'),
        FieldPanel('date'),
        FieldPanel('monthly_issue'),
        FieldPanel('category'),
        FieldPanel('keywords'),
        InlinePanel('authors', label='Authors'),
        PageChooserPanel('read_next'),
        FieldPanel('related_section_title'),
        FieldPanel('related_section_url')
    ]

    promote_panels = Page.promote_panels + [
        MultiFieldPanel([
            FieldPanel('homepage_pin'),
            FieldPanel('homepage_pin_start'),
            FieldPanel('homepage_pin_expiration'),
            FieldPanel('homepage_hide')
        ],
            heading="Home page feed"
        )
    ]

    search_fields =  ContentPage.search_fields + [
        index.FilterField('category'),
        index.FilterField('date')
    ]

    @property
    def content_section(self):
        return ''

    @property
    def get_update_type(self):
        return constants.update_types['fec-record']

    @property
    def get_author_office(self):
        return 'Information Division'