コード例 #1
0
    def setUp(self):
        self.EventPageForm = get_form_for_model(
            EventPage, form_class=WagtailAdminPageForm, formsets=[])
        self.event = EventPage(title='Abergavenny sheepdog trials',
                               date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))

        self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)
コード例 #2
0
class ContentPage(Page):
    parent_page_types = ['home.HomePage', 'content.ContentPage']
    subpage_types = ['people.People', 'content.ContentPage']
    template = 'content.html'

    # Content fields
    hero_image = ForeignKey('mozimages.MozImage',
                            null=True,
                            blank=True,
                            on_delete=SET_NULL,
                            related_name='+')
    body = CustomStreamField(help_text=(
        'Main page body content. 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',
    )

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

    # Editor panel configuration
    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [ImageChooserPanel('hero_image')],
            heading='Hero image',
            help_text=
            'Image should be at least 1024px x 438px (21:9 aspect ratio)'),
        StreamFieldPanel('body'),
    ]

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

    # 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'),
    ]

    # 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'),
    ])
コード例 #3
0
class BlogArticlePage(MixinSeoFields, Page, MixinPageMethods, GoogleAdsMixin):
    template = "blog_post.haml"
    date = models.DateField("Post date")
    page_title = models.CharField(
        max_length=MAX_BLOG_ARTICLE_TITLE_LENGTH,
        help_text=ArticleBlogPageHelpTexts.ARTICLE_PAGE_TITLE.value)
    body = StreamField([
        (ArticleBodyBlockNames.MARKDOWN.value, MarkdownBlock(icon="code")),
        (ArticleBodyBlockNames.HEADER.value, CharBlock()),
        (ArticleBodyBlockNames.PARAGRAPH.value,
         RichTextBlock(features=RICH_TEXT_BLOCK_FEATURES)),
        (ArticleBodyBlockNames.TABLE.value, TableBlock()),
        (ArticleBodyBlockNames.IMAGE.value, CaptionedImageBlock()),
    ], )

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

    author = models.ForeignKey(Employees, on_delete=models.DO_NOTHING)

    read_time = models.PositiveIntegerField()

    table_of_contents = models.BooleanField(default=False)

    recommended_articles = StreamField(
        [("page",
          PageChooserBlock(can_choose_root=False,
                           page_type="blog.BlogArticlePage"))],
        null=True,
        blank=True,
    )

    views = models.PositiveIntegerField(default=0)

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

    cover_photo_alt_description = models.CharField(max_length=125,
                                                   blank=True,
                                                   default="Open the article")

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

    article_photo_alt_description = models.CharField(max_length=125,
                                                     blank=True,
                                                     default="")

    is_main_article = models.BooleanField(default=False)

    Page._meta.get_field(
        "title").help_text = ArticleBlogPageHelpTexts.PAGE_TITLE.value

    content_panels = Page.content_panels + [
        FieldPanel("page_title", classname="title full"),
        FieldPanel("date"),
        FieldPanel("author"),
        FieldPanel("read_time"),
        StreamFieldPanel("recommended_articles"),
        FieldPanel("views"),
        FieldPanel("is_main_article"),
        ImageChooserPanel("cover_photo"),
        FieldPanel("cover_photo_alt_description"),
        ImageChooserPanel("article_photo"),
        FieldPanel("article_photo_alt_description"),
        FieldPanel("table_of_contents"),
        StreamFieldPanel("body"),
    ]

    @cached_property
    def headers_list(self) -> List[str]:
        list_of_headers = []
        for stream_child in self.body:  # pylint: disable=not-an-iterable
            if stream_child.block.name == ArticleBodyBlockNames.HEADER.value:
                list_of_headers.append(stream_child.value)
        return list_of_headers

    def get_header_id(self, title: str) -> int:
        return self.headers_list.index(title)

    @cached_property
    def intro(self) -> str:
        paragraph_text = self._get_text_for_intro(
            MAX_BLOG_ARTICLE_INTRO_LENGTH)
        if len(paragraph_text) == 0:
            return "Article intro not available."
        words_cycle = cycle(paragraph_text.split())
        intro_text = self._concatenate_intro_text_from_paragraphs_text(
            words_cycle, MAX_BLOG_ARTICLE_INTRO_LENGTH)
        end_ellipsis = INTRO_ELLIPSIS
        return intro_text + end_ellipsis

    def _get_text_for_intro(self, character_limit: int) -> str:
        text_blocks: list = self._get_list_of_text_blocks()
        paragraphs_text = ""
        if len(text_blocks) == 0:
            return paragraphs_text

        blocks_cycle = cycle(text_blocks)
        while len(paragraphs_text) < character_limit:
            paragraphs_text = self._extract_paragraph_text_from_block(
                blocks_cycle, paragraphs_text)
        return paragraphs_text

    def _get_list_of_text_blocks(self) -> list:
        whitelisted_block_names = [
            ArticleBodyBlockNames.PARAGRAPH.value,
            ArticleBodyBlockNames.MARKDOWN.value
        ]
        return list(
            filter(
                lambda body_element: body_element.block.name in
                whitelisted_block_names, self.body))

    @staticmethod
    def _extract_paragraph_text_from_block(blocks: cycle, text: str) -> str:
        space_between_texts = " "
        next_block = next(blocks)
        if next_block.block.name == ArticleBodyBlockNames.MARKDOWN.value:
            source_text = "".join(
                BeautifulSoup(markdown(next_block.value),
                              "html.parser").findAll(text=True))
        else:
            source_text = next_block.value.source
        next_text = strip_tags(source_text)

        if len(text) == 0:
            text = next_text
        else:
            text += f"{space_between_texts}{next_text}"
        return text

    def _concatenate_intro_text_from_paragraphs_text(
            self, words_cycle: cycle, character_limit: int) -> str:
        intro_text = ""
        new_text = next(words_cycle)
        while len(new_text) < character_limit:
            intro_text = new_text
            new_text = self._concatenate_strings(intro_text, words_cycle)
        return intro_text

    @staticmethod
    def _concatenate_strings(text: str, words: cycle) -> str:
        space_between_texts = " "
        text += f"{space_between_texts}{next(words)}"
        return text

    def get_proper_url(self) -> str:
        return self.slug

    def get_absolute_url(self) -> str:
        return self.url_path

    def get_context(self, request: WSGIRequest, *args: Any,
                    **kwargs: Any) -> dict:
        context = super().get_context(request, *args, **kwargs)
        self._increase_view_counter()
        context["URL_PREFIX"] = settings.URL_PREFIX
        context["article_body_block_names"] = ArticleBodyBlockNames
        context["GOOGLE_ADS_CONVERSION_ID"] = settings.GOOGLE_ADS_CONVERSION_ID
        context["GOOGLE_TAG_MANAGER_ID"] = settings.GOOGLE_TAG_MANAGER_ID
        return context

    def _increase_view_counter(self) -> None:
        # increase page view counter
        self.views += 1
        self.full_clean()
        self.save()

    def save(self, *args: Any, **kwargs: Any) -> None:  # pylint: disable=signature-differs
        if not BlogArticlePage.objects.filter(
                is_main_article=True) and not self.is_main_article:
            self.is_main_article = True
        if self.is_main_article:
            try:
                article = BlogArticlePage.objects.get(
                    is_main_article=self.is_main_article)
                article.is_main_article = False
                article.save()
            except BlogArticlePage.DoesNotExist:
                pass

        if self.table_of_contents and len(self.headers_list) == 0:
            self.table_of_contents = False

        self._validate_parent_page()
        super().save(*args, **kwargs)

    def clean(self) -> None:
        super().clean()
        self._clean_recommended_articles()
        self._validate_recommended_articles_uniqueness()

    def _clean_recommended_articles(self) -> None:
        self.recommended_articles = StreamValue(
            stream_block=StreamBlock([("page", PageChooserBlock())]),
            stream_data=[("page", stream_child.value)
                         for stream_child in self.recommended_articles
                         if stream_child.value is not None],
        )

    def _validate_parent_page(self) -> None:
        if not isinstance(self.get_parent().specific, BlogIndexPage):
            raise ValidationError(
                message=f"{self.title} must be child of BlogIndexPage")

    def _validate_recommended_articles_uniqueness(self) -> None:
        article_pages_set = set()
        for stream_child in self.recommended_articles:  # pylint: disable=not-an-iterable
            if stream_child.value in article_pages_set:
                raise ValidationError(
                    message=f"'{stream_child.value}' is listed more than once!"
                )
            article_pages_set.add(stream_child.value)
コード例 #4
0
        if self.signup_limit == -1:
            context['can_signup'] = False
        elif self.signup_limit == 0:
            context['can_signup'] = in_signup_window
        elif self.signups.count() < self.signup_limit:
            context['can_signup'] = in_signup_window
        else:
            context['can_signup'] = False

        return context


