Coverage for apis_core/generic/templatetags/generic.py: 74%
135 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-28 06:34 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-28 06:34 +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, content_types):
141 user = context.request.user
142 return any(
143 [user.has_perm(ct.model_class().get_view_permission()) for ct in content_types]
144 )
147@register.simple_tag
148def content_types_by_natural_keys(natural_keys: tuple = ()) -> list[ContentType]:
149 """
150 Convert a list of natural keys to a list of ContentType models
151 If any of the natural keys does not refer to an existing model, raise a 404
152 """
153 content_types = []
154 for key in natural_keys:
155 app_label, model = key.split(".")
156 content_type = get_object_or_404(ContentType, app_label=app_label, model=model)
157 content_types.append(content_type)
158 return content_types
161@register.simple_tag
162def natural_keys_by_content_types(content_types: tuple = ()) -> list[str]:
163 """
164 Convert a list of ContentType models to their natural key
165 """
166 natural_keys = []
167 for content_type in content_types:
168 natural_keys.append(content_type.app_label + "." + content_type.model)
169 return natural_keys
172@register.filter
173def split(string: str = "", delimiter=",") -> list[str]:
174 """
175 Split a string by a specific delimiter and also strip the string of
176 leading and trailing whitespaces.
177 """
178 return list(map(str.strip, string.split(delimiter)))
181@register.simple_tag
182def model_field_template_lookup_list(model, field, suffix="") -> list[str]:
183 """
184 generate a template path based on the modelname, the fieldname and
185 the suffix. return a list with this template path and a fallback
186 path.
187 """
188 content_type = ContentType.objects.get_for_model(model)
189 path = f"{content_type.app_label}/partials/{content_type.model}_{field.name}_{suffix}.html"
190 return [path, f"generic/partials/default_model_field_{suffix}.html"]
193def template_exists(value):
194 try:
195 template.loader.get_template(value)
196 return True
197 except template.TemplateDoesNotExist:
198 return False
201@register.simple_tag
202def app_templates(prefix: str = "", suffix: str = ""):
203 """
204 List templates found in the installed apps template folder.
205 The template path is prefixed with `prefix` and suffixed with
206 `suffix`
207 """
208 labels = [app.label for app in apps.get_app_configs()]
209 templates = [f"{prefix}{label}{suffix}" for label in labels]
210 existing = [template for template in templates if template_exists(template)]
211 return existing
214@register.simple_tag
215def get_genericmodels() -> list[GenericModel]:
216 """
217 Return a list of `GenericModel` models
218 """
219 return list(filter(lambda x: issubclass(x, GenericModel), apps.get_models()))
222@register.simple_tag
223def regroup_filter_empty(
224 models: list[models.Model], attribute: str
225) -> list[models.Model]:
226 """
227 Group a list of models based on the value of `attribute`. This is similar to the
228 [regroup](https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup)
229 tag, but it filters out `None` values before grouping instead of breaking on them.
230 """
231 models = filter(
232 lambda x: functools.reduce(getattr, attribute.split("."), x), models
233 )
234 groups = defaultdict(list)
235 for model in models:
236 groups[functools.reduce(getattr, attribute.split("."), model)].append(model)
237 return dict(groups)