Coverage for apis_core/generic/templatetags/generic.py: 62%
159 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-14 10:55 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-14 10:55 +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.simple_tag
89def pure_genericmodel_content_types():
90 """
91 Retrieve all models which inherit from GenericModel class
92 but are not Collections, Entities, Relations or History models
93 """
94 parents = []
95 if apps.is_installed("apis_core.collections"):
96 collections = apps.get_app_config("collections")
97 parents.append(collections.models_module.SkosCollection)
98 parents.append(collections.models_module.SkosCollectionContentObject)
99 if apps.is_installed("apis_core.relations"):
100 relations = apps.get_app_config("relations")
101 parents.append(relations.models_module.Relation)
102 if apps.is_installed("apis_core.history"):
103 history = apps.get_app_config("history")
104 parents.append(history.models_module.APISHistoryTableBase)
105 if apps.is_installed("apis_core.apis_entities"):
106 entities = apps.get_app_config("apis_entities")
107 parents.append(entities.models_module.AbstractEntity)
108 if apps.is_installed("apis_core.entities"):
109 entities = apps.get_app_config("entities")
110 parents.append(entities.module.abc.Entity)
111 genericmodels = [
112 ct
113 for ct in set(genericmodel_content_types())
114 if not issubclass(ct.model_class(), tuple(parents))
115 ]
116 return genericmodels
119@register.filter
120def get_attribute(obj, attribute):
121 return getattr(obj, attribute, None)
124@register.filter
125def content_type_count(content_type):
126 """
127 Return the number of objects having a specific content type
128 """
129 return content_type.model_class().objects.count()
132@register.simple_tag
133def model_mro_templates(obj, folder="", prefix="", suffix=""):
134 return template_names_via_mro(
135 type(obj), folder=folder, prefix=prefix, suffix=suffix
136 )
139@register.simple_tag(takes_context=True)
140def any_view_permission(context, models):
141 user = context.request.user
142 return any([user.has_perm(model.get_view_permission()) for model in models])
145@register.simple_tag
146def content_types_by_natural_keys(natural_keys: tuple = ()) -> list[ContentType]:
147 """
148 Convert a list of natural keys to a list of ContentType models
149 If any of the natural keys does not refer to an existing model, raise a 404
150 """
151 content_types = []
152 for key in natural_keys:
153 app_label, model = key.split(".")
154 content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
155 content_types.append(content_type)
156 return content_types
159@register.simple_tag
160def natural_keys_by_content_types(content_types: tuple = ()) -> list[str]:
161 """
162 Convert a list of ContentType models to their natural key
163 """
164 natural_keys = []
165 for content_type in content_types:
166 natural_keys.append(content_type.app_label + "." + content_type.model)
167 return natural_keys
170@register.filter
171def split(string: str = "", delimiter=",") -> list[str]:
172 """
173 Split a string by a specific delimiter and also strip the string of
174 leading and trailing whitespaces.
175 """
176 return list(map(str.strip, string.split(delimiter)))
179@register.simple_tag
180def model_field_template_lookup_list(model, field, suffix="") -> list[str]:
181 """
182 generate a template path based on the modelname, the fieldname and
183 the suffix. return a list with this template path and a fallback
184 path.
185 """
186 content_type = ContentType.objects.get_for_model(model)
187 path = f"{content_type.app_label}/partials/{content_type.model}_{field.name}_{suffix}.html"
188 return [path, f"generic/partials/default_model_field_{suffix}.html"]
191def template_exists(value):
192 try:
193 template.loader.get_template(value)
194 return True
195 except template.TemplateDoesNotExist:
196 return False
199@register.simple_tag
200def app_templates(prefix: str = "", suffix: str = ""):
201 """
202 List templates found in the installed apps template folder.
203 The template path is prefixed with `prefix` and suffixed with
204 `suffix`
205 """
206 labels = [app.label for app in apps.get_app_configs()]
207 templates = [f"{prefix}{label}{suffix}" for label in labels]
208 existing = [template for template in templates if template_exists(template)]
209 return existing
212@register.simple_tag
213def get_genericmodels() -> list[GenericModel]:
214 """
215 Return a list of `GenericModel` models
216 """
217 return list(filter(lambda x: issubclass(x, GenericModel), apps.get_models()))
220@register.simple_tag
221def regroup_filter_empty(
222 models: list[models.Model], attribute: str
223) -> list[models.Model]:
224 """
225 Group a list of models based on the value of `attribute`. This is similar to the
226 [regroup](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup)
227 tag, but it filters out `None` values before grouping instead of breaking on them.
228 """
229 models = filter(
230 lambda x: functools.reduce(getattr, attribute.split("."), x), models
231 )
232 groups = defaultdict(list)
233 for model in models:
234 groups[functools.reduce(getattr, attribute.split("."), model)].append(model)
235 return dict(groups)
238@register.simple_tag
239def get_pure_genericmodels() -> list[GenericModel]:
240 parents = []
241 if apps.is_installed("apis_core.collections"):
242 collections = apps.get_app_config("collections")
243 parents.append(collections.models_module.SkosCollection)
244 parents.append(collections.models_module.SkosCollectionContentObject)
245 if apps.is_installed("apis_core.relations"):
246 relations = apps.get_app_config("relations")
247 parents.append(relations.models_module.Relation)
248 if apps.is_installed("apis_core.history"):
249 history = apps.get_app_config("history")
250 parents.append(history.models_module.APISHistoryTableBase)
251 if apps.is_installed("apis_core.apis_entities"):
252 entities = apps.get_app_config("apis_entities")
253 parents.append(entities.models_module.AbstractEntity)
254 if apps.is_installed("apis_core.entities"):
255 entities = apps.get_app_config("entities")
256 parents.append(entities.module.abc.Entity)
257 genericmodels = [
258 model
259 for model in set(get_genericmodels())
260 if not issubclass(model, tuple(parents))
261 ]
262 return genericmodels
265@register.simple_tag
266def sort_object_on_attribute(objects: list[object], attribute: str) -> list[object]:
267 """
268 Sort a list of objects based on an attribute. The attribute can be nested, like
269 `some.nested.attribute`
270 """
271 return sorted(
272 objects, key=lambda x: functools.reduce(getattr, attribute.split("."), x)
273 )