EventPage.content_panels = [
    MultiFieldPanel([
        FieldPanel('title', classname="full title"),
        FieldPanel('cancelled'),
        FieldPanel('description'),
        FieldPanel('category'),
        FieldPanel('location'),
        FieldPanel('facebook_link'),
        FieldPanel('start'),
        FieldPanel('finish'),
        StreamFieldPanel('body'),
    ],
                    heading='Event details'),
    MultiFieldPanel([
        FieldPanel('signup_limit'),
        FieldPanel('signup_open'),
        FieldPanel('signup_close'),
        FieldPanel('signup_freshers_open')
コード例 #5
0
class Constant(models.Model):
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField(null=True, blank=True)

    key = models.SlugField(
        max_length=255,
        unique=True,
        help_text=mark_safe(
            _("The key that can be used in tags to include the value.<br/>"
              "For example: <code>{{ ga_id }}</code>.")),
    )
    value = models.CharField(
        max_length=255,
        help_text=_(
            "The value to be rendered when this constant is included."),
    )

    panels = [
        FieldPanel("name", classname="full title"),
        FieldPanel("description", classname="full"),
        MultiFieldPanel(
            [FieldRowPanel([FieldPanel("key"),
                            FieldPanel("value")])],
            heading=_("Data")),
    ]

    def as_dict(self):
        return {
            "name": self.name,
            "description": self.description,
            "key": self.key,
            "value": self.value,
        }

    def get_value(self):
        return self.value

    @classmethod
    def create_context(cls):
        context = cache.get("wtm_constant_cache", {})

        if not context:
            for constant in cls.objects.all():
                context[constant.key] = constant.get_value()

            timeout = getattr(settings, "WTM_CACHE_TIMEOUT", 60 * 30)
            cache.set("wtm_constant_cache", context, timeout)

        return context

    def clean(self):
        from wagtail_tag_manager.models.variables import Variable

        if Variable.objects.filter(key=self.key).exists():
            raise ValidationError(
                f"A variable with the key '{ self.key }' already exists.")
        else:
            super().clean()

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        self.full_clean()
        return super().save(force_insert, force_update, using, update_fields)

    def __str__(self):
        return self.name
コード例 #6
0
class ArticlePage(
        BasicPageAbstract,
        ContentPage,
        FeatureablePageAbstract,
        FromTheArchivesPageAbstract,
        ShareablePageAbstract,
        ThemeablePageAbstract,
):
    class ArticleTypes(models.TextChoices):
        CIGI_IN_THE_NEWS = ('cigi_in_the_news', 'CIGI in the News')
        INTERVIEW = ('interview', 'Interview')
        NEWS_RELEASE = ('news_release', 'News Release')
        OP_ED = ('op_ed', 'Op-Ed')
        OPINION = ('opinion', 'Opinion')

    class Languages(models.TextChoices):
        DA = ('da', 'Danish')
        DE = ('de', 'German')
        EL = ('el', 'Greek')
        EN = ('en', 'English')
        ES = ('es', 'Spanish')
        FR = ('fr', 'French')
        ID = ('id', 'Indonesian')
        IT = ('it', 'Italian')
        NL = ('nl', 'Dutch')
        PL = ('pl', 'Polish')
        PT = ('pt', 'Portugese')
        RO = ('ro', 'Romanian')
        SK = ('sk', 'Slovak')
        SV = ('sv', 'Swedish')
        TR = ('tr', 'Turkish')
        ZH = ('zh', 'Chinese')

    class HeroTitlePlacements(models.TextChoices):
        BOTTOM = ('bottom', 'Bottom')
        TOP = ('top', 'Top')

    article_series = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Opinion series',
    )
    article_type = models.ForeignKey(
        'articles.ArticleTypePage',
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='articles',
    )
    body = StreamField(
        BasicPageAbstract.body_default_blocks + [
            BasicPageAbstract.body_accordion_block,
            BasicPageAbstract.body_autoplay_video_block,
            BasicPageAbstract.body_chart_block,
            BasicPageAbstract.body_embedded_tiktok_block,
            BasicPageAbstract.body_external_quote_block,
            BasicPageAbstract.body_external_video_block,
            BasicPageAbstract.body_extract_block,
            BasicPageAbstract.body_highlight_title_block,
            BasicPageAbstract.body_image_full_bleed_block,
            BasicPageAbstract.body_image_scroll_block,
            BasicPageAbstract.body_poster_block,
            BasicPageAbstract.body_pull_quote_left_block,
            BasicPageAbstract.body_pull_quote_right_block,
            BasicPageAbstract.body_recommended_block,
            BasicPageAbstract.body_text_border_block,
            BasicPageAbstract.body_tool_tip_block,
            BasicPageAbstract.body_tweet_block,
        ],
        blank=True,
    )
    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.',
    )
    embed_youtube_label = models.CharField(
        max_length=255,
        blank=True,
        help_text='Add a label to appear below the embedded video.',
    )
    footnotes = RichTextField(
        blank=True,
        features=[
            'bold',
            'endofarticle',
            'h3',
            'h4',
            'italic',
            'link',
            'ol',
            'ul',
            'subscript',
            'superscript',
            'anchor',
        ],
    )
    hero_title_placement = models.CharField(
        blank=True,
        max_length=16,
        choices=HeroTitlePlacements.choices,
        verbose_name='Hero Title Placement',
        help_text=
        'Placement of the title within the hero section. Currently only works on the Longform 2 theme.',
    )
    hide_excerpt = models.BooleanField(
        default=False,
        verbose_name='Hide Excerpt',
        help_text=
        'For "CIGI in the News" only: when enabled, hide excerpt and display full article instead',
    )
    image_banner = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Image',
    )
    image_banner_small = models.ForeignKey('images.CigionlineImage',
                                           null=True,
                                           blank=True,
                                           on_delete=models.SET_NULL,
                                           related_name='+',
                                           verbose_name='Banner Image Small')
    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 used in feature sections',
    )
    interviewers = StreamField(
        [
            ('interviewer',
             PageChooserBlock(required=True, page_type='people.PersonPage')),
        ],
        blank=True,
    )
    language = models.CharField(
        blank=True,
        max_length=2,
        choices=Languages.choices,
        verbose_name='Language',
        help_text=
        'If this content is in a language other than English, please select the language from the list.',
    )
    multimedia_series = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )
    related_files = StreamField(
        [
            ('file', DocumentChooserBlock()),
        ],
        blank=True,
    )
    short_description = RichTextField(
        blank=True,
        null=False,
        features=['bold', 'italic', 'link'],
    )
    video_banner = models.ForeignKey(
        'wagtailmedia.Media',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Video',
    )
    website_button_text = models.CharField(
        blank=True,
        max_length=64,
        help_text=
        'Override the button text for the article website. If empty, the button will read "View Full Article".'
    )
    website_url = models.URLField(blank=True, max_length=512)
    works_cited = RichTextField(
        blank=True,
        features=[
            'bold',
            'endofarticle',
            'h3',
            'h4',
            'italic',
            'link',
            'ol',
            'ul',
            'subscript',
            'superscript',
        ],
    )

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

    @property
    def cigi_people_mentioned_ids(self):
        return [item.person.id for item in self.cigi_people_mentioned.all()]

    @property
    def expired_image(self):
        if self.publishing_date:
            return self.publishing_date < datetime.datetime(
                2017, 1, 1).astimezone(pytz.timezone('America/Toronto'))
        return False

    @property
    def article_series_description(self):
        if self.article_series:
            return self.article_series.specific.series_items_description
        return None

    @property
    def article_series_disclaimer(self):
        if self.article_series:
            for series_item in self.article_series.specific.article_series_items:
                if series_item.content_page.specific == self and not series_item.hide_series_disclaimer:
                    return self.article_series.specific.series_items_disclaimer
        return None

    def is_opinion(self):
        return self.article_type.title in [
            'Op-Eds',
            'Opinion',
        ]

    def get_template(self, request, *args, **kwargs):
        standard_template = super(ArticlePage,
                                  self).get_template(request, *args, **kwargs)
        if self.theme:
            return f'themes/{self.get_theme_dir()}/article_page.html'
        return standard_template

    content_panels = [
        BasicPageAbstract.title_panel,
        MultiFieldPanel([
            FieldPanel('short_description'),
            StreamFieldPanel('body'),
            FieldPanel('footnotes'),
            FieldPanel('works_cited'),
        ],
                        heading='Body',
                        classname='collapsible collapsed'),
        MultiFieldPanel(
            [
                PageChooserPanel(
                    'article_type',
                    ['articles.ArticleTypePage'],
                ),
                FieldPanel('hide_excerpt'),
                FieldPanel('publishing_date'),
                FieldPanel('website_url'),
                FieldPanel('website_button_text'),
                FieldPanel('language'),
            ],
            heading='General Information',
            classname='collapsible collapsed',
        ),
        ContentPage.authors_panel,
        MultiFieldPanel(
            [
                ImageChooserPanel('image_hero'),
                ImageChooserPanel('image_poster'),
                ImageChooserPanel('image_banner'),
                ImageChooserPanel('image_banner_small'),
            ],
            heading='Images',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('embed_youtube'),
                FieldPanel('embed_youtube_label'),
                MediaChooserPanel('video_banner'),
            ],
            heading='Media',
            classname='collapsible collapsed',
        ),
        ContentPage.recommended_panel,
        MultiFieldPanel(
            [
                FieldPanel('topics'),
                FieldPanel('projects'),
                PageChooserPanel(
                    'article_series',
                    ['articles.ArticleSeriesPage'],
                ),
                PageChooserPanel(
                    'multimedia_series',
                    ['multimedia.MultimediaSeriesPage'],
                ),
                InlinePanel('cigi_people_mentioned', label='People Mentioned'),
                StreamFieldPanel('interviewers'),
                StreamFieldPanel('related_files'),
            ],
            heading='Related',
            classname='collapsible collapsed',
        ),
        FromTheArchivesPageAbstract.from_the_archives_panel,
    ]
    promote_panels = Page.promote_panels + [
        FeatureablePageAbstract.feature_panel,
        ShareablePageAbstract.social_panel,
        SearchablePageAbstract.search_panel,
    ]
    settings_panels = Page.settings_panels + [
        ThemeablePageAbstract.theme_panel,
    ]

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

    parent_page_types = ['articles.ArticleListPage']
    subpage_types = []
    templates = 'articles/article_page.html'

    @property
    def is_title_bottom(self):
        return self.title in [
            'Can the G20 Save Globalization\'s Waning Reputation?',
            'Shoshana Zuboff on the Undetectable, Indecipherable World of Surveillance Capitalism'
        ]

    @property
    def article_series_category(self):
        category = ''
        for series_item in self.article_series.specific.article_series_items:
            if series_item.category_title:
                category = series_item.category_title
            if series_item.content_page.id == self.id:
                return category

    class Meta:
        verbose_name = 'Opinion'
        verbose_name_plural = 'Opinions'
コード例 #7
0
class PrimaryPage(FoundationMetadataPageMixin, Page):
    """
    Basically a straight copy of modular page, but with
    restrictions on what can live 'under it'.

    Ideally this is just PrimaryPage(ModularPage) but
    setting that up as a migration seems to be causing
    problems.
    """
    header = models.CharField(max_length=250, blank=True)

    banner = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_banner',
        verbose_name='Hero Image',
    )

    intro = models.CharField(
        max_length=250,
        blank=True,
        help_text='Intro paragraph to show in hero cutout box')

    narrowed_page_content = models.BooleanField(
        default=False,
        help_text=
        'For text-heavy pages, turn this on to reduce the overall width of the content on the page.'
    )

    zen_nav = models.BooleanField(
        default=False,
        help_text=
        'For secondary nav pages, use this to collapse the primary nav under a toggle hamburger.'
    )

    body = StreamField(base_fields)

    settings_panels = Page.settings_panels + [
        MultiFieldPanel([
            FieldPanel('narrowed_page_content'),
        ]),
        MultiFieldPanel([
            FieldPanel('zen_nav'),
        ])
    ]

    content_panels = Page.content_panels + [
        FieldPanel('header'),
        ImageChooserPanel('banner'),
        FieldPanel('intro'),
        StreamFieldPanel('body'),
    ]

    parent_page_types = [
        'Homepage',
        'PrimaryPage',
    ]

    subpage_types = ['PrimaryPage', 'RedirectingPage']

    show_in_menus_default = True

    def get_context(self, request):
        context = super(PrimaryPage, self).get_context(request)
        return get_page_tree_information(self, context)
