예제 #1
0
def register(cls, admin_cls):
    cls.add_to_class(
        '_ct_inventory',
        JSONField(_('content types'), editable=False, blank=True, null=True))
    cls.content_proxy_class = TrackerContentProxy

    post_save.connect(post_save_handler, sender=cls)
예제 #2
0
def register(cls, admin_cls):
    cls.add_to_class('_ct_inventory', JSONField(_('content types'), editable=False, blank=True, null=True))
    cls.content_proxy_class = TrackerContentProxy

    pre_save.connect(single_pre_save_handler, sender=cls)
    if hasattr(cls, 'get_descendants'):
        post_save.connect(tree_post_save_handler, sender=cls)
예제 #3
0
    def handle_model(self):
        self.model.add_to_class(
            "_ct_inventory",
            JSONField(_("content types"), editable=False, blank=True, null=True),
        )
        self.model.content_proxy_class = TrackerContentProxy

        pre_save.connect(single_pre_save_handler, sender=self.model)
        if hasattr(self.model, "get_descendants"):
            post_save.connect(tree_post_save_handler, sender=self.model)
예제 #4
0
파일: models.py 프로젝트: natea/feincms
class ApplicationContent(models.Model):
    #: parameters is used to serialize instance-specific data which will be
    # provided to the view code. This allows customization (e.g. "Embed
    # MyBlogApp for blog <slug>")
    parameters = JSONField(null=True, editable=False)

    ALL_APPS_CONFIG = {}

    class Meta:
        abstract = True
        verbose_name = _('application content')
        verbose_name_plural = _('application contents')

    @classmethod
    def initialize_type(cls, APPLICATIONS):
        # Generate a more flexible application configuration structure from
        # the legacy pattern:

        # TODO: Consider changing the input signature to something cleaner, at
        # the cost of a one-time backwards incompatible change

        for i in APPLICATIONS:
            if not 2 <= len(i) <= 3:
                raise ValueError(
                    "APPLICATIONS must be provided with tuples containing at least two parameters (urls, name) and an optional extra config dict"
                )

            urls, name = i[0:2]

            if len(i) == 3:
                app_conf = i[2]

                if not isinstance(app_conf, dict):
                    raise ValueError(
                        "The third parameter of an APPLICATIONS entry must be a dict or the name of one!"
                    )
            else:
                app_conf = {}

            cls.ALL_APPS_CONFIG[urls] = {
                "urls": urls,
                "name": name,
                "config": app_conf
            }

        cls.add_to_class(
            'urlconf_path',
            models.CharField(_('application'),
                             max_length=100,
                             choices=[(c['urls'], c['name'])
                                      for c in cls.ALL_APPS_CONFIG.values()]))

        class ApplicationContentItemEditorForm(ItemEditorForm):
            app_config = {}
            custom_fields = {}

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

                instance = kwargs.get("instance", None)

                if instance:
                    self.app_config = cls.ALL_APPS_CONFIG[
                        instance.urlconf_path]['config']
                    self.custom_fields = {}
                    admin_fields = self.app_config.get('admin_fields', {})

                    if isinstance(admin_fields, dict):
                        self.custom_fields.update(admin_fields)
                    else:
                        get_fields = urlresolvers.get_callable(admin_fields)
                        self.custom_fields.update(
                            get_fields(self, *args, **kwargs))

                    for k, v in self.custom_fields.items():
                        self.fields[k] = v

            def clean(self, *args, **kwargs):
                cleaned_data = super(ApplicationContentItemEditorForm,
                                     self).clean(*args, **kwargs)

                # TODO: Check for newly added instances so we can force a re-validation of their custom fields

                return cleaned_data

            def save(self, commit=True, *args, **kwargs):
                # Django ModelForms return the model instance from save. We'll
                # call save with commit=False first to do any necessary work &
                # get the model so we can set .parameters to the values of our
                # custom fields before calling save(commit=True)

                m = super(ApplicationContentItemEditorForm,
                          self).save(commit=False, *args, **kwargs)

                m.parameters = dict((k, self.cleaned_data[k])
                                    for k in self.custom_fields
                                    if k in self.cleaned_data)

                if commit:
                    m.save(**kwargs)

                return m

        #: This provides hooks for us to customize the admin interface for embedded instances:
        cls.feincms_item_editor_form = ApplicationContentItemEditorForm

        # Make sure the patched reverse() method has all information it needs
        cls.parent.field.rel.to.register_request_processors(
            retrieve_page_information)

    def __init__(self, *args, **kwargs):
        super(ApplicationContent, self).__init__(*args, **kwargs)
        self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path,
                                                   {}).get('config', {})

    def process(self, request, **kwargs):
        page_url = self.parent.get_absolute_url()

        # Get the rest of the URL

        # Provide a way for appcontent items to customize URL processing by
        # altering the perceived path of the page:
        if "path_mapper" in self.app_config:
            path_mapper = urlresolvers.get_callable(
                self.app_config["path_mapper"])
            path, page_url = path_mapper(request.path,
                                         page_url,
                                         appcontent_parameters=self.parameters)
        else:
            path = request._feincms_extra_context['extra_path']

        # Resolve the module holding the application urls.
        urlconf_path = self.app_config.get('urls', self.urlconf_path)

        # Change the prefix and urlconf for the monkey-patched reverse function ...
        _local.urlconf = (urlconf_path, page_url)

        try:
            fn, args, kwargs = resolve(path, urlconf_path)
        except (ValueError, Resolver404):
            del _local.urlconf
            raise Resolver404

        #: Variables from the ApplicationContent parameters are added to request
        #  so we can expose them to our templates via the appcontent_parameters
        #  context_processor
        request._feincms_extra_context.update(self.parameters)

        view_wrapper = self.app_config.get("view_wrapper", None)
        if view_wrapper:
            fn = partial(urlresolvers.get_callable(view_wrapper),
                         view=fn,
                         appcontent_parameters=self.parameters)

        try:
            output = fn(request, *args, **kwargs)
        finally:
            # We want exceptions to propagate, but we cannot allow the
            # modifications to reverse() to stay here.
            del _local.urlconf

        if isinstance(output, HttpResponse):
            if self.send_directly(request, output):
                return output
            elif output.status_code == 200:
                self.rendered_result = mark_safe(
                    output.content.decode('utf-8'))
                self.rendered_headers = {}
                # Copy relevant headers for later perusal
                for h in ('Cache-Control', 'Last-Modified', 'Expires'):
                    if h in output:
                        self.rendered_headers.setdefault(h,
                                                         []).append(output[h])
        else:
            self.rendered_result = mark_safe(output)

        return True  # successful

    def send_directly(self, request, response):
        return response.status_code != 200 or request.is_ajax() or getattr(
            response, 'standalone', False)

    def render(self, **kwargs):
        return getattr(self, 'rendered_result', u'')

    def finalize(self, request, response):
        headers = getattr(self, 'rendered_headers', None)
        if headers:
            self._update_response_headers(request, response, headers)

    def save(self, *args, **kwargs):
        super(ApplicationContent, self).save(*args, **kwargs)
        # Clear reverse() cache
        _empty_reverse_cache()

    def delete(self, *args, **kwargs):
        super(ApplicationContent, self).delete(*args, **kwargs)
        # Clear reverse() cache
        _empty_reverse_cache()

    def _update_response_headers(self, request, response, headers):
        """
        Combine all headers that were set by the different content types
        We are interested in Cache-Control, Last-Modified, Expires
        """
        from django.utils.http import http_date

        # Ideally, for the Cache-Control header, we'd want to do some intelligent
        # combining, but that's hard. Let's just collect and unique them and let
        # the client worry about that.
        cc_headers = set()
        for x in (cc.split(",") for cc in headers.get('Cache-Control', ())):
            cc_headers |= set((s.strip() for s in x))

        if len(cc_headers):
            response['Cache-Control'] = ", ".join(cc_headers)
        else:  # Default value
            response['Cache-Control'] = 'no-cache, must-revalidate'

        # Check all Last-Modified headers, choose the latest one
        lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())]
        if len(lm_list) > 0:
            response['Last-Modified'] = http_date(mktime(max(lm_list)))

        # Check all Expires headers, choose the earliest one
        lm_list = [parsedate(x) for x in headers.get('Expires', ())]
        if len(lm_list) > 0:
            response['Expires'] = http_date(mktime(min(lm_list)))
