Beispiel #1
0
class SublandingPage(CFGOVPage):
    portal_topic = models.ForeignKey(
        'v1.PortalTopic',
        blank=True,
        null=True,
        related_name='portal_pages',
        on_delete=models.SET_NULL,
        help_text='Select a topic if this is a MONEY TOPICS portal page.')
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('notification', molecules.Notification()),
        ('featured_content', organisms.FeaturedContent()),
        ('full_width_text', organisms.FullWidthText()),
        ('info_unit_group', organisms.InfoUnitGroup()),
        ('well', organisms.Well()),
        ('snippet_list', organisms.ResourceList()),
        ('post_preview_snapshot', organisms.PostPreviewSnapshot()),
        ('contact', organisms.MainContactInfo()),
        ('table_block',
         organisms.AtomicTableBlock(table_options={'renderer': 'html'})),
        ('reg_comment', organisms.RegComment()),
        ('feedback', v1_blocks.Feedback()),
    ],
                          blank=True)
    sidebar_breakout = StreamField([
        ('slug', blocks.CharBlock(icon='title')),
        ('heading', blocks.CharBlock(icon='title')),
        ('paragraph', blocks.RichTextBlock(icon='edit')),
        ('breakout_image',
         blocks.StructBlock([
             ('image', ImageChooserBlock()),
             ('is_round',
              blocks.BooleanBlock(required=False, default=True,
                                  label='Round?')),
             ('icon', blocks.CharBlock(help_text='Enter icon class name.')),
             ('heading',
              blocks.CharBlock(required=False, label='Introduction Heading')),
             ('body',
              blocks.TextBlock(required=False, label='Introduction Body')),
         ],
                            heading='Breakout Image',
                            icon='image')),
        ('related_posts', organisms.RelatedPosts()),
        ('job_listing_list', JobListingList()),
    ],
                                   blank=True)

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

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

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

    template = 'sublanding-page/index.html'

    objects = PageManager()

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

    def get_browsefilterable_posts(self, limit):
        filter_pages = [
            p.specific for p in self.get_appropriate_descendants()
            if 'FilterablePage' in p.specific_class.__name__
            and 'archive' not in p.title.lower()
        ]
        posts_list = []
        for page in filter_pages:
            posts_list.extend(AbstractFilterPage.objects.live().filter(
                CFGOVPage.objects.child_of_q(page)))

        return sorted(posts_list, key=lambda p: p.date_published,
                      reverse=True)[:limit]
class SublandingPage(CFGOVPage):
    header = StreamField([
        ('hero', molecules.Hero()),
    ], blank=True)
    content = StreamField([
        ('text_introduction', molecules.TextIntroduction()),
        ('featured_content', molecules.FeaturedContent()),
        ('info_unit_group', organisms.InfoUnitGroup()),
        ('image_text_25_75_group', organisms.ImageText2575Group()),
        ('image_text_50_50_group', organisms.ImageText5050Group()),
        ('full_width_text', organisms.FullWidthText()),
        ('half_width_link_blob_group', organisms.HalfWidthLinkBlobGroup()),
        ('third_width_link_blob_group', organisms.ThirdWidthLinkBlobGroup()),
        ('post_preview_snapshot', organisms.PostPreviewSnapshot()),
        ('well', organisms.Well()),
        ('table', organisms.Table(editable=False)),
        ('table_block',
         organisms.AtomicTableBlock(table_options={'renderer': 'html'})),
        ('contact', organisms.MainContactInfo()),
        ('formfield_with_button', molecules.FormFieldWithButton()),
        ('reg_comment', organisms.RegComment()),
        ('feedback', v1_blocks.Feedback()),
        ('snippet_list', organisms.SnippetList()),
    ],
                          blank=True)
    sidebar_breakout = StreamField([
        ('slug', blocks.CharBlock(icon='title')),
        ('heading', blocks.CharBlock(icon='title')),
        ('paragraph', blocks.RichTextBlock(icon='edit')),
        ('breakout_image',
         blocks.StructBlock([
             ('image', ImageChooserBlock()),
             ('is_round',
              blocks.BooleanBlock(required=False, default=True,
                                  label='Round?')),
             ('icon', blocks.CharBlock(help_text='Enter icon class name.')),
             ('heading',
              blocks.CharBlock(required=False, label='Introduction Heading')),
             ('body',
              blocks.TextBlock(required=False, label='Introduction Body')),
         ],
                            heading='Breakout Image',
                            icon='image')),
        ('related_posts', organisms.RelatedPosts()),
        ('job_listing_list', JobListingList()),
    ],
                                   blank=True)

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

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

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

    template = 'sublanding-page/index.html'

    objects = PageManager()

    def get_browsefilterable_posts(self, limit):
        filter_pages = [
            p.specific for p in self.get_appropriate_descendants()
            if 'FilterablePage' in p.specific_class.__name__
            and 'archive' not in p.title.lower()
        ]
        posts_list = []
        for page in filter_pages:
            eligible_children = AbstractFilterPage.objects.live().filter(
                CFGOVPage.objects.child_of_q(page))

            form = FilterableListForm(filterable_pages=eligible_children)
            for post in form.get_page_set():
                posts_list.append(post)
        return sorted(posts_list, key=lambda p: p.date_published,
                      reverse=True)[:limit]