コード例 #8
0
ファイル: models.py プロジェクト: nbuonin/decruck-wagtail
class CompositionPage(Page):
    composition_title = RichTextField(features=['bold', 'italic'])
    description = StreamField([('rich_text', RichTextBlock()),
                               ('image', ImageChooserBlock())],
                              blank=True)
    location = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )
    genre = ParentalManyToManyField(Genre,
                                    blank=True,
                                    related_name='compositions')
    instrumentation = ParentalManyToManyField(
        'Instrument',
        blank=True,
    )
    orchestration = RichTextField(
        blank=True,
        features=['bold', 'italic'],
        help_text=(
            'If the composition is for an ensemble, use this field to enter '
            'the orchestration of the work.'))
    duration = DurationField(null=True,
                             blank=True,
                             help_text='Expects data in the format "HH:MM:SS"')
    dedicatee = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )
    text_source = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='The source of the text used in the compostition.')
    collaborator = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='Others that Decruck collaborated with.')
    manuscript_status = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='Notes about the location and condition of the manuscript.')
    recording = StreamField([('rich_text', RichTextBlock()),
                             ('image', ImageChooserBlock())],
                            blank=True)
    information_up_to_date = BooleanField(default=False)
    scanned = BooleanField(default=False)
    premiere = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
    )

    # For preview score
    preview_score = FileField(
        upload_to='composition_preview_scores/',
        blank=True,
        null=True,
        validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
    preview_score_checksum = CharField(editable=False,
                                       max_length=256,
                                       blank=True)
    preview_score_checked = False
    preview_score_updated = False

    # Extended Date Time Format
    nat_lang_edtf_string = CharField(
        verbose_name='Natural Language Date',
        help_text=('The EDTF date in natural language. This field is help '
                   'users who aren\'t familiar with the EDTF. It does not '
                   'change how the date is represented.'),
        blank=True,
        max_length=256)
    edtf_string = CharField(
        verbose_name='EDTF Date',
        help_text=mark_safe(
            'A date in the <a href="https://www.loc.gov/standards/datetime/" '
            'target="_blank"><strong>Extended Date Time Format</strong></a>'),
        blank=True,
        max_length=256)
    lower_fuzzy = DateField(editable=False, null=True, blank=True)
    upper_fuzzy = DateField(editable=False, null=True, blank=True)
    lower_strict = DateField(editable=False, null=True, blank=True)
    upper_strict = DateField(editable=False, null=True, blank=True)
    nat_lang_year = CharField(editable=False, max_length=9, blank=True)

    def instrumentation_list(self):
        return ', '.join([str(i) for i in self.instrumentation.all()])

    class Meta:
        verbose_name = "Composition"

    def get_context(self, request, *args, **kwargs):
        ctx = super().get_context(request, *args, **kwargs)

        try:
            search_idx = request.session['comp_search_index']
            if search_idx:
                idx = search_idx.index(self.pk)
                prev_url = None
                next_url = None
                if idx > 0:
                    pk = search_idx[idx - 1]
                    prev_url = CompositionPage.objects.get(pk=pk).url

                if idx < len(search_idx) - 1:
                    pk = search_idx[idx + 1]
                    next_url = CompositionPage.objects.get(pk=pk).url

                ctx['prev_url'] = prev_url
                ctx['next_url'] = next_url
                ctx['comp_search_qs'] = request.\
                    session.get('comp_search_qs', '')
        except (KeyError, ValueError):
            pass

        return ctx

    def clean(self):
        super().clean()
        # Per Django docs: validate and modify values in Model.clean()
        # https://docs.djangoproject.com/en/3.1/ref/models/instances/#django.db.models.Model.clean

        # Check that nat_lang_edtf_string and edtf_string are either both set, or both unset
        if (self.nat_lang_edtf_string
                and not self.edtf_string) or (not self.nat_lang_edtf_string
                                              and self.edtf_string):
            raise ValidationError(
                'If setting a date on a composition, an EDTF string and a natural language EDTF string must be provided.'
            )

        # Validate edtf_string
        if self.edtf_string and self.nat_lang_edtf_string:
            try:
                e = parse_edtf(self.edtf_string)
            except EDTFParseException:
                raise ValidationError({
                    'edtf_string':
                    '{} is not a valid EDTF string'.format(self.edtf_string)
                })

            self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy())
            self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy())
            self.lower_strict = struct_time_to_date(e.lower_strict())
            self.upper_strict = struct_time_to_date(e.upper_strict())

            if self.lower_strict.year != self.upper_strict.year:
                self.nat_lang_year = '{}-{}'.format(self.lower_strict.year,
                                                    self.upper_strict.year)
            else:
                self.nat_lang_year = str(self.lower_strict.year)

    def save(self, *args, **kwargs):
        # If there's no preview score file, then just save the model
        if not self.preview_score:
            return super().save(*args, **kwargs)

        if self.preview_score_checked:
            # This was the cause of a subtle bug. Because this method can be
            # called multiple times during model creation, leaving this flag
            # set would cause the post save hook to fire multiple times.
            self.preview_score_updated = False
            return super().save(*args, **kwargs)

        h = hashlib.md5()
        for chunk in iter(lambda: self.preview_score.read(8192), b''):
            h.update(chunk)

        self.preview_score.seek(0)
        checksum = h.hexdigest()
        if not self.preview_score_checksum == checksum:
            self.preview_score_checksum = checksum
            self.preview_score_updated = True

        self.preview_score_checked = True
        return super().save(*args, **kwargs)

    content_panels = Page.content_panels + [
        FieldPanel('composition_title'),
        StreamFieldPanel('description'),
        MultiFieldPanel(
            [FieldPanel('edtf_string'),
             FieldPanel('nat_lang_edtf_string')],
            help_text='Enter a date in the LOC Extended Date Time Format',
            heading='Date'),
        FieldPanel('location'),
        FieldPanel('instrumentation'),
        FieldPanel('orchestration'),
        FieldPanel('duration'),
        FieldPanel('dedicatee'),
        FieldPanel('premiere'),
        FieldPanel('genre'),
        FieldPanel('text_source'),
        FieldPanel('collaborator'),
        FieldPanel('manuscript_status'),
        FieldPanel('information_up_to_date'),
        FieldPanel('scanned'),
        FieldPanel('preview_score'),
        StreamFieldPanel('recording'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('description', partial_match=True),
        index.SearchField('location', partial_match=True),
        index.SearchField('dedicatee', partial_match=True),
        index.SearchField('premiere', partial_match=True),
        index.SearchField('text_source', partial_match=True),
        index.SearchField('collaborator', partial_match=True),
        index.SearchField('manuscript_status', partial_match=True),
        index.SearchField('recording', partial_match=True),
        index.RelatedFields('genre', [
            index.SearchField('genre_en', partial_match=True),
            index.SearchField('genre_fr', partial_match=True),
        ]),
        index.RelatedFields('instrumentation', [
            index.SearchField('instrument_en', partial_match=True),
            index.SearchField('instrument_fr', partial_match=True),
        ]),
    ]

    parent_page_types = ['CompositionListingPage']
コード例 #9
0
class Event(BasePage):
    resource_type = "event"
    parent_page_types = ["events.Events"]
    subpage_types = []
    template = "event.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,
    )
    image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
    )
    start_date = DateField(default=datetime.date.today)
    end_date = DateField(blank=True, null=True)
    latitude = FloatField(blank=True, null=True)
    longitude = FloatField(blank=True, null=True)
    register_url = URLField("Register URL", blank=True, null=True)
    body = CustomStreamField(
        blank=True,
        null=True,
        help_text=(
            "Optional body content. Supports rich text, images, embed via URL, "
            "embed via HTML, and inline code snippets"
        ),
    )
    venue_name = CharField(max_length=100, blank=True, default="")
    venue_url = URLField("Venue URL", max_length=100, blank=True, default="")
    address_line_1 = CharField(max_length=100, blank=True, default="")
    address_line_2 = CharField(max_length=100, blank=True, default="")
    address_line_3 = CharField(max_length=100, blank=True, default="")
    city = CharField(max_length=100, blank=True, default="")
    state = CharField("State/Province/Region", max_length=100, blank=True, default="")
    zip_code = CharField("Zip/Postal code", max_length=100, blank=True, default="")
    country = CountryField(blank=True, default="")
    agenda = StreamField(
        StreamBlock([("agenda_item", AgendaItemBlock())], required=False),
        blank=True,
        null=True,
        help_text="Optional list of agenda items for this event",
    )
    speakers = StreamField(
        StreamBlock(
            [
                ("speaker", PageChooserBlock(target_model="people.Person")),
                ("external_speaker", ExternalSpeakerBlock()),
            ],
            required=False,
        ),
        blank=True,
        null=True,
        help_text="Optional list of speakers for this event",
    )

    # 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
    keywords = ClusterTaggableManager(through=EventTag, 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"
            ),
        ),
        MultiFieldPanel(
            [
                FieldPanel("start_date"),
                FieldPanel("end_date"),
                FieldPanel("latitude"),
                FieldPanel("longitude"),
                FieldPanel("register_url"),
            ],
            heading="Event details",
            classname="collapsible",
            help_text=mark_safe(
                "Optional time and location information for this event. Latitude and "
                "longitude are used to show a map of the event’s location. For more "
                "information on finding these values for a given location, "
                "'<a href='https://support.google.com/maps/answer/18539'>"
                "see this article</a>"
            ),
        ),
        StreamFieldPanel("body"),
        MultiFieldPanel(
            [
                FieldPanel("venue_name"),
                FieldPanel("venue_url"),
                FieldPanel("address_line_1"),
                FieldPanel("address_line_2"),
                FieldPanel("address_line_3"),
                FieldPanel("city"),
                FieldPanel("state"),
                FieldPanel("zip_code"),
                FieldPanel("country"),
            ],
            heading="Event address",
            classname="collapsible",
            help_text=(
                "Optional address fields. The city and country are also shown "
                "on event cards"
            ),
        ),
        StreamFieldPanel("agenda"),
        StreamFieldPanel("speakers"),
    ]

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

    # Meta panels
    meta_panels = [
        MultiFieldPanel(
            [InlinePanel("topics")],
            heading="Topics",
            help_text=(
                "These are the topic pages the event 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 = [FieldPanel("slug")]

    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 is_upcoming(self):
        """Returns whether an event is in the future."""
        return self.start_date >= datetime.date.today()

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

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

    @property
    def country_group(self):
        return (
            {"slug": self.country.code.lower(), "title": self.country.name}
            if self.country
            else {"slug": ""}
        )

    @property
    def event_dates(self):
        """Return a formatted string of the event start and end dates"""
        event_dates = self.start_date.strftime("%b %-d")
        if self.end_date and self.end_date != self.start_date:
            event_dates += " &ndash; "
            start_month = self.start_date.strftime("%m")
            if self.end_date.strftime("%m") == start_month:
                event_dates += self.end_date.strftime("%-d")
            else:
                event_dates += self.end_date.strftime("%b %-d")
        return event_dates

    @property
    def event_dates_full(self):
        """Return a formatted string of the event start and end dates,
        including the year"""
        return self.event_dates + self.start_date.strftime(", %Y")

    def has_speaker(self, person):
        for speaker in self.speakers:  # pylint: disable=not-an-iterable
            if speaker.block_type == "speaker" and str(speaker.value) == str(
                person.title
            ):
                return True
        return False
コード例 #10
0
ファイル: workgroup.py プロジェクト: patta42/website
class WorkGroup ( UserGeneratedPage2, ActiveInactiveMixin ):
    subpage_types = [
        'userinput.MemberContainer',
        'userinput.ProjectContainer',

    ]
    parent_page_types = [ 'userinput.WorkGroupContainer' ]
    child_template = 'userinput/workgroup_child.html'
    view_template =  'userinput/workgroup_view.html'

    objects = ActiveInactivePageManager()
    
    class Meta:
        verbose_name = _('workgroup')
        verbose_name_plural = _('workgroups')
        

    department_de = models.CharField(
        max_length = 64,
        blank = True,
        null = True,
        verbose_name = _('department (de)')
    )
    department_en = models.CharField(
        max_length = 64,
        blank = True,
        null = True,
        verbose_name = _('department (en)')
    )

    institute_en = models.CharField(
        max_length = 64,
        verbose_name = _('institute (en)')
    )
    institute_de = models.CharField(
        max_length = 64,
        verbose_name = _('institute (de)')
    )

    university_de = models.CharField(
        max_length = 64,
        verbose_name = _('university (de)')
    )
    university_en = models.CharField(
        max_length = 64,
        verbose_name = _('university (en)')
    )
        
    department = TranslatedField('department')
    institute = TranslatedField('institute')
    university = TranslatedField('university')

    homepage = models.CharField(
        max_length = 128,
        blank = True, null = True,
        verbose_name = _( 'internet address' ),
        help_text = _('Please include http:// or https:// and www, if required.')
    )

    content_panels = [
        MultiFieldPanel([
            FieldPanel('title_de'),
            FieldPanel('institute_de'),
            FieldPanel('department_de'),
            FieldPanel('university_de'),
        ], heading =_ ('German Information')),
        MultiFieldPanel([
            FieldPanel('title'),
            FieldPanel('institute_en'),
            FieldPanel('department_en'),
            FieldPanel('university_en'),
        ], heading =_ ('English Information')),
        MultiFieldPanel([
            FieldPanel('homepage'),
        ], heading =_ ('Additional Information')),
    ]
    comment_panel = [
        FieldPanel('internal_rubion_comment')
    ]

    edit_handler = TabbedInterface([
        ObjectList( content_panels, _('Information')),
        ObjectList( comment_panel, _('Internal comments')),
    ])

    @property
    def under_revision( self ):
        return self.has_unpublished_changes

    @property
    def is_active( self ):
        return not self.locked 

    @property
    def has_active_projects( self ):
        return self.get_projects().filter(expire_at__gte = datetime.datetime.now()).exists()
    
    def add_member_container( self ):
        # Generates a container for the workgroup members

        if len( MemberContainer.objects.child_of( self ) ) == 0:
            title = "Members"
            title_de = "Mitglieder"
            mc = MemberContainer()
            mc.title = title
            mc.title_de = title_de
            mc.slug = "members"
            self.add_child( instance = mc )
        return mc

    def add_project_container( self ):
        # Generate a container for Projects
        
        if len( ProjectContainer.objects.child_of( self ) ) == 0:
            title = "Projects"
            title_de = "Projekte"
            pc = ProjectContainer()
            pc.title = title
            pc.title_de = title_de
            pc.slug = "project"
            self.add_child( instance = pc )
        return pc
    
    def after_create_hook( self, request ):

        # Auto-generate child containers        
        self.add_member_container()
        self.add_project_container()

        # Adding a workgroup requires revision by RUBION
        
#        self.unpublish()
#        self.save_revision()

    def serve_success( self, request, edit = False ):
        if edit:
        # if edited, the workgroup is available
            return redirect( self.url )
        else:
        # Created, workgroup awaits verification. Show add user page to add work group leader
            ident = Identification()
            ident.page = self
            ident.create_user = True
            ident.login_user = True
            ident.mail_text = 'Workgroup.create.identify'
            ident.save()
            pk = ident.id
            return redirect( reverse('rubauth.identify', kwargs = {'pk' : pk}) )
 

    def create_project_page( self ):
        pc = ProjectContainer.objects.child_of(self).first()
        return UGCCreatePage2.objects.child_of(pc).first()

    def create_member_page( self ):
        mc = MemberContainer.objects.child_of(self).first()
        return UGCCreatePage2.objects.child_of(mc).first()

    def get_member_container( self ):
        return MemberContainer.objects.child_of(self).first()
    
    def get_head( self ):
        return RUBIONUser.objects.live().descendant_of(self).filter(is_leader=True).first()
    
    # for displaying in the admin overview:
    get_head.short_description = _('Group leader')


    def get_members( self ):
        return RUBIONUser.objects.live().descendant_of(self).filter(expire_at__isnull = True)

    def get_projects( self ):
        return Project.objects.live().descendant_of(self)

    def get_methods( self ):
        
        projects = self.get_projects()
        methods = []
        for project in projects:
            pr_methods = project.get_methods()
            for method in pr_methods:
                if method not in methods:
                    methods.append(method) 

        return methods

    def create_group_leader ( self, user ):
        mc = MemberContainer.objects.child_of(self).first()
        group_leader = RUBIONUser()
        group_leader.is_leader = True
        group_leader.linked_user = user
        group_leader.owner = user
        group_leader.is_rub = user.username.find('@') == -1
        group_leader.may_create_projects = True

        if not group_leader.is_rub:
            group_leader.title = user.username
            group_leader.title_de = user.username
            group_leader.slug = slugify(user.username)

            # Avoid cleaning on save...
            group_leader.dont_clean = True
        else:
            group_leader.title = '{}, {}'.format(user.last_name, user.first_name)
            group_leader.title_de = '{}, {}'.format(user.last_name, user.first_name)
            group_leader.slug = slugify('{}, {}'.format(user.last_name, user.first_name))
            group_leader.first_name_db = user.first_name
            group_leader.last_name_db = user.last_name

        mc.add_child(group_leader)

        group_leader.save_revision(
            submitted_for_moderation = True,
            user = user
        )

        return group_leader

    def validate( self, request, user = None, username = None):
        if RUBIONUser.exists( user ):
            return TemplateResponse(
                request,
                'userinput/errors/user_has_workgroup.html',
                {
                    'user' : user
                }
            )
        else:
            self.save()
            r_user = self.create_group_leader( user )
            self.owner = r_user.linked_user
            self.save_revision(submitted_for_moderation = True, user = r_user.linked_user)
            
            return redirect (r_user.full_url + r_user.reverse_subpage('edit'))

    def user_passes_test( self, user, what ):
        if not user.is_authenticated:
            return False
        if user.is_superuser:
            return True
        r_user = RUBIONUser.objects.get( linked_user = user )
        user_in_wg = r_user.get_workgroup() == self

        # Every RUBION-User may see the workgroups
        if what == self.VIEW:
            if self.under_revision:
                # Instead of returning False, which would result in a `403 Forbidden`
                # response, I raise a `404 Not found` here. A 403 would indicate that the group 
                # has applied to work in RUBION, which should be treated confidential.
                #
                # Of course, someone would have to guess the name of the group to construct 
                # the URL. But anyway...
                if  not user_in_wg:
                    raise Http404()

            return user.is_authenticated

        if what == self.EDIT:
            r_user = RUBIONUser.objects.get( linked_user = user )
            return r_user == self.get_head()
        
        return False

    def get_context(self, request):
        context = super(WorkGroup, self).get_context(request)
        context['user_may_edit'] = self.user_passes_test(request.user, self.EDIT)
        context['user_is_workgroup_member'] = False
        if request.user.is_authenticated:
            try:
                r_user = RUBIONUser.objects.get(linked_user = request.user)
            except:
                r_user = None
            if r_user:
                is_workgroup_member = ( r_user.get_workgroup() == self ) 
                context['user_is_workgroup_member'] = is_workgroup_member
                if is_workgroup_member:
                    context['user_may_add_projects'] = r_user.may('project')
                    context['user_may_add_members'] = r_user.may('member')
        return context


    def clean( self ):
        if not self.slug:
            self.slug = self._get_autogenerated_slug( slugify( self.title ) )

    def inactivate( self, user = None ):
        # inactivate this group, all projects and users
        super().inactivate(user = user)
        for member in self.get_members():
            member.inactivate(user = user)

        for project in self.get_projects():
            if project.is_active:
                project.inactivate(user = user)
        
                
    def is_inactivated(self):
        return self.locked and (self.expire_at is not None and self.expire_at < datetime.datetime.now())
        

    def __str__( self ):
        if self.department:
            return '{}, {}, {}, {}'.format( self.title_trans, self.institute, self.department, self.university )
        else:
            return '{}, {}, {}'.format( self.title_trans, self.institute, self.university )
コード例 #11
0
ファイル: models.py プロジェクト: ResetNetwork/apply-app
class PartnerPage(BasePage):
    STATUS = [('active', 'Active'), ('inactive', 'Inactive')]

    class Meta:
        verbose_name = _('Partner Page')

    parent_page_types = ['partner.PartnerIndexPage']
    subpage_types = []

    status = models.CharField(choices=STATUS,
                              default='current_partner',
                              max_length=20)
    public = models.BooleanField(default=True)
    description = RichTextField(blank=True)
    web_url = models.URLField(blank=True)
    logo = models.OneToOneField('images.CustomImage',
                                null=True,
                                blank=True,
                                related_name='+',
                                on_delete=models.SET_NULL)

    content_panels = Page.content_panels + [
        FieldPanel('status'),
        FieldPanel('public'),
        FieldPanel('description'),
        FieldPanel('web_url'),
        ImageChooserPanel('logo'),
    ]

    def __str__(self):
        return self.title

    def get_context(self, request):
        context = super(PartnerPage, self).get_context(request)
        context['total_investments'] = sum(
            investment.amount_committed
            for investment in self.investments.all())
        return context

    def get_absolute_url(self):
        return self.url

    @property
    def category_questions(self):
        category_questions = {}
        if not self.investments.exists():
            return
        for investment in self.investments.all():
            for category in investment.categories.all():
                if category.name in category_questions.keys():
                    if category.value not in category_questions[category.name]:
                        category_questions[category.name].append(
                            category.value)
                else:
                    category_questions[category.name] = [category.value]
        return category_questions

    def serve(self, request, *args, **kwargs):
        if not self.public:
            raise Http404
        return super(PartnerPage, self).serve(request, *args, **kwargs)
コード例 #12
0
class HomePage(RoutablePageMixin, Page):
    templates = 'home/home_page.html'

    subpage_types = [
        'blog.BlogListingPage',
        'contact.ContactPage',
        'flex.FlexPage',    
    ]
    parent_page_type = [
        'wagtailcore.Page'
    ]
    #max_count = 1

    banner_title = models.CharField(max_length=100, blank=False, null=True)
    banner_subtitle = RichTextField(features=['bold', 'italic'])
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    banner_cta = models.ForeignKey(
        'wagtailcore.Page',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    content = StreamField(
        [
            ('cta', blocks.CTABlock()),
        ],
        null=True,
        blank=True
    )

    api_fields = [
        APIField('banner_title'),
        APIField('banner_subtitle'),
        APIField('banner_image'),
        APIField('banner_cta'),
        APIField('carousel_images'),
        APIField('content'),
    ]

    max_count = 1

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [InlinePanel("carousel_images", max_num=5, min_num=1, label="Image")],
            heading="Carousel Images",
        ),
        StreamFieldPanel("content"),
    ]

    banner_panels = [
        MultiFieldPanel(
            [
                FieldPanel("banner_title"),
                FieldPanel("banner_subtitle"),
                ImageChooserPanel("banner_image"),
                PageChooserPanel("banner_cta"),
            ],
            heading="Banner Options",
        ),
    ]

    edit_handler = TabbedInterface(
    [
        ObjectList(content_panels, heading='Content'),
        ObjectList(banner_panels, heading="Banner Settings"),
        ObjectList(Page.promote_panels, heading='Promotional Stuff'),
        ObjectList(Page.settings_panels, heading='Settings Stuff'),
    ]
    )

    @route(r'^subscribe/$')
    def the_subscribe_page(self, request, *args, **kwargs):
        context = self.get_context(request, *args, **kwargs)
        return render(request, 'home/subscribe.html', context)
コード例 #13
0
class LabPage(BasePage):
    subpage_types = ['RFPPage']
    parent_page_types = ['LabIndex']

    introduction = models.TextField(blank=True)
    icon = models.ForeignKey('images.CustomImage',
                             null=True,
                             blank=True,
                             related_name='+',
                             on_delete=models.SET_NULL)
    lab_type = models.ForeignKey(
        'wagtailcore.Page',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='lab_public',
    )
    lab_link = models.CharField(blank=True,
                                max_length=255,
                                verbose_name='External link',
                                validators=[MailToAndURLValidator()])
    link_text = models.CharField(
        max_length=255,
        help_text='Text to display on the button for external links',
        blank=True)
    body = StreamField(LabBlock())

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

    content_panels = BasePage.content_panels + [
        ImageChooserPanel('icon'),
        FieldPanel('introduction'),
        MultiFieldPanel([
            PageChooserPanel('lab_type', 'funds.LabType'),
            FieldRowPanel([
                FieldPanel('lab_link'),
                FieldPanel('link_text'),
            ]),
        ],
                        heading='Link for lab application'),
        StreamFieldPanel('body'),
        InlinePanel('related_pages', label="Related pages"),
    ]

    def get_context(self, request):
        context = super().get_context(request)
        context['rfps'] = self.get_children().live().public()
        return context

    can_open = True

    @property
    def is_open(self):
        try:
            return bool(self.lab_type.specific.open_round)
        except AttributeError:
            return bool(self.lab_link)

    def clean(self):
        if self.lab_type and self.lab_link:
            raise ValidationError({
                'lab_type':
                'Cannot link to both a Lab page and external link',
                'lab_link':
                'Cannot link to both a Lab page and external link',
            })

        if not self.lab_type and not self.lab_link:
            raise ValidationError({
                'lab_type':
                'Please provide a way for applicants to apply',
                'lab_link':
                'Please provide a way for applicants to apply',
            })

        if self.lab_type and self.link_text:
            raise ValidationError({
                'link_text':
                'Cannot customise the text for internal lab pages, leave blank',
            })

        if self.lab_link and not self.link_text:
            raise ValidationError({
                'link_text':
                'Please provide some text for the link button',
            })
コード例 #14
0
class JournalPage(AbstractBase):
    authors = ParentalManyToManyField(settings.AUTH_USER_MODEL,
                                      blank=True,
                                      verbose_name=_('Authors'),
                                      related_name='author_posts')
    date = models.DateTimeField(verbose_name="Post date", default=timezone.now)
    categories = ParentalManyToManyField('journal.JournalCategory', blank=True)
    tags = ClusterTaggableManager(through='journal.JournalPageTag', blank=True)
    body = StreamField(BLOCK_TYPES)

    content_panels = AbstractBase.content_panels + [
        FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        FieldPanel('tags'),
        InlinePanel(
            'gallery_images',
            label=_('gallery images'),
            help_text=_(
                "Gallery images are displayed along the left side of the page")
        ),
        StreamFieldPanel('body'),
    ]

    promote_panels = AbstractBase.promote_panels + [
        FieldPanel(
            'authors',
            help_text=
            _("If left blank, this will be set to the currently logged in user"
              )),
        FieldPanel('date')
    ]

    parent_page_types = ['journal.JournalIndexPage']

    search_fields = AbstractBase.search_fields + [
        index.SearchField('body'),
        index.SearchField('authors'),
        index.FilterField('date')
    ]

    @property
    def journal_index_page(self):
        return self.get_parent().specific

    def get_context(self, request, *args, **kwargs):
        context = super(JournalPage, self).get_context(request, *args,
                                                       **kwargs)
        context['journal_index_page'] = self.journal_index_page
        context['post'] = self
        return context

    def save_revision(self, *args, **kwargs):
        if self.get_parent(
        ).slug == 'whats-blooming-now' and self.banner is None:
            # Get the appropriate banner based on the current month
            season = get_season(date.today())
            banner_query = Image.objects.filter().search(
                "what's blooming now banner " + season)
            try:
                banner = banner_query[0]
                self.banner = banner
            except IndexError as e:
                logger.error(
                    '[!] Failed to find seasonal banner for Journal Page: ', e)
        if not self.authors.all():
            self.authors.add(self.owner)
        return super().save_revision(*args, **kwargs)
コード例 #15
0
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

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

    @property
    def blog_index(self):
        # Find closest ancestor which is a blog index
        return self.get_ancestors().type(BlogIndexPage).last()

    content_panels = [
        FieldPanel('title', classname="full title"),
        FieldPanel('date'),
        StreamFieldPanel('body'),
        InlinePanel('carousel_items', label="Carousel items"),
        InlinePanel('related_links', label="Related links"),
    ]


BlogPage.promote_panels = Page.promote_panels + [
    ImageChooserPanel('feed_image'),
    FieldPanel('tags'),
]
コード例 #16
0
class Events(BasePage):
    parent_page_types = ["home.HomePage"]
    subpage_types = ["events.Event"]
    template = "events.html"

    # Content fields
    featured = StreamField(
        StreamBlock(
            [
                (
                    "event",
                    PageChooserBlock(
                        target_model=("events.Event", "externalcontent.ExternalEvent")
                    ),
                ),
                ("external_page", FeaturedExternalBlock()),
            ],
            max_num=1,
            required=False,
        ),
        null=True,
        blank=True,
        help_text="Optional space to show a featured event",
    )
    body = CustomStreamField(
        help_text=(
            "Main page body content. Supports rich text, images, embed via URL, "
            "embed via HTML, and inline code snippets"
        )
    )

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

    # Content panels
    content_panels = BasePage.content_panels + [
        StreamFieldPanel("featured"),
        StreamFieldPanel("body"),
    ]

    # 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"), 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 = "Events"

    @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 events(self):
        """Return future events in chronological order"""
        return get_combined_events(self, start_date__gte=get_past_event_cutoff())

    @property
    def past_events(self):
        """Return past events in reverse chronological order"""
        return get_combined_events(
            self, reverse=True, start_date__lt=datetime.date.today()
        )

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

        return {
            "countries": True,
            "months": True,
            "topics": Topic.published_objects.order_by("title"),
        }
コード例 #17
0
ファイル: models.py プロジェクト: nbuonin/decruck-wagtail
class CompositionEDTF(Model):
    composition = ParentalKey('CompositionPage',
                              on_delete=CASCADE,
                              unique=True,
                              related_name='date')
    nat_lang_edtf_string = CharField(
        verbose_name='Natural Language Date',
        help_text=('The EDTF date in natural language. This field is help '
                   'users who aren\'t familiar with the EDTF. It does not '
                   'change how the date is represented.'),
        max_length=256)
    edtf_string = CharField(
        verbose_name='EDTF Date',
        help_text=mark_safe(
            'A date in the <a href="https://www.loc.gov/standards/datetime/" '
            'target="_blank"><strong>Extended Date Time Format</strong></a>'),
        max_length=256)
    lower_fuzzy = DateField(editable=False)
    upper_fuzzy = DateField(editable=False)
    lower_strict = DateField(editable=False)
    upper_strict = DateField(editable=False)
    nat_lang_year = CharField(editable=False, max_length=9)

    panels = [FieldPanel('edtf_string'), FieldPanel('nat_lang_edtf_string')]

    def __str__(self):
        return self.edtf_string

    def clean(self):
        try:
            e = parse_edtf(self.edtf_string)
        except EDTFParseException:
            raise ValidationError({
                'edtf_string':
                '{} is not a valid EDTF string'.format(self.edtf_string)
            })

        self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy())
        self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy())
        self.lower_strict = struct_time_to_date(e.lower_strict())
        self.upper_strict = struct_time_to_date(e.upper_strict())

        if self.lower_strict.year != self.upper_strict.year:
            self.nat_lang_year = '{}-{}'.format(self.lower_strict.year,
                                                self.upper_strict.year)
        else:
            self.nat_lang_year = str(self.lower_strict.year)

    def save(self, *args, **kwargs):
        try:
            e = parse_edtf(self.edtf_string)
        except EDTFParseException:
            raise ValidationError('{} is not a valid EDTF string'.format(
                self.edtf_string))

        self.lower_fuzzy = struct_time_to_date(e.lower_fuzzy())
        self.upper_fuzzy = struct_time_to_date(e.upper_fuzzy())
        self.lower_strict = struct_time_to_date(e.lower_strict())
        self.upper_strict = struct_time_to_date(e.upper_strict())

        if self.lower_strict.year != self.upper_strict.year:
            self.nat_lang_year = '{}-{}'.format(self.lower_strict.year,
                                                self.upper_strict.year)
        else:
            self.nat_lang_year = str(self.lower_strict.year)

        super().save(*args, **kwargs)
