def test_get_form_without_model(self):
     edit_handler = ObjectList()
     with self.assertRaisesMessage(
             AttributeError,
             'ObjectList is not bound to a model yet. '
             'Use `.bind_to(model=model)` before using this method.'):
         edit_handler.get_form_class()
Example #2
0
 def get_edit_handler(self):
     if hasattr(self.model, 'edit_handler'):
         edit_handler = self.model.edit_handler
     else:
         fields_to_exclude = self.model_admin.get_form_fields_exclude(request=self.request)
         panels = extract_panel_definitions_from_model_class(self.model, exclude=fields_to_exclude)
         edit_handler = ObjectList(panels)
     return edit_handler.bind_to(model=self.model)
Example #3
0
    def test_render_with_panel_overrides(self):
        """
        Check that inline panel renders the panels listed in the InlinePanel definition
        where one is specified
        """
        speaker_object_list = ObjectList([
            InlinePanel('speakers', label="Speakers", panels=[
                FieldPanel('first_name', widget=forms.Textarea),
                ImageChooserPanel('image'),
            ]),
        ]).bind_to_model(EventPage)
        speaker_inline_panel = speaker_object_list.children[0]
        EventPageForm = speaker_object_list.get_form_class()

        # speaker_inline_panel should instruct the form class to include a 'speakers' formset
        self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))

        event_page = EventPage.objects.get(slug='christmas')

        form = EventPageForm(instance=event_page)
        panel = speaker_inline_panel.bind_to_instance(
            instance=event_page, form=form, request=self.request)

        result = panel.render_as_field()

        # rendered panel should contain first_name rendered as a text area, but no last_name field
        self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
        self.assertIn('Father</textarea>', result)
        self.assertNotIn('<label for="id_speakers-0-last_name">Surname:</label>', result)

        # test for #338: surname field should not be rendered as a 'stray' label-less field
        self.assertTagInHTML('<input id="id_speakers-0-last_name">', result, count=0, allow_extra_attrs=True)

        self.assertIn('<label for="id_speakers-0-image">Image:</label>', result)
        self.assertIn('Choose an image', result)

        # rendered panel must also contain hidden fields for id, DELETE and ORDER
        self.assertTagInHTML(
            '<input id="id_speakers-0-id" name="speakers-0-id" type="hidden">',
            result, allow_extra_attrs=True
        )
        self.assertTagInHTML(
            '<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden">',
            result, allow_extra_attrs=True
        )
        self.assertTagInHTML(
            '<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden">',
            result, allow_extra_attrs=True
        )

        # rendered panel must contain maintenance form for the formset
        self.assertTagInHTML(
            '<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden">',
            result, allow_extra_attrs=True
        )

        # render_js_init must provide the JS initializer
        self.assertIn('var panel = InlinePanel({', panel.render_js_init())
Example #4
0
def get_snippet_edit_handler(model):
    if model not in SNIPPET_EDIT_HANDLERS:
        if hasattr(model, 'edit_handler'):
            # use the edit handler specified on the page class
            edit_handler = model.edit_handler
        else:
            panels = extract_panel_definitions_from_model_class(model)
            edit_handler = ObjectList(panels)

        SNIPPET_EDIT_HANDLERS[model] = edit_handler.bind_to_model(model)

    return SNIPPET_EDIT_HANDLERS[model]
Example #5
0
 def get_edit_handler(self):
     if hasattr(self.model, 'edit_handler'):
         edit_handler = self.model.edit_handler
     elif hasattr(self.model, 'panels'):
         edit_handler = ObjectList(self.model.panels)
     else:
         edit_handler = TabbedInterface([
             ObjectList(self.model.content_panels, heading=_("Content")),
             ObjectList(self.model.settings_panels, heading=_("Settings"),
                        classname="settings"),
         ])
     return edit_handler.bind_to_model(self.model)
Example #6
0
    def test_render(self):
        """
        Check that the inline panel renders the panels set on the model
        when no 'panels' parameter is passed in the InlinePanel definition
        """
        speaker_object_list = ObjectList([
            InlinePanel('speakers', label="Speakers", classname="classname-for-speakers")
        ]).bind_to_model(EventPage)
        EventPageForm = speaker_object_list.get_form_class()

        # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset
        self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))

        event_page = EventPage.objects.get(slug='christmas')

        form = EventPageForm(instance=event_page)
        panel = speaker_object_list.bind_to_instance(instance=event_page,
                                                     form=form,
                                                     request=self.request)

        result = panel.render_as_field()

        self.assertIn('<li class="object classname-for-speakers">', result)
        self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result)
        self.assertIn('value="Father"', result)
        self.assertIn('<label for="id_speakers-0-last_name">Surname:</label>', result)
        self.assertIn('<label for="id_speakers-0-image">Image:</label>', result)
        self.assertIn('Choose an image', result)

        # rendered panel must also contain hidden fields for id, DELETE and ORDER
        self.assertTagInHTML(
            '<input id="id_speakers-0-id" name="speakers-0-id" type="hidden">',
            result, allow_extra_attrs=True
        )
        self.assertTagInHTML(
            '<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden">',
            result, allow_extra_attrs=True
        )
        self.assertTagInHTML(
            '<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden">',
            result, allow_extra_attrs=True
        )

        # rendered panel must contain maintenance form for the formset
        self.assertTagInHTML(
            '<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden">',
            result, allow_extra_attrs=True
        )

        # rendered panel must include the JS initializer
        self.assertIn('var panel = InlinePanel({', result)
    def test_autodetect_page_type(self):
        # Model has a foreign key to EventPage, which we want to autodetect
        # instead of specifying the page type in PageChooserPanel
        MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)

        result = page_chooser_panel.render_as_field()
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
            id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

        self.assertIn(expected_js, result)
class TestObjectList(TestCase):
    def setUp(self):
        self.request = RequestFactory().get('/')
        user = AnonymousUser()  # technically, Anonymous users cannot access the admin
        self.request.user = user
        # a custom ObjectList for EventPage
        self.event_page_object_list = ObjectList([
            FieldPanel('title', widget=forms.Textarea),
            FieldPanel('date_from'),
            FieldPanel('date_to'),
            InlinePanel('speakers', label="Speakers"),
        ], heading='Event details', classname="shiny").bind_to(
            model=EventPage, request=self.request)

    def test_get_form_class(self):
        EventPageForm = self.event_page_object_list.get_form_class()
        form = EventPageForm()

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

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

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

        object_list = self.event_page_object_list.bind_to(
            instance=event,
            form=form,
        )

        result = object_list.render()

        # result should contain ObjectList furniture
        self.assertIn('<ul class="objects">', result)

        # result should contain h2 headings (including labels) for children
        self.assertInHTML('<h2><label for="id_date_from">Start date</label></h2>', result)

        # result should include help text for children
        self.assertIn('<div class="object-help help"><span class="icon-help-inverse" aria-hidden="true"></span>Not required if event is on a single day</div>', result)

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

        # this result should not include fields that are not covered by the panel definition
        self.assertNotIn('signup_link', result)
    def _patch_other_models(self, model):
        if hasattr(model, 'edit_handler'):
            edit_handler = model.edit_handler
            for tab in edit_handler:
                tab.children = self._patch_panels(tab.children)
        elif hasattr(model, 'panels'):
            model.panels = self._patch_panels(model.panels)
        else:
            panels = extract_panel_definitions_from_model_class(model)
            translation_registered_fields = translator.get_options_for_model(model).fields
            panels = filter(lambda field: field.field_name not in translation_registered_fields, panels)
            edit_handler = ObjectList(panels)

            SNIPPET_EDIT_HANDLERS[model] = edit_handler.bind_to_model(model)
Example #10
0
    def test_override_page_type(self):
        # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
        # to restrict the chooser to that page type
        MyPageObjectList = ObjectList([
            PageChooserPanel('page', 'tests.EventPage')
        ]).bind_to_model(EventPageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)

        result = page_chooser_panel.render_as_field()
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
            id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

        self.assertIn(expected_js, result)
Example #11
0
    def test_render_js_init_with_can_choose_root_true(self):
        # construct an alternative page chooser panel object, with can_choose_root=True

        MyPageObjectList = ObjectList([
            PageChooserPanel('page', can_choose_root=True)
        ]).bind_to_model(PageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)

        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
        result = page_chooser_panel.render_as_field()

        # the canChooseRoot flag on createPageChooser should now be true
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, true, null);'.format(
            id="id_page", model="wagtailcore.page", parent=self.events_index_page.id)
        self.assertIn(expected_js, result)
Example #12
0
 def setUp(self):
     # a custom ObjectList for EventPage
     self.EventPageObjectList = ObjectList([
         FieldPanel('title', widget=forms.Textarea),
         FieldPanel('date_from'),
         FieldPanel('date_to'),
         InlinePanel('speakers', label="Speakers"),
     ], heading='Event details', classname="shiny").bind_to_model(EventPage)
Example #13
0
 def setUp(self):
     self.request = RequestFactory().get('/')
     user = AnonymousUser()  # technically, Anonymous users cannot access the admin
     self.request.user = user
     # a custom ObjectList for EventPage
     self.event_page_object_list = ObjectList([
         FieldPanel('title', widget=forms.Textarea),
         FieldPanel('date_from'),
         FieldPanel('date_to'),
         InlinePanel('speakers', label="Speakers"),
     ], heading='Event details', classname="shiny").bind_to_model(EventPage)
    def test_no_thousand_separators_in_js(self):
        """
        Test that the USE_THOUSAND_SEPARATOR setting does not screw up the rendering of numbers
        (specifically maxForms=1000) in the JS initializer:
        https://github.com/wagtail/wagtail/pull/2699
        https://github.com/wagtail/wagtail/issues/3227
        """
        speaker_object_list = ObjectList([
            InlinePanel('speakers', label="Speakers", panels=[
                FieldPanel('first_name', widget=forms.Textarea),
                ImageChooserPanel('image'),
            ]),
        ]).bind_to(model=EventPage, request=self.request)
        speaker_inline_panel = speaker_object_list.children[0]
        EventPageForm = speaker_object_list.get_form_class()
        event_page = EventPage.objects.get(slug='christmas')
        form = EventPageForm(instance=event_page)
        panel = speaker_inline_panel.bind_to(instance=event_page, form=form)

        self.assertIn('maxForms: 1000', panel.render_js_init())
Example #15
0
    def setUp(self):
        model = PageChooserModel  # a model with a foreign key to Page which we want to render as a page chooser

        # a PageChooserPanel class that works on PageChooserModel's 'page' field
        self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel)
        self.MyPageChooserPanel = self.EditHandler.children[0]

        # build a form class containing the fields that MyPageChooserPanel wants
        self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel)

        # a test instance of PageChooserModel, pointing to the 'christmas' page
        self.christmas_page = Page.objects.get(slug='christmas')
        self.events_index_page = Page.objects.get(slug='events')
        self.test_instance = model.objects.create(page=self.christmas_page)

        self.form = self.PageChooserForm(instance=self.test_instance)
        self.page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=self.form)
Example #16
0
class ActivityPage(CFGOVPage):
    """A model for the Activity Detail page."""

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

    template = 'page.html'

    parent_page_types = ['books.BookIndex']

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

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

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

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

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

    def clean(self):
        errors = {}

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

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

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

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

        if errors:
            raise ValidationError(errors)

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

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

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

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

    def __str__(self):
        return self.book_title
