Example #1
0
def page_for_app_request(request, queryset=None):
    """page_for_app_request(request, *, queryset=None)
    Returns the current page if we're inside an app. Should only be called
    while processing app views. Will pass along exceptions caused by
    non-existing or duplicated apps (this should never happen inside an app
    because :func:`~feincms3.apps.apps_urlconf` wouldn't have added the app
    in the first place if a matching page wouldn't exist, but still.)

    Example::

        def article_detail(request, slug):
            page = page_for_app_request(request)
            page.activate_language(request)
            instance = get_object_or_404(Article, slug=slug)
            return render(request, 'articles/article_detail.html', {
                'article': article,
                'page': page,
            })

    It is possible to override the queryset used to fetch a page instance. The
    default implementation simply uses the first concrete subclass of
    :class:`~feincms3.apps.AppsMixin`.
    """

    page_model = concrete_model(AppsMixin)
    if queryset is None:
        queryset = page_model.objects
    # Unguarded - if this fails, we shouldn't even be here.
    return queryset.get(
        language_code=request.resolver_match.namespaces[0]
        [len(page_model.LANGUAGE_CODES_NAMESPACE) + 1:],
        app_instance_namespace=request.resolver_match.namespaces[1],
    )
Example #2
0
def page_for_app_request(request):
    """
    Returns the current page if we're inside an app. Should only be called
    while processing app views. Will pass along exceptions caused by
    non-existing or duplicated apps (this should never happen inside an app
    because :func:`~feincms3.apps.apps_urlconf` wouldn't have added the app
    in the first place if a matching page wouldn't exist, but still.)

    Example::

        def article_detail(request, slug):
            page = page_for_app_request(request)
            page.activate_language(request)
            instance = get_object_or_404(Article, slug=slug)
            return render(request, 'articles/article_detail.html', {
                'article': article,
                'page': page,
            })
    """

    # Unguarded - if this fails, we shouldn't even be here.
    page = concrete_model(AppsMixin).objects.get(
        language_code=request.resolver_match.namespaces[0],
        app_instance_namespace=request.resolver_match.namespaces[1],
    )
    return page
Example #3
0
    def test_subclasses(self):
        class A(object):
            pass

        class B(A):
            pass

        class C(B):
            pass

        self.assertEqual(
            set(iterate_subclasses(A)),
            {B, C},
        )

        class Test(models.Model):
            class Meta:
                abstract = True

        class Test2(Test):
            class Meta:
                abstract = True

        self.assertEqual(
            concrete_model(Test),
            None,
        )
Example #4
0
def reverse_app(namespaces, viewname, *args, **kwargs):
    """
    Reverse app URLs, preferring the active language.

    ``reverse_app`` first generates a list of viewnames and passes them on
    to ``reverse_any``.

    Assuming that we're trying to reverse the URL of an article detail view,
    that the project is configured with german, english and french as available
    languages, french as active language and that the current article is a
    publication, the viewnames are:

    - ``apps-fr.publications.article-detail``
    - ``apps-fr.articles.article-detail``
    - ``apps-de.publications.article-detail``
    - ``apps-de.articles.article-detail``
    - ``apps-en.publications.article-detail``
    - ``apps-en.articles.article-detail``

    reverse_app tries harder returning an URL in the correct language than
    returning an URL for the correct instance namespace.

    Example::

        url = reverse_app(
            ("category-1", "blog"),
            "post-detail",
            kwargs={"year": 2016, "slug": "my-cat"},
        )
    """

    page_model = concrete_model(AppsMixin)
    current = get_language()
    language_codes_namespaces = []
    if current is not None:
        language_codes_namespaces.append(
            "%s-%s" % (page_model.LANGUAGE_CODES_NAMESPACE, current))
    language_codes_namespaces.extend(
        "%s-%s" % (page_model.LANGUAGE_CODES_NAMESPACE, language[0])
        for language in settings.LANGUAGES if language[0] != current)
    viewnames = [
        ":".join(r) for r in itertools.product(
            language_codes_namespaces,
            (namespaces if isinstance(namespaces, (list, tuple)) else (
                namespaces, )),
            (viewname, ),
        )
    ]
    return reverse_any(viewnames, *args, **kwargs)
