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

        self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)
Example #2
0
class TestFieldPanel(TestCase):
    class FakeClass(object):
        required = False
        widget = 'fake widget'

    class FakeField(object):
        label = 'label'
        help_text = 'help text'
        errors = ['errors']
        id_for_label = 'id for label'

    class FakeForm(dict):
        def __init__(self, *args, **kwargs):
            self.fields = self.fields_iterator()

        def fields_iterator(self):
            for i in self:
                yield i

    def setUp(self):
        fake_field = self.FakeField()
        fake_field.field = self.FakeClass()
        self.field_panel = FieldPanel('barbecue', 'snowman')(
            instance=True,
            form={'barbecue': fake_field})

    def test_render_as_object(self):
        result = self.field_panel.render_as_object()
        self.assertIn('<legend>label</legend>',
                      result)
        self.assertIn('<p class="error-message">',
                      result)

    def test_render_js_unknown_widget(self):
        field = self.FakeField()
        bound_field = self.FakeField()
        widget = self.FakeField()
        field.widget = widget
        bound_field.field = field
        self.field_panel.bound_field = bound_field
        result = self.field_panel.render_js()
        self.assertEqual(result,
                         '')

    def test_render_as_field(self):
        field = self.FakeField()
        bound_field = self.FakeField()
        bound_field.field = field
        self.field_panel.bound_field = bound_field
        result = self.field_panel.render_as_field()
        self.assertIn('<p class="help">help text</p>',
                      result)
        self.assertIn('<span>errors</span>',
                      result)

    def test_rendered_fields(self):
        result = self.field_panel.rendered_fields()
        self.assertEqual(result, ['barbecue'])

    def test_field_type(self):
        fake_object = self.FakeClass()
        another_fake_object = self.FakeClass()
        fake_object.field = another_fake_object
        self.field_panel.bound_field = fake_object
        self.assertEqual(self.field_panel.field_type(), 'fake_class')

    def test_widget_overrides(self):
        result = FieldPanel('barbecue', 'snowman').widget_overrides()
        self.assertEqual(result, {})

    def test_required_formsets(self):
        result = FieldPanel('barbecue', 'snowman').required_formsets()
        self.assertEqual(result, [])

    def test_get_form_class(self):
        result = FieldPanel('barbecue', 'snowman').get_form_class(Page)
        self.assertTrue(issubclass(result, WagtailAdminModelForm))

    def test_render_js(self):
        result = self.field_panel.render_js()
        self.assertEqual(result, "")

    def test_render_missing_fields(self):
        fake_form = self.FakeForm()
        fake_form["foo"] = "bar"
        self.field_panel.form = fake_form
        self.assertEqual(self.field_panel.render_missing_fields(), "bar")

    def test_render_form_content(self):
        fake_form = self.FakeForm()
        fake_form["foo"] = "bar"
        self.field_panel.form = fake_form
        self.assertIn("bar", self.field_panel.render_form_content())
Example #3
0
class AnswerPage(CFGOVPage):
    """
    Page type for Ask CFPB answers.
    """
    from ask_cfpb.models import Answer
    last_edited = models.DateField(
        blank=True,
        null=True,
        help_text="Change the date to today if you make a significant change.")
    question = models.TextField(blank=True)
    statement = models.TextField(
        blank=True,
        help_text=(
            "(Optional) Use this field to rephrase the question title as "
            "a statement. Use only if this answer has been chosen to appear "
            "on a money topic portal (e.g. /consumer-tools/debt-collection)."))
    answer = RichTextField(blank=True)
    snippet = RichTextField(blank=True, help_text='Optional answer intro')
    search_tags = models.CharField(
        max_length=1000,
        blank=True,
        help_text="PLEASE DON'T USE. THIS FIELD IS NOT YET ACTIVATED.")
    answer_base = models.ForeignKey(
        Answer,
        blank=True,
        null=True,
        related_name='answer_pages',
        on_delete=models.SET_NULL)
    redirect_to = models.ForeignKey(
        Answer,
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
        related_name='redirected_pages',
        help_text="Choose another Answer to redirect this page to")

    content = StreamField([
        ('feedback', v1_blocks.Feedback()),
    ], blank=True)

    content_panels = CFGOVPage.content_panels + [
        MultiFieldPanel([
            FieldRowPanel([FieldPanel('last_edited')])
        ],
            heading="Visible time stamp"),
        FieldPanel('question'),
        FieldPanel('statement'),
        FieldPanel('snippet'),
        FieldPanel('answer'),
        FieldPanel('search_tags'),
        FieldPanel('redirect_to'),
    ]

    sidebar = StreamField([
        ('call_to_action', molecules.CallToAction()),
        ('related_links', molecules.RelatedLinks()),
        ('related_metadata', molecules.RelatedMetadata()),
        ('email_signup', organisms.EmailSignUp()),
        ('sidebar_contact', organisms.SidebarContactInfo()),
        ('rss_feed', molecules.RSSFeed()),
        ('social_media', molecules.SocialMedia()),
        ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)),
    ], blank=True)

    sidebar_panels = [StreamFieldPanel('sidebar'), ]

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

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

    objects = CFGOVPageManager()

    def get_context(self, request, *args, **kwargs):
        context = super(AnswerPage, self).get_context(request)
        context['answer_id'] = self.answer_base.id
        context['related_questions'] = self.answer_base.related_questions.all()
        context['description'] = self.snippet if self.snippet \
            else Truncator(self.answer).words(40, truncate=' ...')
        context['audiences'] = [
            {'text': audience.name,
             'url': '/ask-cfpb/audience-{}'.format(
                    slugify(audience.name))}
            for audience in self.answer_base.audiences.all()]
        if self.language == 'es':
            tag_dict = self.Answer.valid_tags(language='es')
            context['tags_es'] = [tag for tag in self.answer_base.clean_tags_es
                                  if tag in tag_dict['valid_tags']]
            context['tweet_text'] = Truncator(self.question).chars(
                100, truncate=' ...')
            context['disclaimer'] = get_reusable_text_snippet(
                SPANISH_DISCLAIMER_SNIPPET_TITLE)
            context['category'] = self.answer_base.category.first()
        elif self.language == 'en':
            # we're not using tags on English pages yet, so cut the overhead
            # tag_dict = self.Answer.valid_tags()
            # context['tags'] = [tag for tag in self.answer_base.clean_tags
            #                    if tag in tag_dict['valid_tags']]
            context['about_us'] = get_reusable_text_snippet(
                ABOUT_US_SNIPPET_TITLE)
            context['disclaimer'] = get_reusable_text_snippet(
                ENGLISH_DISCLAIMER_SNIPPET_TITLE)
            context['last_edited'] = (
                self.last_edited or self.answer_base.last_edited)
            # breadcrumbs and/or category should reflect
            # the referrer if it is a consumer tools portal or
            # ask category page
            context['category'], context['breadcrumb_items'] = \
                get_question_referrer_data(
                    request, self.answer_base.category.all())
            subcategories = []
            for subcat in self.answer_base.subcategory.all():
                if subcat.parent == context['category']:
                    subcategories.append(subcat)
                for related in subcat.related_subcategories.all():
                    if related.parent == context['category']:
                        subcategories.append(related)
            context['subcategories'] = set(subcategories)

        return context

    def get_template(self, request):
        printable = request.GET.get('print', False)
        if self.language == 'es':
            if printable == 'true':
                return 'ask-cfpb/answer-page-spanish-printable.html'

            return 'ask-cfpb/answer-page-spanish.html'

        return 'ask-cfpb/answer-page.html'

    def __str__(self):
        if self.answer_base:
            return '{}: {}'.format(self.answer_base.id, self.title)
        else:
            return self.title

    @property
    def status_string(self):
        if self.redirect_to:
            if not self.live:
                return _("redirected but not live")
            else:
                return _("redirected")
        else:
            return super(AnswerPage, self).status_string

    # Returns an image for the page's meta Open Graph tag
    @property
    def meta_image(self):
        if self.answer_base.social_sharing_image:
            return self.answer_base.social_sharing_image

        if not self.answer_base.category.exists():
            return None

        return self.answer_base.category.first().category_image

    # Overrides the default of page.id for comparing against split testing
    # clusters. See: core.feature_flags.in_split_testing_cluster
    @property
    def split_test_id(self):
        return self.answer_base.id
Example #4
0
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+')
    banner_image.help_text = "A big wide image (at least 1440x650px) to "\
                             "grab the viewer's attention"

    welcome = RichTextField(default='', blank=True)
    welcome.help_text = "A short introductory message"
    body = RichTextField(default='', blank=True)
    body.help_text = "An area of text for whatever you like"


HomePage.content_panels = [
    ImageChooserPanel('banner_image'),
    FieldPanel('welcome', classname="full"),
    InlinePanel(HomePage, 'highlights', label="Highlights"),
    FieldPanel('body', classname="full"),
]

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


class ContactPage(Page):
    class Meta:
        verbose_name = "ContactPage"
        description = "This is for our contact details."

    body = RichTextField(blank=True)
