djangocms-render-context 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl

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.
Files changed (28) hide show
  1. djangocms_render_context/__init__.py +1 -1
  2. djangocms_render_context/cms_plugins.py +9 -0
  3. djangocms_render_context/forms.py +68 -3
  4. djangocms_render_context/loaders.py +21 -1
  5. djangocms_render_context/locale/cs/LC_MESSAGES/django.mo +0 -0
  6. djangocms_render_context/locale/cs/LC_MESSAGES/django.po +41 -7
  7. djangocms_render_context/locale/cs/LC_MESSAGES/djangojs.mo +0 -0
  8. djangocms_render_context/locale/cs/LC_MESSAGES/djangojs.po +130 -0
  9. djangocms_render_context/migrations/0001_initial.py +0 -2
  10. djangocms_render_context/migrations/0002_rendercontext_mimetype.py +33 -0
  11. djangocms_render_context/migrations/0003_rendercontext_config_and_path.py +46 -0
  12. djangocms_render_context/models.py +49 -8
  13. djangocms_render_context/static/djangocms_render_context/css-dist/icon-family-Material-Icons.css +20 -0
  14. djangocms_render_context/static/djangocms_render_context/css-dist/jspreadsheet.min.css +8 -0
  15. djangocms_render_context/static/djangocms_render_context/css-dist/jsuites.min.css +8 -0
  16. djangocms_render_context/static/djangocms_render_context/js/spreadsheet.js +711 -0
  17. djangocms_render_context/static/djangocms_render_context/js-dist/csv.min.js +1 -0
  18. djangocms_render_context/static/djangocms_render_context/js-dist/index.min.js +8 -0
  19. djangocms_render_context/static/djangocms_render_context/js-dist/index.min.js.map +1 -0
  20. djangocms_render_context/static/djangocms_render_context/js-dist/js-yaml.min.js +2 -0
  21. djangocms_render_context/static/djangocms_render_context/js-dist/jsuites.min.js +8 -0
  22. djangocms_render_context/static/djangocms_render_context/js-dist/jsuites.min.js.map +1 -0
  23. djangocms_render_context/utils.py +16 -0
  24. {djangocms_render_context-1.0.0.dist-info → djangocms_render_context-1.2.0.dist-info}/METADATA +17 -1
  25. djangocms_render_context-1.2.0.dist-info/RECORD +31 -0
  26. {djangocms_render_context-1.0.0.dist-info → djangocms_render_context-1.2.0.dist-info}/WHEEL +1 -1
  27. djangocms_render_context-1.0.0.dist-info/RECORD +0 -17
  28. {djangocms_render_context-1.0.0.dist-info → djangocms_render_context-1.2.0.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- __version__ = "1.0.0"
1
+ __version__ = "1.2.0"
@@ -21,6 +21,7 @@ class RenderContextPlugin(CMSPluginBase):
21
21
  _("Sources"),
22
22
  {
23
23
  "fields": (
24
+ "mimetype",
24
25
  "context",
25
26
  "file",
26
27
  (
@@ -30,6 +31,13 @@ class RenderContextPlugin(CMSPluginBase):
30
31
  ),
31
32
  },
32
33
  ),
34
+ (
35
+ _("Source settings"),
36
+ {
37
+ "classes": ["collapse"],
38
+ "fields": ("config",),
39
+ },
40
+ ),
33
41
  (
34
42
  _("Templates"),
35
43
  {
@@ -37,6 +45,7 @@ class RenderContextPlugin(CMSPluginBase):
37
45
  "fields": (
38
46
  "template",
39
47
  "template_list",
48
+ "path",
40
49
  ),
41
50
  },
42
51
  ),
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from collections.abc import Callable
2
3
  from typing import Union
3
4
 
@@ -9,21 +10,39 @@ from django.forms import ALL_FIELDS, ModelForm
9
10
  from django.template import Context, Template, TemplateDoesNotExist
10
11
  from django.template.exceptions import TemplateSyntaxError
11
12
  from django.template.loader import get_template
13
+ from django.urls import reverse_lazy
14
+ from django.urls.exceptions import NoReverseMatch
12
15
  from django.utils.translation import gettext_lazy as _
13
16
  from filer.models.filemodels import File
17
+ from sekizai.context import SekizaiContext
14
18
 
15
19
  from .cache import get_cache_key
16
20
  from .exceptions import SourceParseFailure
17
- from .loaders import SUPPORTED_FILE_TYPES, get_data_from_file, get_data_from_url
21
+ from .loaders import (
22
+ CONTEXT_DATA_FORMATS,
23
+ LOADERS,
24
+ SUPPORTED_FILE_TYPES,
25
+ get_data_from_file,
26
+ get_data_from_url,
27
+ value_to_bytes,
28
+ )
29
+ from .utils import get_context_from_path
18
30
 
19
31
  TEMPLATES = (("", ""),)
20
32
 
21
33
  sourceType = Union[str, File] # noqa: UP007
22
34
 
35
+ logger = logging.getLogger(__name__)
36
+
23
37
 
24
38
  class RenderContextForm(ModelForm):
25
39
  """Render Context Form."""
26
40
 
41
+ mimetype = forms.ChoiceField(
42
+ label=_("Type of Data for context"),
43
+ choices=CONTEXT_DATA_FORMATS,
44
+ help_text=_("The format must be consistent with the data in the context."),
45
+ )
27
46
  template_list = forms.ChoiceField(
28
47
  label=_("Template list"),
29
48
  required=False,
@@ -31,9 +50,39 @@ class RenderContextForm(ModelForm):
31
50
  help_text=_("List of templates. If Template is specified, the template set in the list will not be used."),
32
51
  )
33
52
 
53
+ class Media:
54
+ js = [
55
+ # "https://cdn.jsdelivr.net/npm/jspreadsheet-ce@5/dist/index.min.js",
56
+ "djangocms_render_context/js-dist/index.min.js",
57
+ # "https://cdn.jsdelivr.net/npm/jsuites@5/dist/jsuites.min.js",
58
+ "djangocms_render_context/js-dist/jsuites.min.js",
59
+ # https://github.com/nodeca/js-yaml
60
+ "djangocms_render_context/js-dist/js-yaml.min.js",
61
+ # https://github.com/rufuspollock/csv.js
62
+ "djangocms_render_context/js-dist/csv.min.js",
63
+ "djangocms_render_context/js/spreadsheet.js",
64
+ ]
65
+ css = {
66
+ "all": [
67
+ # "https://cdn.jsdelivr.net/npm/jspreadsheet-ce@5/dist/jspreadsheet.min.css",
68
+ "djangocms_render_context/css-dist/jspreadsheet.min.css",
69
+ # "https://cdn.jsdelivr.net/npm/jsuites@5/dist/jsuites.min.css",
70
+ "djangocms_render_context/css-dist/jsuites.min.css",
71
+ # "https://fonts.googleapis.com/icon?family=Material+Icons",
72
+ "djangocms_render_context/css-dist/icon-family-Material-Icons.css",
73
+ "djangocms_render_context/css/spreadsheet.css",
74
+ ],
75
+ }
76
+
34
77
  def __init__(self, *args, **kwargs):
35
78
  super().__init__(*args, **kwargs)
36
79
  self.fields["file"].help_text += " " + _("Supported formats are:") + " " + ", ".join(SUPPORTED_FILE_TYPES)
80
+ pathname = getattr(settings, "DJANGOCMS_RENDER_CONTEXT_JSI18N", "jsi18n")
81
+ try:
82
+ path = reverse_lazy(pathname, kwargs={"packages": "djangocms_render_context"})
83
+ self.media._js_lists[-1].append(path)
84
+ except NoReverseMatch as err:
85
+ logger.error(err)
37
86
 
38
87
  def clean_template_list(self) -> None:
39
88
  if self.cleaned_data["template_list"]:
@@ -51,10 +100,16 @@ class RenderContextForm(ModelForm):
51
100
  except SourceParseFailure as error:
52
101
  self.add_error(name, error)
53
102
 
103
+ def check_context_data(self, value: str, mimetype: str) -> Context:
104
+ try:
105
+ return Context(LOADERS[mimetype](value_to_bytes(value, mimetype)))
106
+ except SourceParseFailure as error:
107
+ self.add_error("context", error)
108
+
54
109
  def check_context(self, cleaned_data: dict) -> Context:
55
110
  context = Context({})
56
111
  if cleaned_data["context"]:
57
- context = Context(cleaned_data["context"])
112
+ context = self.check_context_data(cleaned_data["context"], cleaned_data["mimetype"])
58
113
  elif cleaned_data["file"]:
59
114
  context = self.check_value("file", cleaned_data["file"], get_data_from_file)
60
115
  elif cleaned_data["source"]:
@@ -78,15 +133,25 @@ class RenderContextForm(ModelForm):
78
133
  template = get_template(cleaned_data["template_list"]).template
79
134
  if template:
80
135
  try:
81
- template.render(context)
136
+ template.render(SekizaiContext(context))
82
137
  except (TemplateDoesNotExist, TemplateSyntaxError) as error:
83
138
  self.add_error(field_name, error)
84
139
 
140
+ def check_path(self, cleaned_data: dict, context: Context) -> Context:
141
+ """Check path."""
142
+ if cleaned_data["path"]:
143
+ try:
144
+ context = Context(get_context_from_path(context, cleaned_data["path"]))
145
+ except (IndexError, KeyError, ValueError, TypeError) as error:
146
+ self.add_error("path", _("The data path is not valid. Error: %s") % error)
147
+ return context
148
+
85
149
  def clean(self) -> None:
86
150
  """Clean form."""
87
151
  cleaned_data = super().clean()
88
152
  if self.is_valid():
89
153
  context = self.check_context(cleaned_data)
154
+ context = self.check_path(cleaned_data, context)
90
155
  self.check_template(cleaned_data, context)
91
156
 
92
157
  def save(self, *args, **kwargs):
@@ -1,3 +1,5 @@
1
+ import base64
2
+ import binascii
1
3
  import csv
2
4
  import json
3
5
  import xml.etree.ElementTree as ET
@@ -47,6 +49,17 @@ def get_data_from_file(source: File) -> dataType:
47
49
  return LOADERS[source.mime_type](source.file.read())
48
50
 
49
51
 
52
+ def value_to_bytes(value: str, mimetype: str) -> bytes:
53
+ if mimetype == "application/vnd.oasis.opendocument.spreadsheet":
54
+ try:
55
+ content = base64.b64decode(value)
56
+ except (binascii.Error, ValueError) as err:
57
+ raise SourceParseFailure(err) from err
58
+ else:
59
+ content = value.encode("utf8")
60
+ return content
61
+
62
+
50
63
  def load_json(content: bytes) -> dataType:
51
64
  try:
52
65
  return json.load(BytesIO(content))
@@ -90,7 +103,7 @@ def load_spreadsheet(content: bytes) -> dataType:
90
103
  try:
91
104
  handle = zipfile.ZipFile(BytesIO(content))
92
105
  payload = handle.read("content.xml")
93
- except (zipfile.BadZipFile, KeyError) as err:
106
+ except (zipfile.BadZipFile, KeyError, ValueError) as err:
94
107
  raise SourceParseFailure(err) from err
95
108
  finally:
96
109
  if handle is None:
@@ -212,3 +225,10 @@ LOADERS = {
212
225
  "application/xml": load_xml,
213
226
  "application/vnd.oasis.opendocument.spreadsheet": load_spreadsheet,
214
227
  }
228
+ CONTEXT_DATA_FORMATS = (
229
+ ("application/json", "JSON"),
230
+ ("application/yaml", "YAML"),
231
+ ("application/xml", "XML"),
232
+ ("text/csv", "CSV"),
233
+ ("application/vnd.oasis.opendocument.spreadsheet", "ODS (in Base64)"),
234
+ )
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: PACKAGE VERSION\n"
9
9
  "Report-Msgid-Bugs-To: \n"
10
- "POT-Creation-Date: 2025-12-11 13:17+0100\n"
10
+ "POT-Creation-Date: 2026-01-09 09:26+0100\n"
11
11
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12
12
  "Last-Translator: Zdeněk Böhm <zdenek.bohm@nic.cz>\n"
13
13
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,6 +17,16 @@ msgstr ""
17
17
  "Content-Transfer-Encoding: 8bit\n"
18
18
  "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
19
19
 
20
+ msgid ""
21
+ "Additional configuration for spreadsheet with source data. The configuration "
22
+ "is automatically added or updated when the spreadsheet is saved."
23
+ msgstr ""
24
+ "Další konfigurace pro tabulku se zdrojovými daty. Konfigurace je automaticky "
25
+ "doplněna nebo aktualizována při ukládání tabulky."
26
+
27
+ msgid "Configuration"
28
+ msgstr "Konfigurace"
29
+
20
30
  msgid ""
21
31
  "Context data file. If Data is specified for the context, the source file is "
22
32
  "not used even though it is specified."
@@ -25,11 +35,11 @@ msgstr ""
25
35
  "zdrojový soubor nepoužije i přesto, že je zadán."
26
36
 
27
37
  msgid ""
28
- "Context data in JSON format. They take precedence over the source file and "
29
- "source URL."
38
+ "Context data in selected format. They take precedence over the source file "
39
+ "and source URL."
30
40
  msgstr ""
31
- "Data pro kontext ve formátu JSON. Mají přednost před zdrojovým souborem a "
32
- "URL zdroje."
41
+ "Data pro kontext v nastaveném formátu. Mají přednost před zdrojovým souborem "
42
+ "a URL zdroje."
33
43
 
34
44
  msgid "Data for context"
35
45
  msgstr "Data pro kontext"
@@ -37,10 +47,15 @@ msgstr "Data pro kontext"
37
47
  msgid "Data not entered."
38
48
  msgstr "Data nejsou zadána."
39
49
 
50
+ msgid "Data path"
51
+ msgstr "Cesta k datům"
52
+
40
53
  msgid ""
41
54
  "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."
55
+ "selected from the list. The context is available through the \"data\" value."
56
+ msgstr ""
57
+ "Django šablona. Má přednost přes šablonou vybranou ze seznamu. Kontext je "
58
+ "dostupný přes hodnotu \"data\"."
44
59
 
45
60
  msgid "Download period from source URL"
46
61
  msgstr "Perioda stahování ze URL zdroje"
@@ -70,6 +85,9 @@ msgstr "URL zdroje"
70
85
  msgid "Source file"
71
86
  msgstr "Zdrojový soubor."
72
87
 
88
+ msgid "Source settings"
89
+ msgstr "Nastavení zdroje"
90
+
73
91
  msgid "Sources"
74
92
  msgstr "Zdroje"
75
93
 
@@ -94,6 +112,19 @@ msgstr ""
94
112
  "kontext nebo Zdrojový soubor, tak se URL zdroje nepoužije i přesto, že je "
95
113
  "zadáno."
96
114
 
115
+ msgid "The data path is not valid. Error: %s"
116
+ msgstr "Cesta k datům není platná. Chyba: %s"
117
+
118
+ msgid "The format must be consistent with the data in the context."
119
+ msgstr "Formát musí odpovídat datům v kontextu."
120
+
121
+ msgid ""
122
+ "The path to the data in the form \"name.name.name\". The \"data\" context in "
123
+ "the template starts from this key."
124
+ msgstr ""
125
+ "Cesta k datům ve tvaru \"name.name.name\". Od tohoto klíče začíná kontext "
126
+ "\"data\" v šabloně."
127
+
97
128
  msgid ""
98
129
  "The time in minutes during which data will not be retrieved from the Source "
99
130
  "URL, but will remain in the cache. If set to zero, data from the source URL "
@@ -103,5 +134,8 @@ msgstr ""
103
134
  "zůstanou ponechána v cache. Je-li nastavena nula, tak se data z URL zdroje "
104
135
  "do cache neukládají, ale načítají se pokaždé."
105
136
 
137
+ msgid "Type of Data for context"
138
+ msgstr "Typ dat pro kontext"
139
+
106
140
  msgid "Unsupported file mime type: "
107
141
  msgstr "Nepodporovaný mime typ souboru: "
@@ -0,0 +1,130 @@
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: 2026-01-20 08:21+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 "(Press Alt+Enter to keep changes. / Alt+Q to cancel.)"
21
+ msgstr "(Alt+Enter ponechat změny. / Alt+Q ztušit.)"
22
+
23
+ msgid "About"
24
+ msgstr "O aplikaci"
25
+
26
+ msgid "Add comments"
27
+ msgstr "Přidat poznámku"
28
+
29
+ msgid "Are you sure?"
30
+ msgstr "Jste si jistí?"
31
+
32
+ msgid "Clear comments"
33
+ msgstr "Smazat poznámku"
34
+
35
+ msgid "Close this window."
36
+ msgstr "Zavřít toto okno."
37
+
38
+ msgid "Copy"
39
+ msgstr "Kopírovat"
40
+
41
+ msgid "Cut"
42
+ msgstr "Vyjmout"
43
+
44
+ msgid "Data conversion error"
45
+ msgstr "Chyba v konverzi dat"
46
+
47
+ msgid "Data serialization error"
48
+ msgstr "Chyba při serializaci dat"
49
+
50
+ msgid "Delete selected columns"
51
+ msgstr "Smazat vybrané sloupce"
52
+
53
+ msgid "Delete selected rows"
54
+ msgstr "Smazat vybrané řádky"
55
+
56
+ msgid "Display"
57
+ msgstr "Zobrazit"
58
+
59
+ msgid "Edit CSV"
60
+ msgstr "Editovat CSV"
61
+
62
+ msgid "Edit comments"
63
+ msgstr "Upravit poznámku"
64
+
65
+ msgid "Enter a URL:"
66
+ msgstr "Zadejte URL:"
67
+
68
+ msgid "Error in Source settings"
69
+ msgstr "Chyba v Nastavení zdroje"
70
+
71
+ msgid "Error message"
72
+ msgstr "Chybové hlášení"
73
+
74
+ msgid "Insert a new column after"
75
+ msgstr "Vložit nový sloupec za tento"
76
+
77
+ msgid "Insert a new column before"
78
+ msgstr "Vložit nový sloupec před tento"
79
+
80
+ msgid "Insert a new row after"
81
+ msgstr "Vložit nový řádek za tento"
82
+
83
+ msgid "Insert a new row before"
84
+ msgstr "Vložit nový řádek před tento"
85
+
86
+ msgid "Order ascending"
87
+ msgstr "Setřídit vzestupně"
88
+
89
+ msgid "Order descending"
90
+ msgstr "Setřídit sestupně"
91
+
92
+ msgid "Parse Data Error"
93
+ msgstr "Chyba při parsování dat"
94
+
95
+ msgid "Paste"
96
+ msgstr "Vložit"
97
+
98
+ msgid "Rename this cell"
99
+ msgstr "Přejmenovat tuto buňku"
100
+
101
+ msgid "Rename this column"
102
+ msgstr "Přejmenovat tento sloupec"
103
+
104
+ msgid "Save as"
105
+ msgstr "Uložit jako"
106
+
107
+ msgid "Show data in spreadsheet."
108
+ msgstr "Zobrazit data v tabulce."
109
+
110
+ msgid "Source"
111
+ msgstr "Zdroj"
112
+
113
+ msgid "Spreadsheet"
114
+ msgstr "Tabulka"
115
+
116
+ msgid ""
117
+ "This data cannot be serialized in the selected format. Please select a "
118
+ "different format."
119
+ msgstr ""
120
+ "Tato data nelze to vybraného formátu serializovat. Vyberte prosím jiný "
121
+ "formát."
122
+
123
+ msgid "Toggle source"
124
+ msgstr "Přepnout na zdroj"
125
+
126
+ msgid "Toggle spreadsheet"
127
+ msgstr "Přepnout na tabulku"
128
+
129
+ msgid "View source data."
130
+ msgstr "Zobrazit zdrojová data."
@@ -1,7 +1,6 @@
1
1
  # Generated by Django 5.2.8 on 2025-12-10 09:02
2
2
 
3
3
  import django.db.models.deletion
4
- import djangocms_render_context.encoders
5
4
  import filer.fields.file
6
5
  from django.db import migrations, models
7
6
 
@@ -35,7 +34,6 @@ class Migration(migrations.Migration):
35
34
  "context",
36
35
  models.JSONField(
37
36
  blank=True,
38
- encoder=djangocms_render_context.encoders.PrettyJsonEncoder,
39
37
  help_text="Context data in JSON format. They take precedence over the source file and source URL.",
40
38
  null=True,
41
39
  verbose_name="Data for context",
@@ -0,0 +1,33 @@
1
+ # Generated by Django 5.2.9 on 2026-01-09 07:40
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("djangocms_render_context", "0001_initial"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="rendercontext",
15
+ name="mimetype",
16
+ field=models.CharField(
17
+ default="application/json",
18
+ help_text="The format must be consistent with the data in the context.",
19
+ max_length=255,
20
+ verbose_name="Type of Data for context",
21
+ ),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name="rendercontext",
25
+ name="context",
26
+ field=models.TextField(
27
+ blank=True,
28
+ help_text="Context data in selected format. They take precedence over the source file and source URL.",
29
+ null=True,
30
+ verbose_name="Data for context",
31
+ ),
32
+ ),
33
+ ]
@@ -0,0 +1,46 @@
1
+ # Generated by Django 5.2.9 on 2026-01-29 13:20
2
+
3
+ import djangocms_render_context.encoders
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("djangocms_render_context", "0002_rendercontext_mimetype"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name="rendercontext",
16
+ name="config",
17
+ field=models.JSONField(
18
+ blank=True,
19
+ encoder=djangocms_render_context.encoders.PrettyJsonEncoder,
20
+ help_text="Additional configuration for spreadsheet with source data. The configuration is automatically added or updated when the spreadsheet is saved.",
21
+ null=True,
22
+ verbose_name="Configuration",
23
+ ),
24
+ ),
25
+ migrations.AddField(
26
+ model_name="rendercontext",
27
+ name="path",
28
+ field=models.CharField(
29
+ blank=True,
30
+ help_text='The path to the data in the form "name.name.name". The "data" context in the template starts from this key.',
31
+ max_length=255,
32
+ null=True,
33
+ verbose_name="Data path",
34
+ ),
35
+ ),
36
+ migrations.AlterField(
37
+ model_name="rendercontext",
38
+ name="template",
39
+ field=models.TextField(
40
+ blank=True,
41
+ help_text='Django template for the context. It takes precedence over the template selected from the list. The context is available through the "data" value.',
42
+ null=True,
43
+ verbose_name="Template",
44
+ ),
45
+ ),
46
+ ]
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from typing import Any, Optional, Union
2
3
 