コード例 #18
0
class HomePage(RoutablePageMixin, Page):
    """Home page model."""

    template = "home/home_page.html"
    max_count = 1

    banner_title = models.CharField(max_length=100, blank=False, null=True)
    banner_subtitle = RichTextField(features=["bold", "italic"])
    banner_image = models.ForeignKey(
        "wagtailimages.Image",
        null=True,
        blank=False,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    banner_cta = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )

    content = StreamField([("cta", blocks.CTABlock())], null=True, blank=True)

    api_fields = [
        APIField("banner_title"),
        APIField("banner_subtitle"),
        APIField("banner_image"),
        APIField("banner_cta"),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel("banner_title"),
                FieldPanel("banner_subtitle"),
                ImageChooserPanel("banner_image"),
                PageChooserPanel("banner_cta"),
            ],
            heading="Banner Options",
        ),
        MultiFieldPanel(
            [
                InlinePanel(
                    "carousel_images", max_num=5, min_num=1, label="Image")
            ],
            heading="Carousel Images",
        ),
        StreamFieldPanel("content"),
    ]

    class Meta:

        verbose_name = "Home Page"
        verbose_name_plural = "Home Pages"

    @route(r'^subscribe/$')
    def the_subscribe_page(self, request, *args, **kwargs):
        context = self.get_context(request, *args, **kwargs)
        return render(request, "home/subscribe.html", context)