예제 #5
0
class ApplicationContent(models.Model):
    #: parameters is used to serialize instance-specific data which will be
    # provided to the view code. This allows customization (e.g. "Embed
    # MyBlogApp for blog <slug>")
    parameters = JSONField(null=True, editable=False)

    ALL_APPS_CONFIG = {}

    class Meta:
        abstract = True
        verbose_name = _('application content')
        verbose_name_plural = _('application contents')

    @classmethod
    def initialize_type(cls, APPLICATIONS):
        for i in APPLICATIONS:
            if not 2 <= len(i) <= 3:
                raise ValueError(
                    "APPLICATIONS must be provided with tuples containing at"
                    " least two parameters (urls, name) and an optional extra"
                    " config dict")

            urls, name = i[0:2]

            if len(i) == 3:
                app_conf = i[2]

                if not isinstance(app_conf, dict):
                    raise ValueError(
                        "The third parameter of an APPLICATIONS entry must be"
                        " a dict or the name of one!")
            else:
                app_conf = {}

            cls.ALL_APPS_CONFIG[urls] = {
                "urls": urls,
                "name": name,
                "config": app_conf
            }

        cls.add_to_class(
            'urlconf_path',
            models.CharField(_('application'),
                             max_length=100,
                             choices=[(c['urls'], c['name'])
                                      for c in cls.ALL_APPS_CONFIG.values()]))

        class ApplicationContentItemEditorForm(ItemEditorForm):
            app_config = {}
            custom_fields = {}

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

                instance = kwargs.get("instance", None)

                if instance:
                    try:
                        # TODO use urlconf_path from POST if set
                        # urlconf_path = request.POST.get('...urlconf_path',
                        #     instance.urlconf_path)
                        self.app_config = cls.ALL_APPS_CONFIG[
                            instance.urlconf_path]['config']
                    except KeyError:
                        self.app_config = {}

                    self.custom_fields = {}
                    admin_fields = self.app_config.get('admin_fields', {})

                    if isinstance(admin_fields, dict):
                        self.custom_fields.update(admin_fields)
                    else:
                        get_fields = get_object(admin_fields)
                        self.custom_fields.update(
                            get_fields(self, *args, **kwargs))

                    params = self.instance.parameters
                    for k, v in self.custom_fields.items():
                        v.initial = params.get(k)
                        self.fields[k] = v
                        if k in params:
                            self.fields[k].initial = params[k]

            def save(self, commit=True, *args, **kwargs):
                # Django ModelForms return the model instance from save. We'll
                # call save with commit=False first to do any necessary work &
                # get the model so we can set .parameters to the values of our
                # custom fields before calling save(commit=True)

                m = super(ApplicationContentItemEditorForm,
                          self).save(commit=False, *args, **kwargs)

                m.parameters = dict((k, self.cleaned_data[k])
                                    for k in self.custom_fields
                                    if k in self.cleaned_data)

                if commit:
                    m.save(**kwargs)

                return m

        # This provides hooks for us to customize the admin interface for
        # embedded instances:
        cls.feincms_item_editor_form = ApplicationContentItemEditorForm

        # Clobber the app_reverse cache when saving application contents
        # and/or pages
        page_class = cls.parent.field.rel.to
        signals.post_save.connect(cycle_app_reverse_cache, sender=cls)
        signals.post_delete.connect(cycle_app_reverse_cache, sender=cls)
        signals.post_save.connect(cycle_app_reverse_cache, sender=page_class)
        signals.post_delete.connect(cycle_app_reverse_cache, sender=page_class)

    def __init__(self, *args, **kwargs):
        super(ApplicationContent, self).__init__(*args, **kwargs)
        self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path,
                                                   {}).get('config', {})

    def process(self, request, **kw):
        page_url = self.parent.get_absolute_url()

        # Provide a way for appcontent items to customize URL processing by
        # altering the perceived path of the page:
        if "path_mapper" in self.app_config:
            path_mapper = get_object(self.app_config["path_mapper"])
            path, page_url = path_mapper(request.path,
                                         page_url,
                                         appcontent_parameters=self.parameters)
        else:
            path = request._feincms_extra_context['extra_path']

        # Resolve the module holding the application urls.
        urlconf_path = self.app_config.get('urls', self.urlconf_path)

        try:
            fn, args, kwargs = resolve(path, urlconf_path)
        except (ValueError, Resolver404):
            raise Resolver404(
                str('Not found (resolving %r in %r failed)') %
                (path, urlconf_path))

        # Variables from the ApplicationContent parameters are added to request
        # so we can expose them to our templates via the appcontent_parameters
        # context_processor
        request._feincms_extra_context.update(self.parameters)

        # Save the application configuration for reuse elsewhere
        request._feincms_extra_context.update({
            'app_config':
            dict(
                self.app_config,
                urlconf_path=self.urlconf_path,
            ),
        })

        view_wrapper = self.app_config.get("view_wrapper", None)
        if view_wrapper:
            fn = partial(get_object(view_wrapper),
                         view=fn,
                         appcontent_parameters=self.parameters)

        output = fn(request, *args, **kwargs)

        if isinstance(output, HttpResponse):
            if self.send_directly(request, output):
                return output
            elif output.status_code == 200:

                # If the response supports deferred rendering, render the
                # response right now. We do not handle template response
                # middleware.
                if hasattr(output, 'render') and callable(output.render):
                    output.render()

                self.rendered_result = mark_safe(
                    output.content.decode('utf-8'))
                self.rendered_headers = {}

                # Copy relevant headers for later perusal
                for h in ('Cache-Control', 'Last-Modified', 'Expires'):
                    if h in output:
                        self.rendered_headers.setdefault(h,
                                                         []).append(output[h])

        elif isinstance(output, tuple) and 'view' in kw:
            kw['view'].template_name = output[0]
            kw['view'].request._feincms_extra_context.update(output[1])
        else:
            self.rendered_result = mark_safe(output)

        return True  # successful

    def send_directly(self, request, response):
        mimetype = response.get('Content-Type', 'text/plain')
        if ';' in mimetype:
            mimetype = mimetype.split(';')[0]
        mimetype = mimetype.strip()

        return (response.status_code != 200 or request.is_ajax()
                or getattr(response, 'standalone', False)
                or mimetype not in ('text/html', 'text/plain'))

    def render(self, **kwargs):
        return getattr(self, 'rendered_result', '')

    def finalize(self, request, response):
        headers = getattr(self, 'rendered_headers', None)
        if headers:
            self._update_response_headers(request, response, headers)

    def _update_response_headers(self, request, response, headers):
        """
        Combine all headers that were set by the different content types
        We are interested in Cache-Control, Last-Modified, Expires
        """

        # Ideally, for the Cache-Control header, we'd want to do some
        # intelligent combining, but that's hard. Let's just collect and unique
        # them and let the client worry about that.
        cc_headers = set(('must-revalidate', ))
        for x in (cc.split(",") for cc in headers.get('Cache-Control', ())):
            cc_headers |= set((s.strip() for s in x))

        if len(cc_headers):
            response['Cache-Control'] = ", ".join(cc_headers)
        else:  # Default value
            response['Cache-Control'] = 'no-cache, must-revalidate'

        # Check all Last-Modified headers, choose the latest one
        lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())]
        if len(lm_list) > 0:
            response['Last-Modified'] = http_date(mktime(max(lm_list)))

        # Check all Expires headers, choose the earliest one
        lm_list = [parsedate(x) for x in headers.get('Expires', ())]
        if len(lm_list) > 0:
            response['Expires'] = http_date(mktime(min(lm_list)))

    @classmethod
    def closest_match(cls, urlconf_path):
        page_class = cls.parent.field.rel.to

        contents = cls.objects.filter(
            parent__in=page_class.objects.active(),
            urlconf_path=urlconf_path,
        ).order_by('pk').select_related('parent')

        if len(contents) > 1:
            try:
                current = short_language_code(get_language())
                return [
                    content for content in contents
                    if short_language_code(content.parent.language) == current
                ][0]

            except (AttributeError, IndexError):
                pass

        try:
            return contents[0]
        except IndexError:
            pass

        return None
