Ejemplo n.º 1
0
 def setUp(self):
     # a custom tabbed interface for EventPage
     self.EventPageTabbedInterface = TabbedInterface([
         ObjectList([
             FieldPanel('title', widget=forms.Textarea),
             FieldPanel('date_from'),
             FieldPanel('date_to'),
         ], heading='Event details', classname="shiny"),
         ObjectList([
             InlinePanel('speakers', label="Speakers"),
         ], heading='Speakers'),
     ]).bind_to_model(EventPage)
Ejemplo n.º 2
0
def get_page_edit_handler(page_class):
    if page_class not in PAGE_EDIT_HANDLERS:
        if hasattr(page_class, 'edit_handler'):
            # use the edit handler specified on the page class
            edit_handler = page_class.edit_handler
        else:
            # construct a TabbedInterface made up of content_panels, promote_panels
            # and settings_panels, skipping any which are empty
            tabs = []

            if page_class.content_panels:
                tabs.append(ObjectList(page_class.content_panels, heading=_('Content')))
            if page_class.promote_panels:
                tabs.append(ObjectList(page_class.promote_panels, heading=_('Promote')))
            if page_class.settings_panels:
                tabs.append(ObjectList(page_class.settings_panels, heading=_('Settings'), classname="settings"))

            edit_handler = TabbedInterface(tabs)

        PAGE_EDIT_HANDLERS[page_class] = edit_handler.bind_to_model(page_class)

    return PAGE_EDIT_HANDLERS[page_class]
Ejemplo n.º 3
0
class HomePage(CFGOVPage):
    header = StreamField([
        ('info_unit', molecules.InfoUnit()),
        ('half_width_link_blob', molecules.HalfWidthLinkBlob()),
    ], blank=True)

    latest_updates = StreamField([
        ('posts', blocks.ListBlock(blocks.StructBlock([
            ('categories', blocks.ChoiceBlock(choices=ref.limited_categories,
                                              required=False)),
            ('link', atoms.Hyperlink()),
            ('date', blocks.DateTimeBlock(required=False)),
        ]))),
    ], blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('latest_updates'),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    # Sets page to only be createable at the root
    parent_page_types = ['wagtailcore.Page']

    template = 'index.html'

    objects = PageManager()

    def get_category_name(self, category_icon_name):
        cats = dict(ref.limited_categories)
        return cats[str(category_icon_name)]

    def get_context(self, request):
        context = super(HomePage, self).get_context(request)
        return context
Ejemplo n.º 4
0
class ProjectPage(ThemeablePage):
    description = RichTextField(blank=True, default="")

    search_fields = Page.search_fields + [
        index.SearchField('description', partial_match=True),
    ]

    def search_result_text(self):
        if self.description:
            self.search_result_text = self.description[0:240]
        return self.search_result_text

    def project_articles(self):
        return self.articlepage_set.filter(
            live=True).order_by("-first_published_at")

    def project_series(self):
        return self.seriespage_set.filter(
            live=True).order_by("-first_published_at")

    def get_related_series(self, series_page):
        return self.seriespage_set.filter(live=True).exclude(
            pk=series_page.pk).order_by("-first_published_at")

    def __str__(self):
        return "{}".format(self.title)

    content_panels = Page.content_panels + [
        RichTextFieldPanel('description'),
    ]

    style_panels = ThemeablePage.style_panels

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Ejemplo n.º 5
0
class CampsitePage(Page, ApiCampsiteMixin, MetaMixin):
    subpage_types = []
    parent_page_types = ['CampsiteIndexPage']
    alerts = GenericRelation(Alert)

    facilities = ClusterTaggableManager(through=CampsitePageFacility,
                                        blank=True,
                                        related_name='facility_campsites')
    landscapes = ClusterTaggableManager(through=CampsitePageLandscape,
                                        blank=True,
                                        related_name='landscape_campsites')
    activities = ClusterTaggableManager(through=CampsitePageActivity,
                                        blank=True,
                                        related_name='activity_campsites')

    content_panels = Page.content_panels + [
        ImageChooserPanel('meta_image'),
        FieldPanel('facilities'),
        FieldPanel('landscapes'),
        FieldPanel('activities'),
    ]
    api_panels = COMMON_PANELS + [
        # campsite specific fields
        FieldPanel('dogs_allowed'),
        FieldPanel('is_free'),
        FieldPanel('powered_sites'),
        FieldPanel('unpowered_sites'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(api_panels, heading='API fields'),
        ObjectList(MetaMixin.meta_panels, heading='Meta'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])

    class Meta:
        verbose_name = "Campsite Page"
Ejemplo n.º 6
0
class AdvertWithTabbedInterface(models.Model):
    url = models.URLField(null=True, blank=True)
    text = models.CharField(max_length=191)
    something_else = models.CharField(max_length=191)

    advert_panels = [
        FieldPanel('url'),
        FieldPanel('text'),
    ]

    other_panels = [
        FieldPanel('something_else'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(advert_panels, heading='Advert'),
        ObjectList(other_panels, heading='Other'),
    ])

    def __str__(self):
        return self.text
class BrowseFilterablePage(FilterableFeedPageMixin, FilterableListMixin,
                           CFGOVPage):
    header = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', organisms.FeaturedContent()),
    ])
    content = StreamField(BrowseFilterableContent)

    secondary_nav_exclude_sibling_pages = models.BooleanField(default=False)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    sidefoot_panels = CFGOVPage.sidefoot_panels + [
        FieldPanel('secondary_nav_exclude_sibling_pages'),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(sidefoot_panels, heading='SideFoot'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'browse-filterable/index.html'

    objects = PageManager()

    search_fields = CFGOVPage.search_fields + [
        index.SearchField('content'),
        index.SearchField('header')
    ]

    @property
    def page_js(self):
        return (super(BrowseFilterablePage, self).page_js +
                ['secondary-navigation.js'])
Ejemplo n.º 8
0
class PayingForCollegePage(CFGOVPage):
    """A base class for our suite of PFC pages."""
    header = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', organisms.FeaturedContent()),
    ],
                         blank=True)

    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]
    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])
    objects = CFGOVPageManager()

    class Meta:
        abstract = True
Ejemplo n.º 9
0
class AnswerCategoryPage(FilterableFeedPageMixin, FilterableListMixin,
                         CFGOVPage):
    """
    Page type for Ask CFPB parent-category pages.
    """
    from .django import Category

    objects = PageManager()
    content = StreamField([
        ('filter_controls', FilterControls()),
    ], null=True)
    ask_category = models.ForeignKey(Category,
                                     blank=True,
                                     null=True,
                                     on_delete=models.PROTECT,
                                     related_name='category_page')
    content_panels = CFGOVPage.content_panels + [
        FieldPanel('ask_category', Category),
        StreamFieldPanel('content'),
    ]
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])
    template = 'ask-cfpb/category-page.html'

    def add_page_js(self, js):
        super(AnswerCategoryPage, self).add_page_js(js)
        js['template'] += ['secondary-navigation.js']

    def get_context(self, request, *args, **kwargs):
        context = super(AnswerCategoryPage,
                        self).get_context(request, *args, **kwargs)
        context.update({
            'choices':
            self.ask_category.subcategories.all().values_list('slug', 'name')
        })
        return context