Example #5
0
class BlogPage(BlogRoutes, Page):
    description = models.CharField(
        verbose_name=_('Description'),
        max_length=255,
        blank=True,
        help_text=_("The blog description that will appear under the title."))
    header_image = models.ForeignKey(get_image_model_path(),
                                     verbose_name=_('Header image'),
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+')

    display_comments = models.BooleanField(default=False,
                                           verbose_name=_('Display comments'))
    display_categories = models.BooleanField(
        default=True, verbose_name=_('Display categories'))
    display_tags = models.BooleanField(default=True,
                                       verbose_name=_('Display tags'))
    display_popular_entries = models.BooleanField(
        default=True, verbose_name=_('Display popular entries'))
    display_last_entries = models.BooleanField(
        default=True, verbose_name=_('Display last entries'))
    display_archive = models.BooleanField(default=True,
                                          verbose_name=_('Display archive'))

    disqus_api_secret = models.TextField(blank=True)
    disqus_shortname = models.CharField(max_length=128, blank=True)

    num_entries_page = models.IntegerField(default=5,
                                           verbose_name=_('Entries per page'))
    num_last_entries = models.IntegerField(
        default=3, verbose_name=_('Last entries limit'))
    num_popular_entries = models.IntegerField(
        default=3, verbose_name=_('Popular entries limit'))
    num_tags_entry_header = models.IntegerField(
        default=5, verbose_name=_('Tags limit entry header'))

    extra = BlogManager()

    content_panels = Page.content_panels + [
        FieldPanel('description', classname="full"),
        ImageChooserPanel('header_image'),
    ]
    settings_panels = Page.settings_panels + [
        MultiFieldPanel([
            FieldPanel('display_categories'),
            FieldPanel('display_tags'),
            FieldPanel('display_popular_entries'),
            FieldPanel('display_last_entries'),
            FieldPanel('display_archive'),
        ],
                        heading=_("Widgets")),
        MultiFieldPanel([
            FieldPanel('num_entries_page'),
            FieldPanel('num_last_entries'),
            FieldPanel('num_popular_entries'),
            FieldPanel('num_tags_entry_header'),
        ],
                        heading=_("Parameters")),
        MultiFieldPanel([
            FieldPanel('display_comments'),
            FieldPanel('disqus_api_secret'),
            FieldPanel('disqus_shortname'),
        ],
                        heading=_("Comments")),
    ]
    subpage_types = ['puput.EntryPage']

    def get_entries(self):
        return EntryPage.objects.descendant_of(self).live().order_by(
            '-date').select_related('owner')

    def get_context(self, request, *args, **kwargs):
        context = super(BlogPage, self).get_context(request, *args, **kwargs)
        context['entries'] = self.entries
        context['blog_page'] = self
        context['search_type'] = getattr(self, 'search_type', "")
        context['search_term'] = getattr(self, 'search_term', "")
        return context

    @property
    def last_url_part(self):
        """
        Get the BlogPage url without the domain
        """
        return self.get_url_parts()[-1]

    class Meta:
        verbose_name = _('Blog')
Example #6
0
class MenuItem(models.Model):
    allow_subnav = False

    link_page = models.ForeignKey(
        'wagtailcore.Page',
        verbose_name=_('link to an internal page'),
        blank=True,
        null=True,
        on_delete=models.CASCADE,
    )
    link_url = models.CharField(
        verbose_name=_('link to a custom URL'),
        max_length=255,
        blank=True,
        null=True,
    )
    link_text = models.CharField(
        verbose_name=_('link text'),
        max_length=255,
        blank=True,
        help_text=_("Must be set if you wish to link to a custom URL."),
    )
    handle = models.CharField(
        verbose_name=_('handle'),
        max_length=100,
        blank=True,
        help_text=_(
            "Use this field to optionally specify an additional value for "
            "each menu item, which you can then reference in custom menu "
            "templates."))
    url_append = models.CharField(
        verbose_name=_("append to URL"),
        max_length=255,
        blank=True,
        help_text=_(
            "Use this to optionally append a #hash or querystring to the "
            "above page's URL."))

    objects = MenuItemManager()

    class Meta:
        abstract = True
        verbose_name = _("menu item")
        verbose_name_plural = _("menu items")

    @property
    def menu_text(self):
        return self.link_text or self.link_page.title

    def relative_url(self, site=None):
        if self.link_page:
            return self.link_page.relative_url(site) + self.url_append
        return self.link_url + self.url_append

    def clean(self, *args, **kwargs):
        super(MenuItem, self).clean(*args, **kwargs)

        if self.link_url and not self.link_text:
            raise ValidationError({
                'link_text': [
                    _("This must be set if you're linking to a custom URL."),
                ]
            })

        if not self.link_url and not self.link_page:
            raise ValidationError({
                'link_url': [
                    _("This must be set if you're not linking to a page."),
                ]
            })

        if self.link_url and self.link_page:
            raise ValidationError(
                _("You cannot link to both a page and URL. Please review your "
                  "link and clear any unwanted values."))

    def __str__(self):
        return self.menu_text

    panels = (
        PageChooserPanel('link_page'),
        FieldPanel('link_url'),
        FieldPanel('url_append'),
        FieldPanel('link_text'),
        FieldPanel('handle'),
        FieldPanel('allow_subnav'),
    )
Example #7
0
class PaginaDePictos(Page):
    cuaderno = ParentalManyToManyField(
        'Cuaderno',
        blank=True,
        help_text="Selecciona el cuaderno o cuadernos en que debe de aparecer")
    subtitulo = models.CharField(
        "Subtítulo de la página",
        max_length=254,
        blank=True,
        help_text="Texto que puede aparecer debajo del título de la página")
    tit1 = models.CharField("Título de la línea", max_length=254, blank=True)
    tit2 = models.CharField("Título de la línea", max_length=254, blank=True)
    tit3 = models.CharField("Título de la línea", max_length=254, blank=True)
    tit4 = models.CharField("Título de la línea", max_length=254, blank=True)
    observaciones = RichTextField(blank=True)
    generarpdf = models.BooleanField("Generar pdf", default=True)
    '''
    def serve(self, request):
        if "format" in request.GET:
            if request.GET['format'] == 'pdf':
                if self.generarpdf:
                    open('{}{}.pdf'.format(ruta_pdf, self.slug), 'wb').write(generar_pdf(self))
                    self.generarpdf = False
                    self.save()
                

                response = HttpResponse(content_type='application/pdf')
                response['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(self.slug)
                response.write(pdf)
                return response
            else:
                # Unrecognised format error
                message = 'No podemos exportar en este formato: ' + request.GET['format']
                return HttpResponse(message, content_type='text/plain')
        else:
            # Display event page as usual
            return super(PaginaDePictos, self).serve(request)
    '''

    search_fields = Page.search_fields + [  # Inherit search_fields from Page
        index.RelatedFields('linea1', [
            index.SearchField('tit1'),
            index.SearchField('imagen'),
            index.SearchField('texto')
        ])
    ]

    content_panels = Page.content_panels + [
        FieldPanel('generarpdf'),
        FieldRowPanel([
            FieldPanel('cuaderno',
                       widget=forms.CheckboxSelectMultiple,
                       classname="col5"),
            FieldPanel('subtitulo', classname="col7")
        ], ),
        MultiFieldPanel(
            [
                FieldPanel('tit1'),
                InlinePanel('linea1', label="Pictos"),
            ],
            heading="Primera fila de pictos",
            classname="collapsible collapsed",
        ),
        MultiFieldPanel(
            [
                FieldPanel('tit2'),
                InlinePanel('linea2', label="Pictos"),
            ],
            heading="Segunda fila de pictos",
            classname="collapsible collapsed",
        ),
        MultiFieldPanel(
            [
                FieldPanel('tit3'),
                InlinePanel('linea3', label="Pictos"),
            ],
            heading="Tercera fila de pictos",
            classname="collapsible collapsed",
        ),
        MultiFieldPanel(
            [
                FieldPanel('tit4'),
                InlinePanel('linea4', label="Pictos"),
            ],
            heading="Cuarta fila de pictos",
            classname="collapsible collapsed",
        ),
        FieldPanel('observaciones'),
    ]
Example #8
0
class GenreFeaturePageRelationship(Orderable, models.Model):
    page = ParentalKey('FeatureContentPage',
                       related_name='feature_page_genre_relationship')
    genre = models.ForeignKey('genre.GenreClass',
                              related_name='genre_feature_page_relationship')
    panels = [FieldPanel('genre')]
Example #9
0
class GenreClassAlbumRelationship(Orderable, models.Model):
    page = ParentalKey('Album', related_name='album_genre_relationship')
    genres = models.ForeignKey('genre.GenreClass',
                               related_name="genre_album_relationship")
    panels = [FieldPanel('genres')]
Example #10
0
    except EmptyPage:
        items = paginator.page(paginator.num_pages)
    except PageNotAnInteger:
        items = paginator.page(1)

    return items


class PersonIndexPage(Page, WithStreamField):
    subpage_types = [
        'Person',
    ]


PersonIndexPage.content_panels = [
    FieldPanel('title', classname='full title'),
    StreamFieldPanel('body'),
]

PersonIndexPage.promote_panels = Page.promote_panels


class Person(Page, WithStreamField):
    search_fields = Page.search_fields + []
    image = models.ForeignKey(
        'wagtailimages.Image',
        on_delete=models.PROTECT,
    )
    subpage_types = [
        'ImagePage',
    ]
Example #11
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)

    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()),
        ('contact', organisms.MainContactInfo()),
        ('sidebar_contact', organisms.SidebarContactInfo()),
        ('rss_feed', molecules.RSSFeed()),
    ], blank=True)

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

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

    # 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 generate_view_more_url(self, request):
        tags = []
        activity_log = CFGOVPage.objects.get(slug='activity-log').specific
        index = util.get_form_id(activity_log, request.GET)
        for tag in self.tags.names():
            tags.append('filter%s_topics=' % index + urllib.quote_plus(tag))
        tags = '&'.join(tags)
        return get_protected_url({'request': request}, activity_log) + '?' + tags

    def related_posts(self, block, hostname):
        from . import AbstractFilterPage
        related = {}
        queries = {}
        query = models.Q(('tags__name__in', self.tags.names()))
        search_types = [
            ('blog', 'posts', 'Blog', query),
            ('newsroom', 'newsroom', 'Newsroom', query),
            ('events', 'events', 'Events', query),
        ]
        for parent_slug, search_type, search_type_name, search_query in search_types:
            try:
                parent = Page.objects.get(slug=parent_slug)
                search_query &= Page.objects.child_of_q(parent)
                if 'specific_categories' in block.value:
                    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:
                        search_query &= Q(('categories__name__in', categories))
                if parent_slug == 'events':
                    try:
                        parent_slug = 'archive-past-events'
                        parent = Page.objects.get(slug=parent_slug)
                        q = (Page.objects.child_of_q(parent) & query)
                        if 'specific_categories' in block.value:
                            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:
                                q &= Q(('categories__name__in', categories))
                        search_query |= q
                    except Page.DoesNotExist:
                        print 'archive-past-events does not exist'
                queries[search_type_name] = search_query
            except Page.DoesNotExist:
                print parent_slug, 'does not exist'
        for parent_slug, search_type, search_type_name, search_query in search_types:
            if 'relate_%s' % search_type in block.value \
                    and block.value['relate_%s' % search_type]:
                related[search_type_name] = \
                    AbstractFilterPage.objects.live_shared(hostname).filter(
                        queries[search_type_name]).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, site):
        ancestors = self.get_ancestors()
        home_page_children = site.root_page.get_children()
        for i, ancestor in enumerate(ancestors):
            if ancestor in home_page_children:
                return ancestors[i+1:]
        return []

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

    def get_appropriate_siblings(self, hostname, inclusive=True):
        return CFGOVPage.objects.live_shared(hostname).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')

    @property
    def status_string(self):
        if not self.live:
            if self.expired:
                return _("expired")
            elif self.approved_schedule:
                return _("scheduled")
            elif self.shared:
                return _("shared")
            else:
                return _("draft")
        else:
            if self.has_unpublished_changes:
                return _("live + draft")
            else:
                return _("live")

    def sharable_pages(self):
        """
        Return a queryset of the pages that this user has permission to share.
        """
        # Deal with the trivial cases first...
        if not self.user.is_active:
            return Page.objects.none()
        if self.user.is_superuser:
            return Page.objects.all()

        sharable_pages = Page.objects.none()

        for perm in self.permissions.filter(permission_type='share'):
            # User has share permission on any subpage of perm.page
            # (including perm.page itself).
            sharable_pages |= Page.objects.descendant_of(perm.page,
                                                         inclusive=True)

        return sharable_pages

    def can_share_pages(self):
        """Return True if the user has permission to publish any pages"""
        return self.sharable_pages().exists()

    def route(self, request, path_components):
        if path_components:
            # Request is for a child of this page.
            child_slug = path_components[0]
            remaining_components = path_components[1:]

            try:
                subpage = self.get_children().get(slug=child_slug)
            except Page.DoesNotExist:
                raise Http404

            return subpage.specific.route(request, remaining_components)

        else:
            # Request is for this very page.
            # If we're on the production site, make sure the version of the page
            # displayed is the latest version that has `live` set to True for
            # the live site or `shared` set to True for the staging site.
            staging_hostname = os.environ.get('STAGING_HOSTNAME')
            revisions = self.revisions.all().order_by('-created_at')
            for revision in revisions:
                page_version = json.loads(revision.content_json)
                if request.site.hostname != staging_hostname:
                    if page_version['live']:
                        return RouteResult(revision.as_page_object())
                else:
                    if page_version['shared']:
                        return RouteResult(revision.as_page_object())
            raise Http404

    def permissions_for_user(self, user):
        """
        Return a CFGOVPagePermissionTester object defining what actions the
        user can perform on this page
        """
        user_perms = CFGOVUserPagePermissionsProxy(user)
        return user_perms.for_page(self)

    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 dictionary 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] = list(set(js_files))
        return js