コード例 #19
0
ファイル: models.py プロジェクト: nbuonin/decruck-wagtail
class ScorePage(RoutablePageMixin, Page):
    cover_image = ForeignKey('wagtailimages.Image',
                             null=True,
                             blank=True,
                             on_delete=PROTECT,
                             related_name='cover_image')
    description = StreamField([('rich_text', RichTextBlock()),
                               ('image', ImageChooserBlock())])
    duration = DurationField(null=True,
                             blank=True,
                             help_text='Expects data in the format "HH:MM:SS"')
    file = FileField(
        upload_to='scores/',
        validators=[FileExtensionValidator(allowed_extensions=['pdf', 'zip'])])
    preview_score = FileField(
        upload_to='preview_scores/',
        validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
    preview_score_checksum = CharField(editable=False,
                                       max_length=256,
                                       blank=True)
    preview_score_checked = False
    preview_score_updated = False
    genre = ParentalManyToManyField(Genre, blank=True, related_name='scores')
    date = CharField(max_length=256, blank=True)
    instrumentation = ParentalManyToManyField(
        'Instrument',
        blank=True,
        help_text='The instrumentation of the compostition.')
    price = DecimalField(max_digits=6, decimal_places=2)
    materials = RichTextField(
        blank=True,
        features=['bold', 'italic', 'link', 'document-link'],
        help_text='The materials sent in the PDF file.')

    def save(self, *args, **kwargs):
        if self.preview_score_checked:
            # This was the cause of a subtle bug. Because this method can be
            # called multiple times during model creation, leaving this flag
            # set would cause the post save hook to fire multiple times.
            self.preview_score_updated = False
            return super().save(*args, **kwargs)

        h = hashlib.md5()
        for chunk in iter(lambda: self.preview_score.read(8192), b''):
            h.update(chunk)

        self.preview_score.seek(0)
        checksum = h.hexdigest()
        if not self.preview_score_checksum == checksum:
            self.preview_score_checksum = checksum
            self.preview_score_updated = True

        self.preview_score_checked = True
        return super().save(*args, **kwargs)

    @route(r'^([\w-]+)/$')
    def get_score_file(self, request, score_link_slug):
        if request.method == 'GET':
            item_link = get_object_or_404(OrderItemLink, slug=score_link_slug)

            if item_link.is_expired():
                raise Http404()

            item_link.access_ip = request.META.get('REMOTE_ADDR', '0.0.0.0')
            item_link.save()

            return render(request, "main/score_page_download.html", {
                'self': self,
                'page': self,
            })
        else:
            raise Http404()

    @route(r'^$')
    def score(self, request):
        cart_page = ShoppingCartPage.objects.first()
        if request.method == 'POST':
            in_cart = toggle_score_in_cart(request, self.pk)
            return render(
                request, "main/score_page.html", {
                    'self': self,
                    'page': self,
                    'in_cart': in_cart,
                    'cart_page': cart_page
                })
        else:
            return render(
                request, "main/score_page.html", {
                    'self': self,
                    'page': self,
                    'in_cart': score_in_cart(request, self.pk),
                    'cart_page': cart_page
                })

    class Meta:
        verbose_name = "Score Page"

    content_panels = Page.content_panels + [
        FieldPanel('date'),
        FieldPanel('duration'),
        FieldPanel('genre'),
        FieldPanel('instrumentation'),
        FieldPanel('price'),
        StreamFieldPanel('description'),
        FieldPanel('materials'),
        FieldPanel('file'),
        FieldPanel('preview_score'),
        ImageChooserPanel('cover_image')
    ]
コード例 #20
0
class Topic(BasePage):
    resource_type = "topic"
    parent_page_types = ["Topics"]
    subpage_types = ["Topic"]
    template = "topic.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,
    )
    featured = StreamField(
        StreamBlock(
            [
                (
                    "post",
                    PageChooserBlock(target_model=(
                        "articles.Article",
                        "externalcontent.ExternalArticle",
                    )),
                ),
                ("external_page", FeaturedExternalBlock()),
            ],
            max_num=4,
            required=False,
        ),
        null=True,
        blank=True,
        help_text="Optional space for featured posts, max. 4",
    )
    tabbed_panels = StreamField(
        StreamBlock([("panel", TabbedPanelBlock())], max_num=3,
                    required=False),
        null=True,
        blank=True,
        help_text=
        "Optional tabbed panels for linking out to other resources, max. 3",
        verbose_name="Tabbed panels",
    )
    latest_articles_count = IntegerField(
        choices=RESOURCE_COUNT_CHOICES,
        default=3,
        help_text="The number of posts to display for this topic.",
    )

    # 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
    icon = FileField(
        upload_to="topics/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],
    )
    color = CharField(max_length=14, choices=COLOR_CHOICES, default="blue-40")
    keywords = ClusterTaggableManager(through=TopicTag, blank=True)

    # Content panels
    content_panels = BasePage.content_panels + [
        FieldPanel("description"),
        StreamFieldPanel("featured"),
        StreamFieldPanel("tabbed_panels"),
        FieldPanel("latest_articles_count"),
        MultiFieldPanel(
            [InlinePanel("people")],
            heading="People",
            help_text=
            "Optional list of people associated with this topic as experts",
        ),
    ]

    # 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",
            help_text=(
                "Theme settings used on topic page and any tagged content. "
                "For example, a post tagged with this topic "
                "will use the color specified here as its accent color."),
        ),
        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"), 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 experts(self):
        """Return Person instances for topic experts"""
        return [person.person for person in self.people.all()]

    @property
    def videos(self):
        """Return the latest videos and external videos for this topic. """
        return get_combined_videos(self, topics__topic__pk=self.pk)

    @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()]