Ejemplo n.º 10
0
class BrowseFilterablePage(FilterableFeedPageMixin, FilterableListMixin,
                           CFGOVPage):
    header = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', molecules.FeaturedContent()),
    ])
    content = StreamField([
        ('full_width_text', organisms.FullWidthText()),
        ('filter_controls', organisms.FilterControls()),
        ('feedback', v1_blocks.Feedback()),
    ])

    secondary_nav_exclude_sibling_pages = models.BooleanField(default=False)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    sidefoot_panels = CFGOVPage.sidefoot_panels + [
        FieldPanel('secondary_nav_exclude_sibling_pages'),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(sidefoot_panels, heading='SideFoot'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'browse-filterable/index.html'

    objects = PageManager()

    def add_page_js(self, js):
        super(BrowseFilterablePage, self).add_page_js(js)
        js['template'] += ['secondary-navigation.js']
Ejemplo n.º 11
0
class MerchantPage(Page):
    logo = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    def get_context(self, request):
        context = super(MerchantPage, self).get_context(request)
        context['product_list'] = Product.objects.order_by('?')[:8]
        context['random_categories'] = Category.objects.order_by('?')[:2]
        return context

    subpage_types = ['products.Category']

    content_panels = [
        FieldPanel('title'),
        ImageChooserPanel('logo')
    ]
    main_slide_panels = [
        InlinePanel('related_slide_pictures', label='Slide principal')
    ]
    social_panels = [
        InlinePanel('related_social_media_links', label='Redes sociales')
    ]
    contact_panels = [
        InlinePanel('related_contact_config', label='Contacto')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Configuración general'),
        ObjectList(main_slide_panels, heading='Slide principal'),
        ObjectList(social_panels, heading='Redes sociales'),
        ObjectList(contact_panels, heading='Contacto'),
        ObjectList(Page.promote_panels, heading='SEO')
    ])
Ejemplo n.º 12
0
class GeoPage(Page):
    address = models.CharField(max_length=250, blank=True, null=True)
    location = models.PointField(srid=4326, null=True, blank=True)

    content_panels = Page.content_panels + [
        InlinePanel('related_locations', label="Related locations"),
    ]

    location_panels = [
        MultiFieldPanel([
            FieldPanel('address'),
            GeoPanel('location', address_field='address'),
        ],
                        heading='Location')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(location_panels, heading='Location'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Ejemplo n.º 13
0
class ExternalArticleListPage(PaginatedListPageMixin, ThemeablePage):
    subpage_types = ['ExternalArticlePage']

    articles_per_page = models.IntegerField(default=20)
    counter_field_name = 'articles_per_page'
    counter_context_name = 'articles'

    @property
    def subpages(self):
        subpages = ExternalArticlePage.objects.live().descendant_of(self).order_by('-first_published_at')
        return subpages

    content_panels = Page.content_panels + [
        FieldPanel('articles_per_page'),
    ]

    style_panels = ThemeablePage.style_panels

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
    ])
Ejemplo n.º 14
0
class EventListPage(PaginatedListPageMixin, Page):
    subpage_types = ['EventPage']

    events_per_page = models.IntegerField(default=20)
    counter_field_name = 'events_per_page'
    counter_context_name = 'events'

    @property
    def subpages(self):
        # Get list of live event pages that are descendants of this page
        subpages = EventPage.objects.live().descendant_of(self).order_by(
            '-date')

        return subpages

    content_panels = Page.content_panels + [FieldPanel('events_per_page')]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Ejemplo n.º 15
0
class LandingPage(CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
        ('text_introduction', molecules.TextIntroduction()),
    ],
                         blank=True)

    content = StreamField([
        ('image_text_25_75_group', organisms.ImageText2575Group()),
        ('image_text_50_50_group', organisms.ImageText5050Group()),
        ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()),
        ('well', organisms.Well()),
    ],
                          blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

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

    template = 'landing-page/index.html'
class SublandingFilterablePage(FilterableFeedPageMixin, base.CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('full_width_text', organisms.FullWidthText()),
        ('filter_controls', organisms.FilterControls()),
        ('featured_content', molecules.FeaturedContent()),
    ])

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'sublanding-page/index.html'

    def get_context(self, request, *args, **kwargs):
        context = super(SublandingFilterablePage,
                        self).get_context(request, *args, **kwargs)
        return filterable_context.get_context(self, request, context)

    def get_form_class(self):
        return forms.FilterableListForm

    def get_page_set(self, form, hostname):
        return filterable_context.get_page_set(self, form, hostname)
Ejemplo n.º 17
0
class HomePage(CFGOVPage):
    header = StreamField([
        ('info_unit', molecules.InfoUnit()),
        ('half_width_link_blob', molecules.HalfWidthLinkBlob()),
    ],
                         blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        InlinePanel(
            'excluded_updates',
            label='Pages excluded from Latest Updates',
            help_text=('This block automatically displays the six most '
                       'recently published blog posts, press releases, '
                       'speeches, testimonies, events, or op-eds. If you want '
                       'to exclude a page from appearing as a recent update '
                       'in this block, add it as an excluded page.')),
    ]

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    # Sets page to only be createable at the root
    parent_page_types = ['wagtailcore.Page']

    objects = PageManager()

    search_fields = CFGOVPage.search_fields + [index.SearchField('header')]

    @property
    def page_js(self):
        return super(HomePage, self).page_js + ['home-page.js']

    def get_category_name(self, category_icon_name):
        cats = dict(ref.limited_categories)
        return cats[str(category_icon_name)]

    def get_context(self, request):
        context = super(HomePage, self).get_context(request)
        context.update({
            'carousel_items': self.get_carousel_items(),
            'info_units': self.get_info_units(),
            'latest_updates': self.get_latest_updates(request),
        })
        return context

    def get_carousel_items(self):
        return _carousel_items_by_language[self.language]

    def get_info_units(self):
        return [
            molecules.InfoUnit().to_python(info_unit)
            for info_unit in _info_units_by_language[self.language]
        ]

    def get_latest_updates(self, request):
        # TODO: There should be a way to express this as part of the query
        # rather than evaluating it in Python.
        excluded_updates_pks = [
            e.excluded_page.pk for e in self.excluded_updates.all()
        ]

        latest_pages = CFGOVPage.objects.in_site(
            request.site).exclude(pk__in=excluded_updates_pks).filter(
                Q(content_type__app_label='v1') &
                (Q(content_type__model='blogpage')
                 | Q(content_type__model='eventpage')
                 | Q(content_type__model='newsroompage')),
                live=True,
            ).distinct().order_by('-first_published_at')[:6]

        return latest_pages

    def get_template(self, request):
        if flag_enabled('NEW_HOME_PAGE', request=request):
            return 'home-page/index_new.html'
        else:
            return 'home-page/index_%s.html' % self.language