Beispiel #3
0
class CFGOVPage(Page):
    authors = ClusterTaggableManager(through=CFGOVAuthoredPages,
                                     blank=True,
                                     verbose_name='Authors',
                                     help_text='A comma separated list of ' +
                                     'authors.',
                                     related_name='authored_pages')
    tags = ClusterTaggableManager(through=CFGOVTaggedPages,
                                  blank=True,
                                  related_name='tagged_pages')
    shared = models.BooleanField(default=False)
    has_unshared_changes = models.BooleanField(default=False)
    language = models.CharField(choices=ref.supported_languagues,
                                default='en',
                                max_length=2)

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

    objects = CFGOVPageManager()

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

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

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

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

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

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

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

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

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

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

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

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

    def get_appropriate_page_version(self, request):
        # If we're on the production site, make sure the version of the page
        # displayed is the latest version that has `live` set to True for
        # the live site or `shared` set to True for the staging site.
        revisions = self.revisions.all().order_by('-created_at')
        for revision in revisions:
            page_version = json.loads(revision.content_json)
            if not request.is_staging:
                if page_version['live']:
                    return revision.as_page_object()
            else:
                if page_version['shared']:
                    return revision.as_page_object()

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

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

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

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

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

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

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

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

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

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

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

            return HttpResponseBadRequest(self.url)

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

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

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

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

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

    @property
    def status_string(self):
        if self.expired:
            return _("expired")
        elif self.approved_schedule:
            return _("scheduled")
        elif self.live and self.shared:
            if self.has_unpublished_changes:
                if self.has_unshared_changes:
                    for revision in self.revisions.order_by(
                            '-created_at', '-id'):
                        content = json.loads(revision.content_json)
                        if content['shared']:
                            if content['live']:
                                return _('live + draft')
                            else:
                                return _('live + (shared + draft)')
                else:
                    return _("live + shared")
            else:
                return _("live")
        elif self.live:
            return _("live")
        elif self.shared:
            if self.has_unshared_changes:
                return _("shared + draft")
            else:
                return _("shared")
        else:
            return _("draft")

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

        sharable_pages = Page.objects.none()

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

        return sharable_pages

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

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

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

            return subpage.specific.route(request, remaining_components)

        else:
            # Request is for this very page.
            page = self.get_appropriate_page_version(request)
            if page:
                return RouteResult(page)
            raise Http404

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

    class Meta:
        app_label = 'v1'

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

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

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

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

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

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

    # Returns all the JS files specific to this page and it's current Streamfield's blocks
    @property
    def media(self):
        js = OrderedDict()
        for key in ['template', 'organisms', 'molecules', 'atoms']:
            js.update({key: []})
        self.add_page_js(js)
        self._add_streamfield_js(js)
        for key, js_files in js.iteritems():
            js[key] = OrderedDict.fromkeys(js_files).keys()
        return js