from typing import Any
from django.apps import apps
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ImproperlyConfigured
from django.views.generic.base import ContextMixin, TemplateView
from django.utils.translation import gettext as __, get_language
from rest_framework import permissions, viewsets
# FIXME: circular dependencies among apps
from ox.apps.core.serializers import UserSerializer, GroupSerializer
from ox.core.assets import Assets
from .panels import Panels, registry
__all__ = ("AppMixin", "UserAuthMixin", "AppView", "UserAppView")
[docs]
class AppMixin(ContextMixin):
"""Base mixin for applications."""
title: str = ""
"""Application title (as displayed in ``<title>`` and top bar)."""
app_config_name: str | None = None
"""AppConfig name of the related application.
If none provided, retrieve it based of request's resolver match.
"""
default_panel: str = ""
"""Default panel to display."""
panels: Panels = None
"""Application's panels descriptors."""
assets: Assets = None
"""Use theses assets instead of app config's one. """
def get_context_data(self, **kwargs):
kwargs["app_config"] = self.get_app_config()
kwargs["app_data"] = self.get_app_data()
kwargs["panels"] = self.panels
kwargs["assets"] = self.get_assets()
kwargs.setdefault("title", self.title)
return super().get_context_data(**kwargs)
[docs]
def get_app_config(self):
"""Return application config.
Set to request resolved match application name by default.
"""
app_name = self.app_config_name or self.request.resolver_match.app_name
self.app_config = apps.get_app_config(app_name)
return self.app_config
[docs]
def get_app_data(self, **kwargs):
"""Return application data to pass down to js application."""
if current := self.request.GET.get("panel", self.default_panel):
kwargs.setdefault("panel", current)
kwargs["nav"] = self.get_app_nav()
kwargs["host"] = settings.SITE_URL
kwargs["language"] = get_language()
kwargs["languages"] = [(k, __(v)) for k, v in settings.LANGUAGES]
return kwargs
[docs]
def get_app_nav(self) -> dict[str, Any]:
"""Return application navigation menu."""
return registry.nav_data
[docs]
def get_assets(self) -> Assets | None:
"""
Return assets to use with the view.
It retuns assigned :py:attr:`assets` or app config one if any.
"""
if self.assets:
return self.assets
return self.app_config and getattr(self.app_config, "assets", None) or None
[docs]
class UserAuthMixin:
"""Provide request's user in Application's initial data, as ``user``."""
user_ser_class = UserSerializer
group_ser_class = GroupSerializer
def get_app_data(self, **kwargs):
if not kwargs.get("user"):
kwargs["user"] = self.user_ser_class(self.request.user).data
kwargs["groups"] = self.group_ser_class(self.request.user.groups, many=True).data
kwargs.setdefault("urls", {})
# urls["user"] = reverse("auth:api:")
return super().get_app_data(**kwargs)
[docs]
class AppView(UserAuthMixin, AppMixin, TemplateView):
"""Base view used for ox based applications."""
template_name = "ox/core/app.html"
[docs]
def get_template_names(self) -> list[str]:
"""
By default return a list with:
- :py:attr:`template_name` value
- ``{self.app_config.root_url}/app.html``, if app_config is found.
"""
try:
names = super().get_template_names()
except ImproperlyConfigured:
names = []
if hasattr(self, "app_config"):
names.append(f"{self.app_config.root_url}/app.html")
return names
def get(self, *args, service=None, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
[docs]
class UserAppView(LoginRequiredMixin, AppView):
"""Application view requiring user to be authentified."""
pass
class ModelViewSet(viewsets.ModelViewSet):
"""Base model viewset use by Oxylus application.
Lookup objects by uuid.
"""
permission_classes = [permissions.DjangoModelPermissions]
lookup_field = "uuid"
filterset_fields = {
"uuid": ["exact", "in"],
}