Ejemplo n.º 18
0
class HomePage(ThemeablePage):
    subpage_types = [
        article_models.ArticleListPage,
        article_models.SeriesListPage,
        article_models.TopicListPage,
        people_models.ContributorListPage,
        project_models.ProjectListPage,
        newsletter_models.NewsletterListPage,
        event_models.EventListPage,
        article_models.ExternalArticleListPage,
        jobs_models.JobPostingListPage,
        StreamPage,
    ]

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

    number_of_rows_of_articles = models.IntegerField(default=12,
                                                     verbose_name="Rows")
    number_of_columns_of_articles = models.IntegerField(default=3,
                                                        verbose_name="Columns")
    number_of_rows_of_series = models.IntegerField(default=1,
                                                   verbose_name="Rows")
    number_of_rows_of_external_articles = models.IntegerField(
        default=2, verbose_name="Rows")
    number_of_columns_of_external_articles = models.IntegerField(
        default=2, verbose_name="Columns")
    number_of_rows_of_visualizations = models.IntegerField(default=2,
                                                           verbose_name="Rows")
    number_of_columns_of_visualizations = models.IntegerField(
        default=2, verbose_name="Columns")
    full_bleed_image_size = models.PositiveSmallIntegerField(
        default=90,
        help_text=
        "Enter a value from 0 - 100, indicating the percentage of the screen to use for the featured image layout."
    )

    _articles = None
    _most_popular_article = None

    def __str__(self):
        return self.title

    def get_article_set(self, columns, rows, article_list, used):
        if columns == 0 and rows == 0 or not article_list:
            return []

        current_set = []
        while rows > 0:
            row, height = self._fill_row(columns, article_list, used, rows)
            if height == 0:
                break
            current_set.append(row)
            rows = rows - height

        return current_set

    def _fill_row(self, columns, article_list, used, max_height):
        if columns == 0 or not article_list:
            return [], 0

        for article in article_list:
            typed_article = article.content_type.get_object_for_this_type(
                id=article.id)
            if typed_article.feature_style.number_of_columns <= columns \
                    and typed_article.id not in used\
                    and typed_article.feature_style.number_of_rows <= max_height:

                columns = columns - typed_article.feature_style.number_of_columns
                used.append(typed_article.id)
                row = [typed_article]
                max_height = min(max_height,
                                 typed_article.feature_style.number_of_rows)

                if max_height > 1 and columns > 0:
                    subset = self.get_article_set(columns, max_height,
                                                  article_list, used)
                    row.append(subset)
                else:
                    recursive_row, height = self._fill_row(
                        columns, article_list, used, max_height)
                    row.extend(recursive_row)
                return row, max_height

        return [], 0

    @property
    def articles(self):
        if self._articles is not None:
            return self._articles

        article_content_type = ContentType.objects.get_for_model(
            article_models.ArticlePage)
        series_content_type = ContentType.objects.get_for_model(
            article_models.SeriesPage)

        articles = Page.objects.live().filter(
            models.Q(content_type=article_content_type)
            | models.Q(content_type=series_content_type)).annotate(
                sticky=models.Case(
                    models.When(models.Q(seriespage__sticky=True)
                                | (models.Q(articlepage__sticky=True)),
                                then=models.Value(1)),
                    default=models.Value(0),
                    output_field=models.IntegerField())).exclude(
                        models.Q(seriespage__slippery=True)
                        | models.Q(articlepage__slippery=True)).order_by(
                            "-sticky", "-first_published_at")

        articles = list(articles[:42])

        used = []
        if self.featured_item:
            used.append(self.featured_item.id)
        self._articles = self.get_article_set(
            self.number_of_columns_of_articles,
            self.number_of_rows_of_articles, articles, used)

        return self._articles

    @property
    def most_popular_article(self):
        if self._most_popular_article is not None:
            return self._most_popular_article

        def get_views(article):
            try:
                if article.analytics:
                    return article.analytics.last_period_views
            except:
                return 0

        # flatten the list of lists with generator
        articles = itertools.chain.from_iterable(self.articles)
        articles_by_popularity = sorted(articles, key=get_views)

        self._most_popular_article = articles_by_popularity[-1]
        return self._most_popular_article

    @property
    def typed_featured_item(self):
        if self.featured_item:
            featured_item = self.featured_item.content_type.get_object_for_this_type(
                id=self.featured_item.id)
            return featured_item

    def get_rows(self, number_of_rows, number_of_columns, items):
        rows = []

        for row_index in range(0, number_of_rows):
            row = items[(row_index *
                         number_of_columns):(row_index * number_of_columns) +
                        number_of_columns]
            rows.append(row)

        return rows

    @property
    def external_articles(self):
        number_of_external_articles = self.number_of_rows_of_external_articles * self.number_of_columns_of_external_articles
        external_article_list = article_models.ExternalArticlePage.objects.live(
        ).order_by("-first_published_at")[:number_of_external_articles]

        return self.get_rows(self.number_of_rows_of_external_articles,
                             self.number_of_columns_of_external_articles,
                             external_article_list)

    @property
    def graphics(self):
        number_of_graphics = self.number_of_rows_of_visualizations * self.number_of_columns_of_visualizations
        graphics_list = article_models.ArticlePage.objects.live().filter(
            visualization=True).annotate(sticky_value=models.Case(
                models.When(models.Q(sticky_for_type_section=True),
                            then=models.Value(1)),
                default=models.Value(0),
                output_field=models.IntegerField())).exclude(
                    slippery_for_type_section=True).order_by(
                        "-sticky_value",
                        "-first_published_at")[:number_of_graphics]

        return self.get_rows(self.number_of_rows_of_visualizations,
                             self.number_of_columns_of_visualizations,
                             graphics_list)

    @property
    def series(self):
        number_of_series = self.number_of_rows_of_series
        series_list = article_models.SeriesPage.objects.live().annotate(
            sticky_value=models.Case(models.When(
                models.Q(sticky_for_type_section=True), then=models.Value(1)),
                                     default=models.Value(0),
                                     output_field=models.IntegerField())
        ).exclude(slippery_for_type_section=True).order_by(
            "-sticky_value", "-first_published_at")[:number_of_series]

        return self.get_rows(self.number_of_rows_of_series, 1, series_list)

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            PageChooserPanel("featured_item", "wagtailcore.Page"),
            FieldPanel("full_bleed_image_size"),
        ],
                        heading="Main Feature"),
        MultiFieldPanel([
            FieldPanel("number_of_rows_of_articles"),
            FieldPanel("number_of_columns_of_articles"),
        ],
                        heading="Main Feed Settings"),
        MultiFieldPanel([
            FieldPanel("number_of_rows_of_series"),
        ],
                        heading="Series Section Settings"),
        MultiFieldPanel([
            FieldPanel("number_of_rows_of_external_articles"),
            FieldPanel("number_of_columns_of_external_articles"),
        ],
                        heading="External Articles Section Settings"),
        MultiFieldPanel([
            FieldPanel("number_of_rows_of_visualizations"),
            FieldPanel("number_of_columns_of_visualizations"),
        ],
                        heading="Visualization Section Settings"),
    ]

    style_panels = ThemeablePage.style_panels

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels,
                   heading='Settings',
                   classname="settings"),
    ])