Example #12
0
class BaseForm(ClusterableModel):
    """ A form base class, any form should inherit from this. """

    name = models.CharField(
        max_length=255
    )
    slug = models.SlugField(
        allow_unicode=True,
        max_length=255,
        unique=True,
        help_text=_('Used to identify the form in template tags')
    )
    content_type = models.ForeignKey(
        'contenttypes.ContentType',
        verbose_name=_('content type'),
        related_name='streamforms',
        on_delete=models.SET(get_default_form_content_type)
    )
    template_name = models.CharField(
        verbose_name='template',
        max_length=255,
        choices=get_setting('FORM_TEMPLATES')
    )
    submit_button_text = models.CharField(
        max_length=100,
        default='Submit'
    )
    store_submission = models.BooleanField(
        default=False
    )
    add_recaptcha = models.BooleanField(
        default=False,
        help_text=_('Add a reCapcha field to the form.')
    )
    success_message = models.CharField(
        blank=True,
        max_length=255,
        help_text=_('An optional success message to show when the form has been successfully submitted')
    )
    error_message = models.CharField(
        blank=True,
        max_length=255,
        help_text=_('An optional error message to show when the form has validation errors')
    )
    post_redirect_page = models.ForeignKey(
        'wagtailcore.Page',
        models.SET_NULL,
        null=True,
        blank=True,
        related_name='+',
        help_text=_('The page to redirect to after a successful submission')
    )

    settings_panels = [
        FieldPanel('name', classname='full'),
        FieldPanel('slug'),
        FieldPanel('template_name'),
        FieldPanel('submit_button_text'),
        MultiFieldPanel([
            FieldPanel('success_message'),
            FieldPanel('error_message'),
        ], 'Messages'),
        FieldPanel('store_submission'),
        PageChooserPanel('post_redirect_page')
    ]

    field_panels = [
        InlinePanel('form_fields', label='Fields'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(settings_panels, heading='General'),
        ObjectList(field_panels, heading='Fields'),
    ])

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.id:
            self.content_type = ContentType.objects.get_for_model(self)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ['name', ]
        verbose_name = _('form')

    def copy(self):
        """ Copy this form and its fields. """

        exclude_fields = ['id', 'slug']
        specific_self = self.specific
        specific_dict = {}

        for field in specific_self._meta.get_fields():

            # ignore explicitly excluded fields
            if field.name in exclude_fields:
                continue  # pragma: no cover

            # ignore reverse relations
            if field.auto_created:
                continue  # pragma: no cover

            # ignore m2m relations - they will be copied as child objects
            # if modelcluster supports them at all (as it does for tags)
            if field.many_to_many:
                continue  # pragma: no cover

            # ignore parent links (baseform_ptr)
            if isinstance(field, models.OneToOneField) and field.rel.parent_link:
                continue  # pragma: no cover

            specific_dict[field.name] = getattr(specific_self, field.name)

        # new instance from prepared dict values, in case the instance class implements multiple levels inheritance
        form_copy = self.specific_class(**specific_dict)

        # a dict that maps child objects to their new ids
        # used to remap child object ids in revisions
        child_object_id_map = defaultdict(dict)

        # create the slug - temp as will be changed from the copy form
        form_copy.slug = uuid.uuid4()

        form_copy.save()

        # copy child objects
        for child_relation in get_all_child_relations(specific_self):
            accessor_name = child_relation.get_accessor_name()
            parental_key_name = child_relation.field.attname
            child_objects = getattr(specific_self, accessor_name, None)

            if child_objects:
                for child_object in child_objects.all():
                    old_pk = child_object.pk
                    child_object.pk = None
                    setattr(child_object, parental_key_name, form_copy.id)
                    child_object.save()

                    # add mapping to new primary key (so we can apply this change to revisions)
                    child_object_id_map[accessor_name][old_pk] = child_object.pk

            else:  # we should never get here as there is always a FormField child class
                pass  # pragma: no cover

        return form_copy

    copy.alters_data = True

    def get_data_fields(self):
        """
        Returns a list of tuples with (field_name, field_label).
        """

        data_fields = [
            ('submit_time', _('Submission date')),
        ]
        data_fields += [
            (field.clean_name, field.label)
            for field in self.get_form_fields()
        ]

        return data_fields

    def get_form(self, *args, **kwargs):
        form_class = self.get_form_class()
        form_params = self.get_form_parameters()
        form_params.update(kwargs)

        return form_class(*args, **form_params)

    def get_form_class(self):
        fb = FormBuilder(self.get_form_fields(), add_recaptcha=self.add_recaptcha)
        return fb.get_form_class()

    def get_form_fields(self):
        """
        Form expects `form_fields` to be declared.
        If you want to change backwards relation name,
        you need to override this method.
        """

        return self.form_fields.all()

    def get_form_parameters(self):
        return {}

    def get_submission_class(self):
        """
        Returns submission class.

        You can override this method to provide custom submission class.
        Your class must be inherited from AbstractFormSubmission.
        """

        return FormSubmission

    def process_form_submission(self, form):
        """
        Accepts form instance with submitted data.
        Creates submission instance if self.store_submission = True.

        You can override this method if you want to have custom creation logic.
        For example, you want to additionally send an email.
        """

        if self.store_submission:
            return self.get_submission_class().objects.create(
                form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
                form=self
            )

    @cached_property
    def specific(self):
        """
        Return this form in its most specific subclassed form.
        """

        # the ContentType.objects manager keeps a cache, so this should potentially
        # avoid a database lookup over doing self.content_type. I think.
        content_type = ContentType.objects.get_for_id(self.content_type_id)
        model_class = content_type.model_class()
        if model_class is None:
            # Cannot locate a model class for this content type. This might happen
            # if the codebase and database are out of sync (e.g. the model exists
            # on a different git branch and we haven't rolled back migrations before
            # switching branches); if so, the best we can do is return the form
            # unchanged.
            return self  # pragma: no cover
        elif isinstance(self, model_class):
            # self is already the an instance of the most specific class
            return self
        else:
            return content_type.get_object_for_this_type(id=self.id)

    @cached_property
    def specific_class(self):
        """
        Return the class that this page would be if instantiated in its
        most specific form
        """

        content_type = ContentType.objects.get_for_id(self.content_type_id)
        return content_type.model_class()
