Coverage for apis_core/generic/templatetags/generic.py: 70%
135 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-30 06:11 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-30 06:11 +0000
1import functools
2from collections import defaultdict
4from django import template
5from django.apps import apps
6from django.contrib.contenttypes.fields import GenericForeignKey
7from django.contrib.contenttypes.models import ContentType
8from django.core.exceptions import ObjectDoesNotExist
9from django.db import models
10from django.shortcuts import get_object_or_404
12from apis_core.core.templatetags.core import get_model_fields
13from apis_core.generic.abc import GenericModel
14from apis_core.generic.helpers import template_names_via_mro
16register = template.Library()
19@register.filter
20def contenttype(model):
21 return ContentType.objects.get_for_model(model)
24@register.simple_tag
25def modeldict(instance, fields=None, exclude=None, exclude_noneditable=True):
26 data = {}
27 for f in get_model_fields(instance):
28 if not getattr(f, "editable", False) and exclude_noneditable:
29 continue
30 if fields is not None and f.name not in fields:
31 continue
32 if exclude and f.name in exclude:
33 continue
34 field = instance._meta.get_field(f.name)
35 try:
36 data[field] = getattr(instance, field.name)
37 except ObjectDoesNotExist as e:
38 data[field] = f"{field.value_from_object(instance)} ({e})"
39 if fn := getattr(instance, f"get_{field.name}_display", False):
40 data[field] = fn()
41 if getattr(field, "m2m_field_name", False):
42 values = getattr(instance, field.name).all()
43 data[field] = ", ".join([str(value) for value in values])
44 # if there are generic foreign keys defined in the model,
45 # return the object they point to and remove the
46 # individual attributes they are composed of. if the
47 # generic fk does **not** resolve, keep everything as is
48 for field in filter(
49 lambda x: isinstance(x, GenericForeignKey), get_model_fields(instance)
50 ):
51 if getattr(instance, field.name):
52 data[field] = getattr(instance, field.name)
53 ct_field = instance._meta.get_field(field.ct_field)
54 del data[ct_field]
55 fk_field = instance._meta.get_field(field.fk_field)
56 del data[fk_field]
57 return data
60@register.simple_tag
61def contenttypes(app_labels=None):
62 if app_labels:
63 app_labels = app_labels.split(",")
64 return ContentType.objects.filter(app_label__in=app_labels)
65 return ContentType.objects.all()
68def is_genericmodel(content_type: ContentType):
69 model_class = content_type.model_class()
70 return model_class is not None and issubclass(model_class, GenericModel)
73@register.simple_tag
74def genericmodel_content_types():
75 """
76 Retrieve all models which inherit from GenericModel class
77 and return their ContentType.
78 """
79 genericmodels = list(
80 filter(
81 lambda content_type: is_genericmodel(content_type),
82 ContentType.objects.all(),
83 )
84 )
85 return genericmodels
88@register.filter
89def get_attribute(obj, attribute):
90 return getattr(obj, attribute, None)
93@register.filter
94def content_type_count(content_type):
95 """
96 Return the number of objects having a specific content type
97 """
98 return content_type.model_class().objects.count()
101@register.simple_tag
102def model_mro_templates(obj, folder="", prefix="", suffix=""):
103 return template_names_via_mro(
104 type(obj), folder=folder, prefix=prefix, suffix=suffix
105 )
108@register.simple_tag(takes_context=True)
109def any_view_permission(context, models):
110 user = context.request.user
111 return any([user.has_perm(model.get_view_permission()) for model in models])
114@register.simple_tag
115def content_types_by_natural_keys(natural_keys: tuple = ()) -> list[ContentType]:
116 """
117 Convert a list of natural keys to a list of ContentType models
118 If any of the natural keys does not refer to an existing model, raise a 404
119 """
120 content_types = []
121 for key in natural_keys:
122 app_label, model = key.split(".")
123 content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
124 content_types.append(content_type)
125 return content_types
128@register.simple_tag
129def natural_keys_by_content_types(content_types: tuple = ()) -> list[str]:
130 """
131 Convert a list of ContentType models to their natural key
132 """
133 natural_keys = []
134 for content_type in content_types:
135 natural_keys.append(content_type.app_label + "." + content_type.model)
136 return natural_keys
139@register.filter
140def split(string: str = "", delimiter=",") -> list[str]:
141 """
142 Split a string by a specific delimiter and also strip the string of
143 leading and trailing whitespaces.
144 """
145 return list(map(str.strip, string.split(delimiter)))
148@register.simple_tag
149def model_field_template_lookup_list(model, field, suffix="") -> list[str]:
150 """
151 generate a template path based on the modelname, the fieldname and
152 the suffix. return a list with this template path and a fallback
153 path.
154 """
155 content_type = ContentType.objects.get_for_model(model)
156 path = f"{content_type.app_label}/partials/{content_type.model}_{field.name}_{suffix}.html"
157 return [path, f"generic/partials/default_model_field_{suffix}.html"]
160def template_exists(value):
161 try:
162 template.loader.get_template(value)
163 return True
164 except template.TemplateDoesNotExist:
165 return False
168@register.simple_tag
169def app_templates(prefix: str = "", suffix: str = ""):
170 """
171 List templates found in the installed apps template folder.
172 The template path is prefixed with `prefix` and suffixed with
173 `suffix`
174 """
175 labels = [app.label for app in apps.get_app_configs()]
176 templates = [f"{prefix}{label}{suffix}" for label in labels]
177 existing = [template for template in templates if template_exists(template)]
178 return existing
181@register.simple_tag
182def get_genericmodels() -> list[GenericModel]:
183 """
184 Return a list of `GenericModel` models
185 """
186 return list(filter(lambda x: issubclass(x, GenericModel), apps.get_models()))
189@register.simple_tag
190def regroup_filter_empty(
191 models: list[models.Model], attribute: str
192) -> list[models.Model]:
193 """
194 Group a list of models based on the value of `attribute`. This is similar to the
195 [regroup](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup)
196 tag, but it filters out `None` values before grouping instead of breaking on them.
197 """
198 models = filter(
199 lambda x: functools.reduce(getattr, attribute.split("."), x), models
200 )
201 groups = defaultdict(list)
202 for model in models:
203 groups[functools.reduce(getattr, attribute.split("."), model)].append(model)
204 return dict(groups)
207@register.simple_tag
208def get_pure_genericmodels() -> list[GenericModel]:
209 parents = []
210 if apps.is_installed("apis_core.collections"):
211 collections = apps.get_app_config("collections")
212 parents.append(collections.models_module.SkosCollection)
213 parents.append(collections.models_module.SkosCollectionContentObject)
214 if apps.is_installed("apis_core.relations"):
215 relations = apps.get_app_config("relations")
216 parents.append(relations.models_module.Relation)
217 if apps.is_installed("apis_core.history"):
218 history = apps.get_app_config("history")
219 parents.append(history.models_module.APISHistoryTableBase)
220 if apps.is_installed("apis_core.entities"):
221 entities = apps.get_app_config("entities")
222 parents.append(entities.module.abc.Entity)
223 genericmodels = [
224 model
225 for model in set(get_genericmodels())
226 if not issubclass(model, tuple(parents))
227 ]
228 return genericmodels
231@register.simple_tag
232def sort_object_on_attribute(objects: list[object], attribute: str) -> list[object]:
233 """
234 Sort a list of objects based on an attribute. The attribute can be nested, like
235 `some.nested.attribute`
236 """
237 return sorted(
238 objects, key=lambda x: functools.reduce(getattr, attribute.split("."), x)
239 )