djangocms-render-context 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- djangocms_render_context-1.0.0/MANIFEST.in +2 -0
- djangocms_render_context-1.0.0/PKG-INFO +93 -0
- djangocms_render_context-1.0.0/README.md +54 -0
- djangocms_render_context-1.0.0/djangocms_render_context/__init__.py +1 -0
- djangocms_render_context-1.0.0/djangocms_render_context/cache.py +2 -0
- djangocms_render_context-1.0.0/djangocms_render_context/cms_plugins.py +50 -0
- djangocms_render_context-1.0.0/djangocms_render_context/encoders.py +8 -0
- djangocms_render_context-1.0.0/djangocms_render_context/exceptions.py +2 -0
- djangocms_render_context-1.0.0/djangocms_render_context/forms.py +95 -0
- djangocms_render_context-1.0.0/djangocms_render_context/loaders.py +214 -0
- djangocms_render_context-1.0.0/djangocms_render_context/locale/cs/LC_MESSAGES/django.mo +0 -0
- djangocms_render_context-1.0.0/djangocms_render_context/locale/cs/LC_MESSAGES/django.po +107 -0
- djangocms_render_context-1.0.0/djangocms_render_context/migrations/0001_initial.py +96 -0
- djangocms_render_context-1.0.0/djangocms_render_context/migrations/__init__.py +0 -0
- djangocms_render_context-1.0.0/djangocms_render_context/models.py +96 -0
- djangocms_render_context-1.0.0/djangocms_render_context/utils.py +46 -0
- djangocms_render_context-1.0.0/djangocms_render_context.egg-info/PKG-INFO +93 -0
- djangocms_render_context-1.0.0/djangocms_render_context.egg-info/SOURCES.txt +21 -0
- djangocms_render_context-1.0.0/djangocms_render_context.egg-info/dependency_links.txt +1 -0
- djangocms_render_context-1.0.0/djangocms_render_context.egg-info/requires.txt +14 -0
- djangocms_render_context-1.0.0/djangocms_render_context.egg-info/top_level.txt +1 -0
- djangocms_render_context-1.0.0/pyproject.toml +135 -0
- djangocms_render_context-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: djangocms-render-context
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Render context into template.
|
|
5
|
+
Author-email: Zdeněk Böhm <zdenek.bohm@nic.cz>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context
|
|
8
|
+
Project-URL: Repository, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context.git
|
|
9
|
+
Project-URL: Changelog, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context/-/blob/main/CHANGELOG.md
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Framework :: Django CMS
|
|
17
|
+
Classifier: Framework :: Django CMS :: 4.1
|
|
18
|
+
Classifier: Framework :: Django CMS :: 5.0
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: django-cms<6,>=4.0
|
|
28
|
+
Requires-Dist: django-filer~=3.4
|
|
29
|
+
Requires-Dist: pyyaml
|
|
30
|
+
Requires-Dist: requests
|
|
31
|
+
Provides-Extra: quality
|
|
32
|
+
Requires-Dist: ruff; extra == "quality"
|
|
33
|
+
Requires-Dist: mypy; extra == "quality"
|
|
34
|
+
Requires-Dist: types-PyYAML; extra == "quality"
|
|
35
|
+
Requires-Dist: types-requests; extra == "quality"
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: testfixtures; extra == "test"
|
|
38
|
+
Requires-Dist: responses; extra == "test"
|
|
39
|
+
|
|
40
|
+
# DjangoCMS Render Context
|
|
41
|
+
|
|
42
|
+
The plugin for the [Django CMS](https://www.django-cms.org/) content management system.
|
|
43
|
+
The plugin displays the context in the template. The context can be specified directly in JSON format.
|
|
44
|
+
Or the context can be used as a media file. Or the context can be loaded from a URL.
|
|
45
|
+
The template can be entered directly or selected from a list defined in the settings in the ``DJANGOCMS_RENDER_CONTEXT_TEMPLATES`` constant.
|
|
46
|
+
|
|
47
|
+
Supported source data formats (mimetype):
|
|
48
|
+
|
|
49
|
+
- csv (text/csv)
|
|
50
|
+
- json (application/json)
|
|
51
|
+
- yaml (application/yaml)
|
|
52
|
+
- xml (application/xml)
|
|
53
|
+
- ods (application/vnd.oasis.opendocument.spreadsheet)
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Install the package from pypi.org.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
pip install djangocms-render-context
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Add into `INSTALLED_APPS` in your site `settings.py`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
INSTALLED_APPS = [
|
|
67
|
+
...
|
|
68
|
+
"easy_thumbnails",
|
|
69
|
+
"filer",
|
|
70
|
+
"djangocms_render_context",
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Extra settings
|
|
75
|
+
|
|
76
|
+
This value can be defined in settings.
|
|
77
|
+
|
|
78
|
+
- `DJANGOCMS_RENDER_CONTEXT_TEMPLATES` - List of templates that the plugin can use.
|
|
79
|
+
|
|
80
|
+
For example:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
DJANGOCMS_RENDER_CONTEXT_TEMPLATES = (
|
|
84
|
+
("", ""),
|
|
85
|
+
("plugin.html", "Plugin"),
|
|
86
|
+
("schedule.html", "Schedule"),
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
BSD-3-Clause
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# DjangoCMS Render Context
|
|
2
|
+
|
|
3
|
+
The plugin for the [Django CMS](https://www.django-cms.org/) content management system.
|
|
4
|
+
The plugin displays the context in the template. The context can be specified directly in JSON format.
|
|
5
|
+
Or the context can be used as a media file. Or the context can be loaded from a URL.
|
|
6
|
+
The template can be entered directly or selected from a list defined in the settings in the ``DJANGOCMS_RENDER_CONTEXT_TEMPLATES`` constant.
|
|
7
|
+
|
|
8
|
+
Supported source data formats (mimetype):
|
|
9
|
+
|
|
10
|
+
- csv (text/csv)
|
|
11
|
+
- json (application/json)
|
|
12
|
+
- yaml (application/yaml)
|
|
13
|
+
- xml (application/xml)
|
|
14
|
+
- ods (application/vnd.oasis.opendocument.spreadsheet)
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Install the package from pypi.org.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
pip install djangocms-render-context
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Add into `INSTALLED_APPS` in your site `settings.py`:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
INSTALLED_APPS = [
|
|
28
|
+
...
|
|
29
|
+
"easy_thumbnails",
|
|
30
|
+
"filer",
|
|
31
|
+
"djangocms_render_context",
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Extra settings
|
|
36
|
+
|
|
37
|
+
This value can be defined in settings.
|
|
38
|
+
|
|
39
|
+
- `DJANGOCMS_RENDER_CONTEXT_TEMPLATES` - List of templates that the plugin can use.
|
|
40
|
+
|
|
41
|
+
For example:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
DJANGOCMS_RENDER_CONTEXT_TEMPLATES = (
|
|
45
|
+
("", ""),
|
|
46
|
+
("plugin.html", "Plugin"),
|
|
47
|
+
("schedule.html", "Schedule"),
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
BSD-3-Clause
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from cms.models.placeholdermodel import Placeholder
|
|
2
|
+
from cms.plugin_base import CMSPluginBase
|
|
3
|
+
from cms.plugin_pool import plugin_pool
|
|
4
|
+
from django.template import Template
|
|
5
|
+
from django.template.backends.django import Template as DjangoTemplate
|
|
6
|
+
from django.template.loader import get_template
|
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
|
8
|
+
|
|
9
|
+
from .forms import RenderContextForm
|
|
10
|
+
from .models import RenderContext
|
|
11
|
+
from .utils import create_html
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@plugin_pool.register_plugin
|
|
15
|
+
class RenderContextPlugin(CMSPluginBase):
|
|
16
|
+
model = RenderContext
|
|
17
|
+
form = RenderContextForm
|
|
18
|
+
name = _("Render Context Plugin")
|
|
19
|
+
fieldsets = [
|
|
20
|
+
(
|
|
21
|
+
_("Sources"),
|
|
22
|
+
{
|
|
23
|
+
"fields": (
|
|
24
|
+
"context",
|
|
25
|
+
"file",
|
|
26
|
+
(
|
|
27
|
+
"source",
|
|
28
|
+
"cached",
|
|
29
|
+
),
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
),
|
|
33
|
+
(
|
|
34
|
+
_("Templates"),
|
|
35
|
+
{
|
|
36
|
+
"classes": ["collapse"],
|
|
37
|
+
"fields": (
|
|
38
|
+
"template",
|
|
39
|
+
"template_list",
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
def get_render_template(self, context: dict, instance: RenderContext, placeholder: Placeholder) -> DjangoTemplate:
|
|
46
|
+
context["data"] = instance.get_data()
|
|
47
|
+
if instance.template_list and not instance.template:
|
|
48
|
+
return get_template(instance.template_list)
|
|
49
|
+
tmpl = Template(instance.template if instance.template else create_html(context["data"]))
|
|
50
|
+
return DjangoTemplate(tmpl, tmpl)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
from django import forms
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.core.cache import cache
|
|
7
|
+
from django.core.exceptions import ValidationError
|
|
8
|
+
from django.forms import ALL_FIELDS, ModelForm
|
|
9
|
+
from django.template import Context, Template, TemplateDoesNotExist
|
|
10
|
+
from django.template.exceptions import TemplateSyntaxError
|
|
11
|
+
from django.template.loader import get_template
|
|
12
|
+
from django.utils.translation import gettext_lazy as _
|
|
13
|
+
from filer.models.filemodels import File
|
|
14
|
+
|
|
15
|
+
from .cache import get_cache_key
|
|
16
|
+
from .exceptions import SourceParseFailure
|
|
17
|
+
from .loaders import SUPPORTED_FILE_TYPES, get_data_from_file, get_data_from_url
|
|
18
|
+
|
|
19
|
+
TEMPLATES = (("", ""),)
|
|
20
|
+
|
|
21
|
+
sourceType = Union[str, File] # noqa: UP007
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RenderContextForm(ModelForm):
|
|
25
|
+
"""Render Context Form."""
|
|
26
|
+
|
|
27
|
+
template_list = forms.ChoiceField(
|
|
28
|
+
label=_("Template list"),
|
|
29
|
+
required=False,
|
|
30
|
+
choices=getattr(settings, "DJANGOCMS_RENDER_CONTEXT_TEMPLATES", TEMPLATES),
|
|
31
|
+
help_text=_("List of templates. If Template is specified, the template set in the list will not be used."),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, **kwargs):
|
|
35
|
+
super().__init__(*args, **kwargs)
|
|
36
|
+
self.fields["file"].help_text += " " + _("Supported formats are:") + " " + ", ".join(SUPPORTED_FILE_TYPES)
|
|
37
|
+
|
|
38
|
+
def clean_template_list(self) -> None:
|
|
39
|
+
if self.cleaned_data["template_list"]:
|
|
40
|
+
try:
|
|
41
|
+
get_template(self.cleaned_data["template_list"])
|
|
42
|
+
except TemplateSyntaxError as error:
|
|
43
|
+
raise ValidationError(error) from error
|
|
44
|
+
except TemplateDoesNotExist as error:
|
|
45
|
+
raise ValidationError(f'Template "{error}" does not exist.') from error
|
|
46
|
+
return self.cleaned_data["template_list"]
|
|
47
|
+
|
|
48
|
+
def check_value(self, name: str, value: sourceType, loader: Callable) -> Context:
|
|
49
|
+
try:
|
|
50
|
+
return Context(loader(value))
|
|
51
|
+
except SourceParseFailure as error:
|
|
52
|
+
self.add_error(name, error)
|
|
53
|
+
|
|
54
|
+
def check_context(self, cleaned_data: dict) -> Context:
|
|
55
|
+
context = Context({})
|
|
56
|
+
if cleaned_data["context"]:
|
|
57
|
+
context = Context(cleaned_data["context"])
|
|
58
|
+
elif cleaned_data["file"]:
|
|
59
|
+
context = self.check_value("file", cleaned_data["file"], get_data_from_file)
|
|
60
|
+
elif cleaned_data["source"]:
|
|
61
|
+
context = self.check_value("source", cleaned_data["source"], get_data_from_url)
|
|
62
|
+
if context is None:
|
|
63
|
+
self.add_error(ALL_FIELDS, "Failed to set context.")
|
|
64
|
+
context = Context({})
|
|
65
|
+
return context
|
|
66
|
+
|
|
67
|
+
def check_template(self, cleaned_data: dict, context: Context) -> None:
|
|
68
|
+
template = None
|
|
69
|
+
field_name = ALL_FIELDS
|
|
70
|
+
if cleaned_data["template"]:
|
|
71
|
+
field_name = "template"
|
|
72
|
+
try:
|
|
73
|
+
template = Template(cleaned_data["template"])
|
|
74
|
+
except TemplateSyntaxError as error:
|
|
75
|
+
self.add_error("template", error)
|
|
76
|
+
elif cleaned_data["template_list"]:
|
|
77
|
+
field_name = "template_list"
|
|
78
|
+
template = get_template(cleaned_data["template_list"]).template
|
|
79
|
+
if template:
|
|
80
|
+
try:
|
|
81
|
+
template.render(context)
|
|
82
|
+
except (TemplateDoesNotExist, TemplateSyntaxError) as error:
|
|
83
|
+
self.add_error(field_name, error)
|
|
84
|
+
|
|
85
|
+
def clean(self) -> None:
|
|
86
|
+
"""Clean form."""
|
|
87
|
+
cleaned_data = super().clean()
|
|
88
|
+
if self.is_valid():
|
|
89
|
+
context = self.check_context(cleaned_data)
|
|
90
|
+
self.check_template(cleaned_data, context)
|
|
91
|
+
|
|
92
|
+
def save(self, *args, **kwargs):
|
|
93
|
+
if self.instance.pk:
|
|
94
|
+
cache.delete(get_cache_key(self.instance.pk))
|
|
95
|
+
return super().save(*args, **kwargs)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
import json
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
import zipfile
|
|
5
|
+
from io import BytesIO, StringIO
|
|
6
|
+
from mimetypes import guess_type
|
|
7
|
+
from typing import Union, cast
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
import yaml
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
from django.core.cache import cache
|
|
13
|
+
from django.utils.translation import gettext_lazy as _
|
|
14
|
+
from filer.models.filemodels import File
|
|
15
|
+
|
|
16
|
+
# Use the C (faster) implementation if possible
|
|
17
|
+
try:
|
|
18
|
+
from yaml import CSafeLoader as SafeLoader
|
|
19
|
+
except ImportError: # pragma: no cover
|
|
20
|
+
from yaml import SafeLoader # type: ignore
|
|
21
|
+
|
|
22
|
+
from .cache import get_cache_key
|
|
23
|
+
from .exceptions import SourceParseFailure
|
|
24
|
+
|
|
25
|
+
dataType = Union[dict, list] # noqa: UP007
|
|
26
|
+
valueType = Union[str, list] # noqa: UP007
|
|
27
|
+
cellType = Union[dict, str, list] # noqa: UP007
|
|
28
|
+
nodeTextType = Union[str, None] # noqa: UP007
|
|
29
|
+
payloadType = Union[str, dict] # noqa: UP007
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_mime_type_message(mime_type: nodeTextType) -> str:
|
|
33
|
+
return (
|
|
34
|
+
_("Unsupported file mime type: ")
|
|
35
|
+
+ str(mime_type)
|
|
36
|
+
+ ". "
|
|
37
|
+
+ _("Only allowed are:")
|
|
38
|
+
+ " "
|
|
39
|
+
+ ", ".join(SUPPORTED_FILE_TYPES)
|
|
40
|
+
+ "."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_data_from_file(source: File) -> dataType:
|
|
45
|
+
if source.mime_type not in LOADERS:
|
|
46
|
+
raise SourceParseFailure(get_mime_type_message(source.mime_type))
|
|
47
|
+
return LOADERS[source.mime_type](source.file.read())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_json(content: bytes) -> dataType:
|
|
51
|
+
try:
|
|
52
|
+
return json.load(BytesIO(content))
|
|
53
|
+
except json.decoder.JSONDecodeError as err:
|
|
54
|
+
raise SourceParseFailure(err) from err
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_yaml(content: bytes) -> dataType:
|
|
58
|
+
try:
|
|
59
|
+
return yaml.load(content, Loader=SafeLoader)
|
|
60
|
+
except yaml.YAMLError as err:
|
|
61
|
+
raise SourceParseFailure(err) from err
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_csv(content: bytes) -> list:
|
|
65
|
+
try:
|
|
66
|
+
body = content.decode("utf8")
|
|
67
|
+
except UnicodeDecodeError as err:
|
|
68
|
+
raise SourceParseFailure(err) from err
|
|
69
|
+
reader = csv.reader(StringIO(body))
|
|
70
|
+
try:
|
|
71
|
+
return list(reader)
|
|
72
|
+
except csv.Error as err:
|
|
73
|
+
raise SourceParseFailure(err) from err
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def load_xml(content: bytes) -> dataType:
|
|
77
|
+
data = []
|
|
78
|
+
try:
|
|
79
|
+
doc = ET.parse(BytesIO(content))
|
|
80
|
+
except ET.ParseError as err:
|
|
81
|
+
raise SourceParseFailure(err) from err
|
|
82
|
+
root = doc.getroot()
|
|
83
|
+
for row in root:
|
|
84
|
+
data.append([column.text for column in row])
|
|
85
|
+
return data
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def load_spreadsheet(content: bytes) -> dataType:
|
|
89
|
+
handle = None
|
|
90
|
+
try:
|
|
91
|
+
handle = zipfile.ZipFile(BytesIO(content))
|
|
92
|
+
payload = handle.read("content.xml")
|
|
93
|
+
except (zipfile.BadZipFile, KeyError) as err:
|
|
94
|
+
raise SourceParseFailure(err) from err
|
|
95
|
+
finally:
|
|
96
|
+
if handle is None:
|
|
97
|
+
pass
|
|
98
|
+
else:
|
|
99
|
+
handle.close()
|
|
100
|
+
try:
|
|
101
|
+
doc = ET.parse(BytesIO(payload))
|
|
102
|
+
except ET.ParseError as err:
|
|
103
|
+
raise SourceParseFailure(err) from err
|
|
104
|
+
return pase_data(cast(ET.ElementTree, doc))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def pase_data(doc: ET.ElementTree) -> dataType:
|
|
108
|
+
data: list[list[cellType]] = []
|
|
109
|
+
ns = {
|
|
110
|
+
"table": "urn:oasis:names:tc:opendocument:xmlns:table:1.0",
|
|
111
|
+
"text": "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
|
112
|
+
"xlink": "http://www.w3.org/1999/xlink",
|
|
113
|
+
}
|
|
114
|
+
max_gap = getattr(settings, "DJANGOCMS_RENDER_CONTEXT_ODS_MAX_GAP", 51)
|
|
115
|
+
root = doc.getroot()
|
|
116
|
+
if root is None:
|
|
117
|
+
return data
|
|
118
|
+
table = root.find(".//table:table", ns)
|
|
119
|
+
if table is not None:
|
|
120
|
+
for row in table.findall("table:table-row", ns):
|
|
121
|
+
repeated = row.get(qname(ns, "table:number-rows-repeated"))
|
|
122
|
+
if repeated is not None:
|
|
123
|
+
offset = int(repeated) - 1
|
|
124
|
+
if offset < max_gap:
|
|
125
|
+
data.extend([] * offset)
|
|
126
|
+
data.append(parse_cells(ns, max_gap, row))
|
|
127
|
+
while data and data[-1] == [""]:
|
|
128
|
+
data.pop()
|
|
129
|
+
return data
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def parse_cells(ns: dict[str, str], max_gap: int, row: ET.Element) -> list[cellType]:
|
|
133
|
+
line: list[cellType] = []
|
|
134
|
+
for cell in row.findall("table:table-cell", ns):
|
|
135
|
+
repeated = cell.get(qname(ns, "table:number-columns-repeated"))
|
|
136
|
+
if repeated is not None:
|
|
137
|
+
offset = int(repeated) - 1
|
|
138
|
+
if offset < max_gap:
|
|
139
|
+
line.extend([""] * offset)
|
|
140
|
+
payload: list[payloadType] = []
|
|
141
|
+
for text in cell.findall("text:p", ns):
|
|
142
|
+
if text.text is not None:
|
|
143
|
+
payload.append({"p": text.text})
|
|
144
|
+
for link in text.findall("text:a", ns):
|
|
145
|
+
href = link.get(qname(ns, "xlink:href"))
|
|
146
|
+
payload.append({"href": href, "text": none_to_str(link.text)})
|
|
147
|
+
if len(payload) == 1:
|
|
148
|
+
payload = payload[0] # type: ignore
|
|
149
|
+
if "p" in payload:
|
|
150
|
+
payload = payload["p"] # type: ignore
|
|
151
|
+
if payload == []:
|
|
152
|
+
payload = "" # type: ignore
|
|
153
|
+
line.append(payload)
|
|
154
|
+
return line
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def none_to_str(value: nodeTextType) -> str:
|
|
158
|
+
return "" if value is None else str(value)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def qname(ns: dict[str, str], prefix_and_name: str) -> str:
|
|
162
|
+
"""Create qualified xml element name."""
|
|
163
|
+
parts = prefix_and_name.split(":", 1)
|
|
164
|
+
prefix, name = parts
|
|
165
|
+
return str(ET.QName(ns[prefix], name))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_source(url: str):
|
|
169
|
+
"""Load data from the source."""
|
|
170
|
+
timeout = getattr(settings, "DJANGOCMS_RENDER_CONTEXT_LOAD_TIMEOUT", 6)
|
|
171
|
+
verify = getattr(settings, "DJANGOCMS_RENDER_CONTEXT_VERIFY", True)
|
|
172
|
+
response = requests.get(url, timeout=timeout, verify=verify)
|
|
173
|
+
response.raise_for_status()
|
|
174
|
+
return response
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_data_from_url(source: str) -> dataType:
|
|
178
|
+
data: dataType = []
|
|
179
|
+
try:
|
|
180
|
+
response = load_source(source)
|
|
181
|
+
except requests.RequestException as err:
|
|
182
|
+
raise SourceParseFailure(err) from err
|
|
183
|
+
content_type = response.headers.get("Content-Type")
|
|
184
|
+
if content_type not in LOADERS:
|
|
185
|
+
content_type = guess_type(source)[0]
|
|
186
|
+
if content_type not in LOADERS:
|
|
187
|
+
raise SourceParseFailure(get_mime_type_message(content_type))
|
|
188
|
+
try:
|
|
189
|
+
data = LOADERS[content_type](response.content)
|
|
190
|
+
except SourceParseFailure as err:
|
|
191
|
+
raise SourceParseFailure(_("Resource parsing failed.")) from err
|
|
192
|
+
return data
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_cached_data_from_url(identifier: int, source: str, timeout: int) -> dataType:
|
|
196
|
+
key = get_cache_key(identifier)
|
|
197
|
+
if timeout:
|
|
198
|
+
data = cache.get(key)
|
|
199
|
+
if data is None:
|
|
200
|
+
data = get_data_from_url(source)
|
|
201
|
+
cache.set(key, data, timeout * 60)
|
|
202
|
+
else:
|
|
203
|
+
data = get_data_from_url(source)
|
|
204
|
+
return data
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
SUPPORTED_FILE_TYPES = ("csv", "json", "yaml", "xml", "ods")
|
|
208
|
+
LOADERS = {
|
|
209
|
+
"text/csv": load_csv,
|
|
210
|
+
"application/json": load_json,
|
|
211
|
+
"application/yaml": load_yaml,
|
|
212
|
+
"application/xml": load_xml,
|
|
213
|
+
"application/vnd.oasis.opendocument.spreadsheet": load_spreadsheet,
|
|
214
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# SOME DESCRIPTIVE TITLE.
|
|
2
|
+
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
3
|
+
# This file is distributed under the same license as the PACKAGE package.
|
|
4
|
+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
5
|
+
#
|
|
6
|
+
msgid ""
|
|
7
|
+
msgstr ""
|
|
8
|
+
"Project-Id-Version: PACKAGE VERSION\n"
|
|
9
|
+
"Report-Msgid-Bugs-To: \n"
|
|
10
|
+
"POT-Creation-Date: 2025-12-11 13:17+0100\n"
|
|
11
|
+
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
12
|
+
"Last-Translator: Zdeněk Böhm <zdenek.bohm@nic.cz>\n"
|
|
13
|
+
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
14
|
+
"Language: cs\n"
|
|
15
|
+
"MIME-Version: 1.0\n"
|
|
16
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
17
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
18
|
+
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
|
19
|
+
|
|
20
|
+
msgid ""
|
|
21
|
+
"Context data file. If Data is specified for the context, the source file is "
|
|
22
|
+
"not used even though it is specified."
|
|
23
|
+
msgstr ""
|
|
24
|
+
"Soubor se zdrojovými daty. Pokud jsou zadána Data pro kontext, tak se "
|
|
25
|
+
"zdrojový soubor nepoužije i přesto, že je zadán."
|
|
26
|
+
|
|
27
|
+
msgid ""
|
|
28
|
+
"Context data in JSON format. They take precedence over the source file and "
|
|
29
|
+
"source URL."
|
|
30
|
+
msgstr ""
|
|
31
|
+
"Data pro kontext ve formátu JSON. Mají přednost před zdrojovým souborem a "
|
|
32
|
+
"URL zdroje."
|
|
33
|
+
|
|
34
|
+
msgid "Data for context"
|
|
35
|
+
msgstr "Data pro kontext"
|
|
36
|
+
|
|
37
|
+
msgid "Data not entered."
|
|
38
|
+
msgstr "Data nejsou zadána."
|
|
39
|
+
|
|
40
|
+
msgid ""
|
|
41
|
+
"Django template for the context. It takes precedence over the template "
|
|
42
|
+
"selected from the list."
|
|
43
|
+
msgstr "Django šablona. Má přednost přes šablonou vybranou ze seznamu."
|
|
44
|
+
|
|
45
|
+
msgid "Download period from source URL"
|
|
46
|
+
msgstr "Perioda stahování ze URL zdroje"
|
|
47
|
+
|
|
48
|
+
msgid ""
|
|
49
|
+
"List of templates. If Template is specified, the template set in the list "
|
|
50
|
+
"will not be used."
|
|
51
|
+
msgstr ""
|
|
52
|
+
"Seznam šablon. Pokud je zadána Šablona, tak se šablona nastavená v seznamu "
|
|
53
|
+
"nepoužije."
|
|
54
|
+
|
|
55
|
+
msgid "No template."
|
|
56
|
+
msgstr "Žádná šablona."
|
|
57
|
+
|
|
58
|
+
msgid "Only allowed are:"
|
|
59
|
+
msgstr "Povolené jsou jen:"
|
|
60
|
+
|
|
61
|
+
msgid "Render Context Plugin"
|
|
62
|
+
msgstr "Vykreslování kontextu"
|
|
63
|
+
|
|
64
|
+
msgid "Resource parsing failed."
|
|
65
|
+
msgstr "Parsování zdroje selhalo."
|
|
66
|
+
|
|
67
|
+
msgid "Source URL"
|
|
68
|
+
msgstr "URL zdroje"
|
|
69
|
+
|
|
70
|
+
msgid "Source file"
|
|
71
|
+
msgstr "Zdrojový soubor."
|
|
72
|
+
|
|
73
|
+
msgid "Sources"
|
|
74
|
+
msgstr "Zdroje"
|
|
75
|
+
|
|
76
|
+
msgid "Supported formats are:"
|
|
77
|
+
msgstr "Podporované formáty jsou:"
|
|
78
|
+
|
|
79
|
+
msgid "Template"
|
|
80
|
+
msgstr "Šablona"
|
|
81
|
+
|
|
82
|
+
msgid "Template list"
|
|
83
|
+
msgstr "Seznam šablon"
|
|
84
|
+
|
|
85
|
+
msgid "Templates"
|
|
86
|
+
msgstr "Šablony"
|
|
87
|
+
|
|
88
|
+
msgid ""
|
|
89
|
+
"The URL of the source from which the data will be downloaded. If Data for "
|
|
90
|
+
"Context or Source File is specified, the source URL is not used even though "
|
|
91
|
+
"it is specified."
|
|
92
|
+
msgstr ""
|
|
93
|
+
"URL zdroje, ze kterého se data budou stahovat. Pokud jsou zadána Data pro "
|
|
94
|
+
"kontext nebo Zdrojový soubor, tak se URL zdroje nepoužije i přesto, že je "
|
|
95
|
+
"zadáno."
|
|
96
|
+
|
|
97
|
+
msgid ""
|
|
98
|
+
"The time in minutes during which data will not be retrieved from the Source "
|
|
99
|
+
"URL, but will remain in the cache. If set to zero, data from the source URL "
|
|
100
|
+
"is not cached, but is loaded every time."
|
|
101
|
+
msgstr ""
|
|
102
|
+
"Doba v minutách, po kterou se data nebudou načítat z URL zdroje, ale "
|
|
103
|
+
"zůstanou ponechána v cache. Je-li nastavena nula, tak se data z URL zdroje "
|
|
104
|
+
"do cache neukládají, ale načítají se pokaždé."
|
|
105
|
+
|
|
106
|
+
msgid "Unsupported file mime type: "
|
|
107
|
+
msgstr "Nepodporovaný mime typ souboru: "
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2025-12-10 09:02
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import djangocms_render_context.encoders
|
|
5
|
+
import filer.fields.file
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
initial = True
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
("cms", "0034_remove_pagecontent_placeholders"),
|
|
15
|
+
("filer", "0017_image__transparent"),
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
operations = [
|
|
19
|
+
migrations.CreateModel(
|
|
20
|
+
name="RenderContext",
|
|
21
|
+
fields=[
|
|
22
|
+
(
|
|
23
|
+
"cmsplugin_ptr",
|
|
24
|
+
models.OneToOneField(
|
|
25
|
+
auto_created=True,
|
|
26
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
27
|
+
parent_link=True,
|
|
28
|
+
primary_key=True,
|
|
29
|
+
related_name="%(app_label)s_%(class)s",
|
|
30
|
+
serialize=False,
|
|
31
|
+
to="cms.cmsplugin",
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
"context",
|
|
36
|
+
models.JSONField(
|
|
37
|
+
blank=True,
|
|
38
|
+
encoder=djangocms_render_context.encoders.PrettyJsonEncoder,
|
|
39
|
+
help_text="Context data in JSON format. They take precedence over the source file and source URL.",
|
|
40
|
+
null=True,
|
|
41
|
+
verbose_name="Data for context",
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
(
|
|
45
|
+
"source",
|
|
46
|
+
models.URLField(
|
|
47
|
+
blank=True,
|
|
48
|
+
help_text="The URL of the source from which the data will be downloaded. If Data for Context or Source File is specified, "
|
|
49
|
+
"the source URL is not used even though it is specified.",
|
|
50
|
+
null=True,
|
|
51
|
+
verbose_name="Source URL",
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
(
|
|
55
|
+
"cached",
|
|
56
|
+
models.PositiveSmallIntegerField(
|
|
57
|
+
default=5,
|
|
58
|
+
help_text="The time in minutes during which data will not be retrieved from the Source URL, but will remain in the cache. "
|
|
59
|
+
"If set to zero, data from the source URL is not cached, but is loaded every time.",
|
|
60
|
+
verbose_name="Download period from source URL",
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
(
|
|
64
|
+
"template",
|
|
65
|
+
models.TextField(
|
|
66
|
+
blank=True,
|
|
67
|
+
help_text="Django template for the context. It takes precedence over the template selected from the list.",
|
|
68
|
+
null=True,
|
|
69
|
+
verbose_name="Template",
|
|
70
|
+
),
|
|
71
|
+
),
|
|
72
|
+
(
|
|
73
|
+
"template_list",
|
|
74
|
+
models.CharField(
|
|
75
|
+
blank=True,
|
|
76
|
+
help_text="List of templates. If Template is specified, the template set in the list will not be used.",
|
|
77
|
+
max_length=255,
|
|
78
|
+
null=True,
|
|
79
|
+
verbose_name="Template list",
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
(
|
|
83
|
+
"file",
|
|
84
|
+
filer.fields.file.FilerFileField(
|
|
85
|
+
blank=True,
|
|
86
|
+
help_text="Context data file. If Data is specified for the context, the source file is not used even though it is specified.",
|
|
87
|
+
null=True,
|
|
88
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
89
|
+
to="filer.file",
|
|
90
|
+
verbose_name="Source file",
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
],
|
|
94
|
+
bases=("cms.cmsplugin",),
|
|
95
|
+
),
|
|
96
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from cms.models.pluginmodel import CMSPlugin
|
|
4
|
+
from django.db import models
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
from filer.fields.file import FilerFileField
|
|
7
|
+
|
|
8
|
+
from .encoders import PrettyJsonEncoder
|
|
9
|
+
from .exceptions import SourceParseFailure
|
|
10
|
+
from .loaders import get_cached_data_from_url, get_data_from_file
|
|
11
|
+
|
|
12
|
+
LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RenderContext(CMSPlugin):
|
|
16
|
+
context = models.JSONField(
|
|
17
|
+
verbose_name=_("Data for context"),
|
|
18
|
+
null=True,
|
|
19
|
+
blank=True,
|
|
20
|
+
encoder=PrettyJsonEncoder,
|
|
21
|
+
help_text=_("Context data in JSON format. They take precedence over the source file and source URL."),
|
|
22
|
+
)
|
|
23
|
+
file = FilerFileField(
|
|
24
|
+
verbose_name=_("Source file"),
|
|
25
|
+
null=True,
|
|
26
|
+
blank=True,
|
|
27
|
+
on_delete=models.SET_NULL,
|
|
28
|
+
help_text=_(
|
|
29
|
+
"Context data file. If Data is specified for the context, the source file is not used even "
|
|
30
|
+
"though it is specified."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
source = models.URLField(
|
|
34
|
+
verbose_name=_("Source URL"),
|
|
35
|
+
null=True,
|
|
36
|
+
blank=True,
|
|
37
|
+
help_text=_(
|
|
38
|
+
"The URL of the source from which the data will be downloaded. If Data for Context or Source File"
|
|
39
|
+
" is specified, the source URL is not used even though it is specified."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
cached = models.PositiveSmallIntegerField(
|
|
43
|
+
verbose_name=_("Download period from source URL"),
|
|
44
|
+
default=5,
|
|
45
|
+
help_text=_(
|
|
46
|
+
"The time in minutes during which data will not be retrieved from the Source URL, but will remain "
|
|
47
|
+
"in the cache. If set to zero, data from the source URL is not cached, but is loaded every time."
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
template = models.TextField(
|
|
52
|
+
verbose_name=_("Template"),
|
|
53
|
+
null=True,
|
|
54
|
+
blank=True,
|
|
55
|
+
help_text=_("Django template for the context. It takes precedence over the template selected from the list."),
|
|
56
|
+
)
|
|
57
|
+
template_list = models.CharField(
|
|
58
|
+
verbose_name=_("Template list"),
|
|
59
|
+
null=True,
|
|
60
|
+
blank=True,
|
|
61
|
+
max_length=255,
|
|
62
|
+
help_text=_("List of templates. If Template is specified, the template set in the list will not be used."),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
text = []
|
|
67
|
+
if self.context:
|
|
68
|
+
text.append(self._meta.get_field("context").verbose_name)
|
|
69
|
+
elif self.file:
|
|
70
|
+
text.append(self._meta.get_field("file").verbose_name)
|
|
71
|
+
elif self.source:
|
|
72
|
+
text.append(self._meta.get_field("source").verbose_name)
|
|
73
|
+
else:
|
|
74
|
+
text.append(_("Data not entered."))
|
|
75
|
+
if self.template:
|
|
76
|
+
text.append(self._meta.get_field("template").verbose_name)
|
|
77
|
+
elif self.template_list:
|
|
78
|
+
text.append(self._meta.get_field("template_list").verbose_name)
|
|
79
|
+
else:
|
|
80
|
+
text.append(_("No template."))
|
|
81
|
+
return " + ".join([str(t) for t in text])
|
|
82
|
+
|
|
83
|
+
def get_data(self):
|
|
84
|
+
if self.context:
|
|
85
|
+
return self.context
|
|
86
|
+
elif self.file:
|
|
87
|
+
try:
|
|
88
|
+
return get_data_from_file(self.file)
|
|
89
|
+
except SourceParseFailure as err:
|
|
90
|
+
LOGGER.error(err)
|
|
91
|
+
elif self.source:
|
|
92
|
+
try:
|
|
93
|
+
return get_cached_data_from_url(self.pk, self.source, self.cached)
|
|
94
|
+
except SourceParseFailure as err:
|
|
95
|
+
LOGGER.error(err)
|
|
96
|
+
return None
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from collections.abc import ValuesView
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
from django.utils.formats import localize
|
|
6
|
+
|
|
7
|
+
dataType = Union[str, dict, list, ValuesView, None] # noqa: UP007
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_data(data: dataType, level: int = 0) -> str:
|
|
11
|
+
wrapper, separator = "{}", "\n"
|
|
12
|
+
if level == 0:
|
|
13
|
+
separator = "</tr>\n<tr>\n"
|
|
14
|
+
if level == 1:
|
|
15
|
+
wrapper = "<td>{}</td>"
|
|
16
|
+
if data is None:
|
|
17
|
+
content = wrapper.format("")
|
|
18
|
+
elif isinstance(data, (date, datetime)):
|
|
19
|
+
content = wrapper.format(localize(data))
|
|
20
|
+
elif not isinstance(data, (tuple, list, dict, ValuesView)):
|
|
21
|
+
content = wrapper.format(data)
|
|
22
|
+
else:
|
|
23
|
+
content = collect_data(data, wrapper, separator, level)
|
|
24
|
+
return content
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def collect_data(data: dataType, wrapper: str, separator: str, level: int) -> str:
|
|
28
|
+
content = []
|
|
29
|
+
if isinstance(data, (tuple, list, ValuesView)):
|
|
30
|
+
for item in data:
|
|
31
|
+
content.append(wrapper.format(get_data(item, level + 1)))
|
|
32
|
+
elif isinstance(data, dict):
|
|
33
|
+
if "href" in data and "text" in data:
|
|
34
|
+
href, text = data.pop("href"), data.pop("text")
|
|
35
|
+
content.append(f"""<a href="{href}">{text}</a>""")
|
|
36
|
+
if "p" in data:
|
|
37
|
+
para = data.pop("p")
|
|
38
|
+
content.append(f"""<p>{para}</p>""")
|
|
39
|
+
content.append(wrapper.format(get_data(data.values(), level + 1)))
|
|
40
|
+
else:
|
|
41
|
+
content.append(wrapper.format(get_data(data, level + 1)))
|
|
42
|
+
return separator.join(content)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_html(data: dataType) -> str:
|
|
46
|
+
return f"""<table class="rc-data"><tbody><tr>{get_data(data)}</tr></tbody></table>"""
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: djangocms-render-context
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Render context into template.
|
|
5
|
+
Author-email: Zdeněk Böhm <zdenek.bohm@nic.cz>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context
|
|
8
|
+
Project-URL: Repository, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context.git
|
|
9
|
+
Project-URL: Changelog, https://gitlab.nic.cz/djangocms-apps/djangocms-render-context/-/blob/main/CHANGELOG.md
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Framework :: Django
|
|
12
|
+
Classifier: Framework :: Django :: 4.2
|
|
13
|
+
Classifier: Framework :: Django :: 5.0
|
|
14
|
+
Classifier: Framework :: Django :: 5.1
|
|
15
|
+
Classifier: Framework :: Django :: 5.2
|
|
16
|
+
Classifier: Framework :: Django CMS
|
|
17
|
+
Classifier: Framework :: Django CMS :: 4.1
|
|
18
|
+
Classifier: Framework :: Django CMS :: 5.0
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: django-cms<6,>=4.0
|
|
28
|
+
Requires-Dist: django-filer~=3.4
|
|
29
|
+
Requires-Dist: pyyaml
|
|
30
|
+
Requires-Dist: requests
|
|
31
|
+
Provides-Extra: quality
|
|
32
|
+
Requires-Dist: ruff; extra == "quality"
|
|
33
|
+
Requires-Dist: mypy; extra == "quality"
|
|
34
|
+
Requires-Dist: types-PyYAML; extra == "quality"
|
|
35
|
+
Requires-Dist: types-requests; extra == "quality"
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: testfixtures; extra == "test"
|
|
38
|
+
Requires-Dist: responses; extra == "test"
|
|
39
|
+
|
|
40
|
+
# DjangoCMS Render Context
|
|
41
|
+
|
|
42
|
+
The plugin for the [Django CMS](https://www.django-cms.org/) content management system.
|
|
43
|
+
The plugin displays the context in the template. The context can be specified directly in JSON format.
|
|
44
|
+
Or the context can be used as a media file. Or the context can be loaded from a URL.
|
|
45
|
+
The template can be entered directly or selected from a list defined in the settings in the ``DJANGOCMS_RENDER_CONTEXT_TEMPLATES`` constant.
|
|
46
|
+
|
|
47
|
+
Supported source data formats (mimetype):
|
|
48
|
+
|
|
49
|
+
- csv (text/csv)
|
|
50
|
+
- json (application/json)
|
|
51
|
+
- yaml (application/yaml)
|
|
52
|
+
- xml (application/xml)
|
|
53
|
+
- ods (application/vnd.oasis.opendocument.spreadsheet)
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Install the package from pypi.org.
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
pip install djangocms-render-context
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Add into `INSTALLED_APPS` in your site `settings.py`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
INSTALLED_APPS = [
|
|
67
|
+
...
|
|
68
|
+
"easy_thumbnails",
|
|
69
|
+
"filer",
|
|
70
|
+
"djangocms_render_context",
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Extra settings
|
|
75
|
+
|
|
76
|
+
This value can be defined in settings.
|
|
77
|
+
|
|
78
|
+
- `DJANGOCMS_RENDER_CONTEXT_TEMPLATES` - List of templates that the plugin can use.
|
|
79
|
+
|
|
80
|
+
For example:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
DJANGOCMS_RENDER_CONTEXT_TEMPLATES = (
|
|
84
|
+
("", ""),
|
|
85
|
+
("plugin.html", "Plugin"),
|
|
86
|
+
("schedule.html", "Schedule"),
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
BSD-3-Clause
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
djangocms_render_context/__init__.py
|
|
5
|
+
djangocms_render_context/cache.py
|
|
6
|
+
djangocms_render_context/cms_plugins.py
|
|
7
|
+
djangocms_render_context/encoders.py
|
|
8
|
+
djangocms_render_context/exceptions.py
|
|
9
|
+
djangocms_render_context/forms.py
|
|
10
|
+
djangocms_render_context/loaders.py
|
|
11
|
+
djangocms_render_context/models.py
|
|
12
|
+
djangocms_render_context/utils.py
|
|
13
|
+
djangocms_render_context.egg-info/PKG-INFO
|
|
14
|
+
djangocms_render_context.egg-info/SOURCES.txt
|
|
15
|
+
djangocms_render_context.egg-info/dependency_links.txt
|
|
16
|
+
djangocms_render_context.egg-info/requires.txt
|
|
17
|
+
djangocms_render_context.egg-info/top_level.txt
|
|
18
|
+
djangocms_render_context/locale/cs/LC_MESSAGES/django.mo
|
|
19
|
+
djangocms_render_context/locale/cs/LC_MESSAGES/django.po
|
|
20
|
+
djangocms_render_context/migrations/0001_initial.py
|
|
21
|
+
djangocms_render_context/migrations/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
djangocms_render_context
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "djangocms-render-context"
|
|
7
|
+
description = "Render context into template."
|
|
8
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
9
|
+
license = "BSD-3-Clause"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Zdeněk Böhm", email = "zdenek.bohm@nic.cz" },
|
|
12
|
+
]
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Framework :: Django",
|
|
17
|
+
"Framework :: Django :: 4.2",
|
|
18
|
+
"Framework :: Django :: 5.0",
|
|
19
|
+
"Framework :: Django :: 5.1",
|
|
20
|
+
"Framework :: Django :: 5.2",
|
|
21
|
+
"Framework :: Django CMS",
|
|
22
|
+
"Framework :: Django CMS :: 4.1",
|
|
23
|
+
"Framework :: Django CMS :: 5.0",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Programming Language :: Python :: 3.9",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
]
|
|
31
|
+
dynamic = [ "version" ]
|
|
32
|
+
dependencies = [
|
|
33
|
+
"django-cms>=4.0,<6",
|
|
34
|
+
"django-filer~=3.4",
|
|
35
|
+
"pyyaml",
|
|
36
|
+
"requests",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
quality = [
|
|
41
|
+
"ruff",
|
|
42
|
+
"mypy",
|
|
43
|
+
"types-PyYAML",
|
|
44
|
+
"types-requests",
|
|
45
|
+
]
|
|
46
|
+
test = [
|
|
47
|
+
"testfixtures",
|
|
48
|
+
"responses",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://gitlab.nic.cz/djangocms-apps/djangocms-render-context"
|
|
53
|
+
Repository = "https://gitlab.nic.cz/djangocms-apps/djangocms-render-context.git"
|
|
54
|
+
Changelog = "https://gitlab.nic.cz/djangocms-apps/djangocms-render-context/-/blob/main/CHANGELOG.md"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
include = ["djangocms_render_context*"]
|
|
58
|
+
|
|
59
|
+
[tool.setuptools.dynamic]
|
|
60
|
+
version = { attr = "djangocms_render_context.__version__" }
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 120
|
|
64
|
+
indent-width = 4
|
|
65
|
+
target-version = "py312"
|
|
66
|
+
exclude = [
|
|
67
|
+
"migrations",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[tool.ruff.lint]
|
|
71
|
+
select = [
|
|
72
|
+
"E", # pycodestyle errors
|
|
73
|
+
"W", # pycodestyle warnings
|
|
74
|
+
"F", # pyflakes
|
|
75
|
+
"I", # isort
|
|
76
|
+
"C", # flake8-comprehensions
|
|
77
|
+
"B", # flake8-bugbear
|
|
78
|
+
"PLE", # pylint error
|
|
79
|
+
"PLR", # pylint refactor
|
|
80
|
+
"PLW", # pylint warning
|
|
81
|
+
"UP", # pyupgrade
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.format]
|
|
85
|
+
quote-style = "double"
|
|
86
|
+
indent-style = "space"
|
|
87
|
+
docstring-code-format = true
|
|
88
|
+
docstring-code-line-length = 80
|
|
89
|
+
|
|
90
|
+
[tool.isort]
|
|
91
|
+
line_length = 120
|
|
92
|
+
skip = [
|
|
93
|
+
"manage.py",
|
|
94
|
+
"*migrations*",
|
|
95
|
+
]
|
|
96
|
+
include_trailing_comma = true
|
|
97
|
+
multi_line_output = 5
|
|
98
|
+
lines_after_imports = 2
|
|
99
|
+
default_section = "THIRDPARTY"
|
|
100
|
+
sections = [
|
|
101
|
+
"FUTURE",
|
|
102
|
+
"STDLIB",
|
|
103
|
+
"DJANGO",
|
|
104
|
+
"CMS",
|
|
105
|
+
"THIRDPARTY",
|
|
106
|
+
"FIRSTPARTY",
|
|
107
|
+
"LOCALFOLDER",
|
|
108
|
+
]
|
|
109
|
+
known_first_party = "djangocms_render_context"
|
|
110
|
+
known_cms = [ "cms" ]
|
|
111
|
+
known_django = "django"
|
|
112
|
+
|
|
113
|
+
[tool.mypy]
|
|
114
|
+
check_untyped_defs = true
|
|
115
|
+
ignore_missing_imports = true
|
|
116
|
+
local_partial_types = true
|
|
117
|
+
python_version = "3.9"
|
|
118
|
+
|
|
119
|
+
[[tool.mypy.overrides]]
|
|
120
|
+
module = "djangocms_render_context.migrations.*"
|
|
121
|
+
ignore_errors = true
|
|
122
|
+
|
|
123
|
+
[tool.bumpversion]
|
|
124
|
+
# Usage: bump-my-version bump patch/minor/major
|
|
125
|
+
current_version = "1.0.0"
|
|
126
|
+
search = "{current_version}"
|
|
127
|
+
replace = "{new_version}"
|
|
128
|
+
commit = true
|
|
129
|
+
tag = true
|
|
130
|
+
tag_name = "{new_version}"
|
|
131
|
+
|
|
132
|
+
[[tool.bumpversion.files]]
|
|
133
|
+
filename = "djangocms_render_context/__init__.py"
|
|
134
|
+
search = '__version__ = "{current_version}"'
|
|
135
|
+
replace = '__version__ = "{new_version}"'
|