Example #13
0
class AbstractEmailForm(BaseForm):
    """
    A form that sends and email.

    You can create custom form model based on this abstract model.
    For example, if you need a form that will send an email.
    """

    # do not add these fields to the email
    ignored_fields = ['recaptcha', 'form_id', 'form_reference']

    subject = models.CharField(
        max_length=255
    )
    from_address = models.EmailField()
    to_addresses = MultiEmailField(
        help_text=_("Add one email per line")
    )
    message = models.TextField()
    exclude_form_data = models.BooleanField(
        default=False,
        help_text=_("Exclude form submission data from email")
    )
    fail_silently = models.BooleanField(
        default=True
    )

    email_panels = [
        FieldPanel('subject', classname="full"),
        FieldPanel('from_address', classname="full"),
        FieldPanel('to_addresses', classname="full"),
        FieldPanel('message', classname="full"),
        FieldPanel('exclude_form_data'),
        FieldPanel('fail_silently'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(BaseForm.settings_panels, heading='General'),
        ObjectList(BaseForm.field_panels, heading='Fields'),
        ObjectList(email_panels, heading='Email Submission'),
    ])

    class Meta(BaseForm.Meta):
        abstract = True

    def process_form_submission(self, form):
        """ Process the form submission and send an email. """

        super().process_form_submission(form)
        self.send_form_mail(form)

    def send_form_mail(self, form):
        """ Send an email. """
        if self.exclude_form_data:
            from django.urls import reverse
            from django.contrib.sites.models import Site

            current_site = Site.objects.get_current().domain
            url = reverse('wagtailstreamforms:streamforms_submissions', args=[str(self.pk)])
            content = [self.message + '\n\nYour form has a new submission.\n', ]
            content.append('To view the submission, go to: ' + current_site + url)

        else:
            content = [self.message + '\n\nSubmission\n', ]
            for name, field in form.fields.items():
                data = form.cleaned_data.get(name)

                if name in self.ignored_fields or not data:
                    continue  # pragma: no cover

                label = field.label or name
                content.append(label + ': ' + six.text_type(data))

        send_mail(
            self.subject,
            '\n'.join(content),
            self.from_address,
            self.to_addresses,
            self.fail_silently
        )
Example #14
0
        else:
            return content_type.get_object_for_this_type(id=self.id)

    @cached_property
    def specific_class(self):
        """
        Return the class that this page would be if instantiated in its
        most specific form
        """

        content_type = ContentType.objects.get_for_id(self.content_type_id)
        return content_type.model_class()


if recaptcha_enabled():  # pragma: no cover
    BaseForm.field_panels.insert(0, FieldPanel('add_recaptcha'))


class BasicForm(BaseForm):
    """ A basic form. """


class AbstractEmailForm(BaseForm):
    """
    A form that sends and email.

    You can create custom form model based on this abstract model.
    For example, if you need a form that will send an email.
    """

    # do not add these fields to the email
Example #15
0
class FormPage(AbstractEmailForm):
    form_builder = HeadingFormBuilder
    landing_page_template = 'pages/form_page_confirmation.html'
    subpage_types = []
    timeline_snippet = models.ForeignKey(TimelineSnippet,
                                         null=True,
                                         blank=True,
                                         on_delete=models.SET_NULL,
                                         related_name='+')
    intro = RichTextField(null=True, blank=True)
    content = StreamField(BASE_BLOCKS + COLUMNS_BLOCKS, null=True, blank=True)
    confirmation_text = RichTextField(
        default=
        'The form was submitted successfully. We will get back to you soon.')
    send_confirmation_email = models.BooleanField(
        default=False, verbose_name='Send confirmation email')
    confirmation_email_subject = models.CharField(
        max_length=500,
        verbose_name='Confirmation email subject',
        null=True,
        blank=True)
    confirmation_email_text = models.TextField(
        default=
        'The form was submitted successfully. We will get back to you soon.',
        null=True,
        blank=True)

    form_title = models.CharField(max_length=500,
                                  verbose_name='Form title',
                                  null=True,
                                  blank=True)
    button_name = models.CharField(max_length=500,
                                   verbose_name='Button name',
                                   default='Submit')

    content_panels = AbstractEmailForm.content_panels + [
        SnippetChooserPanel('timeline_snippet'),
        FieldPanel('intro'),
        StreamFieldPanel('content'),
        MultiFieldPanel([
            FieldPanel('form_title'),
            InlinePanel('form_fields', label="Form fields"),
            FieldPanel('button_name'),
            FieldPanel('confirmation_text', classname="full"),
            FieldPanel('send_confirmation_email'),
            FieldPanel('confirmation_email_subject'),
            FieldPanel('confirmation_email_text', classname="full"),
            MultiFieldPanel([
                FieldPanel('to_address', classname="full"),
                FieldPanel('from_address', classname="full"),
                FieldPanel('subject', classname="full"),
            ], "Email"),
        ], "Form Builder"),
    ]

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

    def get_data_fields(self):
        data_fields = [
            ('identifier', 'Identifier'),
        ]
        data_fields += super(FormPage, self).get_data_fields()
        return data_fields

    def get_submission_class(self):
        return CustomFormSubmission

    def serve(self, request, *args, **kwargs):
        context = self.get_context(request)
        if request.method == 'POST':
            form = self.get_form(request.POST, page=self, user=request.user)

            if form.is_valid():
                self.process_form_submission(form)

                # render the landing_page
                # TODO: It is much better to redirect to it
                return render(request, self.get_landing_page_template(request),
                              self.get_context(request))
            else:
                context[
                    'form_error'] = 'One of the fields below has not been filled out correctly. Please <strong>correct</strong> and <strong>resubmit</strong>.'
        else:
            form = self.get_form(page=self, user=request.user)

        context['form'] = form
        return render(request, self.get_template(request), context)

    def process_form_submission(self, form):
        # add a incremental identifier to every form submission
        next_identifier = CustomFormSubmission.objects.filter(
            page=self).order_by('-identifier').first()
        if next_identifier:
            next_identifier = next_identifier.identifier + 1
        else:
            next_identifier = 1
        self.get_submission_class().objects.create(form_data=json.dumps(
            form.cleaned_data, cls=DjangoJSONEncoder),
                                                   page=self,
                                                   identifier=next_identifier)
        if self.to_address:
            self.send_mail(form)
        if self.send_confirmation_email:
            # quick hack sending a confirmation email to the user
            confirmation_email_address = None
            # check for confirmation email address and filter headings
            filtered_fields = []
            for field in form:
                if isinstance(field.field.widget, EmailInput):
                    confirmation_email_address = field.value()
                elif isinstance(field.field.widget, HeadingWidget):
                    continue
                filtered_fields.append(field)
            if confirmation_email_address:
                extra_content = [
                    '',
                ]
                for field in filtered_fields:
                    value = field.value()
                    if isinstance(value, list):
                        value = ', '.join(value)
                    extra_content.append('{}: {}'.format(field.label, value))
                extra_content = '\n'.join(extra_content)
                send_mail(
                    self.confirmation_email_subject,
                    self.confirmation_email_text + extra_content,
                    [
                        confirmation_email_address,
                    ],
                    self.from_address,
                )
Example #16
0
class Album(ClusterableModel):

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

    title = models.CharField("The album's name", blank=True, max_length=254)

    slug = models.SlugField(
        allow_unicode=True,
        max_length=255,
        help_text=
        "The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
    )

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

    release_date = models.DateField()

    song_details = StreamField(SongStreamBlock(),
                               verbose_name="Songs",
                               blank=True)

    @property
    def url(self):
        return '/albums/' + self.slug

    panels = [
        FieldPanel('title'),
        # FieldPanel('slug'), # Removed from admin view @TODO remove from DB
        InlinePanel('album_artist_relationship',
                    label="Arist(s)",
                    panels=None,
                    min_num=1),
        ImageChooserPanel('image'),
        FieldPanel('release_date'),
        MultiFieldPanel([
            InlinePanel('album_genre_relationship',
                        label="Genre",
                        panels=None,
                        min_num=1,
                        max_num=1)
        ],
                        heading="Genres",
                        classname="collapsible"),
        StreamFieldPanel('song_details'),
    ]

    # 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.artist_name for n in self.album_artist_relationship.all()]
        return artists

    def genres(self):
        genres = [n.genres for n in self.album_genre_relationship.all()]
        return genres

    def subgenres(self):
        subgenres = [
            n.subgenre for n in self.album_subgenre_relationship.all()
        ]
        return subgenres

    def artist_name(self):
        artists = [
            n.artist_name.title for n in self.album_artist_relationship.all()
        ]

        return ", ".join(artists)
        # We return the list as a string by joining all the list items. We do this
        # to avoid returning something like "['Fugazi']" when we want to return
        # "Fugazi"

    def __str__(self):
        string = ("Album: " + self.title + " by " + self.artist_name())
        return string
        # This will return something like Album: 13 Songs by Fugazi
        # Need to get the result of the function call using artist_name(). Rather
        # than just artist_name. c/f http://stackoverflow.com/questions/31937532/python-django-query-error-cant-convert-method-object-to-str-implicitly

    @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 ''

    artist_name.admin_order_field = 'album_artist_relationship__artist_name'

    # artist_name references the string created from the artist_name list whilst
    # the reverse lookup to __artist_name is the related name ForeignKey

    def genre(obj):
        genre = ','.join(
            [str(n.genres) for n in obj.album_genre_relationship.all()])
        return genre
        # Again note we call `genres` because it's what we called the fk

        genre.admin_order_field = 'album_genre_relationship__genre'

    class Meta:
        ordering = ['title']
        verbose_name = "Album"
        verbose_name_plural = "Albums"