3
4
  from cms.models.pluginmodel import CMSPlugin
4
5
  from django.db import models
@@ -7,18 +8,36 @@ from filer.fields.file import FilerFileField
7
8
 
8
9
  from .encoders import PrettyJsonEncoder
9
10
  from .exceptions import SourceParseFailure
10
- from .loaders import get_cached_data_from_url, get_data_from_file
11
+ from .loaders import LOADERS, get_cached_data_from_url, get_data_from_file, value_to_bytes
12
+ from .utils import get_context_from_path
13
+
14
+ contentType = Optional[Union[dict[Any, Any], list[Any]]] # noqa: UP045, UP007
11
15
 
12
16
  LOGGER = logging.getLogger(__name__)
13
17
 
14
18
 
15
19
  class RenderContext(CMSPlugin):
16
- context = models.JSONField(
20
+ mimetype = models.CharField(
21
+ verbose_name=_("Type of Data for context"),
22
+ max_length=255,
23
+ default="application/json",
24
+ help_text=_("The format must be consistent with the data in the context."),
25
+ )
26
+ context = models.TextField(
17
27
  verbose_name=_("Data for context"),
18
28
  null=True,
19
29
  blank=True,
30
+ help_text=_("Context data in selected format. They take precedence over the source file and source URL."),
31
+ )
32
+ config = models.JSONField(
33
+ verbose_name=_("Configuration"),
34
+ null=True,
35
+ blank=True,
20
36
  encoder=PrettyJsonEncoder,
21
- help_text=_("Context data in JSON format. They take precedence over the source file and source URL."),
37
+ help_text=_(
38
+ "Additional configuration for spreadsheet with source data. "
39
+ "The configuration is automatically added or updated when the spreadsheet is saved."
40
+ ),
22
41
  )
23
42
  file = FilerFileField(
24
43
  verbose_name=_("Source file"),
@@ -52,7 +71,10 @@ class RenderContext(CMSPlugin):
52
71
  verbose_name=_("Template"),
53
72
  null=True,
54
73
  blank=True,
55
- help_text=_("Django template for the context. It takes precedence over the template selected from the list."),
74
+ help_text=_(
75
+ "Django template for the context. It takes precedence over the template selected from the list."
76
+ ' The context is available through the "data" value.'
77
+ ),
56
78
  )
57
79
  template_list = models.CharField(
58
80
  verbose_name=_("Template list"),
@@ -61,6 +83,16 @@ class RenderContext(CMSPlugin):
61
83
  max_length=255,
62
84
  help_text=_("List of templates. If Template is specified, the template set in the list will not be used."),
63
85
  )
86
+ path = models.CharField(
87
+ verbose_name=_("Data path"),
88
+ null=True,
89
+ blank=True,
90
+ max_length=255,
91
+ help_text=_(
92
+ 'The path to the data in the form "name.name.name".'
93
+ ' The "data" context in the template starts from this key.'
94
+ ),
95
+ )
64
96
 
65
97
  def __str__(self):
66
98
  text = []
@@ -80,17 +112,26 @@ class RenderContext(CMSPlugin):
80
112
  text.append(_("No template."))
81
113
  return " + ".join([str(t) for t in text])
82
114
 
83
- def get_data(self):
115
+ def get_data_by_path(self, data: contentType) -> contentType:
116
+ if self.path:
117
+ try:
118
+ data = get_context_from_path(data, self.path)
119
+ except (IndexError, KeyError, ValueError, TypeError) as err:
120
+ LOGGER.error(err)
121
+ return {}
122
+ return data
123
+
124
+ def get_data(self) -> contentType:
84
125
  if self.context:
85
- return self.context
126
+ return self.get_data_by_path(LOADERS[self.mimetype](value_to_bytes(self.context, self.mimetype)))
86
127
  elif self.file:
87
128
  try:
88
- return get_data_from_file(self.file)
129
+ return self.get_data_by_path(get_data_from_file(self.file))
89
130
  except SourceParseFailure as err:
90
131
  LOGGER.error(err)
91
132
  elif self.source:
92
133
  try:
93
- return get_cached_data_from_url(self.pk, self.source, self.cached)
134
+ return self.get_data_by_path(get_cached_data_from_url(self.pk, self.source, self.cached))
94
135
  except SourceParseFailure as err:
95
136
  LOGGER.error(err)
96
137
  return None
@@ -0,0 +1,20 @@
1
+ @font-face {
2
+ font-family: 'Material Icons';
3
+ font-style: normal;
4
+ font-weight: 400;
5
+ src: url(https://fonts.gstatic.com/s/materialicons/v145/flUhRq6tzZclQEJ-Vdg-IuiaDsNZ.ttf) format('truetype');
6
+ }
7
+
8
+ .material-icons {
9
+ font-family: 'Material Icons';
10
+ font-weight: normal;
11
+ font-style: normal;
12
+ font-size: 24px;
13
+ line-height: 1;
14
+ letter-spacing: normal;
15
+ text-transform: none;
16
+ display: inline-block;
17
+ white-space: nowrap;
18
+ word-wrap: normal;
19
+ direction: ltr;
20
+ }