Example #18
0
class CourseInformationPage(TranslatedPage, MandatoryIntroductionMixin,
                            BodyMixin, ChildMixin):
    '''
    A general model for a course.
    '''

    subpage_types = ['courses.Course']
    parent_page_types = ['courses.ListOfCoursesPage']

    class Meta:
        verbose_name = _('Definition of a course')

    # ID in the courses register
    course_id = models.CharField(max_length=64, blank=True)

    register_via_website = models.BooleanField(default=False)

    share_data_via_website = models.BooleanField(default=False)

    max_attendees = models.IntegerField(blank=True, null=True, default=None)

    information_list = models.BooleanField(
        default=False,
        help_text=
        _('Is there a list that stores people intereted in this course if no course date is available?'
          ))

    content_panels_de = [
        FieldPanel('title_de'),
        StreamFieldPanel('introduction_de'),
        StreamFieldPanel('body_de'),
    ]
    content_panels_en = [
        FieldPanel('title'),
        StreamFieldPanel('introduction_en'),
        StreamFieldPanel('body_en'),
    ]
    additional_information_panel = [
        FieldPanel('course_id'),
        InlinePanel('contact_persons', label=_('Contact persons'))
    ]
    settings_panel = [
        FieldPanel('information_list'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('register_via_website'),
                FieldPanel('share_data_via_website'),
            ]),
        ],
                        heading=_('Registration options')),
        FieldPanel('max_attendees'),
        InlinePanel('attendee_types', label=_('allowed attendees'))
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels_de, heading=_('Content (de)')),
        ObjectList(content_panels_en, heading=_('Content (en)')),
        ObjectList(additional_information_panel,
                   heading=_('Additional information')),
        ObjectList(settings_panel, heading=_('Settings')),
    ])

    def get_upcoming_courses(self):
        children = Course.objects.live().child_of(self).filter(
            start__gt=datetime.date.today()).order_by('start')
        return children

    def get_admin_display_title(self):
        return self.title_trans
Example #19
0
class DataSharingPage(TranslatedPage):
    parent_page_type = ['courses.Course']

    def clean(self):
        if not self.title:
            self.title = 'Data sharing'
            self.title_de = 'Datenaustausch'

        if not self.slug:
            # we make it a bit harder to guess the URL of a data sharing page
            # However, this is not true security!

            self.slug = self._get_autogenerated_slug('data')

    def get_context(self, request):
        context = super(DataSharingPage, self).get_context(request)
        from .forms import DataUploadForm
        form = DataUploadForm()

        context['form'] = form

        return context

    def serve(self, request, *args, **kwargs):
        if request.method == 'POST':
            from .forms import DataUploadForm
            form = DataUploadForm(request.POST, request.FILES)
            if form.is_valid():
                self.process_submission(form, request)
                return super(DataSharingPage, self).serve(request)
            else:
                context = self.get_context(request)
                context['form'] = form
                return TemplateResponse(
                    request, self.get_template(request, *args, **kwargs),
                    context)
        else:
            return super(DataSharingPage, self).serve(request)

    def process_submission(self, form, request):
        cleaned_data = form.cleaned_data

        document_data = cleaned_data['uploaded_file']
        if document_data:
            DocumentModel = get_document_model()

            doc = DocumentModel(file=document_data,
                                title=document_data.name,
                                collection=ImportantPages.for_site(
                                    request.site).collection_for_user_upload)
            doc.save()

            instance = form.save(commit=False)
            instance.uploaded_on = datetime.datetime.now()
            instance.sharing_page = self
            instance.file_link = doc

            instance.save()

    content_panels = [InlinePanel('shared_data')]

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

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

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

    objects = CFGOVPageManager()

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

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

    template = 'events/event.html'

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

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

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

        return 'future'

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

        return super(EventPage, self).page_js

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

        return static_map_image_url

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

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

    def get_context(self, request):
        context = super(EventPage, self).get_context(request)
        context['event_state'] = self.event_state
        return context