コード例 #21
0
class ParticipatePage2(PrimaryPage):
    parent_page_types = ['Homepage']
    template = 'wagtailpages/static/participate_page2.html'

    ctaHero = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader = models.TextField(blank=True, )

    ctaHeroSubhead = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment = models.TextField(blank=True, )

    ctaButtonTitle = models.CharField(
        verbose_name='Button Text',
        max_length=250,
        blank=True,
    )

    ctaButtonURL = models.TextField(
        verbose_name='Button URL',
        blank=True,
    )

    ctaHero2 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate2',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader2 = models.TextField(blank=True, )

    ctaHeroSubhead2 = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment2 = models.TextField(blank=True, )

    ctaButtonTitle2 = models.CharField(
        verbose_name='Button Text',
        max_length=250,
        blank=True,
    )

    ctaButtonURL2 = models.TextField(
        verbose_name='Button URL',
        blank=True,
    )

    ctaHero3 = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='primary_hero_participate3',
        verbose_name='Primary Hero Image',
    )

    ctaHeroHeader3 = models.TextField(blank=True, )

    ctaHeroSubhead3 = RichTextField(
        features=[
            'bold',
            'italic',
            'link',
        ],
        blank=True,
    )

    ctaCommitment3 = models.TextField(blank=True, )

    ctaFacebook3 = models.TextField(blank=True, )

    ctaTwitter3 = models.TextField(blank=True, )

    ctaEmailShareBody3 = models.TextField(blank=True, )

    ctaEmailShareSubject3 = models.TextField(blank=True, )

    h2 = models.TextField(blank=True, )

    h2Subheader = models.TextField(
        blank=True,
        verbose_name='H2 Subheader',
    )

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            ImageChooserPanel('ctaHero'),
            FieldPanel('ctaHeroHeader'),
            FieldPanel('ctaHeroSubhead'),
            FieldPanel('ctaCommitment'),
            FieldPanel('ctaButtonTitle'),
            FieldPanel('ctaButtonURL'),
        ],
                        heading="Primary CTA"),
        FieldPanel('h2'),
        FieldPanel('h2Subheader'),
        InlinePanel(
            'featured_highlights', label='Highlights Group 1', max_num=3),
        MultiFieldPanel([
            ImageChooserPanel('ctaHero2'),
            FieldPanel('ctaHeroHeader2'),
            FieldPanel('ctaHeroSubhead2'),
            FieldPanel('ctaCommitment2'),
            FieldPanel('ctaButtonTitle2'),
            FieldPanel('ctaButtonURL2'),
        ],
                        heading="CTA 2"),
        InlinePanel(
            'featured_highlights2', label='Highlights Group 2', max_num=6),
        MultiFieldPanel([
            ImageChooserPanel('ctaHero3'),
            FieldPanel('ctaHeroHeader3'),
            FieldPanel('ctaHeroSubhead3'),
            FieldPanel('ctaCommitment3'),
            FieldPanel('ctaFacebook3'),
            FieldPanel('ctaTwitter3'),
            FieldPanel('ctaEmailShareSubject3'),
            FieldPanel('ctaEmailShareBody3'),
        ],
                        heading="CTA 3"),
        InlinePanel('cta4', label='CTA Group 4', max_num=3),
    ]
コード例 #22
0
ファイル: models.py プロジェクト: daanreijnders/website
class Answer(Page):
    template = 'cms/answer_detail.html'

    # Determines type and whether its highlighted in overview list
    type = models.CharField(
        choices=[('answer', 'Antwoord'), ('column', 'Column')],
        max_length=100,
        default='answer',
        help_text=
        _('Choose between answer or discussion piece with a more prominent look'
          ))
    featured = models.BooleanField(default=False)

    content = RichTextField(blank=True)
    excerpt = models.CharField(
        verbose_name=_('Short description'),
        max_length=255,
        blank=False,
        null=True,
        help_text=_(
            'This helps with search engines and when sharing on social media'),
    )
    introduction = TextField(
        verbose_name=_('Introduction'),
        default='',
        blank=True,
        null=True,
        help_text=_(
            'This text is displayed above the tags, useful as a TLDR section'),
    )
    tags = ClusterTaggableManager(through=AnswerTag, blank=True)

    social_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text=
        _('This is the image that will be displayed when sharing on social media'
          ),
    )

    # Freeform content of answer
    page_content = StreamField([
        ('richtext', AnswerRichTextBlock()),
        ('image', AnswerImageBlock()),
        ('quote', QuoteBlock()),
    ])

    # Which experts and how was this answered?
    answer_origin = StreamField([('origin', AnswerOriginBlock())], blank=True)

    # Related items
    related_items = StreamField([('related_items', RelatedItemsBlock())],
                                blank=True)

    parent_page_types = ['AnswerIndexPage']

    content_panels = Page.content_panels + [
        FieldPanel('type'),
        FieldPanel('featured', heading=_("Show this answer on the home page")),
        FieldPanel(
            'excerpt',
            classname='full',
        ),
        FieldPanel('introduction', classname='full'),
        MultiFieldPanel([
            InlinePanel('answer_category_relationship',
                        label=_('Categorie(n)'),
                        panels=None,
                        min_num=1)
        ],
                        heading=_('Categorie(s)')),
        FieldPanel(
            'tags',
            heading=
            "Please use tags with a maximum length of 16 characters per single word to avoid overlap in the mobile view."
        ),
        MultiFieldPanel([
            InlinePanel('answer_expert_relationship',
                        label=_('Expert(s)'),
                        panels=None,
                        min_num=1)
        ],
                        heading=_('Expert(s)')),
        StreamFieldPanel('page_content'),
        StreamFieldPanel('answer_origin'),
        StreamFieldPanel('related_items'),
        ImageChooserPanel(
            'social_image',
            help_text=_('Image to be used when sharing on social media')),
    ]

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

    @property
    def experts(self):
        experts = [n.expert for n in self.answer_expert_relationship.all()]
        return experts

    @property
    def categories(self):
        categories = [
            n.category for n in self.answer_category_relationship.all()
        ]
        return categories

    @property
    def get_tags(self):
        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

    def get_references(self):
        """
        Build reference list, in the order Wagtail returns them.  ### , alphabetically to sort of comply with standards

        TODO: References for articles can be separated from the origin and make them a proper ListBlock that can be
            handled by editors as they see fit. Having the references within a StreamField of 'origins' seems counter
            intuitive.
            
        """
        ref_list = []
        try:
            component = self.answer_origin[0]
        except IndexError:
            return ref_list

        # Access streamfield elements
        for element in component.value['sources']:
            ref_list.append({
                'text': element['reference_text'],
                'url': element['url_or_doi'],
            })

        # Sort by text starting letter, best we can do for now
        # ref_list.sort(key=lambda e: e['text'])
        return ref_list

    def get_primary_expert(self):
        """
        Gets the first expert associated with this answer if it exists.
        """
        try:
            first = self.experts[0]
        except IndexError:
            return _('Unknown')
        else:
            return first

    def get_all_categories(self):
        return [{
            'title': c.name,
            'url': c.get_prefiltered_search_params()
        } for c in self.categories]

    def get_card_data(self):
        return {
            'title': self.title,
            'url': self.url,
            'author': self.get_primary_expert(),
            'categories': self.get_all_categories(),
            'type': 'answer'
        }

    def get_as_overview_row_card(self):
        if self.type == 'answer':
            return render_to_string('core/includes/answer_block.html',
                                    context=self.get_card_data())
        else:  # It's a column
            return render_to_string('core/includes/column_block.html',
                                    context=self.get_card_data())

    def get_as_home_row_card(self):
        return render_to_string('core/includes/answer_home_block.html',
                                context=self.get_card_data())

    def get_as_related_row_card(self):
        return render_to_string('core/includes/related_item_block.html',
                                context=self.get_card_data())

    def get_context(self, request, *args, **kwargs):
        context = super(Answer, self).get_context(request, *args, **kwargs)

        categories = AnswerCategory.objects.all()

        context.update({
            'categories': categories,
            'answers_page': AnswerIndexPage.objects.first().url,
            'experts_page': ExpertIndexPage.objects.first(),
        })
        return context

    class Meta:
        ordering = [
            '-first_published_at',
        ]
コード例 #23
0
class ArticleSeriesPage(
        BasicPageAbstract,
        ContentPage,
        FeatureablePageAbstract,
        FromTheArchivesPageAbstract,
        ShareablePageAbstract,
        ThemeablePageAbstract,
):
    credits = RichTextField(
        blank=True,
        features=[
            'bold',
            'italic',
            'link',
            'name',
        ],
    )
    credits_stream_field = StreamField(
        [('title',
          StructBlock([
              ('title', CharBlock()),
              ('people', StreamBlock([('name', CharBlock())])),
          ]))],
        blank=True,
    )
    credits_artwork = models.CharField(
        max_length=255,
        blank=True,
    )
    featured_items = StreamField(
        [
            ('featured_item',
             PageChooserBlock(
                 required=True,
                 page_type=[
                     'articles.ArticlePage', 'multimedia.MultimediaPage'
                 ],
             )),
        ],
        blank=True,
    )
    image_banner = models.ForeignKey(
        'images.CigionlineImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Image',
    )
    image_banner_small = models.ForeignKey('images.CigionlineImage',
                                           null=True,
                                           blank=True,
                                           on_delete=models.SET_NULL,
                                           related_name='+',
                                           verbose_name='Banner Image Small')
    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.',
    )
    short_description = RichTextField(
        blank=True,
        null=False,
        features=['bold', 'italic', 'link'],
    )
    series_items_description = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
    )
    series_videos_description = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
        help_text=
        'To be displayed on video/multimedia pages of the series in place of Series Items Description'
    )
    series_items_disclaimer = RichTextField(
        blank=True,
        null=True,
        features=['bold', 'italic', 'link'],
    )
    video_banner = models.ForeignKey(
        'wagtailmedia.Media',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name='Banner Video',
    )

    @property
    def image_poster_caption(self):
        return self.image_poster.caption

    @property
    def image_poster_url(self):
        return self.image_poster.get_rendition('fill-672x895').url

    @property
    def article_series_items(self):
        return self.series_items.prefetch_related(
            'content_page',
            'content_page__authors__author',
        ).all()

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

    def get_template(self, request, *args, **kwargs):
        standard_template = super(ArticleSeriesPage,
                                  self).get_template(request, *args, **kwargs)
        if self.theme:
            return f'themes/{self.get_theme_dir()}/article_series_page.html'
        return standard_template

    content_panels = [
        BasicPageAbstract.title_panel,
        MultiFieldPanel(
            [
                FieldPanel('short_description'),
                StreamFieldPanel('body'),
            ],
            heading='Body',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('publishing_date'),
            ],
            heading='General Information',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('series_items_description'),
                FieldPanel('series_videos_description'),
                FieldPanel('series_items_disclaimer'),
                InlinePanel('series_items'),
            ],
            heading='Series Items',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('credits'),
                FieldPanel('credits_artwork'),
                StreamFieldPanel('credits_stream_field'),
            ],
            heading='Credits',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                ImageChooserPanel('image_hero'),
                ImageChooserPanel('image_banner'),
                ImageChooserPanel('image_banner_small'),
                ImageChooserPanel('image_poster'),
            ],
            heading='Image',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                MediaChooserPanel('video_banner'),
            ],
            heading='Media',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                StreamFieldPanel('featured_items'),
            ],
            heading='Featured Series Items',
            classname='collapsible collapsed',
        ),
        MultiFieldPanel(
            [
                FieldPanel('topics'),
            ],
            heading='Related',
            classname='collapsible collapsed',
        ),
    ]

    promote_panels = Page.promote_panels + [
        FeatureablePageAbstract.feature_panel,
        ShareablePageAbstract.social_panel,
        SearchablePageAbstract.search_panel,
    ]

    settings_panels = Page.settings_panels + [
        ThemeablePageAbstract.theme_panel,
    ]

    search_fields = Page.search_fields \
        + BasicPageAbstract.search_fields \
        + ContentPage.search_fields

    parent_page_types = ['home.HomePage']
    subpage_types = []
    templates = 'articles/article_series_page.html'

    @property
    def series_contributors_by_article(self):
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            people_string = ''

            for person in people:
                person_string = person.author.title
                people_string += person_string

                # Add each person as well so if there's an article with just
                # a single author who's already been in another article in
                # collaboration, then we won't add their name to the list
                # again.
                if len(people) > 1:
                    item_people.add(person_string)

            if people_string not in item_people:
                series_contributors.append({
                    'item': series_item.content_page,
                    'contributors': people
                })
                item_people.add(people_string)

        return series_contributors

    @property
    def series_contributors(self):
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            for person in people:
                if person.author.title not in item_people:
                    series_contributors.append({
                        'id': person.author.id,
                        'title': person.author.title,
                        'url': person.author.url,
                    })
                    item_people.add(person.author.title)
        return series_contributors

    @property
    def series_contributors_by_person(self):
        # Series contributors ordered by last name
        series_contributors = []
        item_people = set()

        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()

            # Skip items that have more than 2 authors/speakers. For
            # example, in the After COVID series, there is an introductory
            # video with many authors.
            if len(people) > 2:
                continue
            else:
                for person in people:
                    if person.author.title not in item_people:
                        series_contributors.append({
                            'item':
                            series_item.content_page,
                            'contributors': [person.author],
                            'last_name':
                            person.author.last_name,
                        })
                        item_people.add(person.author.title)

        series_contributors.sort(key=lambda x: x['last_name'])
        return series_contributors

    @property
    def series_authors(self):
        series_authors = []
        series_people = set()
        for series_item in self.article_series_items:
            people = series_item.content_page.authors.all()
            for person in people:
                if person.author.title not in series_people:
                    series_authors.append(person.author)
                    series_people.add(person.author.title)
        return series_authors

    class Meta:
        verbose_name = 'Opinion Series'
        verbose_name_plural = 'Opinion Series'
