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], )
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
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, )
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), ])
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