Ejemplo n.º 19
0
class CFGOVPage(Page):
    authors = ClusterTaggableManager(through=CFGOVAuthoredPages,
                                     blank=True,
                                     verbose_name='Authors',
                                     help_text='A comma separated list of ' +
                                     'authors.',
                                     related_name='authored_pages')
    tags = ClusterTaggableManager(through=CFGOVTaggedPages,
                                  blank=True,
                                  related_name='tagged_pages')
    shared = models.BooleanField(default=False)
    has_unshared_changes = models.BooleanField(default=False)
    language = models.CharField(choices=ref.supported_languagues,
                                default='en',
                                max_length=2)

    # This is used solely for subclassing pages we want to make at the CFPB.
    is_creatable = False

    objects = CFGOVPageManager()

    # These fields show up in either the sidebar or the footer of the page
    # depending on the page type.
    sidefoot = StreamField([
        ('call_to_action', molecules.CallToAction()),
        ('related_links', molecules.RelatedLinks()),
        ('related_posts', organisms.RelatedPosts()),
        ('related_metadata', molecules.RelatedMetadata()),
        ('email_signup', organisms.EmailSignUp()),
        ('sidebar_contact', organisms.SidebarContactInfo()),
        ('rss_feed', molecules.RSSFeed()),
        ('social_media', molecules.SocialMedia()),
        ('reusable_text', ReusableTextChooserBlock(ReusableText)),
    ],
                           blank=True)

    # Panels
    sidefoot_panels = [
        StreamFieldPanel('sidefoot'),
    ]

    settings_panels = [
        MultiFieldPanel(Page.promote_panels, 'Settings'),
        InlinePanel('categories', label="Categories", max_num=2),
        FieldPanel('tags', 'Tags'),
        FieldPanel('authors', 'Authors'),
        MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'),
        FieldPanel('language', 'language'),
    ]

    # Tab handler interface guide because it must be repeated for each subclass
    edit_handler = TabbedInterface([
        ObjectList(Page.content_panels, heading='General Content'),
        ObjectList(sidefoot_panels, heading='Sidebar/Footer'),
        ObjectList(settings_panels, heading='Configuration'),
    ])

    def get_authors(self):
        """ Returns a sorted list of authors. Default is alphabetical """
        return self.alphabetize_authors()

    def alphabetize_authors(self):
        """
        Alphabetize authors of this page by last name,
        then first name if needed
        """
        # First sort by first name
        author_names = self.authors.order_by('name')
        # Then sort by last name
        return sorted(author_names, key=lambda x: x.name.split()[-1])

    def generate_view_more_url(self, request):
        activity_log = CFGOVPage.objects.get(slug='activity-log').specific
        tags = []
        index = activity_log.form_id()
        tags = urlencode([('filter%s_topics' % index, tag)
                          for tag in self.tags.slugs()])
        return (get_protected_url({'request': request}, activity_log) + '?' +
                tags)

    def related_posts(self, block, hostname):
        from v1.models.learn_page import AbstractFilterPage
        related = {}
        query = models.Q(('tags__name__in', self.tags.names()))
        search_types = [
            ('blog', 'posts', 'Blog', query),
            ('newsroom', 'newsroom', 'Newsroom', query),
            ('events', 'events', 'Events', query),
        ]

        def fetch_children_by_specific_category(block, parent_slug):
            """
            This used to be a Page.objects.get, which would throw
            an exception if the requested parent wasn't found. As of
            Django 1.6, you can now do Page.objects.filter().first();
            the advantage here is that you can check for None right
            away and not have to rely on catching exceptions, which
            in any case didn't do anything useful other than to print
            an error message. Instead, we just return an empty query
            which has no effect on the final result.
            """
            parent = Page.objects.filter(slug=parent_slug).first()
            if parent:
                child_query = Page.objects.child_of_q(parent)
                if 'specific_categories' in block.value:
                    child_query &= specific_categories_query(
                        block, parent_slug)
            else:
                child_query = Q()
            return child_query

        def specific_categories_query(block, parent_slug):
            specific_categories = ref.related_posts_category_lookup(
                block.value['specific_categories'])
            choices = [c[0] for c in ref.choices_for_page_type(parent_slug)]
            categories = [c for c in specific_categories if c in choices]
            if categories:
                return Q(('categories__name__in', categories))
            else:
                return Q()

        for parent_slug, search_type, search_type_name, search_query in \
                search_types:
            search_query &= fetch_children_by_specific_category(
                block, parent_slug)
            if parent_slug == 'events':
                search_query |= fetch_children_by_specific_category(
                    block, 'archive-past-events') & query
            relate = block.value.get('relate_{}'.format(search_type), None)
            if relate:
                related[search_type_name] = (AbstractFilterPage.objects.live(
                ).filter(search_query).distinct().exclude(id=self.id).order_by(
                    '-date_published')[:block.value['limit']])

        # Return a dictionary of lists of each type when there's at least one
        # hit for that type.
        return {
            search_type: queryset
            for search_type, queryset in related.items() if queryset
        }

    def get_breadcrumbs(self, request):
        ancestors = self.get_ancestors()
        home_page_children = request.site.root_page.get_children()
        for i, ancestor in enumerate(ancestors):
            if ancestor in home_page_children:
                return [ancestor for ancestor in ancestors[i + 1:]]
        return []

    def get_appropriate_descendants(self, hostname, inclusive=True):
        return CFGOVPage.objects.live().descendant_of(self, inclusive)

    def get_appropriate_siblings(self, hostname, inclusive=True):
        return CFGOVPage.objects.live().sibling_of(self, inclusive)

    def get_next_appropriate_siblings(self, hostname, inclusive=False):
        return self.get_appropriate_siblings(
            hostname=hostname,
            inclusive=inclusive).filter(path__gte=self.path).order_by('path')

    def get_prev_appropriate_siblings(self, hostname, inclusive=False):
        return self.get_appropriate_siblings(
            hostname=hostname,
            inclusive=inclusive).filter(path__lte=self.path).order_by('-path')

    def get_context(self, request, *args, **kwargs):
        context = super(CFGOVPage, self).get_context(request, *args, **kwargs)
        for hook in hooks.get_hooks('cfgovpage_context_handlers'):
            hook(self, request, context, *args, **kwargs)
        return context

    def serve(self, request, *args, **kwargs):
        """
        If request is ajax, then return the ajax request handler response, else
        return the super.
        """
        if request.method == 'POST':
            return self.serve_post(request, *args, **kwargs)

        return super(CFGOVPage, self).serve(request, *args, **kwargs)

    def serve_post(self, request, *args, **kwargs):
        """
        Attempts to retreive form_id from the POST request and returns a JSON
        response.

        If form_id is found, it returns the response from the block method
        retrieval.

        If form_id is not found, it returns an error response.
        """
        form_id = request.POST.get('form_id', None)
        if not form_id:
            if request.is_ajax():
                return JsonResponse({'result': 'error'}, status=400)

            return HttpResponseBadRequest(self.url)

        sfname, index = form_id.split('-')[1:]

        streamfield = getattr(self, sfname)
        module = streamfield[int(index)]

        result = module.block.get_result(self, request, module.value, True)

        if isinstance(result, HttpResponse):
            return result
        else:
            context = self.get_context(request, *args, **kwargs)
            context['form_modules'][sfname].update({int(index): result})

            return TemplateResponse(
                request, self.get_template(request, *args, **kwargs), context)

    class Meta:
        app_label = 'v1'

    def parent(self):
        parent = self.get_ancestors(inclusive=False).reverse()[0].specific
        return parent

    # To be overriden if page type requires JS files every time
    # 'template' is used as the key for front-end consistency
    def add_page_js(self, js):
        js['template'] = []

    # Retrieves the stream values on a page from it's Streamfield
    def _get_streamfield_blocks(self):
        lst = [
            value for key, value in vars(self).iteritems()
            if type(value) is StreamValue
        ]
        return list(chain(*lst))

    # Gets the JS from the Streamfield data
    def _add_streamfield_js(self, js):
        # Create a dict with keys ordered organisms, molecules, then atoms
        for child in self._get_streamfield_blocks():
            self._add_block_js(child.block, js)

    # Recursively search the blocks and classes for declared Media.js
    def _add_block_js(self, block, js):
        self._assign_js(block, js)
        if issubclass(type(block), blocks.StructBlock):
            for child in block.child_blocks.values():
                self._add_block_js(child, js)
        elif issubclass(type(block), blocks.ListBlock):
            self._add_block_js(block.child_block, js)

    # Assign the Media js to the dictionary appropriately
    def _assign_js(self, obj, js):
        try:
            if hasattr(obj.Media, 'js'):
                for key in js.keys():
                    if obj.__module__.endswith(key):
                        js[key] += obj.Media.js
                if not [
                        key
                        for key in js.keys() if obj.__module__.endswith(key)
                ]:
                    js.update({'other': obj.Media.js})
        except:
            pass

    # Returns all the JS files specific to this page and it's current
    # Streamfield's blocks
    @property
    def media(self):
        js = OrderedDict()
        for key in ['template', 'organisms', 'molecules', 'atoms']:
            js.update({key: []})
        self.add_page_js(js)
        self._add_streamfield_js(js)
        for key, js_files in js.iteritems():
            js[key] = OrderedDict.fromkeys(js_files).keys()
        return js
Ejemplo n.º 20
0
    FieldPanel('slug'),
    InlinePanel('advert_placements', label="Adverts"),
]
StandardIndex.promote_panels = []


class StandardChild(Page):
    pass


# Test overriding edit_handler with a custom one
StandardChild.edit_handler = TabbedInterface(
    [
        ObjectList(StandardChild.content_panels, heading='Content'),
        ObjectList(StandardChild.promote_panels, heading='Promote'),
        ObjectList(StandardChild.settings_panels,
                   heading='Settings',
                   classname='settings'),
        ObjectList([], heading='Dinosaurs'),
    ],
    base_form_class=WagtailAdminPageForm)


class BusinessIndex(Page):
    """ Can be placed anywhere, can only have Business children """
    subpage_types = ['tests.BusinessChild', 'tests.BusinessSubIndex']


class BusinessSubIndex(Page):
    """ Can be placed under BusinessIndex, and have BusinessChild children """

    # BusinessNowherePage is 'incorrectly' added here as a possible child.
Ejemplo n.º 21
0
class TabbedSettings(TestSetting):
    edit_handler = TabbedInterface([
        ObjectList([FieldPanel('title')], heading='First tab'),
        ObjectList([FieldPanel('email')], heading='Second tab'),
    ])