コード例 #24
0
class IndexPage(RoutablePageMixin, Page):
    custom_title = models.CharField(max_length=100,
                                    blank=False,
                                    null=False,
                                    help_text="Overwrites default title")
    content_panels = Page.content_panels + [FieldPanel("custom_title")]

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context["posts"] = IndexDetailPage.objects.live().public().order_by(
            Lower("pub_date"))
        context["categories"] = IndexCategory.objects.all()
        json_list = list(context["posts"].values(
            'slug', 'title', 'author_founder', 'rownum', 'pub_date',
            'end_date', 'about', 'location', 'external_link',
            'external_link_two', 'images_list', 'page_ptr_id'))
        context['json_dict'] = json.dumps(json_list)
        context["image_entries"] = []

        for index in context["posts"]:
            for c in index.images_list.all():
                context["image_entries"].append({
                    "slug": index.slug,
                    "img_name": str(c)
                })

        context['json_img_dict'] = json.dumps(list(context["image_entries"]))

        return context

    @route(r"^orderby/(?P<order>[-\w]+)/$", name="orderby_view")
    @never_cache
    def orderby_view(self, request, order):
        context = self.get_context(request)
        try:
            orderby = context["posts"].order_by(Lower(order))
        except Exception:
            orderby = None
        if orderby is None:
            pass
        context["posts"] = orderby
        # clear index page only
        key = make_template_fragment_key("preview_index")
        print(key)
        cache.delete(key)
        print(orderby)
        # cache.clear()
        return render(request, "index/index_page.html", context)

    @route(r"^tag/(?P<cat_slug>[-\w]+)/$", name="tag_view")
    def tag_view(self, request, cat_slug):
        context = self.get_context(request)
        try:
            category = IndexCategory.objects.get(slug=cat_slug)
            render()
        except Exception:
            category = None
        if category is None:
            pass

        context["posts"] = IndexDetailPage.objects.live().public().filter(
            categories__in=[category])
        print(context["posts"])
        return render(request, "index/index_page.html", context)
コード例 #25
0
ファイル: models.py プロジェクト: paulkahura/a4-product
class OrganisationSettings(BaseSetting):
    address = fields.RichTextField()
    contacts = fields.RichTextField()

    panels = [FieldPanel('address'), FieldPanel('contacts')]
コード例 #26
0
class IndexDetailPage(Page):
    about = MarkdownField(null=True, blank=True)
    sourceforabouttext = models.CharField("Source for about text",
                                          max_length=255,
                                          null=True,
                                          blank=True)
    categories = ParentalManyToManyField("index.IndexCategory", blank=True)
    pub_date = models.PositiveSmallIntegerField("Date Published / Created",
                                                null=True,
                                                blank=True)
    end_date = models.PositiveSmallIntegerField("End Date",
                                                null=True,
                                                blank=True)
    author_founder = models.CharField("Author/Founder",
                                      max_length=500,
                                      null=True,
                                      blank=True)
    contributed_by = models.CharField("Contributed By",
                                      max_length=500,
                                      null=True,
                                      blank=True)
    external_link = models.URLField(null=True, blank=True)
    external_link_two = models.URLField(null=True, blank=True)
    autoincrement_num = models.PositiveSmallIntegerField(null=True, blank=True)
    rownum = models.PositiveSmallIntegerField(null=True, blank=True)
    location = models.CharField("location",
                                max_length=255,
                                null=True,
                                blank=True)
    # slug = models.SlugField(verbose_name=_('slug'), allow_unicode=True, max_length=255)

    search_fields = Page.search_fields + [
        index.SearchField('title'),
        index.SearchField('author_founder'),
        index.SearchField('about'),
        index.SearchField('contributed_by'),
        index.SearchField('location'),
        index.SearchField('pub_date'),
        index.SearchField('end_date'),
    ]

    content_panels = Page.content_panels + [
        MarkdownPanel('about', classname="full"),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('pub_date'),
                FieldPanel('end_date'),
            ]),
            FieldRowPanel([
                FieldPanel('author_founder'),
                FieldPanel('location'),
            ]),
            FieldRowPanel([
                FieldPanel('external_link'),
                FieldPanel('external_link_two'),
            ]),
            FieldRowPanel([
                FieldPanel('contributed_by'),
            ]),
        ], 'Details'),
        MultiFieldPanel(
            [
                InlinePanel('images_list', label='Image'),
            ],
            heading="Image(s)",
        ),
        MultiFieldPanel(
            [FieldPanel("categories", widget=forms.CheckboxSelectMultiple)],
            heading="Categories"),
        MultiFieldPanel(
            [
                InlinePanel('collections_list', label='Curator'),
            ],
            heading="Curator(s)",
        ),
    ]

    promote_panels = []

    class Meta:  # noqa
        verbose_name = "Index Detail Page"
        verbose_name_plural = "Index Detail Pages"
コード例 #27
0
class MetaTerm(index.Indexed, MP_Node):
    """ Hierarchal "Meta" terms """
    name = models.CharField(
        max_length=50, unique=True, help_text='Keep the name short, ideally one word.'
    )
    is_archived = models.BooleanField(
        default=False, verbose_name=_("Archived"),
        help_text='Archived terms can be viewed but not set on content.'
    )
    filter_on_dashboard = models.BooleanField(
        default=True, help_text='Make available to filter on dashboard'
    )
    available_to_applicants = models.BooleanField(
        default=False, help_text='Make available to applicants'
    )
    help_text = RichTextField(features=[
        'h2', 'h3', 'bold', 'italic', 'link', 'hr', 'ol', 'ul'], blank=True)

    # node tree specific fields and attributes
    node_order_index = models.IntegerField(blank=True, default=0, editable=False)
    node_child_verbose_name = 'child'

    # important: node_order_by should NOT be changed after first Node created
    node_order_by = ['node_order_index', 'name']

    panels = [
        FieldPanel('name'),
        FieldPanel('parent'),
        MultiFieldPanel(
            [
                FieldPanel('is_archived'),
                FieldPanel('filter_on_dashboard'),
                FieldPanel('available_to_applicants'),
                FieldPanel('help_text'),
            ],
            heading="Options",
        ),
    ]

    def get_as_listing_header(self):
        depth = self.get_depth()
        rendered = render_to_string(
            'categories/admin/includes/meta_term_list_header.html',
            {
                'depth': depth,
                'depth_minus_1': depth - 1,
                'is_root': self.is_root(),
                'name': self.name,
                'is_archived': self.is_archived,
            }
        )
        return rendered
    get_as_listing_header.short_description = 'Name'
    get_as_listing_header.admin_order_field = 'name'

    def get_parent(self, *args, **kwargs):
        return super().get_parent(*args, **kwargs)
    get_parent.short_description = 'Parent'

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

    def delete(self):
        if self.is_root():
            raise PermissionDenied('Cannot delete root term.')
        else:
            super().delete()

    @classmethod
    def get_root_descendants(cls):
        # Meta terms queryset without Root node
        root_node = cls.get_first_root_node()
        if root_node:
            return root_node.get_descendants()
        return cls.objects.none()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Meta Term'
        verbose_name_plural = 'Meta Terms'
コード例 #28
0
ファイル: triggers.py プロジェクト: ZuSe/wagtail-tag-manager
class TriggerCondition(Orderable):
    trigger = ParentalKey(Trigger,
                          on_delete=models.CASCADE,
                          related_name="conditions")

    variable = models.CharField(max_length=255, null=True, blank=False)

    CONDITION_EXACT_MATCH = "exact_match"
    CONDITION_NOT_EXACT_MATCH = "not_exact_match"
    CONDITION_CONTAINS = "contains"
    CONDITION_NOT_CONTAINS = "not_contains"
    CONDITION_STARTS_WITH = "starts_with"
    CONDITION_NOT_STARTS_WITH = "not_starts_with"
    CONDITION_ENDS_WITH = "ends_with"
    CONDITION_NOT_ENDS_WITH = "not_ends_with"

    CONDITION_REGEX_MATCH = "regex_match"
    CONDITION_NOT_REGEX_MATCH = "not_regex_match"
    CONDITION_REGEX_IMATCH = "regex_imatch"
    CONDITION_NOT_REGEX_IMATCH = "not_regex_imatch"

    CONDITION_LT = "lower_than"
    CONDITION_LTE = "lower_than_equal"
    CONDITION_GT = "greater_than"
    CONDITION_GTE = "greater_than_equal"

    CONDITION_CHOICES = (
        (
            _("Text"),
            (
                (CONDITION_EXACT_MATCH, _("exact match")),
                (CONDITION_NOT_EXACT_MATCH, _("not exact match")),
                (CONDITION_CONTAINS, _("contains")),
                (CONDITION_NOT_CONTAINS, _("does not contain")),
                (CONDITION_STARTS_WITH, _("starts with")),
                (CONDITION_NOT_STARTS_WITH, _("does not start with")),
                (CONDITION_ENDS_WITH, _("ends with")),
                (CONDITION_NOT_ENDS_WITH, _("does not end with")),
            ),
        ),
        (
            _("Regex"),
            (
                (CONDITION_REGEX_MATCH, _("matches regex")),
                (CONDITION_NOT_REGEX_MATCH, _("does not match regex")),
                (CONDITION_REGEX_IMATCH,
                 _("matches regex (case insensitive)")),
                (
                    CONDITION_NOT_REGEX_IMATCH,
                    _("does not match regex (case insensitive)"),
                ),
            ),
        ),
        (
            _("Numbers"),
            (
                (CONDITION_LT, _("is lower than")),
                (CONDITION_LTE, _("is lower than or equal to")),
                (CONDITION_GT, _("is greater than")),
                (CONDITION_GTE, _("is greater than or equal to")),
            ),
        ),
    )

    condition_type = models.CharField(max_length=255,
                                      choices=CONDITION_CHOICES,
                                      default=CONDITION_CONTAINS)

    value = models.CharField(max_length=255)

    panels = [
        FieldPanel("variable", widget=VariableSelect),
        FieldPanel("condition_type"),
        FieldPanel("value"),
    ]

    def validate(self, context) -> bool:
        if self.variable in context:
            variable = context.get(self.variable, None)
            validator = getattr(self, self.condition_type)
            return validator(variable, self.value)
        return False

    # Text
    @staticmethod
    def exact_match(variable, value):
        return str(value) == str(variable)

    def not_exact_match(self, *args, **kwargs):
        return not self.exact_match(*args, **kwargs)

    @staticmethod
    def contains(variable, value):
        return str(value) in str(variable)

    def not_contains(self, *args, **kwargs):
        return not self.contains(*args, **kwargs)

    @staticmethod
    def starts_with(variable, value):
        return str(variable).startswith(str(value))

    def not_starts_with(self, *args, **kwargs):
        return not self.starts_with(*args, **kwargs)

    @staticmethod
    def ends_with(variable, value):
        return str(variable).endswith(str(value))

    def not_ends_with(self, *args, **kwargs):
        return not self.ends_with(*args, **kwargs)

    # Regex
    @staticmethod
    def regex_match(variable, value):
        return re.match(value, str(variable)) is not None

    def not_regex_match(self, *args, **kwargs):
        return not self.regex_match(*args, **kwargs)

    @staticmethod
    def regex_imatch(variable, value):
        return re.match(value, str(variable), re.IGNORECASE) is not None

    def not_regex_imatch(self, *args, **kwargs):
        return not self.regex_imatch(*args, **kwargs)

    # Numbers
    @staticmethod
    def lower_than(variable, value):
        return float(variable) < float(value)

    @staticmethod
    def lower_than_equal(variable, value):
        return float(variable) <= float(value)

    @staticmethod
    def greater_than(variable, value):
        return float(variable) > float(value)

    @staticmethod
    def greater_than_equal(variable, value):
        return float(variable) >= float(value)