Example #21
0
class ExternalContent(BasePage):
    is_external = True
    subpage_types = []

    # Card fields
    description = RichTextField(
        blank=True,
        default="",
        features=RICH_TEXT_FEATURES_SIMPLE,
        help_text="Optional short text description, max. 400 characters",
        max_length=400,
    )
    external_url = URLField(
        "URL",
        blank=True,
        default="",
        help_text=(
            "The URL that this content links to, max. 2048 characters "
            "for compatibility with older web browsers"
        ),
        max_length=2048,
    )
    card_image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        help_text="An image in 16:9 aspect ratio",
    )
    card_image_3_2 = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
        verbose_name="Image",
        help_text="An image in 3:2 aspect ratio",
    )
    card_panels = BasePage.content_panels + [
        FieldPanel("description"),
        MultiFieldPanel(
            [ImageChooserPanel("card_image")],
            heading="16:9 Image",
            help_text=(
                "Image used for representing this piece of content as a Card. "
                "Should be 16:9 aspect ratio. "
                "If not specified a fallback will be used. "
                "This image is also shown when sharing this page via social "
                "media unless a social image is specified."
            ),
        ),
        MultiFieldPanel(
            [ImageChooserPanel("card_image_3_2")],
            heading="3:2 Image",
            help_text=(
                "Image used for representing this page as a Card. "
                "Should be 3:2 aspect ratio. "
                "If not specified a fallback will be used. "
            ),
        ),
        FieldPanel("external_url"),
    ]

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

    edit_handler = TabbedInterface(
        [
            ObjectList(card_panels, heading="Card"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )

    class Meta:
        verbose_name_plural = "External Content"

    def get_full_url(self, request=None):
        return self.external_url

    def get_url(self, request=None, current_site=None):
        return self.external_url

    def relative_url(self, current_site, request=None):
        return self.external_url

    @property
    def url(self):
        return self.external_url

    def serve_preview(self, request, mode_name):
        # ExternalContent subclasses don't preview like regular Pages: we just need
        # to show where they link to.
        return HttpResponseRedirect(self.external_url)
Example #22
0
class ExternalArticle(ExternalContent):
    class Meta:
        verbose_name = "External Post"
        verbose_name_plural = "External Posts"

    resource_type = "article"  #  if you change this, amend the related CSS, too

    date = DateField(
        "External Post date",
        default=datetime.date.today,
        help_text="The date the external post was published",
    )
    authors = StreamField(
        StreamBlock(
            [
                ("author", PageChooserBlock(target_model="people.Person")),
                ("external_author", ExternalAuthorBlock()),
            ],
            required=False,
        ),
        blank=True,
        null=True,
        help_text=(
            "Optional list of the external post's authors. "
            "Use ‘External author’ to add "
            "guest authors without creating a profile on the system"
        ),
    )
    read_time = CharField(
        max_length=30,
        blank=True,
        help_text=(
            "Optional, approximate read-time for this external post, "
            "e.g. “2 mins”. This is shown as a small hint when the "
            "external post is displayed as a card."
        ),
    )

    meta_panels = [
        FieldPanel("date"),
        StreamFieldPanel("authors"),
        MultiFieldPanel(
            [InlinePanel("topics")],
            heading="Topics",
            help_text="The topic pages this external post will appear on",
        ),
        FieldPanel("read_time"),
    ]

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

    edit_handler = TabbedInterface(
        [
            ObjectList(ExternalContent.card_panels, heading="Card"),
            ObjectList(meta_panels, heading="Meta"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )

    @property
    def article(self):
        return self

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

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

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

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

    objects = PageManager()

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

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

    @property
    def ordered_grades(self):
        """Return a list of job grades in numerical order.
        Non-numeric grades are sorted alphabetically after numeric grades.
        """
        grades = set(g.grade.grade for g in self.grades.all())
        return sorted(grades, key=lambda g: '{0:0>8}'.format(g))
Example #24
0
class RecipePage(GeneralShelvePage, Tracking, Social):
    DIFFICULTY_OPTIONS = (
        ('easy', 'Easy'),
        ('medium', 'Medium'),
        ('hard', 'Hard'),
    )
    header_image = models.ForeignKey(
        'images.PHEImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    header_image_mobile_rendition = models.CharField(choices=MOBILE_RENDITION_CHOICES,
                                      verbose_name='Mobile Rendition',
                                      default='none',
                                      max_length=10)
    header_image_desktop_rendition = models.CharField(choices=DESKTOP_RENDITION_CHOICES,
                                      verbose_name='Desktop Rendition',
                                      default='none',
                                      max_length=10)
    recipe_name = models.CharField(max_length=255, null=True, blank=True)
    tags = models.CharField(max_length=255, null=True, blank=True)
    serves = models.IntegerField(default=0)
    preparation_time = models.IntegerField(default=0)
    difficulty = models.CharField(max_length=255, choices=DIFFICULTY_OPTIONS, default='medium')
    ingredients_list = RichTextField(null=True, blank=True)
    instructions = RichTextField(null=True, blank=True)

    header_gradient = models.BooleanField(default=False)
    video_id = models.CharField(null=True, blank=True, max_length=50)
    host = models.CharField(choices=VIDEO_HOSTS, max_length=15, default='brightcove', null=True, blank=True)

    content_panels = [
        MultiFieldPanel(
            [
                FieldPanel('title'),
                FieldPanel('tags'),
            ],
            heading='General',
        ),
        MultiFieldPanel(
            [
                ImageChooserPanel('header_image'),
                FieldPanel('header_image_mobile_rendition'),
                FieldPanel('header_image_desktop_rendition'),
                FieldPanel('header_gradient'),
                FieldPanel('recipe_name'),
                FieldPanel('video_id'),
                FieldPanel('host'),
            ],
            heading='Header',
        ),
        MultiFieldPanel(
            [
                FieldPanel('serves'),
                FieldPanel('preparation_time'),
                FieldPanel('difficulty'),
                FieldPanel('ingredients_list'),
                FieldPanel('instructions'),
            ],
            heading='Recipe details',
        ),
        FieldPanel('release'),
        SnippetChooserPanel('page_theme'),
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(GeneralShelvePage.info_content_panels, heading='Notes'),
        ObjectList(GeneralShelvePage.meta_content_panels, heading='Meta'),
        ObjectList(Page.promote_panels, heading='Settings'),
    ])

    @classmethod
    def allowed_subpage_models(cls):
        """
        Returns the list of page types that this page type can have as subpages,
        as a list of model classes
        """
        return [
            OneYouPage, RecipePage, ArticleOneYouPage,
        ]
Example #25
0
class Product(ClusterableModel):
    title = models.CharField(max_length=255)
    description = RichTextField(blank=True)
    includes = RichTextField(blank=True)
    price = models.DecimalField(default=None,
                                max_digits=18,
                                decimal_places=2,
                                blank=True,
                                help_text="Price including VAT")
    sku = models.CharField(max_length=255, blank=True)
    active = models.BooleanField(default=True)
    priority = models.CharField(max_length=255, default="0")
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def avatar_property(self):
        return self.images.first().image

    avatar_property.short_description = "Full name of the person"
    avatar = property(avatar_property)
    oscar_product = models.ForeignKey(OscarProduct,
                                      on_delete=models.CASCADE,
                                      related_name='wag_product',
                                      verbose_name="Oscar Product",
                                      null=True,
                                      blank=True)

    content_panels = [
        FieldPanel('title', classname="full title"),
        FieldPanel('description', classname="full"),
        FieldPanel('includes', classname="full"),
        FieldPanel('price', classname="full"),
    ]
    images_panels = [
        InlinePanel('images', label="Images"),
    ]
    settings_panels = [
        FieldPanel('active'),
        FieldPanel('sku'),
        FieldPanel('priority'),
    ]
    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading='Content'),
        ObjectList(images_panels, heading='Images'),
        ObjectList(settings_panels, heading='Settings'),
    ])

    def save(self, *args, **kwargs):

        if not self.pk:

            product_class = ProductClass.objects.filter(pk=1).first()
            oprod = OscarProduct(title=self.title,
                                 structure="standalone",
                                 product_class=product_class)
            oprod.save()
            if not self.sku:
                self.sku = oprod.id
            partner = Partner.objects.filter(pk=1).first()
            exc_vat = Decimal(self.price) / (
                Decimal('1') + Decimal(str(Selector.strategy(self).rate)))
            stock = StockRecord(partner=partner,
                                product=oprod,
                                partner_sku=self.sku,
                                price_excl_tax=exc_vat)
            stock.save()

            self.oscar_product = oprod

        else:
            self.oscar_product.title = self.title
            self.oscar_product.save()
            stockrecord = self.oscar_product.stockrecords.first()
            exc_vat = Decimal(self.price) / (
                Decimal('1') + Decimal(str(Selector.strategy(self).rate)))
            stockrecord.price_excl_tax = exc_vat
            stockrecord.partner_sku = self.sku
            stockrecord.save()

        super().save(*args, **kwargs)  # Call the "real" save() method.

    def delete(self, *args, **kwargs):
        print(self.oscar_product.id)
        self.oscar_product.delete()
        super(Product, self).delete(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        ordering = ["-active", "-priority"]
Example #26
0
class HomePage(BasePage):
    subpage_types = [
        "articles.Articles",
        "content.ContentPage",
        "events.Events",
        "people.People",
        "topics.Topics",
        "videos.Videos",
    ]
    template = "home.html"

    # Content fields
    subtitle = TextField(max_length=250, blank=True, default="")
    button_text = CharField(max_length=30, blank=True, default="")
    button_url = CharField(max_length=2048, blank=True, default="")
    image = ForeignKey(
        "mozimages.MozImage",
        null=True,
        blank=True,
        on_delete=SET_NULL,
        related_name="+",
    )
    external_promos = StreamField(
        StreamBlock([("external_promo", FeaturedExternalBlock())],
                    max_num=2,
                    required=False),
        null=True,
        blank=True,
        help_text=("Optional promo space under the header "
                   "for linking to external sites, max. 2"),
    )
    featured = StreamField(
        StreamBlock(
            [
                (
                    "article",
                    PageChooserBlock(target_model=(
                        "articles.Article",
                        "externalcontent.ExternalArticle",
                    )),
                ),
                ("external_page", FeaturedExternalBlock()),
            ],
            max_num=4,
            required=False,
        ),
        null=True,
        blank=True,
        help_text="Optional space for featured articles, max. 4",
    )
    about_title = TextField(max_length=250, blank=True, default="")
    about_subtitle = TextField(max_length=250, blank=True, default="")
    about_button_text = CharField(max_length=30, blank=True, default="")
    about_button_url = URLField(max_length=140, blank=True, default="")

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

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

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

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

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

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

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

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

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

        return Topic.published_objects.filter(
            parent_topics__isnull=True).order_by("title")
Example #27
0
class HomePage(BasePage):

    template = "patterns/pages/home/home_page.html"

    # Only allow creating HomePages at the root level
    parent_page_types = ["wagtailcore.Page"]

    # subpage_types = []

    class Meta:
        verbose_name = "Home Page"

    city = models.CharField(null=True, blank=False, max_length=255)
    zip_code = models.CharField(null=True, blank=False, max_length=255)
    address = models.CharField(null=True, blank=False, max_length=255)
    telephone = models.CharField(null=True, blank=False, max_length=255)
    telefax = models.CharField(null=True, blank=False, max_length=255)
    vat_number = models.CharField(null=True, blank=False, max_length=255)
    whatsapp_telephone = models.CharField(null=True,
                                          blank=True,
                                          max_length=255)
    whatsapp_contactline = models.CharField(null=True,
                                            blank=True,
                                            max_length=255)
    tax_id = models.CharField(null=True, blank=False, max_length=255)
    trade_register_number = models.CharField(null=True,
                                             blank=False,
                                             max_length=255)
    court_of_registry = models.CharField(null=True,
                                         blank=False,
                                         max_length=255)
    place_of_registry = models.CharField(null=True,
                                         blank=False,
                                         max_length=255)
    trade_register_number = models.CharField(null=True,
                                             blank=False,
                                             max_length=255)
    ownership = models.CharField(null=True, blank=False, max_length=255)
    email = models.CharField(null=True, blank=False, max_length=255)

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

    about = RichTextField(null=True, blank=False)
    privacy = RichTextField(null=True, blank=False)

    sociallinks = StreamField([(
        "link",
        blocks.URLBlock(
            help_text="Important! Format https://www.domain.tld/xyz"),
    )])

    array = []

    def sociallink_company(self):
        for link in self.sociallinks:
            self.array.append(str(link).split(".")[1])
        return self.array

    # headers = StreamField(
    #     [
    #         (
    #             "code",
    #             blocks.RawHTMLBlock(
    #                 null=True, blank=True, classname="full", icon="code"
    #             ),
    #         )
    #     ],
    #     null=True,
    #     blank=False,
    # )

    # sections = StreamField(
    #     [
    #         (
    #             "code",
    #             blocks.RawHTMLBlock(
    #                 null=True, blank=True, classname="full", icon="code"
    #             ),
    #         )
    #     ],
    #     null=True,
    #     blank=False,
    # )

    # token = models.CharField(null=True, blank=True, max_length=255)

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

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

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

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

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

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

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

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

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

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

        return context

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

    imprint_panels = [
        MultiFieldPanel(
            [
                FieldPanel("city"),
                FieldPanel("zip_code"),
                FieldPanel("address"),
                FieldPanel("telephone"),
                FieldPanel("telefax"),
                FieldPanel("whatsapp_telephone"),
                FieldPanel("whatsapp_contactline"),
                FieldPanel("email"),
                FieldPanel("copyrightholder"),
            ],
            heading="contact",
        ),
        MultiFieldPanel(
            [
                FieldPanel("vat_number"),
                FieldPanel("tax_id"),
                FieldPanel("trade_register_number"),
                FieldPanel("court_of_registry"),
                FieldPanel("place_of_registry"),
                FieldPanel("trade_register_number"),
                FieldPanel("ownership"),
            ],
            heading="legal",
        ),
        StreamFieldPanel("sociallinks"),
        MultiFieldPanel(
            [FieldPanel("about"), FieldPanel("privacy")], heading="privacy"),
    ]

    edit_handler = TabbedInterface([
        ObjectList(Page.content_panels + content_panels, heading="Content"),
        ObjectList(imprint_panels, heading="Imprint"),
        ObjectList(
            BasePage.promote_panels + BasePage.settings_panels,
            heading="Settings",
            classname="settings",
        ),
    ])
Example #28
0
class BaseForm(ClusterableModel):
    """ A form base class, any form should inherit from this. """

    name = models.CharField(
        _('Name'),
        max_length=255
    )
    slug = models.SlugField(
        _('Slug'),
        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(
        _('Template'),
        max_length=255,
        choices=get_setting('FORM_TEMPLATES')
    )
    submit_button_text = models.CharField(
        _('Submit button text'),
        max_length=100,
        default='Submit'
    )
    store_submission = models.BooleanField(
        _('Store submission'),
        default=False
    )
    add_recaptcha = models.BooleanField(
        _('Add recaptcha'),
        default=False,
        help_text=_('Add a reCapcha field to the form.')
    )
    success_message = models.CharField(
        _('Success message'),
        blank=True,
        max_length=255,
        help_text=_('An optional success message to show when the form has been successfully submitted')
    )
    error_message = models.CharField(
        _('Error message'),
        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',
        verbose_name=_('Post redirect page'),
        on_delete=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')
        verbose_name_plural = _('Forms')

    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 #29
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(
        _('Subject'),
        max_length=255
    )
    from_address = models.EmailField(
        _('From address')
    )
    to_addresses = MultiEmailField(
        _('To addresses'),
        help_text=_("Add one email per line")
    )
    message = models.TextField(
        _('Message'),
    )
    fail_silently = models.BooleanField(
        _('Fail silently'),
        default=True
    )

    email_panels = [
        FieldPanel('subject', classname="full"),
        FieldPanel('from_address', classname="full"),
        FieldPanel('to_addresses', classname="full"),
        FieldPanel('message', classname="full"),
        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. """

        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 #30
0
 def test_get_form_without_model(self):
     edit_handler = ObjectList()
     with self.assertRaisesMessage(
             AttributeError, 'ObjectList is not bound to a model yet. '
             'Use `.bind_to(model=model)` before using this method.'):
         edit_handler.get_form_class()
Example #31
0
    def test_render_with_panel_overrides(self):
        """
        Check that inline panel renders the panels listed in the InlinePanel definition
        where one is specified
        """
        speaker_object_list = ObjectList([
            InlinePanel('speakers',
                        label="Speakers",
                        panels=[
                            FieldPanel('first_name', widget=forms.Textarea),
                            ImageChooserPanel('image'),
                        ]),
        ]).bind_to(model=EventPage, request=self.request)
        speaker_inline_panel = speaker_object_list.children[0]
        EventPageForm = speaker_object_list.get_form_class()

        # speaker_inline_panel should instruct the form class to include a 'speakers' formset
        self.assertEqual(['speakers'], list(EventPageForm.formsets.keys()))

        event_page = EventPage.objects.get(slug='christmas')

        form = EventPageForm(instance=event_page)
        panel = speaker_inline_panel.bind_to(instance=event_page, form=form)

        result = panel.render_as_field()

        # rendered panel should contain first_name rendered as a text area, but no last_name field
        self.assertIn('<label for="id_speakers-0-first_name">Name:</label>',
                      result)
        self.assertIn('Father</textarea>', result)
        self.assertNotIn(
            '<label for="id_speakers-0-last_name">Surname:</label>', result)

        # test for #338: surname field should not be rendered as a 'stray' label-less field
        self.assertTagInHTML('<input id="id_speakers-0-last_name">',
                             result,
                             count=0,
                             allow_extra_attrs=True)

        self.assertIn('<label for="id_speakers-0-image">Image:</label>',
                      result)
        self.assertIn('Choose an image', result)

        # rendered panel must also contain hidden fields for id, DELETE and ORDER
        self.assertTagInHTML(
            '<input id="id_speakers-0-id" name="speakers-0-id" type="hidden">',
            result,
            allow_extra_attrs=True)
        self.assertTagInHTML(
            '<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden">',
            result,
            allow_extra_attrs=True)
        self.assertTagInHTML(
            '<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden">',
            result,
            allow_extra_attrs=True)

        # rendered panel must contain maintenance form for the formset
        self.assertTagInHTML(
            '<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden">',
            result,
            allow_extra_attrs=True)

        # render_js_init must provide the JS initializer
        self.assertIn('var panel = InlinePanel({', panel.render_js_init())
Example #32
0
        FieldPanel("seo_title"),
        FieldPanel("slug"),
        InlinePanel("advert_placements", label="Adverts"),
    ]

    promote_panels = []


class StandardChild(Page):
    pass


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


class BusinessIndex(Page):
    """Can be placed anywhere, can only have Business children"""

    subpage_types = ["tests.BusinessChild", "tests.BusinessSubIndex"]
Example #33
0
class ExternalEvent(ExternalContent):
    resource_type = "event"

    start_date = DateField(
        default=datetime.date.today,
        help_text="The date the event is scheduled to start",
    )
    end_date = DateField(
        blank=True, null=True, help_text="The date the event is scheduled to end"
    )
    city = CharField(max_length=100, blank=True, default="")
    country = CountryField(verbose_name="Country or Region", blank=True, default="")

    meta_panels = [
        MultiFieldPanel(
            [FieldPanel("start_date"), FieldPanel("end_date")], heading="Event details"
        ),
        MultiFieldPanel(
            [FieldPanel("city"), FieldPanel("country")], heading="Event address"
        ),
        InlinePanel(
            "topics",
            heading="Topics",
            help_text=(
                "Optional topics this event is associated with. "
                "Adds the event to the list of events on those topic pages"
            ),
        ),
        InlinePanel(
            "speakers",
            heading="Speakers",
            help_text=(
                "Optional speakers associated with this event. "
                "Adds the event to the list of events on their profile pages"
            ),
        ),
    ]

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

    edit_handler = TabbedInterface(
        [
            ObjectList(ExternalContent.card_panels, heading="Card"),
            ObjectList(meta_panels, heading="Meta"),
            ObjectList(settings_panels, heading="Settings", classname="settings"),
        ]
    )

    @property
    def event(self):
        return self

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

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

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

    @property
    def event_dates_full(self):
        """Return a formatted string of the event start and end dates,
        including the year"""
        return self.event_dates + self.start_date.strftime(", %Y")
Example #34
0
class TestPageChooserPanel(TestCase):
    fixtures = ['test.json']

    def setUp(self):
        model = PageChooserModel  # a model with a foreign key to Page which we want to render as a page chooser

        # a PageChooserPanel class that works on PageChooserModel's 'page' field
        self.EditHandler = ObjectList([PageChooserPanel('page')]).bind_to_model(PageChooserModel)
        self.MyPageChooserPanel = self.EditHandler.children[0]

        # build a form class containing the fields that MyPageChooserPanel wants
        self.PageChooserForm = self.EditHandler.get_form_class(PageChooserModel)

        # a test instance of PageChooserModel, pointing to the 'christmas' page
        self.christmas_page = Page.objects.get(slug='christmas')
        self.events_index_page = Page.objects.get(slug='events')
        self.test_instance = model.objects.create(page=self.christmas_page)

        self.form = self.PageChooserForm(instance=self.test_instance)
        self.page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=self.form)

    def test_page_chooser_uses_correct_widget(self):
        self.assertEqual(type(self.form.fields['page'].widget), AdminPageChooser)

    def test_render_js_init(self):
        result = self.page_chooser_panel.render_as_field()
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
            id="id_page", model="wagtailcore.page", parent=self.events_index_page.id)

        self.assertIn(expected_js, result)

    def test_render_js_init_with_can_choose_root_true(self):
        # construct an alternative page chooser panel object, with can_choose_root=True

        MyPageObjectList = ObjectList([
            PageChooserPanel('page', can_choose_root=True)
        ]).bind_to_model(PageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)

        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)
        result = page_chooser_panel.render_as_field()

        # the canChooseRoot flag on createPageChooser should now be true
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, true, null);'.format(
            id="id_page", model="wagtailcore.page", parent=self.events_index_page.id)
        self.assertIn(expected_js, result)

    def test_get_chosen_item(self):
        result = self.page_chooser_panel.get_chosen_item()
        self.assertEqual(result, self.christmas_page)

    def test_render_as_field(self):
        result = self.page_chooser_panel.render_as_field()
        self.assertIn('<p class="help">help text</p>', result)
        self.assertIn('<span class="title">Christmas</span>', result)
        self.assertIn(
            '<a href="/admin/pages/%d/edit/" class="edit-link button button-small button-secondary" target="_blank">'
            'Edit this page</a>' % self.christmas_page.id,
            result)

    def test_render_as_empty_field(self):
        test_instance = PageChooserModel()
        form = self.PageChooserForm(instance=test_instance)
        page_chooser_panel = self.MyPageChooserPanel(instance=test_instance, form=form)
        result = page_chooser_panel.render_as_field()

        self.assertIn('<p class="help">help text</p>', result)
        self.assertIn('<span class="title"></span>', result)
        self.assertIn('Choose a page', result)

    def test_render_error(self):
        form = self.PageChooserForm({'page': ''}, instance=self.test_instance)
        self.assertFalse(form.is_valid())

        page_chooser_panel = self.MyPageChooserPanel(instance=self.test_instance, form=form)
        self.assertIn('<span>This field is required.</span>', page_chooser_panel.render_as_field())

    def test_override_page_type(self):
        # Model has a foreign key to Page, but we specify EventPage in the PageChooserPanel
        # to restrict the chooser to that page type
        MyPageObjectList = ObjectList([
            PageChooserPanel('page', 'tests.EventPage')
        ]).bind_to_model(EventPageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)

        result = page_chooser_panel.render_as_field()
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
            id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

        self.assertIn(expected_js, result)

    def test_autodetect_page_type(self):
        # Model has a foreign key to EventPage, which we want to autodetect
        # instead of specifying the page type in PageChooserPanel
        MyPageObjectList = ObjectList([PageChooserPanel('page')]).bind_to_model(EventPageChooserModel)
        MyPageChooserPanel = MyPageObjectList.children[0]
        PageChooserForm = MyPageObjectList.get_form_class(EventPageChooserModel)
        form = PageChooserForm(instance=self.test_instance)
        page_chooser_panel = MyPageChooserPanel(instance=self.test_instance, form=form)

        result = page_chooser_panel.render_as_field()
        expected_js = 'createPageChooser("{id}", ["{model}"], {parent}, false, null);'.format(
            id="id_page", model="tests.eventpage", parent=self.events_index_page.id)

        self.assertIn(expected_js, result)

    def test_target_models(self):
        result = PageChooserPanel(
            'barbecue',
            'wagtailcore.site'
        ).bind_to_model(PageChooserModel).target_models()
        self.assertEqual(result, [Site])

    def test_target_models_malformed_type(self):
        result = PageChooserPanel(
            'barbecue',
            'snowman'
        ).bind_to_model(PageChooserModel)
        self.assertRaises(ImproperlyConfigured,
                          result.target_models)

    def test_target_models_nonexistent_type(self):
        result = PageChooserPanel(
            'barbecue',
            'snowman.lorry'
        ).bind_to_model(PageChooserModel)
        self.assertRaises(ImproperlyConfigured,
                          result.target_models)
Example #35
0
class HomePage(Page):

    image_1 = models.ForeignKey(
        'a4_candy_cms_images.CustomImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name="Header Image 1",
        help_text="The Image that is shown on top of the page")

    image_2 = models.ForeignKey(
        'a4_candy_cms_images.CustomImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name="Header Image 2",
        help_text="The Image that is shown on top of the page")

    image_3 = models.ForeignKey(
        'a4_candy_cms_images.CustomImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name="Header Image 3",
        help_text="The Image that is shown on top of the page")

    image_4 = models.ForeignKey(
        'a4_candy_cms_images.CustomImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name="Header Image 4",
        help_text="The Image that is shown on top of the page")

    image_5 = models.ForeignKey(
        'a4_candy_cms_images.CustomImage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        verbose_name="Header Image 5",
        help_text="The Image that is shown on top of the page")

    form_page = models.ForeignKey(
        'a4_candy_cms_contacts.FormPage',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
    )

    subtitle_de = models.CharField(max_length=500,
                                   blank=True,
                                   verbose_name="Subtitle")
    subtitle_en = models.CharField(max_length=500,
                                   blank=True,
                                   verbose_name="Subtitle")

    teaser_de = fields.RichTextField(blank=True)
    teaser_en = fields.RichTextField(blank=True)

    body_de = fields.RichTextField(blank=True)
    body_en = fields.RichTextField(blank=True)

    body_streamfield_de = fields.StreamField(
        [('col_list_image_cta_block', cms_blocks.ColumnsImageCTABlock()),
         ('background_cta_block', cms_blocks.ColBackgroundCTABlock()),
         ('columns_cta', cms_blocks.ColumnsCTABlock()),
         ('html', blocks.RawHTMLBlock()),
         ('paragraph', blocks.RichTextBlock()), ('news', NewsBlock()),
         ('use_cases', UseCaseBlock())],
        blank=True)

    body_streamfield_en = fields.StreamField(
        [('col_list_image_cta_block', cms_blocks.ColumnsImageCTABlock()),
         ('background_cta_block', cms_blocks.ColBackgroundCTABlock()),
         ('columns_cta', cms_blocks.ColumnsCTABlock()),
         ('html', blocks.RawHTMLBlock()),
         ('paragraph', blocks.RichTextBlock()), ('news', NewsBlock()),
         ('use_cases', UseCaseBlock())],
        blank=True)

    subtitle = TranslatedField('subtitle_de', 'subtitle_en')

    teaser = TranslatedField('teaser_de', 'teaser_en')

    body_streamfield = TranslatedField('body_streamfield_de',
                                       'body_streamfield_en')

    body = TranslatedField(
        'body_de',
        'body_en',
    )

    @property
    def form(self):
        return self.form_page.get_form()

    @property
    def random_image(self):
        image_numbers = [
            i for i in range(1, 6) if getattr(self, 'image_{}'.format(i))
        ]
        if image_numbers:
            return getattr(self,
                           'image_{}'.format(random.choice(image_numbers)))

    en_content_panels = [
        FieldPanel('subtitle_en'),
        FieldPanel('teaser_en'),
        FieldPanel('body_en'),
        StreamFieldPanel('body_streamfield_en')
    ]

    de_content_panels = [
        FieldPanel('subtitle_de'),
        FieldPanel('teaser_de'),
        FieldPanel('body_de'),
        StreamFieldPanel('body_streamfield_de')
    ]

    common_panels = [
        FieldPanel('title'),
        FieldPanel('slug'),
        PageChooserPanel('form_page'),
        MultiFieldPanel([
            ImageChooserPanel('image_1'),
            ImageChooserPanel('image_2'),
            ImageChooserPanel('image_3'),
            ImageChooserPanel('image_4'),
            ImageChooserPanel('image_5'),
        ],
                        heading="Images",
                        classname="collapsible")
    ]

    edit_handler = TabbedInterface([
        ObjectList(common_panels, heading='Common'),
        ObjectList(en_content_panels, heading='English'),
        ObjectList(de_content_panels, heading='German')
    ])

    subpage_types = ['a4_candy_cms_pages.EmptyPage']
Example #36
0
class UniquePage(Page):
    city = models.CharField(null=True, blank=False, max_length=255)
    zip_code = models.CharField(null=True, blank=False, max_length=255)
    address = models.CharField(null=True, blank=False, max_length=255)
    telephone = models.CharField(null=True, blank=False, max_length=255)
    telefax = models.CharField(null=True, blank=False, max_length=255)
    vat_number = models.CharField(null=True, blank=False, max_length=255)
    whatsapp_telephone = models.CharField(null=True, blank=True, max_length=255)
    whatsapp_contactline = models.CharField(null=True, blank=True, max_length=255)
    tax_id = models.CharField(null=True, blank=False, max_length=255)
    trade_register_number = models.CharField(null=True, blank=False, max_length=255)
    court_of_registry = models.CharField(null=True, blank=False, max_length=255)
    place_of_registry = models.CharField(null=True, blank=False, max_length=255)
    trade_register_number = models.CharField(null=True, blank=False, max_length=255)
    ownership = models.CharField(null=True, blank=False, max_length=255)
    email = models.CharField(null=True, blank=False, max_length=255)

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

    about = RichTextField(null=True, blank=False, features=['bold', 'italic', 'underline', 'strikethrough', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ol', 'ul', 'hr', 'embed', 'link', 'superscript', 'subscript', 'document-link', 'image', 'code'])
    privacy = RichTextField(null=True, blank=False, features=['bold', 'italic', 'underline', 'strikethrough', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'ol', 'ul', 'hr', 'embed', 'link', 'superscript', 'subscript', 'document-link', 'image', 'code'])

    sociallinks = StreamField([
        ('link', blocks.URLBlock(help_text="Important! Format https://www.domain.tld/xyz"))
    ])

    array = []
    def sociallink_company(self):
        for link in self.sociallinks:
            self.array.append(str(link).split(".")[1])
        return self.array


    headers = StreamField([
        ('h_hero', _H_HeroBlock(null=True, blank=False, icon='image')),
        ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code'))
    ], null=True, blank=False)

    sections = StreamField([
        ('s_why', _S_WhyBlock(null=True, blank=False, icon='group')),
        ('s_individual', _S_IndividualBlock(null=True, blank=False, icon='user', max_num=1)),
        ('s_experts', _S_ExpertsBlock(null=True, blank=False, icon='pick', max_num=1)),
        ('s_lab', _S_LabBlock(null=True, blank=False, icon='snippet')),
        ('s_method', _S_MethodBlock(null=True, blank=False, icon='site')),
        ('s_services', _S_ServicesBlock(null=True, blank=False, icon='openquote')),
        ('s_reviews', _S_ReviewsBlock(null=True, blank=False, icon='form')),
        ('s_features', _S_FeaturesBlock(null=True, blank=False, icon='fa-th')),
        ('s_steps', _S_StepsBlock(null=True, blank=False, icon='fa-list-ul')),
        ('s_manifest', _S_ManifestBlock(null=True, blank=False, icon='fa-comments')),
        ('s_facebook', _S_FacebookBlock(null=True, blank=False, icon='fa-facebook-official')),
        ('s_instagram', _S_InstagramBlock(null=True, blank=False, icon='fa-instagram')),
        ('s_pricing', _S_PricingBlock(null=True, blank=False, icon='home')),
        ('s_about', _S_AboutBlock(null=True, blank=False, icon='fa-quote-left')),
        ('code', blocks.RawHTMLBlock(null=True, blank=True, classname="full", icon='code'))
    ], null=True, blank=False)

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

    main_content_panels = [
        StreamFieldPanel('headers'),
        StreamFieldPanel('sections')
    ]

    imprint_panels = [
        MultiFieldPanel(
            [
            FieldPanel('city'),
            FieldPanel('zip_code'),
            FieldPanel('address'),
            FieldPanel('telephone'),
            FieldPanel('telefax'),
            FieldPanel('whatsapp_telephone'),
            FieldPanel('whatsapp_contactline'),
            FieldPanel('email'),
            FieldPanel('copyrightholder')
            ],
            heading="contact",
        ),
        MultiFieldPanel(
            [
            FieldPanel('vat_number'),
            FieldPanel('tax_id'),
            FieldPanel('trade_register_number'),
            FieldPanel('court_of_registry'),
            FieldPanel('place_of_registry'),
            FieldPanel('trade_register_number'),
            FieldPanel('ownership')
            ],
            heading="legal",
        ),
        StreamFieldPanel('sociallinks'),
        MultiFieldPanel(
            [
            FieldPanel('about'),
            FieldPanel('privacy')
            ],
            heading="privacy",
        )
    ]

    token_panel = [
        FieldPanel('token')
    ]

    edit_handler = TabbedInterface([
        ObjectList(Page.content_panels + main_content_panels, heading='Main'),
        ObjectList(imprint_panels, heading='Imprint'),
        ObjectList(Page.promote_panels + token_panel + Page.settings_panels, heading='Settings', classname="settings")
    ])
Example #37
0
class ShortCoursePage(ContactFieldsMixin, BasePage):
    template = "patterns/pages/shortcourses/short_course.html"
    parent_page_types = ["programmes.ProgrammeIndexPage"]
    hero_image = models.ForeignKey(
        "images.CustomImage",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    introduction = models.CharField(max_length=500, blank=True)
    introduction_image = models.ForeignKey(
        get_image_model_string(),
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    video_caption = models.CharField(
        blank=True,
        max_length=80,
        help_text="The text dipsplayed next to the video play button",
    )
    video = models.URLField(blank=True)
    body = RichTextField(blank=True)
    about = StreamField(
        [("accordion_block", AccordionBlockWithTitle())],
        blank=True,
        verbose_name=_("About the course"),
    )

    access_planit_course_id = models.IntegerField(blank=True, null=True)
    frequently_asked_questions = models.ForeignKey(
        "utils.ShortCourseDetailSnippet",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    terms_and_conditions = models.ForeignKey(
        "utils.ShortCourseDetailSnippet",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="+",
    )
    course_details_text = RichTextField(blank=True)
    show_register_link = models.BooleanField(
        default=1,
        help_text="If selected, an automatic 'Register your interest' link will be \
                                                                   visible in the key details section",
    )
    course_details_text = RichTextField(blank=True)
    programme_type = models.ForeignKey(
        ProgrammeType,
        on_delete=models.SET_NULL,
        blank=False,
        null=True,
        related_name="+",
    )
    location = RichTextField(blank=True, features=["link"])
    introduction = models.CharField(max_length=500, blank=True)

    quote_carousel = StreamField(
        [("quote", QuoteBlock())], blank=True, verbose_name=_("Quote carousel")
    )
    staff_title = models.CharField(
        max_length=50,
        blank=True,
        help_text="Heading to display above the short course team members, E.G Programme Team",
    )
    gallery = StreamField(
        [("slide", GalleryBlock())], blank=True, verbose_name="Gallery"
    )
    external_links = StreamField(
        [("link", LinkBlock())], blank=True, verbose_name="External Links"
    )
    application_form_url = models.URLField(
        blank=True,
        help_text="Adding an application form URL will override the Access Planit booking modal",
    )
    manual_registration_url = models.URLField(
        blank=True, help_text="Override the register interest link show in the modal",
    )

    access_planit_and_course_data_panels = [
        MultiFieldPanel(
            [
                FieldPanel("manual_registration_url"),
                HelpPanel(
                    "Defining course details manually will override any Access Planit data configured for this page"
                ),
                InlinePanel("manual_bookings", label="Booking"),
            ],
            heading="Manual course configuration",
        ),
        MultiFieldPanel(
            [FieldPanel("application_form_url")], heading="Application URL"
        ),
        FieldPanel("access_planit_course_id"),
        MultiFieldPanel(
            [
                FieldPanel("course_details_text"),
                SnippetChooserPanel("frequently_asked_questions"),
                SnippetChooserPanel("terms_and_conditions"),
            ],
            heading="course details",
        ),
    ]
    content_panels = BasePage.content_panels + [
        MultiFieldPanel([ImageChooserPanel("hero_image")], heading=_("Hero"),),
        MultiFieldPanel(
            [
                FieldPanel("introduction"),
                ImageChooserPanel("introduction_image"),
                FieldPanel("video"),
                FieldPanel("video_caption"),
                FieldPanel("body"),
            ],
            heading=_("Course Introduction"),
        ),
        StreamFieldPanel("about"),
        FieldPanel("programme_type"),
        StreamFieldPanel("quote_carousel"),
        MultiFieldPanel(
            [FieldPanel("staff_title"), InlinePanel("related_staff", label="Staff")],
            heading="Short course team",
        ),
        StreamFieldPanel("gallery"),
        MultiFieldPanel([*ContactFieldsMixin.panels], heading="Contact information"),
        MultiFieldPanel(
            [InlinePanel("related_programmes", label="Related programmes")],
            heading="Related Programmes",
        ),
        MultiFieldPanel(
            [
                InlinePanel(
                    "related_schools_and_research_pages",
                    label=_("Related Schools and Research centre pages"),
                )
            ],
            heading=_("Related Schools and Research Centre pages"),
        ),
        StreamFieldPanel("external_links"),
    ]
    key_details_panels = [
        InlinePanel("fee_items", label="Fees"),
        FieldPanel("location"),
        FieldPanel("show_register_link"),
        InlinePanel("subjects", label=_("Subjects")),
    ]

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

    search_fields = BasePage.search_fields + [
        index.SearchField("introduction", partial_match=True),
        index.AutocompleteField("introduction", partial_match=True),
        index.RelatedFields(
            "programme_type",
            [
                index.SearchField("display_name", partial_match=True),
                index.AutocompleteField("display_name", partial_match=True),
            ],
        ),
        index.RelatedFields(
            "subjects",
            [
                index.RelatedFields(
                    "subject",
                    [
                        index.SearchField("title", partial_match=True),
                        index.AutocompleteField("title", partial_match=True),
                    ],
                )
            ],
        ),
    ]

    api_fields = [
        # Fields for filtering and display, shared with programmes.ProgrammePage.
        APIField("subjects"),
        APIField("programme_type"),
        APIField("related_schools_and_research_pages"),
        APIField("summary", serializer=CharFieldSerializer(source="introduction")),
        APIField(
            name="hero_image_square",
            serializer=ImageRenditionField("fill-580x580", source="hero_image"),
        ),
    ]

    @property
    def get_manual_bookings(self):
        return self.manual_bookings.all()

    def get_access_planit_data(self):
        access_planit_course_data = AccessPlanitXML(
            course_id=self.access_planit_course_id
        )
        return access_planit_course_data.get_data()

    def _format_booking_bar(self, register_interest_link=None, access_planit_data=None):
        """ Booking bar messaging with the next course data available
        Find the next course date marked as status='available' and advertise
        this date in the booking bar. If there are no courses available, add
        a default message."""

        booking_bar = {
            "message": "Bookings not yet open",
            "action": "Register your interest for upcoming dates",
        }
        # If there are no dates the booking link should go to a form, not open
        # a modal, this link is also used as a generic interest link too though.
        booking_bar["link"] = register_interest_link

        # If manual_booking links are defined, format the booking bar and return
        # it before checking access planit
        if self.manual_bookings.first():
            date = self.manual_bookings.first()
            booking_bar["message"] = "Next course starts"
            booking_bar["date"] = date.start_date
            if date.booking_link:
                booking_bar["action"] = (
                    f"Book from \xA3{date.cost}" if date.cost else "Book now"
                )
            booking_bar["modal"] = "booking-details"
            booking_bar["cost"] = date.cost
            return booking_bar

        # If there is access planit data, format the booking bar
        if access_planit_data:
            for date in access_planit_data:
                if date["status"] == "Available":
                    booking_bar["message"] = "Next course starts"
                    booking_bar["date"] = date["start_date"]
                    booking_bar["cost"] = date["cost"]
                    if self.application_form_url:
                        # URL has been provided to override AccessPlanit booking
                        booking_bar["action"] = "Complete form to apply"
                        booking_bar["link"] = self.application_form_url
                        booking_bar["modal"] = None
                    else:
                        # Open AccessPlanit booking modal
                        booking_bar["action"] = f"Book now from \xA3{date['cost']}"
                        booking_bar["link"] = None
                        booking_bar["modal"] = "booking-details"
                    break
            return booking_bar

        # Check for a manual_registration_url if there is no data
        if self.manual_registration_url:
            booking_bar["link"] = self.manual_registration_url
        return booking_bar

    def clean(self):
        super().clean()
        errors = defaultdict(list)
        if (
            self.show_register_link
            and not self.manual_registration_url
            and not self.access_planit_course_id
        ):
            errors["show_register_link"].append(
                "An access planit course ID or manual registration link is needed to show the register links"
            )
        if self.access_planit_course_id:
            try:
                checker = AccessPlanitCourseChecker(
                    course_id=self.access_planit_course_id
                )
                if not checker.course_exists():
                    errors["access_planit_course_id"].append(
                        "Could not find a course with this ID"
                    )
            except AccessPlanitException:
                errors["access_planit_course_id"].append(
                    "Error checking this course ID in Access Planit. Please try again shortly."
                )

        if errors:
            raise ValidationError(errors)

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        access_planit_data = self.get_access_planit_data()
        context[
            "register_interest_link"
        ] = (
            register_interest_link
        ) = f"{settings.ACCESS_PLANIT_REGISTER_INTEREST_BASE}?course_id={self.access_planit_course_id}"
        context["booking_bar"] = self._format_booking_bar(
            register_interest_link, access_planit_data
        )
        context["booking_bar"] = self._format_booking_bar(
            register_interest_link, access_planit_data
        )
        context["access_planit_data"] = access_planit_data
        context["shortcourse_details_fees"] = self.fee_items.values_list(
            "text", flat=True
        )
        context["related_sections"] = [
            {
                "title": "More opportunities to study at the RCA",
                "related_items": [
                    rel.page.specific
                    for rel in self.related_programmes.select_related("page")
                ],
            }
        ]
        context["related_staff"] = self.related_staff.select_related(
            "image"
        ).prefetch_related("page")
        return context
Example #38
0
class TabbedSettings(TestSetting):
    edit_handler = TabbedInterface([
        ObjectList([FieldPanel('title')], heading='First tab'),
        ObjectList([FieldPanel('email')], heading='Second tab'),
    ])
Example #39
0
class Course(RoutablePageMixin, TranslatedPage, BodyMixin):
    class Meta:
        verbose_name = _('Course date')

    parent_page_types = [CourseInformationPage]

    start = models.DateField(verbose_name=_('start date'))
    end = models.DateField(blank=True,
                           null=True,
                           verbose_name=_('end date'),
                           help_text=_('leave empty for a one-day course'))

    overrule_parent = models.BooleanField(
        default=False, verbose_name=_('Overrule standard settings'))
    register_via_website = models.BooleanField(
        default=False, verbose_name=_('Registration via website'))

    share_data_via_website = models.BooleanField(
        default=False, verbose_name=_('Share data via website'))

    max_attendees = models.IntegerField(blank=True,
                                        null=True,
                                        default=None,
                                        verbose_name=_('Max attendees'))

    script = StreamField(
        [
            ('chapter',
             blocks.CharBlock(
                 classname="full title", required=True, label='Kapitel')),
            ('section', blocks.CharBlock(required=True, label='Unterkapitel')),
            ('file', DocumentChooserBlock(label="Datei mit Folien")),
        ],
        verbose_name=_('Script for the course'),
        help_text=_('This allows to combine several PDFs to a single one'),
        null=True,
        blank=True)

    script_title = models.CharField(max_length=512, blank=True)
    script_subtitle1 = models.CharField(max_length=512, blank=True)
    script_subtitle2 = models.CharField(max_length=512, blank=True)
    script_date = models.CharField(max_length=2,
                                   choices=(
                                       ('d', 'german'),
                                       ('e', 'english'),
                                       ('n', 'no date'),
                                   ),
                                   default='n')

    fill_automatically = models.BooleanField(
        default=False,
        help_text=_('Fill with attendees from waitlist automatically?'))

    content_panels = [
        FieldRowPanel([FieldPanel('start'),
                       FieldPanel('end')]),
        StreamFieldPanel('body_en'),
        StreamFieldPanel('body_de'),
    ]

    settings_panels = [
        FieldPanel('overrule_parent'),
        MultiFieldPanel([
            FieldRowPanel([
                FieldPanel('register_via_website'),
                FieldPanel('share_data_via_website'),
            ]),
        ],
                        heading=_('Registration options')),
        FieldPanel('max_attendees'),
        InlinePanel('attendee_types', label=_('allowed attendees'))
    ]

    script_panel = [
        FieldPanel('script_title'),
        FieldPanel('script_subtitle1'),
        FieldPanel('script_subtitle2'),
        FieldPanel('script_date'),
        StreamFieldPanel('script')
    ]

    edit_handler = TabbedInterface([
        ObjectList(content_panels, heading=_('Date and info')),
        ObjectList(settings_panels, heading=_('Overrule parent settings')),
        ObjectList(script_panel, heading=_('Script')),
    ])

    def clean(self):
        self.title = '{}–{}'.format(self.start, self.end)
        self.title_de = self.title
        if not self.slug:
            self.slug = self._get_autogenerated_slug(slugify(self.title))

    def _get_me_or_parent(self):
        if self.overrule_parent:
            return self
        else:
            return self.get_parent().specific

    @property
    def is_bookable(self):
        return self._get_me_or_parent().register_via_website

    def started_in_past(self):
        return self.start <= datetime.date.today()

    def get_attendee_types(self):
        return self._get_me_or_parent().attendee_types

    def get_max_attendees(self):
        return self._get_me_or_parent().max_attendees

    @property
    def has_data_sharing(self):
        return self._get_me_or_parent().share_data_via_website

    def get_price(self, attendee):

        for at_rel in self.get_attendee_types().all():
            if at_rel.get_attendee_class() == attendee:
                return at_rel.price
        return 0

    def has_waitlist_for(self, attendee):
        return self.get_attendee_types().filter(attendee=attendee.identifier,
                                                waitlist=True).count() > 0

    @property
    def registered_attendees_stats(self):
        '''
        Returns an overview of the the registered attendees
        '''
        attendees = {}

        for atype in self.get_attendee_types().all():
            Klass = atype.get_attendee_class()
            if not Klass:
                continue
            try:
                disp = Klass.display_name_plural
            except AttributeError:
                disp = Klass.display_name

            # The total number of attendees for one attendee-type
            # is the sum of
            #
            # a) those who are validated

            qs = Klass.objects.filter(related_course=self)
            num = qs.filter(is_validated=True).count()

            # b) those who are not yet validated, but the
            # mail asking for validation has been sent within the last week

            minus_one_week = datetime.date.today() - relativedelta(weeks=1)

            num = num + qs.filter(
                is_validated=False,
                validation_mail_sent=True,
                validation_mail_sent_at__gte=minus_one_week).count()

            # c) People from the waitlist that have been asked to
            # attend this course, but have not yet answered and the
            # mail was sent within the last week

            num = num + Klass.objects.filter(
                waitlist_course=self,
                add2course_mail_sent=True,
                add2course_mail_sent_at__gte=minus_one_week).count()

            # This is just in case that two or more attendee types have the
            # same display name. Should not happen! It might be better
            # to solve this by something like:
            # { 'unique' : ('display', num) }
            #
            # (see method get_free_slots below)
            c = 0
            while disp in attendees:
                c += 1
                disp = "{}_{}".format(disp, c)

            attendees[disp] = num
        return attendees

    def get_free_slots(self, Attendee):

        print('Attendee is: {}'.format(Attendee))
        stats = self.registered_attendees_stats

        # we need idx to be a unique index.
        try:
            idx = Attendee.display_name_plural
        except AttributeError:
            idx = Attendee.display_name

        try:
            n_attendees = stats[idx]
        except KeyError:
            # if idx is not in stats, this Attendee type is not allowed
            # for this course. Thus, free slots is 0
            return 0

        # get max_attendees for this attendee-type, if exists

        limit_type = (self.get_attendee_types().get(
            attendee=Attendee.identifier).max_attendees)
        if limit_type is not None:
            attendee_free = limit_type - n_attendees
            if attendee_free < 0:
                # might happen during testing or if someone adds an attendee
                # manually
                attendee_free = 0
        else:
            attendee_free = None

        limit_total = self.get_max_attendees()

        # compute total number of attendees
        total_attendees = 0
        for k, v in stats.items():
            total_attendees = total_attendees + v

        total_free = limit_total - total_attendees
        if total_free < 0:
            # see above...
            total_free = 0

        # Very import to test for None, since 0 is interpreted as False...
        if attendee_free is not None:
            return min(total_free, attendee_free)
        else:
            return total_free

    def get_n_attendees(self):
        c = 0
        for k, v in self.registered_attendees_stats.items():
            c = c + v

        return c

    @property
    def is_fully_booked(self):
        try:
            return self.get_n_attendees() >= self.get_max_attendees()
        except TypeError:
            return False

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

        # test whether there are any free slots in this course

        n_attendees = 0

        for k, v in self.registered_attendees_stats.items():
            n_attendees = n_attendees + v

        # is there any attendee_type with a waitlist?

        waitlist = self.get_attendee_types().all().filter(
            waitlist=True).count() > 0

        if self.get_max_attendees():
            if not waitlist and self.get_max_attendees() <= n_attendees:
                context['fully_booked'] = True
            else:
                context['fully_booked'] = False
        # test if we have a waitlist for any allowed attendee type
        # and no slots left for the type...
        free_courses = {}
        for a2c_rel in self.get_attendee_types().all().filter(waitlist=True):
            if self.get_free_slots(a2c_rel.get_attendee_class()) == 0:

                # Do we have another course, not fully booked, for that attendee type?

                upcoming_courses = self.get_parent(
                ).specific.get_upcoming_courses().exclude(id=self.id)
                for uc in upcoming_courses:
                    if uc.specific.get_free_slots(
                            a2c_rel.get_attendee_class()) > 0:
                        if not a2c_rel.get_attendee_class(
                        ).display_name_plural in free_courses.keys():
                            free_courses[a2c_rel.get_attendee_class().
                                         display_name_plural] = []
                        free_courses[a2c_rel.get_attendee_class().
                                     display_name_plural].append(uc)

        if free_courses:
            context['show_waitlist_info'] = True
            context['free_courses'] = free_courses

        return context

    @route(r'^register/(\w+)/$', name="register")
    @route(r'^register/(\w+)/(\d+)/$', name="register")
    def register_for_course(self, request, attendee, form_count=0):
        # implements a multi-step form for registration.
        # The forms are provided by `forms` list of the corresponding attendee class.

        # get the form count
        form_count = int(form_count)

        # if a form has been filled is stored in request.session.form<number>
        # Avoid that a form is shown before the previous has been filled:
        if form_count > 0:
            if not request.session.get('form{}_filled'.format(form_count - 1)):
                return redirect(self.url + self.reverse_subpage(
                    'register', args=[attendee, form_count - 1]))

        # get the attende by the "register_attendee" mechanism and raise a 404 if not known
        Attendee = get_attendee_class(attendee)

        if not Attendee:
            raise Http404

        # IF THE COURSE IS IN THE PAST, RAISE A 404

        if self.start <= datetime.date.today():
            raise Http404

        # @TODO: Test if attendee is allowed for this course

        # collect all form classes...
        forms = []
        for f in Attendee.forms:
            module_name, class_name = f.rsplit(".", 1)
            module = importlib.import_module(module_name)
            FormKlass = getattr(module, class_name)
            forms.append(FormKlass)

        # Check if there are earlier courses that have a waitlist for this Attendee class

        waitlist_available = False
        previous_courses = self.get_parent().specific.get_upcoming_courses(
        ).filter(start__lt=self.start).order_by('-start')
        for pc in previous_courses.all():
            if pc.get_free_slots(Attendee) == 0 and pc.get_attendee_types(
            ).filter(waitlist=True, attendee=Attendee.identifier).count() > 0:
                waitlist_available = True
                waitlist_course = pc
                break

        # and if there is one, add the respective form class in the second last position
        # but only, if this regsitration is for the course, and not for the waitlist of this course
        has_waitlist = self.has_waitlist_for(Attendee)
        free_slots = self.get_free_slots(Attendee)
        uses_waitlist = has_waitlist and free_slots < 1
        if uses_waitlist:
            waitlist_available = False
        if waitlist_available:
            from .forms import WaitlistForm
            last_form = forms[-1]
            forms[-1] = WaitlistForm
            forms.append(last_form)

        # ... and get the current one
        try:
            CurrentFormKlass = forms[form_count]
        except KeyError:
            raise Http404

        kwargs = {}

        # there might be some values passed from the previous form
        provided_values = request.session.get(
            'provided_values{}'.format(form_count - 1), None)

        if provided_values:
            kwargs['provided_values'] = provided_values

        print('Form count is: {}'.format(form_count))
        if waitlist_available and form_count == len(forms) - 2:
            kwargs['course'] = pc

        # now the usual stuff
        if request.method == 'GET':

            # @TODO.
            # This code might be executed if someone clicks on the "step indicator"
            # try to fill it with the already collected data...
            if request.session.get('form{}_filled'.format(form_count), None):
                data_json = request.session.get('cleaned_data', None)
                if data_json:
                    data = json.loads(data_json)
                else:
                    data = {}
                kwargs['initial'] = data
            else:
                if provided_values:
                    kwargs['initial'] = provided_values

            form = CurrentFormKlass(**kwargs)

        if request.method == 'POST':
            form = CurrentFormKlass(request.POST, **kwargs)

            if form.is_valid():
                if hasattr(form, 'is_validated'):
                    cleaned_data = {'is_validated': True}
                else:
                    cleaned_data = {}

                # Get the json for the data from the session
                data_json = request.session.get('cleaned_data', None)
                if data_json:
                    cleaned_data.update(json.loads(data_json))

                if issubclass(CurrentFormKlass,
                              ModelForm) or (waitlist_available
                                             and form_count == len(forms) - 2):
                    for key, value in form.cleaned_data.items():
                        cleaned_data[key] = value

                else:
                    try:
                        request.session['provided_values{}'.format(
                            form_count)] = form.provided_values
                    except AttributeError:
                        request.session['provided_values{}'.format(
                            form_count)] = {}

                request.session['form{}_filled'.format(form_count)] = True
                request.session['cleaned_data'] = json.dumps(cleaned_data,
                                                             cls=PHJsonEncoder)
                request.session.modified = True

                if form_count < len(forms) - 1:
                    messages.info(
                        request,
                        format_lazy(
                            'Your data for step {} has been saved. Please proceed with step {}.',
                            form_count + 1, form_count + 2))
                    return redirect(self.url + self.reverse_subpage(
                        'register', args=[attendee, form_count + 1]))
                else:
                    instance = form.save(commit=False)
                    for key, value in cleaned_data.items():
                        if key == 'waitlist' and waitlist_available:
                            setattr(instance, 'waitlist_course', pc)
                        else:
                            setattr(instance, key, value)

                    # Attendees that have to pay have the amount of the fee and the
                    # amount already payed in the database. We need to provide values here...
                    if hasattr(instance, 'amount'):
                        instance.amount = self.get_price(Attendee)
                    if hasattr(instance, 'payed'):
                        instance.payed = False

                    instance.related_course = self
                    if self.get_free_slots(Attendee) > 0:
                        instance.save()
                        if not instance.is_validated:
                            return redirect(self.url + self.reverse_subpage(
                                'validation_mail',
                                args=[instance.courseattendee_ptr_id]))
                        else:
                            return redirect(self.url + self.reverse_subpage(
                                'success',
                                args=[instance.courseattendee_ptr_id]))
                    elif has_waitlist:
                        instance.waitlist_course = self
                        instance.related_course = None
                        instance.save()
                        messages.info(request,
                                      _('You were put on the waiting list.'))
                        if not instance.is_validated:
                            return redirect(self.url + self.reverse_subpage(
                                'validation_mail',
                                args=[instance.courseattendee_ptr_id]))
                        else:
                            return redirect(self.url + self.reverse_subpage(
                                'success',
                                args=[instance.courseattendee_ptr_id]))
                    else:
                        messages.error(
                            request,
                            _('The course is fully booked. You have not been registered.'
                              ))
                        messages.info(request,
                                      _('We have not stored your data.'))
                        try:
                            del request.session['cleaned_data']
                        except KeyError:
                            pass
                        return redirect(self.url)

        price = self.get_price(Attendee)
        show_price_hint = price > 0 and form_count == len(forms) - 1

        return TemplateResponse(
            request, 'courses/register.html', {
                'attendee': attendee,
                'attendee_name': Attendee.display_name,
                'forms': forms,
                'current_form': form_count,
                'form': form,
                'page': self,
                'price': price,
                'show_price_hint': show_price_hint,
                'free_slots': free_slots,
                'has_waitlist': has_waitlist
            })

    @route(r'^success/(\d+)/$', name="success")
    def success(self, request, attendee_id):

        attendee = get_object_or_404(CourseAttendee, id=attendee_id)
        if attendee.confirmation_mail_sent:
            raise Http404

        mailtext = EMailText.objects.get(
            identifier='courses.registration.confirmation')
        mailtext.send(
            attendee.email, {
                'attendee': attendee,
                'course': self,
                'waitlist': attendee.waitlist_course == self
            })
        self.send_invoice(attendee)
        try:
            del request.session['cleaned_data']
        except KeyError:
            pass
        return TemplateResponse(request, 'courses/success.html',
                                {'page': self})

    @route(r'^validation_required/(\d+)/$', name="validation_mail")
    def validation_mail(self, request, attendee_id):
        attendee = get_object_or_404(CourseAttendee, id=attendee_id)

        validation = CourseParticipationValidation()
        validation.attendee = attendee
        validation.save()

        now = datetime.datetime.now()

        attendee.validation_mail_sent = True
        attendee.validation_mail_sent_at = now
        attendee.save()

        valid_until = now + relativedelta(weeks=+1)

        if valid_until.date() >= self.start:
            valid_until = self.start + relativedelta(days=-2)

        mailtext = EMailText.objects.get(
            identifier='courses.registration.validation')
        mailtext.send(
            attendee.email, {
                'attendee':
                attendee,
                'course':
                self,
                'validation_url':
                request.build_absolute_uri(
                    reverse('manage-courses:validate_course_registration',
                            kwargs={'uuid': validation.uuid})),
                'validation':
                validation,
                'valid_until':
                valid_until
            })
        try:
            del request.session['cleaned_data']
        except KeyError:
            pass
        return TemplateResponse(request, 'courses/validation_mail.html', {
            'page': self,
            'valid_until': valid_until,
            'attendee': attendee
        })

    def send_invoice(self, attendee):

        # get the specific course attendee object
        for Kls in ATTENDEE_TYPES:
            try:
                s_att = Kls.objects.get(courseattendee_ptr_id=attendee.id)
                break
            except Kls.DoesNotExist:
                pass

        attendee = s_att
        try:
            if attendee.amount == 0:
                return
        except AttributeError:
            return

        parent = self.get_parent().specific
        staff_mails = [
            staff.person.mail for staff in parent.contact_persons.all()
        ]

        pdffile = RUBIONCourseInvoice(
            attendee,
            parent.contact_persons.first().person,
        )
        pdffilename = pdffile.write2file()

        mailtext = EMailText.objects.get(
            identifier='courses.staff.invoice_copy')
        mailtext.send(staff_mails, {
            'course': self,
            'attendee': attendee,
            'invoice_was_sent': False
        },
                      attachements=[pdffilename],
                      lang='de')

        # Should mails be sent automatically or manually?
        # I guess we said manually, at least in the beginning.

        #MAILTEXT = EMailText.objects.get(identifier = 'courses.attendee.invoice' )
        #mailtext.send(
        #    attendee.email,
        #    {
        #        'attendee': attendee,
        #        'course' : self
        #    }
        #)

    def get_data_sharing_page(self):
        if DataSharingPage.objects.child_of(self).exists():
            return DataSharingPage.objects.child_of(self).first()
        else:
            return None

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

        if self.has_data_sharing:
            if not self.get_data_sharing_page():
                dsp = DataSharingPage()
                dsp.clean()
                self.add_child(dsp)
                characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_*?!$&"
                pvr = PageViewRestriction(
                    page=dsp,
                    restriction_type=PageViewRestriction.PASSWORD,
                    password="".join(
                        random.choice(characters) for __ in range(10)))
                pvr.save()
Example #40
0
StandardIndex.content_panels = [
    FieldPanel('title', classname="full title"),
    FieldPanel('seo_title'),
    FieldPanel('slug'),
    InlinePanel('advert_placements', label="Adverts"),
]
StandardIndex.promote_panels = []


class StandardChild(Page):
    pass


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


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


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

    # BusinessNowherePage is 'incorrectly' added here as a possible child.
Example #41
0
class ExperimentPage(Page, CommentsMixIn):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='experiment_pages')
    posted = models.DateTimeField(default=timezone.now)

    hypothesis = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])

    method = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])

    measurement = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])


    importance = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA)
    cost = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA)
    time_required = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA)
    data_reliability = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA)
    action_required = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA)

    observation = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])

    learning = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])

    action = StreamField([
        ('heading', blocks.CharBlock(classname="full title")),
        ('paragraph', blocks.RichTextBlock()),
        ('image', ImageChooserBlock()),
        ('document', DocumentChooserBlock()),
    ])

    ranking_panels = [
        FieldPanel('importance'),
        FieldPanel('cost'),
        FieldPanel('time_required'),
        FieldPanel('data_reliability'),
        FieldPanel('action_required'),
    ]

    content_panels = Page.content_panels + [
        AutocompletePanel('user', target_model=settings.AUTH_USER_MODEL),
        FieldPanel('posted'),
        StreamFieldPanel('hypothesis'),
        StreamFieldPanel('method'),
        StreamFieldPanel('measurement'),
        StreamFieldPanel('observation'),
        StreamFieldPanel('learning'),
        StreamFieldPanel('action'),
        InlinePanel('comments', label="Comments"),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('hypothesis'),
        index.SearchField('method'),
        index.SearchField('measurement'),
        index.SearchField('observation'),
        index.SearchField('learning'),
        index.SearchField('action'),
    ]

    subpage_types = [
        'experiment.ExperimentPage'
    ]

    # parent_page_types = [
    #     'experiment.ExperimentPage',
    #     'homepage.ContentPage',
    # ]


    edit_handler = TabbedInterface(
        [
            ObjectList(content_panels, heading='Content'),
            ObjectList(ranking_panels, heading="Ranking"),
            ObjectList(Page.promote_panels, heading='Promote'),
            ObjectList(Page.settings_panels, heading='Settings'),
        ]
    )


    def __str__(self):
        return f'Experiment Page {self.hypothesis}'

    def serve(self, request):    
        from .forms import CommentForm
        return self.add_comments_and_return(request, CommentForm)
Example #42
0
class EnforcementActionPage(AbstractFilterPage):
    sidebar_header = models.CharField(default='Action details', max_length=100)
    court = models.CharField(default='', max_length=150, blank=True)
    institution_type = models.CharField(max_length=50,
                                        choices=[('Nonbank', 'Nonbank'),
                                                 ('Bank', 'Bank')])

    content = StreamField([
        ('full_width_text', organisms.FullWidthText()),
        ('expandable', organisms.Expandable()),
        ('expandable_group', organisms.ExpandableGroup()),
        ('notification', molecules.Notification()),
        ('table_block',
         organisms.AtomicTableBlock(table_options={'renderer': 'html'})),
        ('feedback', v1_blocks.Feedback()),
    ],
                          blank=True)

    content_panels = [StreamFieldPanel('header'), StreamFieldPanel('content')]

    metadata_panels = [
        MultiFieldPanel([
            FieldPanel('sidebar_header'),
            FieldPanel('court'),
            FieldPanel('institution_type'),
            FieldPanel('date_filed'),
            FieldPanel('tags', 'Tags'),
        ],
                        heading='Basic Metadata'),
        MultiFieldPanel([
            InlinePanel('docket_numbers', label="Docket Number", min_num=1),
        ],
                        heading='Docket Number'),
        MultiFieldPanel([
            InlinePanel('statuses', label="Enforcement Status", min_num=1),
        ],
                        heading='Enforcement Status'),
        MultiFieldPanel([
            InlinePanel('categories', label="Categories", min_num=1,
                        max_num=2),
        ],
                        heading='Categories'),
    ]

    settings_panels = [
        MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'),
        MultiFieldPanel([
            FieldPanel('preview_title'),
            FieldPanel('preview_subheading'),
            FieldPanel('preview_description'),
            FieldPanel('secondary_link_url'),
            FieldPanel('secondary_link_text'),
            ImageChooserPanel('preview_image'),
        ],
                        heading='Page Preview Fields',
                        classname='collapsible'),
        FieldPanel('authors', 'Authors'),
        MultiFieldPanel([
            FieldPanel('date_published'),
            FieldPanel('comments_close_by'),
        ],
                        'Relevant Dates',
                        classname='collapsible'),
        MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'),
        FieldPanel('language', 'Language'),
    ]

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

    template = 'enforcement-action/index.html'

    objects = PageManager()

    search_fields = AbstractFilterPage.search_fields + [
        index.SearchField('content')
    ]
Example #43
0
class RegulationPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage):
    """A routable page for serving an eregulations page by Section ID."""

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

    template = 'regulations3k/browse-regulation.html'

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

    content = StreamField([
        ('info_unit_group', organisms.InfoUnitGroup()),
        ('full_width_text', organisms.FullWidthText()),
    ],
                          null=True,
                          blank=True)

    regulation = models.ForeignKey(Part,
                                   blank=True,
                                   null=True,
                                   on_delete=models.PROTECT,
                                   related_name='page')

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

    secondary_nav_exclude_sibling_pages = models.BooleanField(default=False)

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

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

    def can_serve_draft_versions(self, request):
        perms = request.user.get_all_permissions()
        if (request.user.is_superuser
                or getattr(request, 'served_by_wagtail_sharing', False)
                or 'regulations3k.change_section' in perms):
            return True
        return False

    def get_versions_query(self, request):
        versions = self.regulation.versions

        if not self.can_serve_draft_versions(request):
            versions = versions.filter(draft=False)

        return versions

    def get_effective_version(self, request, date_str=None):
        """ Get the requested effective version if the user has permission """
        query_filter = {}

        if date_str is None:
            query_filter['effective_date__lte'] = date.today()
        else:
            query_filter['effective_date'] = date_str

        draft_permission = self.can_serve_draft_versions(request)
        if not draft_permission:
            query_filter['draft'] = False

        effective_version = self.regulation.versions.filter(
            **query_filter).order_by('-effective_date').first()

        if effective_version is None:
            raise Http404

        return effective_version

    def get_section_query(self, request=None, effective_version=None):
        """Query set for Sections in this regulation's effective version."""
        if effective_version is None:
            effective_version = self.get_effective_version(request)
        return Section.objects.filter(subpart__version=effective_version)

    def get_context(self, request, *args, **kwargs):
        context = super(RegulationPage,
                        self).get_context(request, *args, **kwargs)
        context.update({
            'regulation':
            self.regulation,
            'current_version':
            self.get_effective_version(request),
            'breadcrumb_items':
            self.get_breadcrumbs(request, *args, **kwargs),
            'search_url':
            (self.get_parent().url + 'search-regulations/results/?regs=' +
             self.regulation.part_number),
            'num_versions':
            self.get_versions_query(request).count(),
        })
        return context

    def get_breadcrumbs(self, request, section=None, **kwargs):
        crumbs = super(RegulationPage, self).get_breadcrumbs(request)

        if section is not None:
            crumbs = crumbs + [
                {
                    'href':
                    self.url + self.reverse_subpage(
                        'index',
                        kwargs={
                            k: v
                            for k, v in kwargs.items() if k == 'date_str'
                        }),
                    'title':
                    str(section.subpart.version.part),
                },
            ]

        return crumbs

    def get_urls_for_version(self, effective_version, section=None):
        base_url = self.get_full_url()
        versions_url = urljoin(base_url, 'versions') + '/'

        if effective_version.live_version:
            # This is the current version
            version_url = base_url
        else:
            # It's a past or future version, URLs have the date str
            date_str = str(effective_version.effective_date)
            version_url = urljoin(base_url, date_str) + '/'
            yield version_url

        if section is not None:
            yield urljoin(version_url, section.label) + '/'
        else:
            sections = self.get_section_query(
                effective_version=effective_version)
            yield version_url
            yield versions_url
            for section in sections.all():
                yield urljoin(version_url, section.label) + '/'

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

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

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

        return template.render(context)

    @route(r'^(?:(?P<date_str>[0-9]{4}-[0-9]{2}-[0-9]{2})/)?$', name="index")
    def index_route(self, request, date_str=None):
        request.is_preview = getattr(request, 'is_preview', False)

        effective_version = self.get_effective_version(request,
                                                       date_str=date_str)
        section_query = self.get_section_query(
            effective_version=effective_version)
        sections = list(section_query.all())

        context = self.get_context(request)
        context.update({
            'requested_version':
            effective_version,
            'sections':
            sections,
            'get_secondary_nav_items':
            partial(get_secondary_nav_items,
                    sections=sections,
                    date_str=date_str),
        })

        if date_str is not None:
            context['date_str'] = date_str

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

    @route(r'^versions/(?:(?P<section_label>[0-9A-Za-z-]+)/)?$',
           name="versions")
    def versions_page(self, request, section_label=None):
        section_query = self.get_section_query(request=request)
        sections = list(section_query.all())
        context = self.get_context(request, sections=sections)

        versions = [{
            'effective_date':
            v.effective_date,
            'date_str':
            str(v.effective_date),
            'sections':
            self.get_section_query(effective_version=v).all(),
            'draft':
            v.draft
        } for v in self.get_versions_query(request).order_by('-effective_date')
                    ]

        context.update({
            'versions':
            versions,
            'section_label':
            section_label,
            'get_secondary_nav_items':
            partial(get_secondary_nav_items, sections=sections),
        })

        return TemplateResponse(request, self.template, context)

    @route(r'^(?:(?P<date_str>[0-9]{4}-[0-9]{2}-[0-9]{2})/)?'
           r'(?P<section_label>[0-9A-Za-z-]+)/$',
           name="section")
    def section_page(self, request, date_str=None, section_label=None):
        """ Render a section of the currently effective regulation """

        effective_version = self.get_effective_version(request,
                                                       date_str=date_str)
        section_query = self.get_section_query(
            effective_version=effective_version)

        next_version = self.get_versions_query(request).filter(
            effective_date__gt=effective_version.effective_date).first()

        kwargs = {}
        if date_str is not None:
            kwargs['date_str'] = date_str

        try:
            section = section_query.get(label=section_label)
        except Section.DoesNotExist:
            return redirect(self.url +
                            self.reverse_subpage("index", kwargs=kwargs))

        sections = list(section_query.all())
        current_index = sections.index(section)
        context = self.get_context(request,
                                   section,
                                   sections=sections,
                                   **kwargs)

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

        next_section = get_next_section(sections, current_index)
        previous_section = get_previous_section(sections, current_index)

        context.update({
            'requested_version':
            effective_version,
            'next_version':
            next_version,
            'section':
            section,
            'content':
            content,
            'get_secondary_nav_items':
            partial(get_secondary_nav_items,
                    sections=sections,
                    date_str=date_str),
            'next_section':
            next_section,
            'next_url':
            get_section_url(self, next_section, date_str=date_str),
            'previous_section':
            previous_section,
            'previous_url':
            get_section_url(self, previous_section, date_str=date_str),
        })

        return TemplateResponse(request, self.template, context)
Example #44
0
class TabbedSettings(TestSetting):
    edit_handler = TabbedInterface([
        ObjectList([FieldPanel("title")], heading="First tab"),
        ObjectList([FieldPanel("email")], heading="Second tab"),
    ])
Example #45
0
def get_setting_edit_handler(model):
    if hasattr(model, 'edit_handler'):
        return model.edit_handler.bind_to_model(model)
    panels = extract_panel_definitions_from_model_class(model, ['site'])
    return ObjectList(panels).bind_to_model(model)
Example #46
0
class RegulationsSearchPage(RoutablePageMixin, CFGOVPage):
    """A page for the custom search interface for regulations."""

    objects = PageManager()

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

    def get_template(self, request):
        template = 'regulations3k/search-regulations.html'
        if 'partial' in request.GET:
            template = 'regulations3k/search-regulations-results.html'
        return template

    @route(r'^results/')
    def regulation_results_page(self, request):
        all_regs = Part.objects.order_by('part_number')
        regs = validate_regs_list(request)
        order = validate_order(request)
        search_query = request.GET.get('q', '').strip()
        payload = {
            'search_query': search_query,
            'results': [],
            'total_results': 0,
            'regs': regs,
            'all_regs': [],
        }
        if not search_query or len(urllib.parse.unquote(search_query)) == 1:
            self.results = payload
            return TemplateResponse(request, self.get_template(request),
                                    self.get_context(request))
        sqs = SearchQuerySet().filter(content=search_query)
        payload.update({
            'all_regs': [{
                'short_name':
                reg.short_name,
                'id':
                reg.part_number,
                'num_results':
                sqs.filter(
                    part=reg.part_number).models(SectionParagraph).count(),
                'selected':
                reg.part_number in regs
            } for reg in all_regs]
        })
        payload.update({
            'total_count':
            sum([reg['num_results'] for reg in payload['all_regs']])
        })
        if len(regs) == 1:
            sqs = sqs.filter(part=regs[0])
        elif regs:
            sqs = sqs.filter(part__in=regs)
        if order == 'regulation':
            sqs = sqs.order_by('part', 'section_order')
        sqs = sqs.highlight(pre_tags=['<strong>'],
                            post_tags=['</strong>']).models(SectionParagraph)
        for hit in sqs:
            try:
                snippet = Markup(" ".join(hit.highlighted))
            except TypeError as e:
                logger.warning(
                    "Query string {} produced a TypeError: {}".format(
                        search_query, e))
                continue

            short_name = all_regs.get(part_number=hit.part).short_name
            hit_payload = {
                'id':
                hit.paragraph_id,
                'part':
                hit.part,
                'reg':
                short_name,
                'label':
                hit.title,
                'snippet':
                snippet,
                'url':
                "{}{}/{}/#{}".format(self.parent().url, hit.part,
                                     hit.section_label, hit.paragraph_id),
            }
            payload['results'].append(hit_payload)

        payload.update({'current_count': sqs.count()})
        self.results = payload
        context = self.get_context(request)
        num_results = validate_num_results(request)
        paginator = Paginator(payload['results'], num_results)
        page_number = validate_page_number(request, paginator)
        paginated_page = paginator.page(page_number)
        context.update({
            'current_count':
            payload['current_count'],
            'total_count':
            payload['total_count'],
            'paginator':
            paginator,
            'current_page':
            page_number,
            'num_results':
            num_results,
            'order':
            order,
            'results':
            paginated_page,
            'show_filters':
            any(reg['selected'] is True for reg in payload['all_regs'])
        })
        return TemplateResponse(request, self.get_template(request), context)