Ejemplo n.º 22
0
class ActivityIndexPage(CFGOVPage):
    """
    A model for the Activity Search page.
    """

    subpage_types = ['teachers_digital_platform.ActivityPage']

    objects = CFGOVPageManager()

    header = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
    ],
                         blank=True)

    results = {}
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
    ]

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

    @classmethod
    def can_create_at(cls, parent):
        # You can only create one of these!
        return super(ActivityIndexPage, cls).can_create_at(parent) \
            and not cls.objects.exists()

    def get_template(self, request):
        template = 'teachers_digital_platform/activity_index_page.html'
        if 'partial' in request.GET:
            template = 'teachers_digital_platform/activity_search_facets_and_results.html'  # noqa: E501
        return template

    def get_context(self, request, *args, **kwargs):
        facet_map = (
            ('building_block', (ActivityBuildingBlock, False, 10)),
            ('school_subject', (ActivitySchoolSubject, False, 25)),
            ('topic', (ActivityTopic, True, 25)),
            ('grade_level', (ActivityGradeLevel, False, 10)),
            ('age_range', (ActivityAgeRange, False, 10)),
            ('student_characteristics', (ActivityStudentCharacteristics, False,
                                         10)),  # noqa: E501
            ('activity_type', (ActivityType, False, 10)),
            ('teaching_strategy', (ActivityTeachingStrategy, False, 25)),
            ('blooms_taxonomy_level', (ActivityBloomsTaxonomyLevel, False,
                                       25)),  # noqa: E501
            ('activity_duration', (ActivityDuration, False, 10)),
            ('jump_start_coalition', (ActivityJumpStartCoalition, False, 25)),
            ('council_for_economic_education', (ActivityCouncilForEconEd,
                                                False, 25)),  # noqa: E501
        )
        search_query = request.GET.get('q', '')  # haystack cleans this string
        sqs = SearchQuerySet().models(ActivityPage).filter(live=True)
        total_activities = sqs.count()
        # Load selected facets
        selected_facets = {}
        facet_queries = {}

        for facet, facet_config in facet_map:
            sqs = sqs.facet(str(facet), size=facet_config[2])
            if facet in request.GET and request.GET.get(facet):
                selected_facets[facet] = [
                    int(value) for value in request.GET.getlist(facet)
                    if value.isdigit()
                ]
                facet_queries[facet] = facet + '_exact:' + (
                    " OR " + facet + "_exact:").join(
                        [str(value) for value in selected_facets[facet]])

        payload = {
            'search_query': search_query,
            'results': [],
            'total_results': 0,
            'total_activities': total_activities,
            'selected_facets': selected_facets,
            'facet_queries': facet_queries,
            'all_facets': {},
        }

        # Apply search query if it exists, but don't apply facets
        if search_query:
            sqs = sqs.filter(content=search_query).order_by(
                '-_score', '-date')  # noqa: E501
        else:
            sqs = sqs.order_by('-date')

        # Get all facets and their counts
        facet_counts = sqs.facet_counts()
        all_facets = self.get_all_facets(facet_map, sqs, facet_counts,
                                         facet_queries,
                                         selected_facets)  # noqa: E501

        # List all facet blocks that need to be expanded
        always_expanded = {'building_block', 'topic', 'school_subject'}
        conditionally_expanded = {
            facet_name
            for facet_name, facet_items in all_facets.items()
            if any(facet['selected'] is True for facet in facet_items)
        }
        expanded_facets = always_expanded.union(set(conditionally_expanded))

        payload.update({
            'facet_counts': facet_counts,
            'all_facets': all_facets,
            'expanded_facets': expanded_facets,
        })

        # Apply all the active facet values to our search results
        for facet_narrow_query in facet_queries.values():
            sqs = sqs.narrow(facet_narrow_query)

        results = [activity.object for activity in sqs]
        total_results = sqs.count()

        payload.update({
            'results': results,
            'total_results': total_results,
        })
        self.results = payload
        results_per_page = validate_results_per_page(request)
        paginator = Paginator(payload['results'], results_per_page)
        current_page = validate_page_number(request, paginator)
        paginated_page = paginator.page(current_page)

        context = super(ActivityIndexPage, self).get_context(request)
        context.update({
            'facet_counts': facet_counts,
            'facets': all_facets,
            'activities': paginated_page,
            'total_results': total_results,
            'results_per_page': results_per_page,
            'current_page': current_page,
            'paginator': paginator,
            'show_filters': bool(facet_queries),
        })
        return context

    def get_all_facets(self, facet_map, sqs, facet_counts, facet_queries,
                       selected_facets):  # noqa: E501
        all_facets = {}
        if 'fields' in facet_counts:
            for facet, facet_config in facet_map:
                class_object, is_nested, max_facet_count = facet_config
                all_facets_sqs = sqs
                other_facet_queries = [
                    facet_query for facet_query_name, facet_query in
                    facet_queries.items()  # noqa: E501
                    if facet != facet_query_name
                ]
                for other_facet_query in other_facet_queries:
                    all_facets_sqs = all_facets_sqs.narrow(
                        str(other_facet_query))  # noqa: E501
                narrowed_facet_counts = all_facets_sqs.facet_counts()
                if 'fields' in narrowed_facet_counts and facet in narrowed_facet_counts[
                        'fields']:  # noqa: E501
                    narrowed_facets = [
                        value[0]
                        for value in narrowed_facet_counts['fields'][facet]
                    ]  # noqa: E501
                    narrowed_selected_facets = selected_facets[
                        facet] if facet in selected_facets else [
                        ]  # noqa: E501
                    if is_nested:
                        all_facets[facet] = self.get_nested_facets(
                            class_object, narrowed_facets,
                            narrowed_selected_facets)
                    else:
                        all_facets[facet] = self.get_flat_facets(
                            class_object, narrowed_facets,
                            narrowed_selected_facets)
        return all_facets

    def get_flat_facets(self, class_object, narrowed_facets, selected_facets):
        final_facets = [{
            'selected': result['id'] in selected_facets,
            'id': result['id'],
            'title': result['title'],
        } for result in class_object.objects.filter(
            pk__in=narrowed_facets).values('id', 'title')]  # noqa: E501
        return final_facets

    def get_nested_facets(self,
                          class_object,
                          narrowed_facets,
                          selected_facets,
                          parent=None):  # noqa: E501
        if not parent:
            flat_final_facets = [{
                'selected': result['id'] in selected_facets,
                'id': result['id'],
                'title': result['title'],
                'parent': result['parent'],
            } for result in class_object.objects.filter(
                pk__in=narrowed_facets).get_ancestors(True).values(
                    'id', 'title', 'parent')]  # noqa: E501
            final_facets = []
            root_facets = [
                root_facet for root_facet in flat_final_facets
                if root_facet['parent'] == None
            ]  # noqa: E501
            for root_facet in root_facets:
                children_list = self.get_nested_facets(
                    class_object, narrowed_facets, selected_facets,
                    root_facet['id'])  # noqa: E501
                child_selected = any(child['selected'] is True
                                     or child['child_selected'] is True
                                     for child in children_list  # noqa: E501
                                     )
                final_facets.append({
                    'selected': root_facet['selected'],
                    'child_selected': child_selected,
                    'id': root_facet['id'],
                    'title': root_facet['title'],
                    'parent': root_facet['parent'],
                    'children': children_list
                })
            return final_facets
        else:
            children = [
                {
                    'selected':
                    result['id'] in selected_facets
                    or result['parent'] in selected_facets,  # noqa: E501
                    'id':
                    result['id'],
                    'title':
                    result['title'],
                    'parent':
                    result['parent'],
                    'children':
                    self.get_nested_facets(class_object, narrowed_facets,
                                           selected_facets,
                                           result['id']),  # noqa: E501
                    'child_selected':
                    any(child['selected'] is True
                        or child['child_selected'] is True
                        for child in  # noqa: E501
                        self.get_nested_facets(class_object, narrowed_facets,
                                               selected_facets,
                                               result['id'])  # noqa: E501
                        )
                } for result in class_object.objects.filter(
                    pk__in=narrowed_facets).filter(
                        parent_id=parent).values('id', 'title', 'parent')
            ]  # noqa: E501
            return children

    class Meta:
        verbose_name = "TDP Activity search page"