コード例 #29
0
ファイル: models.py プロジェクト: torchbox/wagtailmedia
    date_to = models.DateField(
        "End date",
        null=True,
        blank=True,
        help_text="Not required if event is on a single day",
    )
    time_from = models.TimeField("Start time", null=True, blank=True)
    time_to = models.TimeField("End time", null=True, blank=True)
    location = models.CharField(max_length=255)
    body = RichTextField(blank=True)
    cost = models.CharField(max_length=255)
    signup_link = models.URLField(blank=True)


EventPage.content_panels = [
    FieldPanel("title", classname="full title"),
    FieldPanel("date_from"),
    FieldPanel("date_to"),
    FieldPanel("time_from"),
    FieldPanel("time_to"),
    FieldPanel("location"),
    FieldPanel("cost"),
    FieldPanel("signup_link"),
    FieldPanel("body", classname="full"),
    InlinePanel("related_media", label="Related media"),
]


class TestMediaBlock(AbstractMediaChooserBlock):
    def render_basic(self, value, context=None):
        if not value:
コード例 #30
0
ファイル: triggers.py プロジェクト: ZuSe/wagtail-tag-manager
class Trigger(ClusterableModel):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(max_length=100, unique=True, editable=False)
    description = models.TextField(null=True, blank=True)

    active = models.BooleanField(
        default=True,
        help_text=_("Uncheck to disable this trigger from firing."))

    TYPE_CLICK_ALL_ELEMENTS = "click_all_elements"
    TYPE_CLICK_SOME_ELEMENTS = "click_some_elements+"
    TYPE_VISIBILITY_ONCE_PER_PAGE = "visibility_once_per_page+"
    TYPE_VISIBILITY_ONCE_PER_ELEMENT = "visibility_once_per_element+"
    TYPE_VISIBILITY_RECURRING = "visibility_recurring+"
    TYPE_FORM_SUBMIT = "form_submit"
    TYPE_HISTORY_CHANGE = "history_change"
    TYPE_JAVASCRIPT_ERROR = "javascript_error"
    TYPE_SCROLL_VERTICAL = "scroll_vertical+"
    TYPE_SCROLL_HORIZONTAL = "scroll_horizontal+"
    TYPE_TIMER_TIMEOUT = "timer_timeout+"
    TYPE_TIMER_INTERVAL = "timer_interval+"
    TYPE_CHOICES = (
        (TYPE_FORM_SUBMIT, _("Form submit")),
        (TYPE_HISTORY_CHANGE, _("History change")),
        (TYPE_JAVASCRIPT_ERROR, _("JavaScript error")),
        (
            _("Click"),
            (
                (TYPE_CLICK_ALL_ELEMENTS, _("Click on all elements")),
                (TYPE_CLICK_SOME_ELEMENTS, _("Click on some elements")),
            ),
        ),
        (
            _("Visibility"),  # TODO: Advanced options...
            (
                (TYPE_VISIBILITY_ONCE_PER_PAGE, _("Monitor once per page")),
                (TYPE_VISIBILITY_ONCE_PER_ELEMENT,
                 _("Monitor once per element")),
                (TYPE_VISIBILITY_RECURRING, _("Monitor recurringingly")),
            ),
        ),
        (
            _("Scroll"),
            (
                (TYPE_SCROLL_VERTICAL, _("Scroll vertical")),
                (TYPE_SCROLL_HORIZONTAL, _("Scroll horizontal")),
            ),
        ),
        (
            _("Timer"),
            (
                (TYPE_TIMER_TIMEOUT, _("Timer with timeout")),
                (TYPE_TIMER_INTERVAL, _("Timer with interval")),
            ),
        ),
    )

    trigger_type = models.CharField(max_length=255,
                                    choices=TYPE_CHOICES,
                                    default=TYPE_FORM_SUBMIT)
    value = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        help_text=mark_safe(
            _("<b>Click:</b> the query selector of the element(s).<br/>"
              "<b>Visibility:</b> the query selector of the element(s).<br/>"
              "<b>Scroll:</b> the distance after which to trigger as percentage.<br/>"
              "<b>Timer:</b> the time in milliseconds after which to trigger.")
        ),
    )

    tags = models.ManyToManyField(
        Tag, help_text=_("The tags to include when this trigger is fired."))

    objects = TriggerQuerySet.as_manager()

    panels = [
        FieldPanel("name", classname="full title"),
        FieldPanel("description", classname="full"),
        MultiFieldPanel(
            [
                FieldPanel("trigger_type"),
                FieldPanel("value"),
                FieldPanel("active")
            ],
            heading=_("Configuration"),
        ),
        InlinePanel("conditions", label=_("Conditions")),
        FieldPanel("tags", widget=CheckboxSelectMultiple),
    ]

    def as_dict(self):
        return {
            "slug": self.slug,
            "type": re.sub(r"[+]", "", self.trigger_type),
            "value": self.get_value(),
        }

    def get_value(self):
        numbered = [
            self.TYPE_SCROLL_VERTICAL,
            self.TYPE_SCROLL_HORIZONTAL,
            self.TYPE_TIMER_TIMEOUT,
            self.TYPE_TIMER_INTERVAL,
        ]

        if self.trigger_type in numbered:
            return int(self.value)

        return self.value

    def validate(self, context) -> bool:
        if self.conditions.count() == 0:
            return True
        return all([
            condition.validate(context) for condition in self.conditions.all()
        ])

    def clean(self):
        super().clean()
        self.slug = slugify(self.name)

        if self.trigger_type.endswith("+") and not self.value:
            raise ValidationError(
                _("A value is required for this trigger type."))
        elif self.value and not self.trigger_type.endswith("+"):
            raise ValidationError(
                _("A value is not allowed for this trigger type."))

    def __str__(self):
        return self.name
コード例 #31
0
ファイル: models.py プロジェクト: adlerhorst-at/engine
class HomePage(BasePage):
    template = 'patterns/pages/home/home_page.html'

    # Only allow creating HomePages at the root level
    parent_page_types = ['wagtailcore.Page']
    subpage_types = [
        'news.NewsIndex', 'standardpages.StandardPage',
        'articles.ArticleIndex', 'people.PersonIndex'
    ]

    hero_title = models.CharField(null=True, blank=False, max_length=80)

    hero_introduction = models.CharField(blank=False, max_length=255)

    hero_button_text = models.CharField(blank=True, max_length=55)

    hero_button_link = models.ForeignKey(
        'wagtailcore.Page',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='+',
    )

    featured_image = models.ForeignKey(
        'images.CustomImage',
        null=True,
        blank=False,
        related_name='+',
        on_delete=models.SET_NULL,
    )

    search_fields = BasePage.search_fields + [
        index.SearchField('hero_introduction'),
    ]

    articles_title = models.CharField(null=True, blank=True, max_length=150)
    articles_link = models.ForeignKey(
        'wagtailcore.Page',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='+',
    )
    articles_linktext = models.CharField(null=True, blank=True, max_length=80)

    featured_pages_title = models.CharField(null=True,
                                            blank=True,
                                            max_length=150)
    pages_link = models.ForeignKey(
        'wagtailcore.Page',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='+',
    )
    pages_linktext = models.CharField(null=True, blank=True, max_length=80)

    news_title = models.CharField(null=True, blank=True, max_length=150)
    news_link = models.ForeignKey(
        'wagtailcore.Page',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
        related_name='+',
    )
    news_linktext = models.CharField(null=True, blank=True, max_length=80)

    content_panels = BasePage.content_panels + [
        MultiFieldPanel(
            [
                FieldPanel('hero_title'),
                FieldPanel('hero_introduction'),
                FieldPanel('hero_button_text'),
                PageChooserPanel('hero_button_link'),
                ImageChooserPanel('featured_image'),
            ],
            heading="Hero Section",
        ),
        InlinePanel('featured_pages',
                    label="Featured Pages",
                    max_num=6,
                    heading='Featured Pages, Maximum 6'),
        MultiFieldPanel(
            [
                FieldPanel('articles_title'),
                PageChooserPanel('articles_link'),
                FieldPanel('featured_pages_title'),
                PageChooserPanel('pages_link'),
                FieldPanel('news_title'),
                PageChooserPanel('news_link'),
            ],
            heading="Front page sections",
        ),
    ]

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context['articles_title'] = self.articles_title
        context['articles_link'] = self.articles_link
        context['articles_linktext'] = self.articles_linktext
        context['featured_pages_title'] = self.featured_pages_title
        context['pages_link'] = self.pages_link
        context['pages_linktext'] = self.pages_linktext
        context['news_title'] = self.news_title
        context['news_link'] = self.news_link
        context['news_linktext'] = self.news_linktext

        if ArticlePage.objects.live().public().count() >= 1:
            latest_articles = ArticlePage.objects.live().public().order_by(
                '-first_published_at')
            context['article_top'] = latest_articles[0]
            context['articles_row_1'] = latest_articles[1:4]
            context['articles_row_2'] = latest_articles[4:7]

            context['featured_row_1'] = self.featured_pages.all()[:3]
            context['featured_row_2'] = self.featured_pages.all()[3:6]

        if NewsPage.objects.live().public().count() >= 1:
            latest_news = NewsPage.objects.live().public().order_by(
                '-first_published_at')
            context['latest_news'] = latest_news[0:8]

        return context
コード例 #32
0
class TestFieldPanel(TestCase):
    def setUp(self):
        self.EventPageForm = get_form_for_model(
            EventPage, form_class=WagtailAdminPageForm, formsets=[])
        self.event = EventPage(title='Abergavenny sheepdog trials',
                               date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))

        self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)

    def test_render_as_object(self):
        form = self.EventPageForm(
            {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22'},
            instance=self.event)

        form.is_valid()

        field_panel = self.EndDatePanel(
            instance=self.event,
            form=form
        )
        result = field_panel.render_as_object()

        # check that label appears as a legend in the 'object' wrapper,
        # but not as a field label (that would be provided by ObjectList instead)
        self.assertIn('<legend>End date</legend>', result)
        self.assertNotIn('<label for="id_date_to">End date:</label>', result)

        # check that help text is not included (it's provided by ObjectList instead)
        self.assertNotIn('Not required if event is on a single day', result)

        # check that the populated form field is included
        self.assertIn('value="2014-07-22"', result)

        # there should be no errors on this field
        self.assertNotIn('<p class="error-message">', result)

    def test_render_as_field(self):
        form = self.EventPageForm(
            {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22'},
            instance=self.event)

        form.is_valid()

        field_panel = self.EndDatePanel(
            instance=self.event,
            form=form
        )
        result = field_panel.render_as_field()

        # check that label is output in the 'field' style
        self.assertIn('<label for="id_date_to">End date:</label>', result)
        self.assertNotIn('<legend>End date</legend>', result)

        # check that help text is included
        self.assertIn('Not required if event is on a single day', result)

        # check that the populated form field is included
        self.assertIn('value="2014-07-22"', result)

        # there should be no errors on this field
        self.assertNotIn('<p class="error-message">', result)

    def test_required_fields(self):
        result = self.EndDatePanel.required_fields()
        self.assertEqual(result, ['date_to'])

    def test_error_message_is_rendered(self):
        form = self.EventPageForm(
            {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-33'},
            instance=self.event)

        form.is_valid()

        field_panel = self.EndDatePanel(
            instance=self.event,
            form=form
        )
        result = field_panel.render_as_field()

        self.assertIn('<p class="error-message">', result)
        self.assertIn('<span>Enter a valid date.</span>', result)