Actually kinda working

main
Estelle Poulin 2 years ago
parent b2562d59fe
commit 63b26ca186

@ -1 +1,54 @@
__version__ = "0.0.1" __version__ = "0.0.1"
from abc import ABC, abstractmethod
from django.urls import reverse
from django.apps import apps
from django.utils.encoding import iri_to_uri
from urllib.parse import quote, urljoin
from django.utils.lorem_ipsum import paragraphs, words
from types import SimpleNamespace
class SubjugateTemplate(ABC):
def __init__(self, engine, context, request):
self.engine = engine
self.context = context
self.request = request
self.vars = SimpleNamespace(**self.context.flatten())
def extend(self, template_name, **kwargs):
return self.engine.get_template(template_name).render(self.context, self.request, **kwargs)
def csrf_token(self):
return self.context.get("csrf_token", "") if self.context else ""
def url(self, path):
return reverse(path)
def static(self, path):
# Damn, I thought doing this was janky as hell but it's official!
if apps.is_installed("django.contrib.staticfiles"):
from django.contrib.staticfiles.storage import staticfiles_storage
return staticfiles_storage.url(path)
else:
try:
from django.conf import settings
except ImportError:
prefix = ""
else:
prefix = iri_to_uri(getattr(settings, "STATIC_URL", ""))
return urljoin(prefix, quote(path))
def filter(self, name, value):
return self.engine.filters[name](value)
def lorem_words(self, count=1, common=True):
return words(count, common).split(" ")
def lorem_paragraphs(self, count=1, common=True):
return paragraphs(count, common)
@abstractmethod
def render(**kwargs):
...

@ -0,0 +1,77 @@
from importlib.util import module_from_spec
from django.template.context import Context, make_context
from django.template.exceptions import TemplateSyntaxError
from subjugate import SubjugateTemplate as SubjugateUserTemplate
import inspect
import re
import importlib.machinery
class SubjugateTemplate:
def find_template_class(self, module, base_class):
userclass = next(
(cls for _, cls in inspect.getmembers(module, inspect.isclass)
if cls.__module__ == module.__name__ and issubclass(cls, base_class)),
None
)
if not userclass:
raise TemplateSyntaxError("Templates must have a subjugate of SubjugateTemplate")
return userclass
def code_to_module(self, code, template_name):
module_name = self.modulify(template_name)
spec = importlib.machinery.ModuleSpec(
name=module_name,
loader=None,
origin=self.origin
)
module = module_from_spec(spec)
exec(code, module.__dict__)
return module
def modulify(self, template_name: str):
# Remove any extensions.
noext = re.sub(r"\..*$", "", template_name)
# Split on any non asciibetical characters.
words = re.split(r"\W+", noext)
# Capitalize each word and join them back together.
base = ".".join((w.lower() for w in words))
# Prefix the class to ensure it doesn't start with a number.
return f"subjugate.usertemplates.{base}"
def classify(self, template_name: str):
# Remove any extensions.
noext = re.sub(r"\..*$", "", template_name)
# Split on any non asciibetical characters.
words = re.split(r"\W+", noext)
# Capitalize each word and join them back together.
base = "".join((w.capitalize() for w in words))
# Prefix the class to ensure it doesn't start with a number.
return f"SubjugateTemplate{base}"
def __init__(self, contents, origin, template_name, engine):
self.contents= contents
self.origin = origin
self.template_name = template_name
self.engine = engine
self.module = self.code_to_module(self.contents, template_name)
self.userclass = self.find_template_class(self.module, SubjugateUserTemplate)
def render(self, context, request, **kwargs):
if isinstance(context, dict) or context is None:
context = make_context(context, request, autoescape=self.engine.autoescape)
return self.userclass(self.engine, context, request).render(**kwargs)
def html(self, context=None, request=None):
return self.render(context, request).render()

@ -0,0 +1,14 @@
from subjugate.template.engine import GenericEngine
from subjugate.template import SubjugateTemplate
class SubjugateTemplates(GenericEngine):
template_cls = SubjugateTemplate
app_dirname = "subjugate"
def __init__(self, params):
params = params.copy()
self.dirs = list(params.pop('DIRS')) or []
self.app_dirs = bool(params.pop('APP_DIRS'))
options = params.pop('OPTIONS')
super().__init__(self.dirs, self.app_dirs, **options)