Ejemplo n.º 23
0
class SublandingPage(CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', molecules.FeaturedContent()),
        ('image_text_25_75_group', organisms.ImageText2575Group()),
        ('image_text_50_50_group', organisms.ImageText5050Group()),
        ('full_width_text', organisms.FullWidthText()),
        ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()),
        ('post_preview_snapshot', organisms.PostPreviewSnapshot()),
        ('well', organisms.Well()),
        ('table', organisms.Table()),
        ('contact', organisms.MainContactInfo()),
        ('formfield_with_button', molecules.FormFieldWithButton()),
        ('reg_comment', organisms.RegComment()),
    ],
                          blank=True)
    sidebar_breakout = StreamField([
        ('slug', blocks.CharBlock(icon='title')),
        ('heading', blocks.CharBlock(icon='title')),
        ('paragraph', blocks.RichTextBlock(icon='edit')),
        ('breakout_image',
         blocks.StructBlock([
             ('image', ImageChooserBlock()),
             ('is_round',
              blocks.BooleanBlock(required=False, default=True,
                                  label='Round?')),
             ('icon', blocks.CharBlock(help_text='Enter icon class name.')),
             ('heading',
              blocks.CharBlock(required=False, label='Introduction Heading')),
             ('body',
              blocks.TextBlock(required=False, label='Introduction Body')),
         ],
                            heading='Breakout Image',
                            icon='image')),
        ('related_posts', organisms.RelatedPosts()),
    ],
                                   blank=True)

    # General content tab
    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        StreamFieldPanel('content'),
    ]

    sidebar_panels = [
        StreamFieldPanel('sidebar_breakout'),
    ] + CFGOVPage.sidefoot_panels

    # Tab handler interface
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='General Content'),
        ObjectList(sidebar_panels, heading='Sidebar'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'sublanding-page/index.html'

    def get_browsefilterable_posts(self, request, limit):
        filter_pages = [
            p.specific
            for p in self.get_appropriate_descendants(request.site.hostname)
            if 'FilterablePage' in p.specific_class.__name__
            and 'archive' not in p.title.lower()
        ]
        filtered_controls = {}
        for page in filter_pages:
            id = str(util.get_form_id(page, request.GET))
            if id not in filtered_controls.keys():
                filtered_controls.update({id: []})
            form_class = page.get_form_class()
            posts = filterable_context.get_page_set(
                page, form_class(parent=page, hostname=request.site.hostname),
                request.site.hostname)
            if filtered_controls[id]:
                filtered_controls[id] += posts
            else:
                filtered_controls[id] = posts
        posts_tuple_list = [(id, post)
                            for id, posts in filtered_controls.iteritems()
                            for post in posts]
        posts = sorted(posts_tuple_list,
                       key=lambda p: p[1].date_published,
                       reverse=True)[:limit]
        return posts
Ejemplo n.º 24
0
class TestTabbedInterface(TestCase):
    def setUp(self):
        # a custom tabbed interface for EventPage
        self.EventPageTabbedInterface = TabbedInterface([
            ObjectList([
                FieldPanel('title', widget=forms.Textarea),
                FieldPanel('date_from'),
                FieldPanel('date_to'),
            ], heading='Event details', classname="shiny"),
            ObjectList([
                InlinePanel('speakers', label="Speakers"),
            ], heading='Speakers'),
        ]).bind_to_model(EventPage)

    def test_get_form_class(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        form = EventPageForm()

        # form must include the 'speakers' formset required by the speakers InlinePanel
        self.assertIn('speakers', form.formsets)

        # form must respect any overridden widgets
        self.assertEqual(type(form.fields['title'].widget), forms.Textarea)

    def test_render(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        event = EventPage(title='Abergavenny sheepdog trials')
        form = EventPageForm(instance=event)

        tabbed_interface = self.EventPageTabbedInterface(
            instance=event,
            form=form
        )

        result = tabbed_interface.render()

        # result should contain tab buttons
        self.assertIn('<a href="#event-details" class="active">Event details</a>', result)
        self.assertIn('<a href="#speakers" class="">Speakers</a>', result)

        # result should contain tab panels
        self.assertIn('<div class="tab-content">', result)
        self.assertIn('<section id="event-details" class="shiny active">', result)
        self.assertIn('<section id="speakers" class=" ">', result)

        # result should contain rendered content from descendants
        self.assertIn('Abergavenny sheepdog trials</textarea>', result)

        # this result should not include fields that are not covered by the panel definition
        self.assertNotIn('signup_link', result)

    def test_required_fields(self):
        # required_fields should report the set of form fields to be rendered recursively by children of TabbedInterface
        result = set(self.EventPageTabbedInterface.required_fields())
        self.assertEqual(result, set(['title', 'date_from', 'date_to']))

    def test_render_form_content(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        event = EventPage(title='Abergavenny sheepdog trials')
        form = EventPageForm(instance=event)

        tabbed_interface = self.EventPageTabbedInterface(
            instance=event,
            form=form
        )

        result = tabbed_interface.render_form_content()
        # rendered output should contain field content as above
        self.assertIn('Abergavenny sheepdog trials</textarea>', result)
        # rendered output should NOT include fields that are in the model but not represented
        # in the panel definition
        self.assertNotIn('signup_link', result)
Ejemplo n.º 25
0
class SeriesPage(ThemeablePage, FeatureStyleFields, Promotable, ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    subtitle = RichTextField(blank=True, default="")
    short_description = RichTextField(blank=True, default="")
    body = article_fields.BodyField(blank=True, default="")

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

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

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

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

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

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

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

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

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

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

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

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

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

                if current_total >= number:
                    return articles

        return articles

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

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

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

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
    ])
Ejemplo n.º 26
0
class TopicListPage(RoutablePageMixin, ThemeablePage):
    articles_per_page = models.IntegerField(default=20)

    @property
    def topics(self):
        popular_topics = Topic.objects.annotate(
            num_articles=Count('article_links') + Count('articles') + Count('series')).order_by("-num_articles")[:25]
        return sorted(popular_topics, key=lambda x: x.name)

    @route(r'^$', name="topic_list")
    def topics_list(self, request):
        context = {
            "self": self,
        }
        return render(request, "articles/topic_list_page.html", context)

    @route(r'^([\w-]+)/$', name="topic")
    def topic_view(self, request, topic_slug):
        topic = get_object_or_404(Topic, slug=topic_slug)

        articles = topic.item_list

        paginator = Paginator(articles, self.articles_per_page)
        page = request.GET.get('page')

        try:
            articles = paginator.page(page)
        except PageNotAnInteger:
            articles = paginator.page(1)
        except EmptyPage:
            articles = paginator.page(paginator.num_pages)

        context = {
            "self": self,
            "topic": topic,
            "articles": articles,
        }
        return render(request, "articles/topic_page.html", context)

    def get_cached_paths(self):
        yield '/'

        for topic in Topic.objects.all():
            articles = ArticlePage.objects.live().filter(
                models.Q(primary_topic=topic) | models.Q(topic_links__topic=topic)
            ).order_by('-first_published_at').distinct()
            paginator = Paginator(articles, self.articles_per_page)

            topic_url = '/{}/'.format(topic.slug)
            yield topic_url

            for page_number in range(2, paginator.num_pages + 1):
                yield topic_url + '?page=' + str(page_number)

    content_panels = Page.content_panels + [
        FieldPanel('articles_per_page'),
    ]

    style_panels = ThemeablePage.style_panels

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(Page.promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
    ])
Ejemplo n.º 27
0
class TourPage(Page):

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

    tour_image = models.ForeignKey('wagtailimages.Image',
                                   null=True,
                                   blank=True,
                                   on_delete=models.SET_NULL,
                                   related_name='+',
                                   help_text='Album cover image')

    tour_listing_introduction = models.TextField(
        "A listing introduction for the tour", blank=True, max_length=254)

    tour_description = RichTextField("A description for the tour")

    content_panels = Page.content_panels + [
        InlinePanel('tour_artist_relationship',
                    label="Arist(s)",
                    panels=None,
                    min_num=1),
        InlinePanel('tour_album_relationship',
                    label="Album(s)",
                    panels=None,
                    min_num=0),
        ImageChooserPanel('tour_image'),
        FieldPanel('tour_listing_introduction'),
        FieldPanel('tour_description'),
    ]

    tour_panels = [
        InlinePanel('tourdates',
                    label="Tour dates",
                    help_text="Enter your tour dates",
                    min_num=1),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading="Tour details",
                   classname="content"),
        ObjectList(tour_panels, heading="Tour dates"),
        ObjectList(Page.promote_panels, heading="Promote"),
        ObjectList(Page.settings_panels,
                   heading="Settings",
                   classname="settings"),
    ])

    # We iterate within the model over the artists, genres and subgenres
    # so they can be accessible to the template via a for loop
    def artists(self):
        artists = [n.artists for n in self.tour_artist_relationship.all()]
        return artists

    def albums(self):
        albums = [n.albums for n in self.tour_album_relationship.all()]

        return albums

    @property
    def album_image(self):
        # fail silently if there is no profile pic or the rendition file can't
        # be found. Note @richbrennan worked out how to do this...
        try:
            return self.image.get_rendition('fill-400x400').img_tag()
        except:
            return ''

    parent_page_types = [
        'tours.TourIndexPage'
        # app.model
    ]

    subpage_types = []