Example #17
0
class HomePageRelatedLink(Orderable, RelatedLink):
    page = ParentalKey('demo.HomePage', related_name='related_links')


class HomePage(Page):
    body = StreamField(DemoStreamBlock())
    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    class Meta:
        verbose_name = "Homepage"


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

HomePage.promote_panels = Page.promote_panels

# Standard index page


class StandardIndexPageRelatedLink(Orderable, RelatedLink):
    page = ParentalKey('demo.StandardIndexPage', related_name='related_links')


class StandardIndexPage(Page):
Example #18
0
class TestFieldPanel(TestCase):
    def setUp(self):
        self.EventPageForm = get_form_for_model(
            EventPage, form_class=WagtailAdminPageForm, formsets=[])
        self.event = EventPage(title='Abergavenny sheepdog trials',
                               date_from=date(2014, 7, 20),
                               date_to=date(2014, 7, 21))

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

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

        form.is_valid()

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

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

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

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

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

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

        form.is_valid()

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

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

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

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

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

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

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

        form.is_valid()

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

        self.assertIn('<p class="error-message">', result)
        self.assertIn('<span>Enter a valid date.</span>', result)
Example #19
0
class FeatureContentPage(Page):
    """
    This is a feature content page for all of your interviews, news etc.
    """

    # TODO This almost entirely duplicates StandardPage class. They should be
    # referencing something to reduce the duplication

    search_fields = Page.search_fields + [
        # Defining what fields the search catches
        index.SearchField('introduction'),
        index.SearchField('body'),
    ]

    date = models.DateField("Post date", help_text='blah')

    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Image to be used where this feature content is listed')

    # Page models mostly use generic Django fields. Here we're using a choice
    # field for how the images should be styled.
    # https://docs.djangoproject.com/en/1.10/ref/models/fields/#choices
    # You can reference core/blocks.py for how you can reference choices within
    # StreamField

    image_choices_list = (
        ('fit', 'Contained width'),
        ('expand', 'Expanded width'),
        ('full', 'Full width'),
        ('hide', 'Hidden (e.g. only display on listings)'),
    )

    image_choices = models.CharField(
        max_length=255,
        choices=image_choices_list,
        help_text='How you want the image to be displayed on the feature page')

    # Note below that standard blocks use 'help_text' for supplementary text
    # rather than 'label' as with StreamField
    introduction = models.TextField(
        blank=True, help_text="Text to show at the top of the individual page")

    # Using CharField for little reason other than showing a different input
    # type Wagtail allows you to use any field type Django follows, so you can
    # use anything from
    # https://docs.djangoproject.com/en/1.9/ref/models/fields/#field-types
    listing_introduction = models.CharField(
        max_length=250,
        blank=True,
        help_text=
        "Text shown on listing pages, if empty will show 'Introduction' field content"
    )

    # Note below we're calling StreamField from another location. The
    # `StandardBlock` class is a shared asset across the site. It is defined in
    # core > blocks.py. It is just as 'correct' to define the StreamField
    # directly within the model, but this method aids consistency.
    body = StreamField(StandardBlock(), help_text="Blah blah blah", blank=True)

    content_panels = Page.content_panels + [
        # The content panels are displaying the components of content we defined
        # in the StandardPage class above. If you add something to the class and
        # want it to appear for the editor you have to define it here too
        # A full list of the panel types you can use is at
        # http://docs.wagtail.io/en/latest/reference/pages/panels.html
        # If you add a different type of panel ensure you've imported it from
        # wagtail.wagtailadmin.edit_handlers in the `From` statements at the top
        # of the model InlinePanel('artist_groups', label="Artist(s)"),
        # SnippetChooserPanel('artist'),
        FieldPanel('date'),
        MultiFieldPanel([
            FieldPanel('introduction'),
            FieldPanel('listing_introduction'),
        ],
                        heading="Introduction",
                        classname="collapsible"),
        MultiFieldPanel([
            ImageChooserPanel('image'),
            FieldPanel('image_choices'),
        ],
                        heading="Image details",
                        classname="collapsible"),
        StreamFieldPanel('body'),
        MultiFieldPanel(
            # This duplicates the album/models.py album classa.
            [
                InlinePanel('feature_page_genre_relationship',
                            label="Genre",
                            panels=None,
                            min_num=1,
                            max_num=3),
            ],
            heading="Genres",
            classname="collapsible"),
        InlinePanel('feature_page_artist_relationship', label="Artists"),
        InlinePanel('feature_page_author_relationship',
                    label="Authors",
                    help_text='something'),
    ]

    @property
    def features_index(self):
        # I'm not convinced this is altogether necessary... but still we're
        # going from feature_content_page -> feature_index_page
        return self.get_ancestors().type(FeatureIndexPage).last()

    parent_page_types = [
        'feature_content_page.FeatureIndexPage'
        # app.model
    ]

    subpage_types = []

    # We're returning artists and authors to allow the template to grab the
    # related content. Note the fact we use the related name
    # `artist_feature_page_relationship` to grab them. In the template we'll use
    # a loop to grab them e.g. {% for artist in page.artists %}
    #
    # You don't need to place this at the end of the model, but conventionally
    # it makes sense to put it here
    def artists(self):
        artists = [
            n.artist for n in self.feature_page_artist_relationship.all()
        ]
        return artists

    def authors(self):
        authors = [
            n.author for n in self.feature_page_author_relationship.all()
        ]
        return authors

    def genres(self):
        genres = [n.genre for n in self.feature_page_genre_relationship.all()]
        return genres

    def subgenres(self):
        subgenres = [
            n.subgenre for n in self.feature_page_subgenre_relationship.all()
        ]
        return subgenres
Example #20
0
class BlogPage(Page):
    header_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    intro = models.CharField(max_length=250)
    # body = RichTextField(blank=True)
    body = StreamField([
        ('h1', CharBlock(icon="title", classanme="title")),
        ('h2', CharBlock(icon="title", classanme="title")),
        ('h3', CharBlock(icon="title", classanme="title")),
        ('h4', CharBlock(icon="title", classanme="title")),
        ('h5', CharBlock(icon="title", classanme="title")),
        ('h6', CharBlock(icon="title", classanme="title")),
        # ('intro', RichTextBlock(icon="pilcrow")),
        ('paragraph', RichTextBlock(icon="pilcrow")),
        ('aligned_image', ImageBlock(label="Aligned image", icon="image")),
        ('pullquote', PullQuoteBlock()),
        ('raw_html', RawHTMLBlock(label='Raw HTML', icon="code")),
        ('embed', EmbedBlock(icon="code")),
    ])
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    date = models.DateField("Post date")
    # categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
    feed_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )


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

    @property
    def has_authors(self):
        for author in self.related_author.all():
            if author.author:
                return True

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

    content_panels = Page.content_panels + [
        # MultiFieldPanel([
        #     FieldPanel('date'),
        #     FieldPanel('tags'),
        #     FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
        # ], heading="Blog information"),
        # FieldPanel('date'),
        ImageChooserPanel('header_image'),
        FieldPanel('date'),
        FieldPanel('intro'),
        StreamFieldPanel('body'),
        InlinePanel('related_author', label="Author"),
        # FieldPanel('tags'),
        ImageChooserPanel('feed_image'),


        # InlinePanel('gallery_images', label='Gallery images')
    ]