예제 #6
0
class ApplicationContent(models.Model):
    #: parameters is used to serialize instance-specific data which will be
    # provided to the view code. This allows customization (e.g. "Embed
    # MyBlogApp for blog <slug>")
    parameters = JSONField(null=True, editable=False)

    ALL_APPS_CONFIG = {}

    class Meta:
        abstract = True
        verbose_name = _('application content')
        verbose_name_plural = _('application contents')

    @classmethod
    def initialize_type(cls, APPLICATIONS):
        # Generate a more flexible application configuration structure from
        # the legacy pattern:

        # TODO: Consider changing the input signature to something cleaner, at
        # the cost of a one-time backwards incompatible change

        for i in APPLICATIONS:
            if not 2 <= len(i) <= 3:
                raise ValueError(
                    "APPLICATIONS must be provided with tuples containing at least two parameters (urls, name) and an optional extra config dict"
                )

            urls, name = i[0:2]

            if len(i) == 3:
                app_conf = i[2]

                if not isinstance(app_conf, dict):
                    raise ValueError(
                        "The third parameter of an APPLICATIONS entry must be a dict or the name of one!"
                    )
            else:
                app_conf = {}

            cls.ALL_APPS_CONFIG[urls] = {
                "urls": urls,
                "name": name,
                "config": app_conf
            }

        cls.add_to_class(
            'urlconf_path',
            models.CharField(_('application'),
                             max_length=100,
                             choices=[(c['urls'], c['name'])
                                      for c in cls.ALL_APPS_CONFIG.values()]))

        class ApplicationContentItemEditorForm(ItemEditorForm):
            app_config = {}
            custom_fields = {}

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

                instance = kwargs.get("instance", None)

                if instance:
                    self.app_config = cls.ALL_APPS_CONFIG[
                        instance.urlconf_path]['config']
                    admin_fields = self.app_config.get('admin_fields', {})

                    if isinstance(admin_fields, dict):
                        self.custom_fields.update(admin_fields)
                    else:
                        get_fields = urlresolvers.get_callable(admin_fields)
                        self.custom_fields.update(
                            get_fields(self, *args, **kwargs))

                    for k, v in self.custom_fields.items():
                        self.fields[k] = v

            def clean(self, *args, **kwargs):
                cleaned_data = super(ApplicationContentItemEditorForm,
                                     self).clean(*args, **kwargs)

                # TODO: Check for newly added instances so we can force a re-validation of their custom fields

                return cleaned_data

            def save(self, commit=True, *args, **kwargs):
                # Django ModelForms return the model instance from save. We'll
                # call save with commit=False first to do any necessary work &
                # get the model so we can set .parameters to the values of our
                # custom fields before calling save(commit=True)

                m = super(ApplicationContentItemEditorForm,
                          self).save(commit=False, *args, **kwargs)

                m.parameters = dict((k, self.cleaned_data[k])
                                    for k in self.custom_fields
                                    if k in self.cleaned_data)

                if commit:
                    m.save(**kwargs)

                return m

        #: This provides hooks for us to customize the admin interface for embedded instances:
        cls.feincms_item_editor_form = ApplicationContentItemEditorForm

    def __init__(self, *args, **kwargs):
        super(ApplicationContent, self).__init__(*args, **kwargs)
        self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path,
                                                   {}).get('config', {})

    def render(self, request, **kwargs):
        return getattr(request, "_feincms_applicationcontents",
                       {}).get(self.id, u'')

    def process(self, request):
        page_url = self.parent.get_absolute_url()

        # Get the rest of the URL

        # Provide a way for appcontent items to customize URL processing by
        # altering the perceived path of the page:
        if "path_mapper" in self.app_config:
            path_mapper = urlresolvers.get_callable(
                self.app_config["path_mapper"])
            path, page_url = path_mapper(request.path,
                                         page_url,
                                         appcontent_parameters=self.parameters)
        else:
            path = re.sub('^' + re.escape(page_url[:-1]), '', request.path)

        # Change the prefix and urlconf for the monkey-patched reverse function ...
        _local.urlconf = (self.urlconf_path, page_url)

        try:
            fn, args, kwargs = resolve(path, self.urlconf_path)
        except (ValueError, Resolver404):
            del _local.urlconf
            raise Resolver404

        #: Variables from the ApplicationContent parameters are added to request
        #  so we can expose them to our templates via the appcontent_parameters
        #  context_processor
        request._feincms_appcontent_parameters.update(self.parameters)

        view_wrapper = self.app_config.get("view_wrapper", None)
        if view_wrapper:
            fn = partial(urlresolvers.get_callable(view_wrapper),
                         view=fn,
                         appcontent_parameters=self.parameters)

        try:
            output = fn(request, *args, **kwargs)
        except:
            # We want exceptions to propagate, but we cannot allow the
            # modifications to reverse() to stay here.
            del _local.urlconf
            raise

        # ... and restore it after processing the view
        del _local.urlconf

        if isinstance(output, HttpResponse):
            if output.status_code == 200:
                if not getattr(output, 'standalone', False):
                    request._feincms_applicationcontents[self.id] = mark_safe(
                        output.content.decode('utf-8'))

            return output
        else:
            request._feincms_applicationcontents[self.id] = mark_safe(output)

    def save(self, *args, **kwargs):
        super(ApplicationContent, self).save(*args, **kwargs)
        # Clear reverse() cache
        _empty_reverse_cache()

    def delete(self, *args, **kwargs):
        super(ApplicationContent, self).delete(*args, **kwargs)
        # Clear reverse() cache
        _empty_reverse_cache()