def menu(menu, level=0, depth=1, **kwargs):
    """menu(menu, level=0, depth=1, **kwargs)
    This tag expects the ``page`` variable to contain the page we're on
    currently. The active pages are fetched using ``.objects.active()`` and
    filtered further according to the arguments passed to the tag. This tag
    depends on :class:`~feincms3.mixins.MenuMixin` and on a ``page`` context
    variable which must be an instance of the pages model.

    **Note**: MPTT levels are zero-based.

    The default is to return all root nodes from the matching ``menu``.
    """
    return concrete_model(MenuMixin).objects.active().filter(
        menu=menu, **kwargs).extra(where=[
            'depth BETWEEN %d AND %d' % (level + 1, level + depth),
        ])
Example #6
0
def apps_urlconf():
    """
    Generates a dynamic URLconf Python module including all applications in
    their assigned place and adding the ``urlpatterns`` from ``ROOT_URLCONF``
    at the end. Returns the value of ``ROOT_URLCONF`` directly if there are
    no active applications.

    Since Django uses an LRU cache for URL resolvers, we try hard to only
    generate a changed URLconf when application URLs actually change.

    The application URLconfs are put in nested namespaces:

    - The outer application namespace is ``apps`` by default. This value can be
      overridden by setting the ``LANGUAGE_CODES_NAMESPACE`` class attribute of
      the page class to a different value. The instance namespaces consist of
      the ``LANGUAGE_CODES_NAMESPACE`` value with a language added at the end.
      As long as you're always using ``reverse_app`` you do not have to know
      the specifics.
    - The inner namespace is the app namespace, where the application
      namespace is defined by the app itself (assign ``app_name`` in the
      same module as ``urlpatterns``) and the instance namespace is defined
      by the application name (from ``APPLICATIONS``).

    Modules stay around as long as the Python (most of the time WSGI) process
    lives. Unloading modules is tricky and probably not worth it since the
    URLconf modules shouldn't gobble up much memory.
    """

    page_model = concrete_model(AppsMixin)
    fields = ('path', 'application', 'app_instance_namespace', 'language_code')
    apps = page_model.objects.active().exclude(
        app_instance_namespace='').values_list(*fields).order_by(*fields)

    if not apps:
        # No point wrapping ROOT_URLCONF if there are no additional URLs
        return settings.ROOT_URLCONF

    key = ','.join(itertools.chain.from_iterable(apps))
    module_name = 'urlconf_%s' % hashlib.md5(key.encode('utf-8')).hexdigest()

    if module_name not in sys.modules:
        app_config = {
            app[0]: app[2]
            for app in page_model.APPLICATIONS if app[0]
        }

        m = types.ModuleType(str(module_name))  # Correct for Python 2 and 3

        mapping = defaultdict(list)
        for path, application, app_instance_namespace, language_code in apps:
            if application not in app_config:
                continue
            mapping[language_code].append(
                url(
                    r'^%s' % re.escape(path.lstrip('/')),
                    include(
                        app_config[application]['urlconf'],
                        namespace=app_instance_namespace,
                    ),
                ))

        m.urlpatterns = [
            url(
                r'',
                include(
                    (instances, page_model.LANGUAGE_CODES_NAMESPACE),
                    namespace='%s-%s' % (
                        page_model.LANGUAGE_CODES_NAMESPACE,
                        language_code,
                    ),
                ),
            ) for language_code, instances in mapping.items()
        ]

        # Append patterns from ROOT_URLCONF instead of including them because
        # i18n_patterns only work in the root URLconf.
        m.urlpatterns += import_module(settings.ROOT_URLCONF).urlpatterns
        sys.modules[module_name] = m

    return module_name