Example #21
0
class FlatMenu(MenuWithMenuItems):
    site = models.ForeignKey('wagtailcore.Site',
                             verbose_name=_('site'),
                             related_name="flat_menus",
                             db_index=True,
                             on_delete=models.CASCADE)
    title = models.CharField(verbose_name=_('title'),
                             max_length=255,
                             help_text=_("For internal reference only."))
    handle = models.SlugField(
        verbose_name=_('handle'),
        max_length=100,
        help_text=_(
            "Used to reference this menu in templates etc. Must be unique "
            "for the selected site."))
    heading = models.CharField(
        verbose_name=_('heading'),
        max_length=255,
        blank=True,
        help_text=_("If supplied, appears above the menu when rendered."))
    max_levels = models.PositiveSmallIntegerField(
        verbose_name=_('maximum levels'),
        choices=app_settings.MAX_LEVELS_CHOICES,
        default=1,
        help_text=mark_safe(
            _("The maximum number of levels to display when rendering this "
              "menu. The value can be overidden by supplying a different "
              "<code>max_levels</code> value to the <code>{% flat_menu %}"
              "</code> tag in your templates.")))
    use_specific = models.PositiveSmallIntegerField(
        verbose_name=_('specific page usage'),
        choices=app_settings.USE_SPECIFIC_CHOICES,
        default=app_settings.USE_SPECIFIC_AUTO,
        help_text=mark_safe(
            _("Controls how 'specific' pages objects are fetched and used when "
              "rendering this menu. This value can be overidden by supplying a "
              "different <code>use_specific</code> value to the <code>"
              "{% flat_menu %}</code> tag in your templates.")))

    base_form_class = FlatMenuAdminForm

    class Meta:
        unique_together = ("site", "handle")
        verbose_name = _("flat menu")
        verbose_name_plural = _("flat menus")

    @classmethod
    def get_for_site(cls, handle, site, fall_back_to_default_site_menus=False):
        """
        Get a FlatMenu instance with a matching `handle` for the `site`
        provided - or for the 'default' site if not found.
        """
        menu = cls.objects.filter(handle__exact=handle, site=site).first()
        if (menu is None and fall_back_to_default_site_menus
                and not site.is_default_site):
            return cls.objects.filter(handle__exact=handle,
                                      site__is_default_site=True).first()
        return menu

    def __str__(self):
        return '%s (%s)' % (self.title, self.handle)

    def clean(self, *args, **kwargs):
        # Raise validation error for unique_together constraint, as it's not
        # currently handled properly by wagtail
        clashes = FlatMenu.objects.filter(site=self.site, handle=self.handle)
        if self.pk:
            clashes = clashes.exclude(pk__exact=self.pk)
        if clashes.count():
            msg = _("Site and handle must create a unique combination. A menu "
                    "already exists with these same two values.")
            raise ValidationError({
                'site': [msg],
                'handle': [msg],
            })
        super(FlatMenu, self).clean(*args, **kwargs)

    panels = (
        MultiFieldPanel(heading=_("Settings"),
                        children=(
                            FieldPanel('title'),
                            FieldPanel('site'),
                            FieldPanel('handle'),
                            FieldPanel('heading'),
                        )),
        InlinePanel('menu_items', label=_("menu items")),
        MultiFieldPanel(
            heading=_("Advanced settings"),
            children=(FieldPanel('max_levels'), FieldPanel('use_specific')),
            classname="collapsible collapsed",
        ),
    )
Example #22
0
    def get_context(self, request, *args, **kwargs):
        context = super(ProductPage, self).get_context(request, *args,
                                                       **kwargs)
        context['products'] = self.get_product_index(
        ).productindexpage.products
        context = get_product_context(context)
        return context

    class Meta:
        verbose_name = _('Product')
        verbose_name_plural = _('Products')

    parent_page_types = ['fundraiser.ProductIndexPage']


ProductPage.content_panels = [
    FieldPanel('title', classname="full title"),
    MultiFieldPanel([
        FieldPanel('tags'),
        InlinePanel('categories', label=_("Categories")),
    ],
                    heading="Tags and Categories"),
    ImageChooserPanel('header_image'),
    FieldPanel('teaser', classname="full intro"),
    FieldPanel('description', classname="full"),
    FieldPanel('organisation', classname="full organisation"),
    FieldPanel('prize', classname="full prize"),
    FieldPanel('stock', classname="full stock"),
]
Example #23
0
class CategoryEntryPage(models.Model):
    category = models.ForeignKey(Category,
                                 related_name="+",
                                 verbose_name=_('Category'))
    page = ParentalKey('EntryPage', related_name='entry_categories')
    panels = [FieldPanel('category')]
Example #24
0
class ProductPage(RoutablePageMixin, Page):
    teaser = models.TextField()
    description = RichTextField(blank=True)
    organisation = models.CharField(max_length=250, blank=True)
    stock = models.PositiveIntegerField()
    prize = models.PositiveIntegerField()
    tags = ClusterTaggableManager(through=ProductPageTag, blank=True)

    date = models.DateField(
        _("Post date"),
        default=datetime.datetime.today,
        help_text=_("This date may be displayed on the blog post. It is not "
                    "used to schedule posts to go live at a later date."))
    header_image = models.ForeignKey('wagtailimages.Image',
                                     null=True,
                                     blank=True,
                                     on_delete=models.SET_NULL,
                                     related_name='+',
                                     verbose_name=_('Header image'))
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        blank=True,
        null=True,
        limit_choices_to=product_limit_author_choices,
        verbose_name=_('Author'),
        on_delete=models.SET_NULL,
        related_name='author_product_pages',
    )

    search_fields = Page.search_fields + (
        index.SearchField('description'),
        index.SearchField('organisation'),
    )

    settings_panels = [
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('go_live_at'),
                FieldPanel('expire_at'),
            ],
                          classname="label-above"),
        ],
                        'Scheduled publishing',
                        classname="publishing"),
        FieldPanel('date'),
        FieldPanel('author'),
    ]

    def save_revision(self, *args, **kwargs):
        if not self.author:
            self.author = self.owner
        return super(ProductPage, self).save_revision(*args, **kwargs)

    def get_absolute_url(self):
        return self.url

    def get_product_index(self):
        # Find closest ancestor which is a product index
        return self.get_ancestors().type(ProductIndexPage).last()

    def get_context(self, request, *args, **kwargs):
        context = super(ProductPage, self).get_context(request, *args,
                                                       **kwargs)
        context['products'] = self.get_product_index(
        ).productindexpage.products
        context = get_product_context(context)
        return context

    class Meta:
        verbose_name = _('Product')
        verbose_name_plural = _('Products')

    parent_page_types = ['fundraiser.ProductIndexPage']
