django-camomilla-cms 6.0.0b14__py2.py3-none-any.whl → 6.0.0b16__py2.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.
- camomilla/__init__.py +1 -1
- camomilla/dynamic_pages_urls.py +2 -1
- camomilla/fields/__init__.py +1 -3
- camomilla/managers/pages.py +22 -33
- camomilla/model_api.py +9 -4
- camomilla/models/media.py +1 -1
- camomilla/models/menu.py +23 -11
- camomilla/models/page.py +76 -38
- camomilla/openapi/schema.py +4 -0
- camomilla/serializers/base/__init__.py +3 -1
- camomilla/serializers/fields/__init__.py +2 -2
- camomilla/serializers/fields/json.py +2 -2
- camomilla/serializers/fields/related.py +5 -1
- camomilla/serializers/mixins/__init__.py +56 -18
- camomilla/serializers/mixins/filter_fields.py +56 -0
- camomilla/serializers/utils.py +3 -1
- camomilla/serializers/validators.py +9 -5
- camomilla/settings.py +0 -4
- camomilla/storages/default.py +6 -0
- camomilla/storages/optimize.py +2 -2
- camomilla/storages/overwrite.py +2 -2
- camomilla/templates/defaults/parts/menu.html +1 -1
- camomilla/theme/__init__.py +1 -1
- camomilla/theme/admin.py +1 -1
- camomilla/translation.py +1 -1
- camomilla/utils/query_parser.py +148 -0
- camomilla/utils/setters.py +37 -0
- camomilla/views/base/__init__.py +2 -2
- camomilla/views/menus.py +0 -3
- camomilla/views/mixins/__init__.py +9 -2
- camomilla/views/mixins/pagination.py +4 -13
- {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/METADATA +2 -3
- {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/RECORD +46 -40
- {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/WHEEL +1 -1
- tests/fixtures/__init__.py +17 -0
- tests/test_api.py +2 -11
- tests/test_camomilla_filters.py +7 -13
- tests/test_media.py +80 -0
- tests/test_model_api.py +68 -0
- tests/test_model_api_permissions.py +39 -0
- tests/test_query_parser.py +59 -0
- tests/test_utils.py +64 -64
- tests/utils/__init__.py +0 -0
- tests/utils/api.py +29 -0
- camomilla/structured/__init__.py +0 -125
- camomilla/structured/cache.py +0 -202
- camomilla/structured/fields.py +0 -150
- camomilla/structured/models.py +0 -47
- camomilla/structured/utils.py +0 -114
- {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
from rest_framework import serializers
|
2
|
+
from camomilla.serializers.fields.related import RelatedField
|
3
|
+
from collections import defaultdict
|
4
|
+
|
5
|
+
|
6
|
+
class FilterFieldsMixin(serializers.ModelSerializer):
|
7
|
+
"""
|
8
|
+
Mixin to filter fields from a serializer, including handling nested fields.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, *args, **kwargs):
|
12
|
+
self.inherited_fields_filter = kwargs.pop("inherited_fields_filter", [])
|
13
|
+
return super().__init__(*args, **kwargs)
|
14
|
+
|
15
|
+
|
16
|
+
inherited_fields_filter = []
|
17
|
+
|
18
|
+
|
19
|
+
def get_default_field_names(self, *args):
|
20
|
+
field_names = super().get_default_field_names(*args)
|
21
|
+
request = self.context.get("request", None)
|
22
|
+
|
23
|
+
if request is not None and request.method == "GET":
|
24
|
+
fields = request.query_params.get("fields", "").split(",")
|
25
|
+
fields = [f for f in fields if f != ""]
|
26
|
+
if len(self.inherited_fields_filter) > 0:
|
27
|
+
fields = self.inherited_fields_filter
|
28
|
+
|
29
|
+
self.filtered_fields = set()
|
30
|
+
self.childs_fields = defaultdict(set)
|
31
|
+
for field in fields:
|
32
|
+
if "__" in field:
|
33
|
+
parent_field, child_field = field.split("__", 1)
|
34
|
+
if parent_field in field_names:
|
35
|
+
self.filtered_fields.add(parent_field)
|
36
|
+
self.childs_fields[parent_field].add(child_field)
|
37
|
+
|
38
|
+
|
39
|
+
else:
|
40
|
+
if field in field_names:
|
41
|
+
self.filtered_fields.add(field)
|
42
|
+
|
43
|
+
if len(self.filtered_fields) > 0:
|
44
|
+
return list(self.filtered_fields)
|
45
|
+
else:
|
46
|
+
return field_names
|
47
|
+
|
48
|
+
return field_names
|
49
|
+
|
50
|
+
def build_field(self, field_name, info, model_class, nested_depth):
|
51
|
+
field_class, field_kwargs = super().build_field(field_name, info, model_class, nested_depth)
|
52
|
+
inherited_fields_filter = self.childs_fields.get(field_name, []) if hasattr(self, "childs_fields") else []
|
53
|
+
if len(inherited_fields_filter) > 0 and issubclass(field_class, RelatedField):
|
54
|
+
field_kwargs["inherited_fields_filter"] = list(inherited_fields_filter)
|
55
|
+
return field_class, field_kwargs
|
56
|
+
|
camomilla/serializers/utils.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
def get_standard_bases() -> tuple:
|
2
2
|
from rest_framework.serializers import ModelSerializer
|
3
3
|
from camomilla.serializers.fields import FieldsOverrideMixin
|
4
|
+
from camomilla.serializers.mixins.filter_fields import FilterFieldsMixin
|
4
5
|
from camomilla.contrib.rest_framework.serializer import RemoveTranslationsMixin
|
5
6
|
from camomilla.serializers.mixins import (
|
6
7
|
JSONFieldPatchMixin,
|
@@ -10,12 +11,13 @@ def get_standard_bases() -> tuple:
|
|
10
11
|
)
|
11
12
|
|
12
13
|
return (
|
14
|
+
SetupEagerLoadingMixin,
|
15
|
+
FilterFieldsMixin,
|
13
16
|
NestMixin,
|
14
17
|
FieldsOverrideMixin,
|
15
18
|
JSONFieldPatchMixin,
|
16
19
|
OrderingMixin,
|
17
20
|
RemoveTranslationsMixin,
|
18
|
-
SetupEagerLoadingMixin,
|
19
21
|
ModelSerializer,
|
20
22
|
)
|
21
23
|
|
@@ -8,7 +8,7 @@ from camomilla.utils.translation import get_nofallbacks, set_nofallbacks
|
|
8
8
|
|
9
9
|
|
10
10
|
class UniquePermalinkValidator:
|
11
|
-
message = _("
|
11
|
+
message = _("There is an other page with same permalink.")
|
12
12
|
|
13
13
|
requires_context = True
|
14
14
|
|
@@ -29,13 +29,17 @@ class UniquePermalinkValidator:
|
|
29
29
|
instance, parent_page_field, None
|
30
30
|
)
|
31
31
|
for language in activate_languages():
|
32
|
-
|
33
|
-
|
32
|
+
autopermalink_f = build_localized_fieldname("autopermalink", language)
|
33
|
+
f_name = build_localized_fieldname("permalink", language)
|
34
|
+
permalink = value.get(f_name, instance and get_nofallbacks(instance, "permalink"))
|
35
|
+
permalink = UrlNode.sanitize_permalink(permalink)
|
36
|
+
autopermalink = value.get(autopermalink_f, instance and get_nofallbacks(instance, "autopermalink"))
|
37
|
+
if autopermalink:
|
38
|
+
continue
|
34
39
|
fake_instance = serializer.Meta.model()
|
35
|
-
set_nofallbacks(fake_instance, "
|
40
|
+
set_nofallbacks(fake_instance, "permalink", permalink)
|
36
41
|
if parent_page:
|
37
42
|
set_nofallbacks(fake_instance, parent_page_field, parent_page)
|
38
|
-
permalink = fake_instance.generate_permalink(safe=False)
|
39
43
|
qs = UrlNode.objects.exclude(**exclude_kwargs)
|
40
44
|
if qs.filter(permalink=permalink).exists():
|
41
45
|
errors[f_name] = self.message
|
camomilla/settings.py
CHANGED
@@ -79,10 +79,6 @@ TEMPLATE_CONTEXT_FILES = pointed_getter(
|
|
79
79
|
django_settings, "CAMOMILLA.RENDER.TEMPLATE_CONTEXT_FILES", []
|
80
80
|
)
|
81
81
|
|
82
|
-
STRUCTURED_FIELD_CACHE_ENABLED = pointed_getter(
|
83
|
-
django_settings, "CAMOMILLA.STRUCTURED_FIELD.CACHE_ENABLED", True
|
84
|
-
)
|
85
|
-
|
86
82
|
DEBUG = pointed_getter(django_settings, "CAMOMILLA.DEBUG", django_settings.DEBUG)
|
87
83
|
|
88
84
|
# camomilla settings example
|
camomilla/storages/optimize.py
CHANGED
@@ -2,13 +2,13 @@ import traceback
|
|
2
2
|
from io import BytesIO
|
3
3
|
|
4
4
|
from django.core.files.base import ContentFile
|
5
|
-
from django.core.files.storage import get_storage_class
|
6
5
|
from PIL import Image
|
7
6
|
|
8
7
|
from camomilla import settings
|
8
|
+
from camomilla.storages.default import get_default_storage_class
|
9
9
|
|
10
10
|
|
11
|
-
class OptimizedStorage(
|
11
|
+
class OptimizedStorage(get_default_storage_class()):
|
12
12
|
MEDIA_MAX_WIDTH = settings.MEDIA_OPTIMIZE_MAX_WIDTH
|
13
13
|
MEDIA_MAX_HEIGHT = settings.MEDIA_OPTIMIZE_MAX_HEIGHT
|
14
14
|
MEDIA_DPI = settings.MEDIA_OPTIMIZE_DPI
|
camomilla/storages/overwrite.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
from
|
1
|
+
from camomilla.storages.default import get_default_storage_class
|
2
2
|
|
3
3
|
|
4
|
-
class OverwriteStorage(
|
4
|
+
class OverwriteStorage(get_default_storage_class()):
|
5
5
|
def _save(self, name, content):
|
6
6
|
if self.exists(name):
|
7
7
|
self.delete(name)
|
@@ -4,7 +4,7 @@
|
|
4
4
|
{% for item in menu.nodes %}
|
5
5
|
<li>
|
6
6
|
{% if item.link.url %}
|
7
|
-
<a href="{{ item.link.url }}">{{ item.title }}</a>
|
7
|
+
<a href="{{ item.link.url }}{% if is_preview %}?preview=true{% endif %}">{{ item.title }}</a>
|
8
8
|
{% else %}
|
9
9
|
<span>{{item.title}}</span>
|
10
10
|
{% endif %}
|
camomilla/theme/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "6.0.0-beta.
|
1
|
+
__version__ = "6.0.0-beta.16"
|
camomilla/theme/admin.py
CHANGED
camomilla/translation.py
CHANGED
@@ -16,7 +16,7 @@ class SeoMixinTranslationOptions(TranslationOptions):
|
|
16
16
|
|
17
17
|
|
18
18
|
class AbstractPageTranslationOptions(SeoMixinTranslationOptions):
|
19
|
-
fields = ("breadcrumbs_title", "
|
19
|
+
fields = ("breadcrumbs_title", "autopermalink", "status", "indexable", "template_data")
|
20
20
|
|
21
21
|
|
22
22
|
@register(Article)
|
@@ -0,0 +1,148 @@
|
|
1
|
+
import re
|
2
|
+
from django.db.models import Q
|
3
|
+
|
4
|
+
CONDITION_PATTERN = re.compile(r"(\w+__\w+='[^']+'|\w+__\w+=\S+)") # Updated regex to handle quoted values
|
5
|
+
LOGICAL_OPERATORS = {"AND", "OR"}
|
6
|
+
|
7
|
+
class ConditionParser:
|
8
|
+
def __init__(self, query):
|
9
|
+
self.query = query
|
10
|
+
|
11
|
+
def parse(self, query=None):
|
12
|
+
"""Parse the query or subquery. If no query is provided, use the instance's query."""
|
13
|
+
if query is None:
|
14
|
+
query = self.query
|
15
|
+
|
16
|
+
tokens = self.tokenize(query)
|
17
|
+
# If there's just one token and it's a dictionary (single condition), return it
|
18
|
+
if len(tokens) == 1 and isinstance(tokens[0], dict):
|
19
|
+
return tokens[0]
|
20
|
+
return self.build_tree(tokens)
|
21
|
+
|
22
|
+
def tokenize(self, query):
|
23
|
+
tokens = []
|
24
|
+
i = 0
|
25
|
+
while i < len(query):
|
26
|
+
if query[i] == '(':
|
27
|
+
j = i + 1
|
28
|
+
open_parens = 1
|
29
|
+
while j < len(query) and open_parens > 0:
|
30
|
+
if query[j] == '(':
|
31
|
+
open_parens += 1
|
32
|
+
elif query[j] == ')':
|
33
|
+
open_parens -= 1
|
34
|
+
j += 1
|
35
|
+
if open_parens == 0:
|
36
|
+
subquery = query[i + 1:j - 1]
|
37
|
+
tokens.append(self.parse(subquery)) # Pass the subquery here
|
38
|
+
i = j
|
39
|
+
else:
|
40
|
+
raise ValueError("Mismatched parentheses")
|
41
|
+
elif query[i:i+3] == 'AND' or query[i:i+2] == 'OR':
|
42
|
+
operator = 'AND' if query[i:i+3] == 'AND' else 'OR'
|
43
|
+
tokens.append(operator)
|
44
|
+
i += 3 if operator == 'AND' else 2
|
45
|
+
else:
|
46
|
+
match = CONDITION_PATTERN.match(query[i:])
|
47
|
+
if match:
|
48
|
+
condition = self.parse_condition(match.group())
|
49
|
+
tokens.append(condition)
|
50
|
+
i += match.end()
|
51
|
+
else:
|
52
|
+
i += 1
|
53
|
+
return tokens
|
54
|
+
|
55
|
+
def parse_condition(self, condition_str):
|
56
|
+
"""Parse a single condition into field lookup and value."""
|
57
|
+
if '=' in condition_str:
|
58
|
+
field_lookup, value = condition_str.split("=")
|
59
|
+
value = value.strip("'").strip('"') # Remove single or double quotes
|
60
|
+
value = self.parse_value(value) # Parse the value
|
61
|
+
return {"field_lookup": field_lookup, "value": value}
|
62
|
+
return None
|
63
|
+
|
64
|
+
def parse_value(self, string: str):
|
65
|
+
"""Parse single condition values based on specific rules."""
|
66
|
+
if string and string.startswith("[") and string.endswith("]"):
|
67
|
+
string = [self.parse_value(substr) for substr in string[1:-1].split(",")]
|
68
|
+
elif string and string.lower() in ["true", "false"]:
|
69
|
+
string = string.lower() == "true"
|
70
|
+
elif string and string.isdigit():
|
71
|
+
string = int(string)
|
72
|
+
return string
|
73
|
+
|
74
|
+
def build_tree(self, tokens):
|
75
|
+
"""Build a tree-like structure with operators and conditions."""
|
76
|
+
if not tokens:
|
77
|
+
return None
|
78
|
+
|
79
|
+
output_stack = []
|
80
|
+
operator_stack = []
|
81
|
+
|
82
|
+
# Process each token in the query
|
83
|
+
for token in tokens:
|
84
|
+
if isinstance(token, dict):
|
85
|
+
# Handle a single condition
|
86
|
+
if operator_stack:
|
87
|
+
operator = operator_stack.pop()
|
88
|
+
if isinstance(output_stack[-1], dict):
|
89
|
+
output_stack[-1] = {
|
90
|
+
"operator": operator,
|
91
|
+
"conditions": [output_stack[-1], token]
|
92
|
+
}
|
93
|
+
else:
|
94
|
+
output_stack[-1]["conditions"].append(token)
|
95
|
+
else:
|
96
|
+
output_stack.append(token)
|
97
|
+
|
98
|
+
elif token in LOGICAL_OPERATORS:
|
99
|
+
# Operator found (AND/OR), handle precedence
|
100
|
+
operator_stack.append(token)
|
101
|
+
|
102
|
+
# If only one item in output_stack, return it directly
|
103
|
+
if len(output_stack) == 1:
|
104
|
+
return output_stack[0]
|
105
|
+
return {"operator": "AND", "conditions": output_stack} # Default to AND if no operators
|
106
|
+
|
107
|
+
def to_q(self, parsed_tree):
|
108
|
+
"""Convert parsed tree structure into Q objects."""
|
109
|
+
if isinstance(parsed_tree, list):
|
110
|
+
# If parsed_tree is a list, combine all conditions with AND by default
|
111
|
+
q_objects = [self.to_q(cond) for cond in parsed_tree]
|
112
|
+
combined_q = Q()
|
113
|
+
for q_obj in q_objects:
|
114
|
+
combined_q &= q_obj
|
115
|
+
return combined_q
|
116
|
+
|
117
|
+
if isinstance(parsed_tree, dict):
|
118
|
+
if "field_lookup" in parsed_tree:
|
119
|
+
# Base case: a single condition
|
120
|
+
return Q(**{parsed_tree["field_lookup"]: parsed_tree["value"]})
|
121
|
+
|
122
|
+
elif "operator" in parsed_tree and "conditions" in parsed_tree:
|
123
|
+
operator = parsed_tree["operator"]
|
124
|
+
conditions = parsed_tree["conditions"]
|
125
|
+
|
126
|
+
q_objects = [self.to_q(cond) for cond in conditions]
|
127
|
+
|
128
|
+
if operator == "AND":
|
129
|
+
combined_q = Q()
|
130
|
+
for q_obj in q_objects:
|
131
|
+
combined_q &= q_obj
|
132
|
+
return combined_q
|
133
|
+
elif operator == "OR":
|
134
|
+
combined_q = Q()
|
135
|
+
for q_obj in q_objects:
|
136
|
+
combined_q |= q_obj
|
137
|
+
return combined_q
|
138
|
+
else:
|
139
|
+
raise ValueError(f"Unknown operator: {operator}")
|
140
|
+
|
141
|
+
raise ValueError("Parsed tree structure is invalid")
|
142
|
+
|
143
|
+
def parse_to_q(self):
|
144
|
+
"""Parse the query and convert to Q object."""
|
145
|
+
parsed_tree = self.parse()
|
146
|
+
if not parsed_tree:
|
147
|
+
return Q() # Return an empty Q if parsing fails
|
148
|
+
return self.to_q(parsed_tree)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from typing import Sequence
|
2
|
+
from .getters import pointed_getter
|
3
|
+
|
4
|
+
|
5
|
+
def set_key(data, key, val):
|
6
|
+
if isinstance(data, Sequence):
|
7
|
+
key = int(key)
|
8
|
+
if key < len(data):
|
9
|
+
data[key] = val
|
10
|
+
else:
|
11
|
+
data.append(val)
|
12
|
+
return data
|
13
|
+
elif isinstance(data, dict):
|
14
|
+
data[key] = val
|
15
|
+
else:
|
16
|
+
setattr(data, key, val)
|
17
|
+
return data
|
18
|
+
|
19
|
+
|
20
|
+
def get_key(data, key, default):
|
21
|
+
if isinstance(data, Sequence):
|
22
|
+
try:
|
23
|
+
return data[int(key)]
|
24
|
+
except IndexError:
|
25
|
+
return default
|
26
|
+
return pointed_getter(data, key, default)
|
27
|
+
|
28
|
+
|
29
|
+
def pointed_setter(data, path, value):
|
30
|
+
path = path.split(".")
|
31
|
+
key = path.pop(0)
|
32
|
+
if not len(path):
|
33
|
+
return set_key(data, key, value)
|
34
|
+
default = [] if path[0].isdigit() else {}
|
35
|
+
return set_key(
|
36
|
+
data, key, pointed_setter(get_key(data, key, default), ".".join(path), value)
|
37
|
+
)
|
camomilla/views/base/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
from ..mixins import OptimViewMixin, PaginateStackMixin, OrderingMixin
|
1
|
+
from ..mixins import OptimViewMixin, PaginateStackMixin, OrderingMixin, CamomillaBasePermissionMixin
|
2
2
|
from rest_framework import viewsets
|
3
3
|
|
4
4
|
|
5
5
|
class BaseModelViewset(
|
6
|
-
OptimViewMixin, OrderingMixin, PaginateStackMixin, viewsets.ModelViewSet
|
6
|
+
CamomillaBasePermissionMixin, OptimViewMixin, OrderingMixin, PaginateStackMixin, viewsets.ModelViewSet
|
7
7
|
):
|
8
8
|
pass
|
camomilla/views/menus.py
CHANGED
@@ -13,8 +13,6 @@ from camomilla.serializers.page import BasicUrlNodeSerializer
|
|
13
13
|
from camomilla.views.base import BaseModelViewset
|
14
14
|
from camomilla.views.decorators import active_lang
|
15
15
|
|
16
|
-
from django.utils.translation import get_language
|
17
|
-
|
18
16
|
|
19
17
|
class MenuViewSet(BaseModelViewset):
|
20
18
|
queryset = Menu.objects.all()
|
@@ -80,5 +78,4 @@ class MenuViewSet(BaseModelViewset):
|
|
80
78
|
def search_urlnode(self, request, *args, **kwargs):
|
81
79
|
url_node = request.GET.get("q", "")
|
82
80
|
qs = UrlNode.objects.filter(permalink__icontains=url_node).order_by("permalink")
|
83
|
-
print(get_language())
|
84
81
|
return Response(BasicUrlNodeSerializer(qs, many=True).data)
|
@@ -30,9 +30,16 @@ class GetUserLanguageMixin(object):
|
|
30
30
|
return super().initialize_request(request, *args, **kwargs)
|
31
31
|
|
32
32
|
def get_queryset(self):
|
33
|
+
if hasattr(super(), "get_queryset"):
|
34
|
+
return super().get_queryset()
|
33
35
|
return self.model.objects.all()
|
34
36
|
|
35
37
|
|
38
|
+
class CamomillaBasePermissionMixin:
|
39
|
+
def get_permissions(self):
|
40
|
+
return [*super().get_permissions(), CamomillaBasePermissions()]
|
41
|
+
|
42
|
+
|
36
43
|
class OptimViewMixin:
|
37
44
|
def get_serializer_class(self):
|
38
45
|
if hasattr(self, "action_serializers"):
|
@@ -46,8 +53,8 @@ class OptimViewMixin:
|
|
46
53
|
def get_queryset(self):
|
47
54
|
queryset = super().get_queryset()
|
48
55
|
serializer = self.get_serializer_class()
|
49
|
-
if hasattr(serializer, "
|
50
|
-
queryset =
|
56
|
+
if hasattr(serializer, "optimize_qs"):
|
57
|
+
queryset = serializer.optimize_qs(queryset, context=self.get_serializer_context())
|
51
58
|
return queryset
|
52
59
|
|
53
60
|
|
@@ -2,6 +2,7 @@ from rest_framework.response import Response
|
|
2
2
|
from django.db.models import Q
|
3
3
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
4
4
|
from django.contrib.postgres.search import SearchVector, SearchQuery, TrigramSimilarity
|
5
|
+
from camomilla.utils.query_parser import ConditionParser
|
5
6
|
|
6
7
|
|
7
8
|
class TrigramSearchMixin:
|
@@ -32,18 +33,9 @@ class PaginateStackMixin:
|
|
32
33
|
list_handler, "shared_model", getattr(list_handler, "model", None)
|
33
34
|
)
|
34
35
|
|
35
|
-
def parse_qs_value(self, string: str):
|
36
|
-
if string and string.startswith("[") and string.endswith("]"):
|
37
|
-
string = [self.parse_qs_value(substr) for substr in string[1:-1].split(",")]
|
38
|
-
elif string and string.lower() in ["true", "false"]:
|
39
|
-
string = string.lower() == "true"
|
40
|
-
elif string and string.isdigit():
|
41
|
-
string = int(string)
|
42
|
-
return string
|
43
|
-
|
44
36
|
def parse_filter(self, filter):
|
45
|
-
|
46
|
-
return
|
37
|
+
parser = ConditionParser(filter)
|
38
|
+
return parser.parse_to_q()
|
47
39
|
|
48
40
|
def handle_pagination(self, list_handler=None, items_per_page=None):
|
49
41
|
list_handler = list_handler if list_handler is not None else self.get_queryset()
|
@@ -84,8 +76,7 @@ class PaginateStackMixin:
|
|
84
76
|
filters = dict(self.request.GET).get("fltr", [])
|
85
77
|
for filter in filters:
|
86
78
|
try:
|
87
|
-
|
88
|
-
list_handler = list_handler.filter(**{filter_name: value})
|
79
|
+
list_handler = list_handler.filter(self.parse_filter(filter))
|
89
80
|
except Exception:
|
90
81
|
pass
|
91
82
|
return list_handler
|
{django_camomilla_cms-6.0.0b14.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-camomilla-cms
|
3
|
-
Version: 6.0.
|
3
|
+
Version: 6.0.0b16
|
4
4
|
Summary: Django powered cms
|
5
5
|
Author-email: Lotrèk <dimmitutto@lotrek.it>
|
6
6
|
License: MIT
|
@@ -20,10 +20,9 @@ Requires-Dist: djangorestframework <4.0.0,>=3.10.0
|
|
20
20
|
Requires-Dist: django-admin-interface <1.0.0,>=0.26.0
|
21
21
|
Requires-Dist: Pillow <10.0.0,>=6.2.0
|
22
22
|
Requires-Dist: django-ckeditor <7.0.0,>=5.7.1
|
23
|
+
Requires-Dist: django-structured-json-field ==0.2.0
|
23
24
|
Requires-Dist: python-magic <0.5,>=0.4
|
24
|
-
Requires-Dist: django-jsonform ~=2.19.0
|
25
25
|
Requires-Dist: Django >=3.2
|
26
|
-
Requires-Dist: pydantic ~=2.2.1
|
27
26
|
|
28
27
|
# camomilla django cms [](https://pypi.org/project/django-camomilla-cms)   [](./LICENSE)
|
29
28
|
|