Ejemplo n.º 28
0
class RegulationPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage):
    """A routable page for serving an eregulations page by Section ID."""

    objects = PageManager()
    parent_page_types = ['regulations3k.RegulationLandingPage']
    subpage_types = []

    template = 'regulations3k/browse-regulation.html'

    header = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
    ],
                         blank=True)

    content = StreamField([], null=True)
    regulation = models.ForeignKey(Part,
                                   blank=True,
                                   null=True,
                                   on_delete=models.PROTECT,
                                   related_name='eregs3k_page')

    content_panels = CFGOVPage.content_panels + [
        StreamFieldPanel('header'),
        FieldPanel('regulation', Part),
    ]

    secondary_nav_exclude_sibling_pages = models.BooleanField(default=False)

    sidefoot_panels = CFGOVPage.sidefoot_panels + [
        FieldPanel('secondary_nav_exclude_sibling_pages'),
    ]

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

    @cached_property
    def section_query(self):
        """Query set for Sections in this regulation's effective version."""
        return Section.objects.filter(
            subpart__version=self.regulation.effective_version, )

    @cached_property
    def sections(self):
        return list(self.section_query.all())

    def get_context(self, request, *args, **kwargs):
        context = super(CFGOVPage, self).get_context(request, *args, **kwargs)
        context.update({
            'get_secondary_nav_items': get_reg_nav_items,
            'regulation': self.regulation,
            'section': None,
            'breadcrumb_items': self.get_breadcrumbs(request)
        })
        return context

    def get_breadcrumbs(self, request, section=None):
        landing_page = self.get_parent()
        crumbs = [{
            'href': landing_page.url,
            'title': landing_page.title,
        }]

        if section is not None:
            crumbs = crumbs + [
                {
                    'href': self.url,
                    'title': str(section.subpart.version.part),
                },
                {
                    'title': section.subpart.title,
                },
            ]

        return crumbs

    @route(r'^(?P<section_label>[0-9A-Za-z-]+)/$', name="section")
    def section_page(self, request, section_label):
        section = self.section_query.get(label=section_label)
        current_index = self.sections.index(section)
        context = self.get_context(request)

        content = regdown(section.contents,
                          url_resolver=get_url_resolver(self),
                          contents_resolver=get_contents_resolver(self),
                          render_block_reference=partial(
                              self.render_interp, context))

        context.update({
            'version':
            self.regulation.effective_version,
            'content':
            content,
            'get_secondary_nav_items':
            get_reg_nav_items,
            'next_section':
            get_next_section(self.sections, current_index),
            'previous_section':
            get_previous_section(self.sections, current_index),
            'section':
            section,
            'breadcrumb_items':
            self.get_breadcrumbs(request, section),
        })

        return TemplateResponse(request, self.template, context)

    def render_interp(self, context, raw_contents, **kwargs):
        template = get_template('regulations3k/inline_interps.html')

        # Extract the title from the raw regdown
        section_title_match = re.search(r'#+\s?(?P<section_title>.*)\s',
                                        raw_contents)
        if section_title_match is not None:
            context.update({'section_title': section_title_match.group(1)})
            span = section_title_match.span()
            raw_contents = raw_contents[:span[0]] + raw_contents[span[1]:]

        context.update({'contents': regdown(raw_contents)})
        context.update(kwargs)

        return template.render(context)
Ejemplo n.º 29
0
class RegulationsSearchPage(RoutablePageMixin, CFGOVPage):
    """A page for the custom search interface for regulations."""

    objects = PageManager()

    parent_page_types = ['regulations3k.RegulationLandingPage']
    subpage_types = []
    results = {}
    content_panels = CFGOVPage.content_panels
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    def get_template(self, request):
        return 'regulations3k/search-regulations.html'

    @route(r'^results/')
    def regulation_results_page(self, request):
        all_regs = Part.objects.order_by('part_number')
        regs = []
        sqs = SearchQuerySet()
        if 'regs' in request.GET:
            regs = request.GET.getlist('regs')
        if len(regs) == 1:
            sqs = sqs.filter(part=regs[0])
        elif regs:
            sqs = sqs.filter(part__in=regs)
        search_query = request.GET.get('q', '')  # haystack cleans this string
        if search_query:
            query_sqs = sqs.filter(content=search_query).models(Section)
        else:
            query_sqs = sqs.models(Section)
        payload = {
            'search_query':
            search_query,
            'results': [],
            'total_results':
            query_sqs.count(),
            'regs':
            regs,
            'all_regs': [{
                'name':
                "Regulation {}".format(reg.letter_code),
                'id':
                reg.part_number,
                'num_results':
                query_sqs.filter(part=reg.part_number).count(),
                'selected':
                reg.part_number in regs
            } for reg in all_regs]
        }
        for hit in query_sqs:
            label_bits = hit.object.label.partition('-')
            _part, _section = label_bits[0], label_bits[2]
            letter_code = LETTER_CODES.get(_part)
            snippet = Truncator(hit.text).words(40, truncate=' ...')
            snippet = snippet.replace('*', '').replace('#', '')
            hit_payload = {
                'id': hit.object.pk,
                'reg': 'Regulation {}'.format(letter_code),
                'label': hit.title,
                'snippet': snippet,
                'url': "/regulations/{}/{}/".format(_part, _section),
            }
            payload['results'].append(hit_payload)
        self.results = payload

        # if we want to paginate results:

        # def get_context(self, request, **kwargs):
        #     context = super(RegulationsSearchPage, self).get_context(
        #         request, **kwargs)
        #     context.update(**kwargs)
        #     paginator = Paginator(payload['results'], 25)
        #     page_number = validate_page_number(request, paginator)
        #     paginated_page = paginator.page(page_number)
        #     context['current_page'] = page_number
        #     context['paginator'] = paginator
        #     context['results'] = paginated_page
        #     return context

        context = self.get_context(request)
        return TemplateResponse(request, self.get_template(request), context)
Ejemplo n.º 30
0
class ActivityPage(CFGOVPage):
    """
    A model for the Activity Detail page.
    """
    # Allow Activity pages to exist under the ActivityIndexPage or the Trash
    parent_page_types = [ActivityIndexPage, HomePage]
    subpage_types = []
    objects = CFGOVPageManager()

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

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

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

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

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

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

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

    class Meta:
        verbose_name = "TDP Activity page"