Example #25
0
class AbstractFobiFormPage(Page):
    """An abstract Fobi form page.

    Pages implementing a Fobi form should inherit from it.

    :property fobi.models.FormEntry form_entry: Form entry to be rendered.
    """

    form_entry = models.ForeignKey('fobi.FormEntry',
                                   verbose_name=_("Form"),
                                   on_delete=models.PROTECT)

    form_template_name = models.CharField(
        _("Form template name"),
        max_length=255,
        null=True,
        blank=True,
        choices=get_form_template_choices(),
        help_text=_(
            "Choose an alternative template to render the form with. Leave "
            "blank to use the default for this page type (e.g. "
            "fobi_form_page.html)."))

    hide_form_title = models.BooleanField(
        _("Hide form title"),
        default=False,
        help_text=_("If checked, no form title is shown."))

    form_title = models.CharField(
        _("Form title"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Overrides the default form title."))

    form_submit_button_text = models.CharField(
        _("Submit button text"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Overrides the default form submit button text."))

    success_page_template_name = models.CharField(
        _("Success page template name"),
        max_length=255,
        null=True,
        blank=True,
        choices=get_success_page_template_choices(),
        help_text=_(
            "Choose an alternative template to render the success page with. "
            "Leave blank to use the default for this page type (e.g. "
            "fobi_form_page_success.html)."))

    hide_success_page_title = models.BooleanField(
        _("Hide success page title"),
        default=False,
        help_text=_("If checked, no success page title is shown."))

    success_page_title = models.CharField(
        _("Success page title"),
        max_length=255,
        null=True,
        blank=True,
        help_text=_("Overrides the default success page title."))

    success_page_text = models.TextField(
        _("Success page text"),
        null=True,
        blank=True,
        help_text=_("Overrides the default success page text."))

    form_page_panels = [
        FieldPanel('hide_form_title'),
        FieldPanel('form_title'),
        FieldPanel('form_entry'),
        FieldPanel('form_submit_button_text'),
    ]

    if get_form_template_choices():
        form_page_panels.append(FieldPanel('form_template_name'))

    success_page_panels = [
        FieldPanel('hide_success_page_title'),
        FieldPanel('success_page_title'),
        FieldPanel('success_page_text'),
    ]

    if get_success_page_template_choices():
        success_page_panels.append(FieldPanel('success_page_template_name'))

    content_panels = Page.content_panels + [
        MultiFieldPanel(form_page_panels, heading=_('Form page')),
        MultiFieldPanel(success_page_panels, heading=_('Success page')),
    ]

    preview_modes = [
        ('form', _('Form page')),
        ('success', _('Success page')),
    ]

    class Meta(object):
        """Meta class."""

        verbose_name = _('Fobi form page')
        verbose_name_plural = _('Fobi form pages')
        abstract = True

    def __init__(self, *args, **kwargs):
        super(AbstractFobiFormPage, self).__init__(*args, **kwargs)

        # Some wagtail magic...
        if not hasattr(self, 'form_template'):
            name, ext = os.path.splitext(self.template)
            self.form_template = name + '_form' + ext

        if not hasattr(self, 'success_template'):
            name, ext = os.path.splitext(self.template)
            self.success_template = name + '_form_success' + ext

    def get_form_template(self, request):
        """Get an alternative template name.

        Get an alternative template name from the object's
        ``form_template_name`` field, or the ``form_template`` attr defined on
        the page type model.

        :param django.http.HttpRequest request:
        """
        return self.form_template_name or self.form_template

    def get_success_template(self, request):
        """Get an alternative template name.

        Get an alternative template name from the object's
        ``success_page_template_name`` field, or the ``success_template`` attr
        defined on the page type model.

        :param django.http.HttpRequest request:
        """
        return self.success_page_template_name or self.success_template

    def serve(self, request, *args, **kwargs):
        """Serve the page using the ``FobiFormProcessor``."""
        fobi_form_processor = FobiFormProcessor()
        response = fobi_form_processor.process(request, instance=self)

        if response:
            return response

        # TODO: Returning HttpResponse seems dirty. See if it can be
        # replaced with TemplateResponse.
        return HttpResponse(fobi_form_processor.rendered_output)

    def serve_preview(self, request, mode):
        """Serve the page in Wagtail's 'preview' mode."""
        if mode == 'success':
            fobi_form_processor = FobiFormProcessor()

            # TODO: Returning HttpResponse seems dirty. See if it can be
            # replaced with TemplateResponse.
            return HttpResponse(
                fobi_form_processor.show_thanks_page(request, self))
        else:
            return super(AbstractFobiFormPage,
                         self).serve_preview(request, mode)
Example #26
0
    def get_context(self, request):
        # Get list of live Gallery pages that are descendants of this page
        pages = GalleryPage.objects.live().descendant_of(self)

        # Update template context
        context = super(GalleryIndexPage, self).get_context(request)
        context['pages'] = pages

        return context

    class Meta:
        verbose_name = "Gallery Index Page"


GalleryIndexPage.content_panels = [
    FieldPanel('title', classname="full title"),
    FieldPanel('intro', classname="full")
]

GalleryIndexPage.promote_panels = [
    MultiFieldPanel(Page.promote_panels, "SEO and metadata fields"),
    ImageChooserPanel('feed_image'),
]


class GalleryPageTag(TaggedItemBase):
    content_object = ParentalKey('photo_gallery.GalleryPage',
                                 related_name='tagged_items')


class GalleryPage(Page):
Example #27
0
class AnswerCategoryPage(RoutablePageMixin, SecondaryNavigationJSMixin,
                         CFGOVPage):
    """
    A routable page type for Ask CFPB category pages and their subcategories.
    """
    from ask_cfpb.models import Answer, Audience, Category, SubCategory

    objects = CFGOVPageManager()
    content = StreamField([], null=True)
    ask_category = models.ForeignKey(
        Category,
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        related_name='category_page')
    ask_subcategory = models.ForeignKey(
        SubCategory,
        blank=True,
        null=True,
        on_delete=models.PROTECT,
        related_name='subcategory_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'),
    ])

    def get_template(self, request):
        if self.language == 'es':
            return 'ask-cfpb/category-page-spanish.html'

        return 'ask-cfpb/category-page.html'

    def get_context(self, request, *args, **kwargs):
        context = super(
            AnswerCategoryPage, self).get_context(request, *args, **kwargs)
        sqs = SearchQuerySet()
        if self.language == 'es':
            sqs = sqs.filter(content=self.ask_category.name_es)
        else:
            sqs = sqs.filter(content=self.ask_category.name)
        sqs = sqs.models(self.Category)
        try:
            facet_map = sqs[0].facet_map
        except IndexError:
            facet_map = self.ask_category.facet_map
        facet_dict = json.loads(facet_map)
        subcat_ids = facet_dict['subcategories'].keys()
        answer_ids = facet_dict['answers'].keys()
        audience_ids = facet_dict['audiences'].keys()
        subcats = self.SubCategory.objects.filter(
            pk__in=subcat_ids).values(
                'id', 'slug', 'slug_es', 'name', 'name_es')
        answers = self.Answer.objects.filter(
            pk__in=answer_ids).order_by('-pk').values(
                'id', 'question', 'question_es',
                'slug', 'slug_es', 'answer_es')
        for a in answers:
            a['answer_es'] = Truncator(a['answer_es']).words(
                40, truncate=' ...')
        audiences = self.Audience.objects.filter(
            pk__in=audience_ids).values('id', 'name')
        context.update({
            'answers': answers,
            'audiences': audiences,
            'facets': facet_dict,
            'choices': subcats,
            'results_count': answers.count(),
            'get_secondary_nav_items': get_ask_nav_items
        })

        if self.language == 'en':
            context['about_us'] = get_reusable_text_snippet(
                ABOUT_US_SNIPPET_TITLE)
            context['disclaimer'] = get_reusable_text_snippet(
                ENGLISH_DISCLAIMER_SNIPPET_TITLE)
            context['breadcrumb_items'] = get_ask_breadcrumbs()
        elif self.language == 'es':
            context['tags'] = self.ask_category.top_tags_es
        return context

    # Returns an image for the page's meta Open Graph tag
    @property
    def meta_image(self):
        return self.ask_category.category_image

    @route(r'^$')
    def category_page(self, request):
        context = self.get_context(request)
        paginator = Paginator(context.get('answers'), 20)
        page_number = validate_page_number(request, paginator)
        page = paginator.page(page_number)
        context.update({
            'paginator': paginator,
            'current_page': page_number,
            'questions': page,
        })

        return TemplateResponse(
            request,
            self.get_template(request),
            context)

    @route(r'^(?P<subcat>[^/]+)/$')
    def subcategory_page(self, request, **kwargs):
        subcat = self.SubCategory.objects.filter(
            slug=kwargs.get('subcat')).first()
        if subcat:
            self.ask_subcategory = subcat
        else:
            raise Http404
        context = self.get_context(request)
        id_key = str(subcat.pk)
        answers = context['answers'].filter(
            pk__in=context['facets']['subcategories'][id_key])
        paginator = Paginator(answers, 20)
        page_number = validate_page_number(request, paginator)
        page = paginator.page(page_number)
        context.update({
            'paginator': paginator,
            'current_page': page_number,
            'results_count': answers.count(),
            'questions': page,
            'breadcrumb_items': get_ask_breadcrumbs(
                self.ask_category)
        })

        return TemplateResponse(
            request, self.get_template(request), context)
Example #28
0
    menu_name = models.CharField(max_length=255, null=False, blank=False)

    @property
    def menu_items_template(self):
        menu_items_template = self.menu_items.all()
        return menu_items_template

    def __str__(self):  # __unicode__ on Python 2
        return self.menu_name

    class Meta:
        verbose_name = "Navigation menu"


Menu.panels = [
    FieldPanel('menu_name', classname='full title'),
    InlinePanel('menu_items',
                label="Menu Items",
                min_num=1,
                help_text='Set the menu items for the current menu.')
]


# Related pages
class RelatedPage(Orderable, models.Model):
    page = models.ForeignKey('wagtailcore.Page',
                             null=True,
                             blank=True,
                             on_delete=models.SET_NULL,
                             related_name='+')
Example #29
0
class FormPage(AbstractEmailForm):
    intro = RichTextField(blank=True)
    side_panel_title = models.CharField(max_length=255, blank=True)
    side_panel_content = RichTextField(blank=True)
    success_message = models.CharField(max_length=255)
    thank_you_page = models.ForeignKey('formbuilder.FormThankYouPage',
                                       null=True,
                                       blank=True,
                                       on_delete=models.SET_NULL,
                                       related_name='+')

    subpage_types = ['formbuilder.FormThankYouPage']

    # Override send_mail method to add 'reply-to' header, timestamp subject
    def send_mail(self, form):
        addresses = [x.strip() for x in self.to_address.split(',')]
        content = []
        reply_to = ([form.data['email']] if 'email' in form.data else None)

        # Add timestamp to subject, to avoid gmail-style conversation views
        time = str(datetime.datetime.now()).split('.')[0]
        subject = '{} [{}]'.format(self.subject, time)

        for field in form:
            value = field.value()
            if isinstance(value, list):
                value = ', '.join(value)
            content.append('{}: {}'.format(field.label, value))
        content = '\n'.join(content)
        connection = get_connection(
            username=settings.EMAIL_HOST_USER_INTERNAL,
            password=settings.EMAIL_HOST_PASSWORD_INTERNAL)
        email = EmailMessage(subject,
                             content,
                             self.from_address,
                             addresses,
                             connection=connection,
                             reply_to=reply_to)
        email.send(fail_silently=False)

    # Override serve method to enable ajax
    def serve(self, request):
        if request.method == 'POST':
            # honeypot
            if len(request.POST.get('url_h', '')):
                messages.success(request, self.success_message)
                return HttpResponseRedirect(self.url)

            form = self.get_form(request.POST)

            if form.is_valid():
                self.process_form_submission(form)
                messages.success(request, self.success_message)

                if request.is_ajax():
                    # Valid ajax post
                    data = {'messages': []}
                    for message in messages.get_messages(request):
                        data['messages'].append({
                            "level": message.level,
                            "level_tag": message.level_tag,
                            "message": message.message,
                        })
                    data['messages_html'] = render_to_string(
                        'includes/messages.html',
                        {'messages': messages.get_messages(request)})
                    return JsonResponse(data)
                else:
                    # Valid (non-ajax) post
                    if self.thank_you_page:
                        return HttpResponseRedirect(self.thank_you_page.url)
                    else:
                        return HttpResponseRedirect(self.url)

            elif request.is_ajax():
                # Invalid ajax post
                data = {'errors': form.errors}
                return JsonResponse(data, status=400)

        else:
            # GET request
            form = self.get_form()

        context = self.get_context(request)
        context['form'] = form
        return render(request, self.template, context)

    content_panels = [
        FieldPanel('title', classname="full title"),
        FieldPanel('intro', classname="full"),
        MultiFieldPanel([
            FieldPanel('side_panel_title'),
            FieldPanel('side_panel_content', classname="full"),
        ], "Side Panel"),
        InlinePanel('form_fields', label="Form fields"),
        FieldPanel('success_message', classname="full"),
        PageChooserPanel('thank_you_page'),
        MultiFieldPanel([
            FieldPanel('to_address', classname="full"),
            FieldPanel('from_address', classname="full"),
            FieldPanel('subject', classname="full"),
        ], "Email")
    ]
Example #30
0
class HomePage(Page):
    body = RichTextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full")
    ]
Example #31
0
 def setUp(self):
     fake_field = self.FakeField()
     fake_field.field = self.FakeClass()
     self.field_panel = FieldPanel('barbecue', 'snowman')(
         instance=True,
         form={'barbecue': fake_field})
Example #32
0
class FeatureIndexPage(Page):
    listing_introduction = models.TextField(
        help_text=
        'Text to describe this section. Will appear on other pages that reference this feature section',
        blank=True)
    introduction = models.TextField(
        help_text='Text to describe this section. Will appear on the page',
        blank=True)
    body = StreamField(
        StandardBlock(),
        blank=True,
        help_text="No good reason to have this here, but in case there's a"
        "feature section I can't think of")

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

    content_panels = Page.content_panels + [
        FieldPanel('listing_introduction'),
        FieldPanel('introduction'),
        StreamFieldPanel('body')
    ]

    parent_page_types = ['home.HomePage']

    # Defining what content type can sit under the parent
    subpage_types = ['FeatureContentPage']

    # @property
    # def features(self):
    #     """
    #     Return feature pages that will live under this index page.
    #     This is now redundant since we return our features via
    #     the `get_filtered_feature_pages` function below.
    #     """
    #     return FeatureContentPage.objects.live().descendant_of(self).order_by('-date')

    def filter_years(self):
        """
        Return a collection of years from the date field of feature pages beneath
        this page.
        """
        years = set()
        features = FeatureContentPage.objects.live().descendant_of(self)
        year_dates = features.dates('date', 'year', order='DESC')
        for date in year_dates:
            years.add(date.year)
        return sorted(years, reverse=True)

    def genres(self):
        """
        Return a list of genres from pages that have a relationship defined
        with a genre and are living beneath this page.
        """
        genres = set()

        for feature_content_page in FeatureContentPage.objects.live(
        ).descendant_of(self):
            feature_genres = [
                d.genre for d in
                feature_content_page.feature_page_genre_relationship.all()
            ]

            for genre in feature_genres:
                genres.add(
                    FilterObject(id=genre.id,
                                 name=genre.title,
                                 slug=genre.slug))

        return sorted(genres, key=lambda d: d.name)

    def paginate(self, request, objects):
        page = request.GET.get('page')
        paginator = Paginator(objects, settings.DEFAULT_PER_PAGE)
        try:
            pages = paginator.page(page)
        except PageNotAnInteger:
            pages = paginator.page(1)
        except EmptyPage:
            pages = paginator.page(paginator.num_pages)
        return pages

    def get_filtered_feature_pages(self, request={}):
        """
        Return a filtered queryset of live feature pages that are decendants of
        this page.
        """
        features = FeatureContentPage.objects.live().descendant_of(self)
        # The first step is to create a `features` variable that we populate
        # with a query. This will return all feature_content_pages that are
        # live (e.g. not draft) and a descendant of this index page

        is_filtering = False
        # Second is to create a filter variable. By default it is set to false

        request_filters = {}
        for k, v in request.GET.items():
            request_filters[k] = (v)
        # Here the `=(v)` will accept any value and will trigger `is_filtering`
        # to be True in year and genre below if populated

        # filter by year
        year = request_filters.get('year', '')
        if year:
            is_filtering = True
            features = features.filter(date__year=year)
        # This appends the `features` variable with a filter. The filter in this
        # case being a the 'year', which we access via the double underscore
        # ('__') reverse lookup from the 'date' field.

        # filter by genre
        genre = request_filters.get('genre', '')
        if genre:
            is_filtering = True
            features = features.filter(
                feature_page_genre_relationship__genre__slug=genre)
        # We filter on genre__slug so that we can guarantee a response
        # that doesn't include spaces e.g. 'heavy-metal' rather than 'heavy
        #  metal' but it gives more useful information than genre__id

        sort_by = request_filters.get('sort_by', 'modified')
        if sort_by == 'date-asc':
            features = features.order_by('first_published_at')
        if sort_by == 'date-desc':
            features = features.order_by('-first_published_at')

        if not is_filtering:
            pass

        filters = {'year': year, 'genre': genre, 'sort_by': sort_by}

        return features, filters, is_filtering

    def get_context(self, request):
        """
        Overriding the context to get more control over what we return.
        See the section `SEPARATED CONTEXT & PAGINATION` at the end of
        this .py file for details on how it works.
        """
        context = super(FeatureIndexPage, self).get_context(request)

        # filters
        features, filters, is_filtering = self.get_filtered_feature_pages(
            request)

        # Pagination
        features = self.paginate(request, features)
        # (request, features) looks for the 'features' on line 392

        context['features'] = features

        context['filters'] = filters
        context['is_filtering'] = is_filtering

        return context

    # We use this property to allow the homepage to get the children of the
    # referenced index pages
    @property
    def children(self):
        return self.get_children().specific().live()
Example #33
0
class TestFieldPanel(TestCase):
    def setUp(self):
        self.EventPageForm = get_form_for_model(EventPage, formsets=[])
        self.event = EventPage(title='Abergavenny sheepdog trials',
            date_from=date(2014, 7, 20), date_to=date(2014, 7, 21))

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

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

        form.is_valid()

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

        # check that label appears in the 'object' wrapper as well as the field
        self.assertIn('<legend>End date</legend>', result)
        self.assertIn('<label for="id_date_to">End date:</label>', result)

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

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

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

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

        form.is_valid()

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

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

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

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

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

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

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

        form.is_valid()

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

        self.assertIn('<p class="error-message">', result)
        self.assertIn('<span>Enter a valid date.</span>', result)
Example #34
0
class ResearchSummariesIndexPage(Page, SocialFields, ListingFields):
    introduction = RichTextField(blank=True)

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

    promote_panels = (Page.promote_panels + SocialFields.promote_panels +
                      ListingFields.promote_panels)

    subpage_types = ['research_summaries.ResearchSummaryPage']

    @classmethod
    def can_create_at(cls, parent):
        """Do not allow to create more than one instance of this page"""
        return super().can_create_at(parent) and not cls.objects.count()

    @cached_property
    def _children_research_summary(self):
        return ResearchSummaryPage.objects.live().public().descendant_of(self)

    @cached_property
    def updated_at(self):
        latest_page = self._children_research_summary.order_by(
            '-updated_at').first()

        latest_date = None
        if latest_page:
            latest_date = latest_page.updated_at

        return latest_date

    def get_context(self, request, *args, **kwargs):
        date_from = request.GET.get('date_from', None)
        search_date_from = None
        date_to = request.GET.get('date_to', None)
        search_date_to = None
        search_research_type = request.GET.get('research_type', None)
        search_rec_opinion = request.GET.get('rec_opinion', None)
        search_query = request.GET.get('query', None)
        page_number = request.GET.get('page', 1)

        search_results = self._children_research_summary

        # Convert dates to the Python format
        if date_from:
            try:
                # Can return None or raise ValueError in case of bad format
                search_date_from = parse_date(date_from, dayfirst=True).date()
            except ValueError:
                search_date_from = None

        if date_to:
            try:
                # Can return None or raise ValueError in case of bad format
                search_date_to = parse_date(date_to, dayfirst=True).date()
            except ValueError:
                search_date_to = None

        # Swap dates around if "date from" happens after "date to".
        if search_date_from and search_date_to:
            if search_date_from > search_date_to:
                search_date_to, search_date_from = search_date_from, search_date_to

        # Search queryset
        if search_date_to:
            search_results = search_results.filter(
                date_of_rec_opinion__lte=search_date_to)

        if search_date_from:
            search_results = search_results.filter(
                date_of_rec_opinion__gte=search_date_from)

        # Research types to be displayed as is
        non_aliased_research_types = dict(
            ResearchType.objects.filter(
                harp_study_type_id__in=ResearchType.NON_ALIASED_STUDY_TYPE_IDS
            ).values_list('pk', 'name'))
        # Add the alias for rest research types
        alias_fake_id = 0
        display_research_types = non_aliased_research_types.copy()
        display_research_types.update({
            alias_fake_id: ResearchType.ALIAS_NAME,
        })

        try:
            search_research_type = int(search_research_type)

            if search_research_type == alias_fake_id:
                search_results = search_results.exclude(
                    research_type__in=non_aliased_research_types.keys())
            elif search_research_type in non_aliased_research_types.keys():
                search_results = search_results.filter(
                    research_type=search_research_type)
        except (TypeError, ValueError):
            pass

        # rec opinion filter
        if search_rec_opinion:
            search_results = search_results.filter(
                rec_opinion=search_rec_opinion)

        if search_query:
            search_results = search_results.search(search_query,
                                                   operator='and')

        # Pagination
        paginator = Paginator(search_results, settings.DEFAULT_PER_PAGE)
        try:
            search_results = paginator.page(page_number)
        except PageNotAnInteger:
            search_results = paginator.page(1)
        except EmptyPage:
            search_results = paginator.page(paginator.num_pages)

        extra_url_params = {
            'date_from': date_from,
            'date_to': date_to,
            'research_type': search_research_type,
            'rec_opinion': search_rec_opinion,
        }

        context = super().get_context(request, *args, **kwargs)
        context.update({
            'search_research_type': search_research_type,
            'search_rec_opinion': search_rec_opinion,
            'search_query': search_query,
            'search_results': search_results,
            'display_research_types': display_research_types,
            'search_date_from': search_date_from,
            'search_date_to': search_date_to,
            'rec_opinions': REC_OPINION_CHOICES,
            'extra_url_params': urlencode(extra_url_params),
        })
        context.update(get_adjacent_pages(paginator, search_results.number))

        return context