@ -0,0 +1,111 @@
from typing import List, Tuple, Union
from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from django.template import Template
from django.template.engine import Engine
from django.template.backends.django import DjangoTemplates
from subjugate.template.loaders import GenericLoader
def derive_from_generic(loader_class, template_cls):
generic_loader = type(
f"{GenericLoader.__name__}{template_cls.__name__}",
(GenericLoader,),
{"template_cls": template_cls},
)
return type(f"Generic{loader_class.__name__}", (loader_class, generic_loader), {})
# This might seem weird but the engine class MUST be a subclass of
# of DjangoTemplates to support cache clearing on modification.
#
# See autoreload.py
class GenericEngine(Engine, DjangoTemplates):
template_cls = Template
app_dirname = "template"
dirs_loader = "django.template.loaders.filesystem.Loader"
appdirs_loader = "subjugate.template.loaders.app_directories.Loader"
cache_loader = "django.template.loaders.cached.Loader"
def __init__(
self,
dirs=[],
app_dirs=False,
builtins=None,
context_processors=[],
file_charset="UTF-8",
debug=False,
loaders=None,
libraries=None,
autoescape=True,
**_,
):
self.app_dirs = app_dirs
self.context_processors = context_processors
self.dirs = dirs
self.file_charset = file_charset
self.debug = debug
self.autoescape = autoescape
self.engine = self
if loaders is None:
self.loaders = self.get_default_loaders()
else:
self.loaders = loaders
if libraries is None:
libraries = {}
if builtins is None:
builtins = []
self.libraries = libraries
self.template_libraries = self.get_template_libraries(libraries)
self.template_builtins = self.get_template_builtins(builtins + self.default_builtins)
self.filters = {}
for lib in self.template_builtins:
self.filters.update(lib.filters)
def __repr__(self):
return f"<{self.__class__.__qualname__}>"
@cached_property
def template_context_processors(self):
return tuple([import_string(path) for path in self.context_processors])
def find_template_loader(self, loader):
if isinstance(loader, (tuple, list)):
loader, *args = loader
else:
args = []
if isinstance(loader, str):
loader_class = import_string(loader)
derived_clss = derive_from_generic(loader_class, self.template_cls)
return derived_clss(self, *args)
else:
raise ImproperlyConfigured(
"Invalid value in template loaders configuration: %r" % loader
)
def get_default_loaders(self):
loaders: List[Union[str, List, Tuple]] = [self.dirs_loader]
if self.app_dirs:
loaders.append((self.appdirs_loader, self.app_dirname))
return [(self.cache_loader, loaders)]
def get_template_loaders(self, template_loaders):
return [ld for tl in template_loaders if (ld := self.find_template_loader(tl))]
def from_string(self, template_code):
self.template_cls(template_code, engine=self)
def get_template(self, template_name):
template, origin = self.find_template(template_name)
if not hasattr(template, "render"):
template = self.template_cls(template, origin, template_name, engine=self)
return template

@ -0,0 +1,5 @@
from .base import GenericLoader
__all__ = [
"GenericLoader",
]

@ -0,0 +1,11 @@
from django.template.loaders import filesystem
from django.template.utils import get_app_template_dirs
class Loader(filesystem.Loader):
def __init__(self, engine, app_dirname, *args):
self.app_dirname = app_dirname
super().__init__(engine, *args)
def get_dirs(self):
return get_app_template_dirs(self.app_dirname)

@ -0,0 +1,41 @@
from django.template.loaders import base
from django.template import TemplateDoesNotExist, Template
# Despite deriving from base.Loader this class is actually more generic and can
# be used to leverage the Django template loader ecosystem for any kind of
# template.
class GenericLoader(base.Loader):
template_cls = Template
def __init__(self, engine):
self.engine = engine
def get_contents(self, _):
return NotImplemented("Subclasses must implement get_contents")
def get_template(self, template_name, skip=None):
tried = []
for origin in self.get_template_sources(template_name):
if skip is not None and origin in skip:
tried.append((origin, "Skipped to avoid recursion"))
continue
try:
contents = self.get_contents(origin)
except TemplateDoesNotExist:
tried.append((origin, "Source does not exist"))
continue
else:
return self.template_cls(
contents,
origin,
origin.template_name,
self.engine,
)
raise TemplateDoesNotExist(template_name, tried=tried)
def from_string(self, template_code):
return self.template_cls(template_code, self.engine)
Loading…
Cancel
Save