Ejemplo n.º 31
0
class JobListingPage(CFGOVPage):
    description = RichTextField('Summary')
    open_date = models.DateField('Open date')
    close_date = models.DateField('Close date')
    salary_min = models.DecimalField('Minimum salary',
                                     max_digits=11,
                                     decimal_places=2)
    salary_max = models.DecimalField('Maximum salary',
                                     max_digits=11,
                                     decimal_places=2)
    division = models.ForeignKey(JobCategory,
                                 on_delete=models.PROTECT,
                                 null=True)
    job_length = models.ForeignKey(JobLength,
                                   on_delete=models.PROTECT,
                                   null=True,
                                   verbose_name="Position length",
                                   blank=True)
    service_type = models.ForeignKey(ServiceType,
                                     on_delete=models.PROTECT,
                                     null=True,
                                     blank=True)
    location = models.ForeignKey(JobLocation,
                                 related_name='job_listings',
                                 on_delete=models.PROTECT)
    allow_remote = models.BooleanField(
        default=False,
        help_text='Adds remote option to jobs with office locations.',
        verbose_name="Location can also be remote")
    responsibilities = RichTextField('Responsibilities', null=True, blank=True)
    travel_required = models.BooleanField(
        blank=False,
        default=False,
        help_text=('Optional: Check to add a "Travel required" section to the '
                   'job description. Section content defaults to "Yes".'))
    travel_details = RichTextField(
        null=True,
        blank=True,
        help_text='Optional: Add content for "Travel required" section.')
    additional_section_title = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        help_text='Optional: Add title for an additional section '
        'that will display at end of job description.')
    additional_section_content = RichTextField(
        null=True,
        blank=True,
        help_text='Optional: Add content for an additional section '
        'that will display at end of job description.')
    content_panels = CFGOVPage.content_panels + [
        MultiFieldPanel([
            FieldPanel('division', classname='full'),
            InlinePanel('grades', label='Grades'),
            FieldRowPanel([
                FieldPanel('open_date', classname='col6'),
                FieldPanel('close_date', classname='col6'),
            ]),
            FieldRowPanel([
                FieldPanel('salary_min', classname='col6'),
                FieldPanel('salary_max', classname='col6'),
            ]),
            FieldRowPanel([
                FieldPanel('service_type', classname='col6'),
                FieldPanel('job_length', classname='col6'),
            ]),
        ],
                        heading='Details'),
        MultiFieldPanel([
            FieldPanel('location', classname='full'),
            FieldPanel('allow_remote', classname='full'),
        ],
                        heading='Location'),
        MultiFieldPanel([
            FieldPanel('description', classname='full'),
            FieldPanel('responsibilities', classname='full'),
            FieldPanel('travel_required', classname='full'),
            FieldPanel('travel_details', classname='full'),
            FieldPanel('additional_section_title', classname='full'),
            FieldPanel('additional_section_content', classname='full'),
        ],
                        heading='Description'),
        InlinePanel('usajobs_application_links',
                    label='USAJobs application links'),
        InlinePanel('email_application_links',
                    label='Email application links'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(CFGOVPage.settings_panels, heading='Configuration'),
    ])

    template = 'job-description-page/index.html'

    objects = PageManager()

    def get_context(self, request, *args, **kwargs):
        context = super(JobListingPage, self).get_context(request)
        try:
            context['about_us'] = ReusableText.objects.get(
                title='About us (For consumers)')
        except Exception:
            pass
        if hasattr(self.location, 'region'):
            context['states'] = [
                state.abbreviation
                for state in self.location.region.states.all()
            ]
            context['location_type'] = 'region'
        else:
            context['states'] = []
            context['location_type'] = 'office'
        context['cities'] = self.location.cities.all()
        return context

    @property
    def page_js(self):
        return super(JobListingPage, self).page_js + ['read-more.js']

    @property
    def ordered_grades(self):
        """Return a list of job grades in numerical order.
        Non-numeric grades are sorted alphabetically after numeric grades.
        """
        grades = set(g.grade.grade for g in self.grades.all())
        return sorted(grades, key=lambda g: '{0:0>8}'.format(g))
Ejemplo n.º 32
0
class TestTabbedInterface(TestCase):
    def setUp(self):
        # a custom tabbed interface for EventPage
        self.EventPageTabbedInterface = TabbedInterface([
            ObjectList([
                FieldPanel('title', widget=forms.Textarea),
                FieldPanel('date_from'),
                FieldPanel('date_to'),
            ],
                       heading='Event details',
                       classname="shiny"),
            ObjectList([
                InlinePanel('speakers', label="Speakers"),
            ],
                       heading='Speakers'),
        ]).bind_to_model(EventPage)

    def test_get_form_class(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        form = EventPageForm()

        # form must include the 'speakers' formset required by the speakers InlinePanel
        self.assertIn('speakers', form.formsets)

        # form must respect any overridden widgets
        self.assertEqual(type(form.fields['title'].widget), forms.Textarea)

    def test_render(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        event = EventPage(title='Abergavenny sheepdog trials')
        form = EventPageForm(instance=event)

        tabbed_interface = self.EventPageTabbedInterface(instance=event,
                                                         form=form)

        result = tabbed_interface.render()

        # result should contain tab buttons
        self.assertIn(
            '<a href="#tab-event-details" class="active">Event details</a>',
            result)
        self.assertIn('<a href="#tab-speakers" class="">Speakers</a>', result)

        # result should contain tab panels
        self.assertIn('<div class="tab-content">', result)
        self.assertIn('<section id="tab-event-details" class="shiny active">',
                      result)
        self.assertIn('<section id="tab-speakers" class=" ">', result)

        # result should contain rendered content from descendants
        self.assertIn('Abergavenny sheepdog trials</textarea>', result)

        # this result should not include fields that are not covered by the panel definition
        self.assertNotIn('signup_link', result)

    def test_required_fields(self):
        # required_fields should report the set of form fields to be rendered recursively by children of TabbedInterface
        result = set(self.EventPageTabbedInterface.required_fields())
        self.assertEqual(result, set(['title', 'date_from', 'date_to']))

    def test_render_form_content(self):
        EventPageForm = self.EventPageTabbedInterface.get_form_class(EventPage)
        event = EventPage(title='Abergavenny sheepdog trials')
        form = EventPageForm(instance=event)

        tabbed_interface = self.EventPageTabbedInterface(instance=event,
                                                         form=form)

        result = tabbed_interface.render_form_content()
        # rendered output should contain field content as above
        self.assertIn('Abergavenny sheepdog trials</textarea>', result)
        # rendered output should NOT include fields that are in the model but not represented
        # in the panel definition
        self.assertNotIn('signup_link', result)
Ejemplo n.º 33
0
class ArticlePage(ThemeablePage, FeatureStyleFields, Promotable, ShareLinksMixin, PageLayoutOptions, VideoDocumentMixin):
    excerpt = RichTextField(blank=True, default="")
    body = article_fields.BodyField()
    chapters = article_fields.ChapterField(blank=True, null=True)
    table_of_contents_heading = models.TextField(blank=True, default="Table of Contents")
    citations_heading = models.TextField(blank=True, default="Works Cited")
    endnotes_heading = models.TextField(blank=True, default="End Notes")
    endnote_identifier_style = models.CharField(
        max_length=20,
        default="roman-lower",
        choices=(
            ('roman-lower', 'Roman Numerals - Lowercase'),
            ('roman-upper', 'Roman Numerals - Uppercase'),
            ('numbers', 'Numbers')
        )
    )

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

    include_author_block = models.BooleanField(default=True)

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

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

    _response_to = False

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

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

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

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

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

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

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

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

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

        return self._response_to

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

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

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

        current_total = len(article_list)

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

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

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

        return article_list

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

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

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

    style_panels = ThemeablePage.style_panels + [

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

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(advanced_content_panels, heading='Advanced Content'),
        ObjectList(style_panels, heading='Page Style Options'),
        ObjectList(promote_panels, heading='Promote'),
        ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
    ])
Ejemplo n.º 34
-1
    def get_edit_handler(self):
        """
        Get the EditHandler to use in the Wagtail admin when editing this page type.
        """
        if hasattr(self, 'edit_handler'):
            return self.edit_handler.bind_to_model(self)

        # construct a TabbedInterface made up of content_panels, promote_panels
        # and settings_panels, skipping any which are empty
        tabs = []

        for tab in self.tabs:
            title, content, *args = tab
            kwargs = args[0] if args else {}
            tabs.append(ObjectList(content, heading=title, **kwargs))

        model = self._meta.model
        EditHandler = TabbedInterface(tabs,
                                      base_form_class=model.base_form_class)
        return EditHandler.bind_to_model(model)