django-camomilla-cms 6.0.0b15__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/managers/pages.py +7 -7
- camomilla/model_api.py +9 -4
- camomilla/models/media.py +1 -1
- camomilla/models/menu.py +3 -2
- camomilla/models/page.py +12 -11
- camomilla/openapi/schema.py +4 -0
- camomilla/serializers/base/__init__.py +3 -1
- camomilla/serializers/fields/json.py +0 -1
- camomilla/serializers/fields/related.py +5 -1
- camomilla/serializers/mixins/__init__.py +51 -6
- camomilla/serializers/mixins/filter_fields.py +56 -0
- camomilla/serializers/utils.py +3 -1
- 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/utils/query_parser.py +148 -0
- camomilla/views/base/__init__.py +2 -2
- camomilla/views/menus.py +0 -2
- camomilla/views/mixins/__init__.py +9 -2
- camomilla/views/mixins/pagination.py +4 -13
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/METADATA +1 -1
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/RECORD +38 -28
- {django_camomilla_cms-6.0.0b15.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
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/LICENSE +0 -0
- {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -1,11 +1,11 @@
|
|
1
|
-
camomilla/__init__.py,sha256=
|
1
|
+
camomilla/__init__.py,sha256=zHhnT2CrQaDldn3CyDbBxGqY4L0nbxsbMQLr6arZrLs,251
|
2
2
|
camomilla/apps.py,sha256=eUwb9ynyiRAc5OXgt7ZsAdhsCOnPCpNdIFYMheNeN-o,532
|
3
3
|
camomilla/authentication.py,sha256=jz6tQT4PPEu-_JLox1LZrOy7EiWBb9MWaObK63MJGus,855
|
4
4
|
camomilla/context_processors.py,sha256=cGowjDZ-oDGYn1j2Pj5QDGCqnzXAOdOwp5dmzin_FTc,165
|
5
5
|
camomilla/defaults.py,sha256=VNQ_sbxu09AyFGNpUUYypIAyhlBhEORD36BBNj7e73I,1220
|
6
6
|
camomilla/dynamic_pages_urls.py,sha256=14H47KlSxmINoJyrsul0KR7Qvk6-1uoXrD1ReV_W4h8,1056
|
7
7
|
camomilla/exceptions.py,sha256=gLniAsK_pmsNNKGMv5Z384LXVbM8oeHcOwz4F91u1LY,111
|
8
|
-
camomilla/model_api.py,sha256=
|
8
|
+
camomilla/model_api.py,sha256=1xQxDWGPQNVqa_c87pLq4gj1ZHta2IFqdeEbzErHZX8,2447
|
9
9
|
camomilla/parsers.py,sha256=fL8XGCGPxJIZNZkPdGtnPSbDP-6-yzGOCVMuLPjkx9Y,1975
|
10
10
|
camomilla/permissions.py,sha256=9NlBO4JMmg36vXCUjPNyq6uZxhkdrnXyIbJVLtWhGWE,1813
|
11
11
|
camomilla/settings.py,sha256=GDJXDcEmyobGrP4MmEH3MAt1KBK74gXmmRR8kFuJzp0,3306
|
@@ -23,16 +23,16 @@ camomilla/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
23
23
|
camomilla/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
24
|
camomilla/management/commands/regenerate_thumbnails.py,sha256=pKToASR8p8TJezGpFfuylsAHtriNueJ7xqJJxq55adY,496
|
25
25
|
camomilla/managers/__init__.py,sha256=Zwp28E1RKafIl0FqcUi1YNHxF19IsMIvhlhS-Njg9Mw,60
|
26
|
-
camomilla/managers/pages.py,sha256=
|
26
|
+
camomilla/managers/pages.py,sha256=ifwYuOCaX8pf1-mR71IXuE5KDl2UWtcuwmGV26Hi9EU,1129
|
27
27
|
camomilla/models/__init__.py,sha256=y7Q34AGhQ1weJUKWb6XjiyUbwRnJeylOBGMErU0wqYg,147
|
28
28
|
camomilla/models/article.py,sha256=LgkZgRsubtDV6NwBz8E2bIgKD6H3I-1QLAxEan5TYYs,1139
|
29
29
|
camomilla/models/content.py,sha256=mIgtifb_WMIt58we5u6qWZemHvuDN1zZaBeCyzHL78A,956
|
30
|
-
camomilla/models/media.py,sha256=
|
31
|
-
camomilla/models/menu.py,sha256=
|
32
|
-
camomilla/models/page.py,sha256=
|
30
|
+
camomilla/models/media.py,sha256=pD-qldiHDOOHgux4lsivQLBcOJJrRx3a4Bg8ODNx7r0,6852
|
31
|
+
camomilla/models/menu.py,sha256=zXL644mQLRxt5aLZKv66l4RGlEi-a33cwmq7xpD9S5E,3661
|
32
|
+
camomilla/models/page.py,sha256=jMtnJ_leps6hnpZ52Ot_nXlgqUO5lUFshUzpYJtrRjk,17237
|
33
33
|
camomilla/models/mixins/__init__.py,sha256=c2NixqvrIX4E9WGRqQbylXlqBWDXEqN9mzs_dpB0hFQ,1248
|
34
34
|
camomilla/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
|
-
camomilla/openapi/schema.py,sha256=
|
35
|
+
camomilla/openapi/schema.py,sha256=08HaV3-X-96b15Bo1X9IJmT2bv-bCqOkVrmWpz-fesM,2381
|
36
36
|
camomilla/serializers/__init__.py,sha256=8v1GsJ_YZ6T72VnKBb5l-8K93oaLf4PIsMt-yFtK-Gk,176
|
37
37
|
camomilla/serializers/article.py,sha256=pYVcS0KztzjzSqgruElQMMEZcqTzmQUqXrdv_Sx5Az4,401
|
38
38
|
camomilla/serializers/content_type.py,sha256=qB2wkmkvQI6LHxfSI6auEh6M9cJRFBaHnpmkBCCzeYo,557
|
@@ -40,23 +40,25 @@ camomilla/serializers/media.py,sha256=H4JVpRVxXVmn_BiqrjihKXpfLm9fLmHDFIICRDGJU4
|
|
40
40
|
camomilla/serializers/menu.py,sha256=TdoyXs40PqxNevnRbBbYOOX9rUv9zQGiHFNduspaZnw,552
|
41
41
|
camomilla/serializers/page.py,sha256=NNjEypVYu_9iKqdHV_-61ea37gxiHlDP5gsloV_i6yg,1834
|
42
42
|
camomilla/serializers/user.py,sha256=CzrHiVRvYYWNE4eNpCNKtJB7DjVqHHwIcP4NUBXMHSo,3706
|
43
|
-
camomilla/serializers/utils.py,sha256=
|
43
|
+
camomilla/serializers/utils.py,sha256=uPQNNkIMZyoPP4umWTgFHNM7KyabBZgs5r5AJyfgtJI,1105
|
44
44
|
camomilla/serializers/validators.py,sha256=EX-EFg6afFr8viPKq-LHeEtRAKPVtO4kIlEymY1j2HA,2043
|
45
|
-
camomilla/serializers/base/__init__.py,sha256=
|
45
|
+
camomilla/serializers/base/__init__.py,sha256=P2153u7T4yQSbgwIC9LJxJFnVSle3H9oEuTUNrnz9x4,869
|
46
46
|
camomilla/serializers/fields/__init__.py,sha256=T_ONowldBTPfgbwFmoefY1r3TQiB2sbWQPpFvBSnzqE,681
|
47
47
|
camomilla/serializers/fields/file.py,sha256=yjKMho2ti9TIAzo6nwyLnNPJ6GVUumL2wxhegvYqI2o,800
|
48
|
-
camomilla/serializers/fields/json.py,sha256=
|
49
|
-
camomilla/serializers/fields/related.py,sha256=
|
50
|
-
camomilla/serializers/mixins/__init__.py,sha256=
|
48
|
+
camomilla/serializers/fields/json.py,sha256=epuvDkKqDHuXAH0jVpOkeljz7LnMJVg7RoWYioPgCNs,1873
|
49
|
+
camomilla/serializers/fields/related.py,sha256=kG5_q1ckm33BVUIgPLZFa6IjL5lQ5DT8_83fUe9plc8,4989
|
50
|
+
camomilla/serializers/mixins/__init__.py,sha256=lDfeIoaMqKH11QHzBNs4hA5aHBV7sJsjzGLew6tdcXU,9367
|
51
|
+
camomilla/serializers/mixins/filter_fields.py,sha256=AwdV5iG-r0JFy4Wtywczu7bqA8Fb6NZyy60Y79Z6cWo,2214
|
51
52
|
camomilla/storages/__init__.py,sha256=ytGgX59Ar8vFfYz7eV8z-j5yO_5FqxdZM25iyLnJuSA,131
|
52
|
-
camomilla/storages/
|
53
|
-
camomilla/storages/
|
53
|
+
camomilla/storages/default.py,sha256=KVuGPIpkM9tDHc0cYG385xkiKOwIvage5IBmdrv2uOk,188
|
54
|
+
camomilla/storages/optimize.py,sha256=VGSXZigzZC8LnPTqyTOpPA2Ba9EJB_KC5bcACoRs4GA,2762
|
55
|
+
camomilla/storages/overwrite.py,sha256=jvW3zHvXNzH9dIjeZmmfXo_O3K1ZQmLQzmlSKAOE8ZA,360
|
54
56
|
camomilla/templates/admin/camomilla/page/change_form.html,sha256=ig7rRUtylDZMINBQuVPpZLmeB4sOTV_VtqnTgzAyxEo,251
|
55
57
|
camomilla/templates/defaults/base.html,sha256=pklt7Pif3g9d7gwgRxCQj7gniJaHD14ZqZID_xIlC0A,6638
|
56
58
|
camomilla/templates/defaults/articles/default.html,sha256=1f89jBvNtTa1mPAbC91yy8CzeAjTWO3hhQsTuQW5OKg,239
|
57
59
|
camomilla/templates/defaults/pages/default.html,sha256=bP81Qb6M56I-fBJMywWwEu_cnERtWIX28UkGrUSRU6M,144
|
58
60
|
camomilla/templates/defaults/parts/langswitch.html,sha256=AkaQzb2KNjRYCMLUn_jE31V36rwBIwp4MneirWPiBcI,3424
|
59
|
-
camomilla/templates/defaults/parts/menu.html,sha256=
|
61
|
+
camomilla/templates/defaults/parts/menu.html,sha256=CSRPx78Z6D6pGtRqqaLmJetUaADrTh9rx_gqS4myndc,381
|
60
62
|
camomilla/templates/defaults/widgets/media_select_multiple.html,sha256=k2XYou8KkPuFLnPMkPJAFJ-zGJj2Xvu6R3ZmiKa3g7Q,3727
|
61
63
|
camomilla/templates_context/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
64
|
camomilla/templates_context/autodiscover.py,sha256=td7SCsqg3iNPnv1HDEDEpwWgWLjy5Zmc8Nbze1_J46I,1907
|
@@ -64,7 +66,7 @@ camomilla/templates_context/rendering.py,sha256=GfTR45_gC7WT7zTKPVXkBDwe22uF63A-
|
|
64
66
|
camomilla/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
65
67
|
camomilla/templatetags/camomilla_filters.py,sha256=35x0-cWrRHeLhqypSLlJzEFY_fQDcRHiwZQpFgIsspE,692
|
66
68
|
camomilla/templatetags/menus.py,sha256=pLFeJ1UAm3flsU9TNo2iQk4otSwZuJPr86Nf408dZ-8,862
|
67
|
-
camomilla/theme/__init__.py,sha256=
|
69
|
+
camomilla/theme/__init__.py,sha256=ZTc6bQJ8jCuKWUOkNmYh8qHpCpZ6iN55XQS_wWviAis,30
|
68
70
|
camomilla/theme/admin.py,sha256=7TgNXOfIy4awtV-KkGEKsmai94qhBiykB0uJWwTZy8U,2461
|
69
71
|
camomilla/theme/apps.py,sha256=ecqG8ntWdOighYOEHQOMVQpa5g_1WEzzpaaGjnOi9uA,1195
|
70
72
|
camomilla/theme/static/admin/css/responsive.css,sha256=yGq6qXrr8xEVsXTnprIBgkX-sMGZrNf0Kkh-xDxf6yE,157
|
@@ -75,6 +77,7 @@ camomilla/theme/templates/rosetta/base.html,sha256=s9Ijf1nZx8um30R5Gk3g-w-RwFGv2
|
|
75
77
|
camomilla/utils/__init__.py,sha256=Ui7nzSh45UMMFtCGF1xFHKyJMNWflG_Nx72HAJHJHFM,100
|
76
78
|
camomilla/utils/getters.py,sha256=6j18grFAZ8BC70SriycFDTQFxTnudGn0uKGA83_Rclk,798
|
77
79
|
camomilla/utils/normalization.py,sha256=RDCZtjwpEEwjvfUjQl2bEWFKw7NxTzkXco72VeO2M9w,255
|
80
|
+
camomilla/utils/query_parser.py,sha256=STNUfoKENcRM_joIKh5AVvYADppsURj9Jpf9OkUQrJU,5818
|
78
81
|
camomilla/utils/seo.py,sha256=8p_a_TGgohenpJb094tT4mMxbn2xzW0qDILuTnjNocM,3324
|
79
82
|
camomilla/utils/setters.py,sha256=LV57SM65rL1_ZQkVzk9al_Q13lndVywXLkqgfIvgS0Y,915
|
80
83
|
camomilla/utils/templates.py,sha256=Lv4-5019cnM30HmdZnYWiU5gxry-eFZVAhwOofGQRDs,598
|
@@ -85,21 +88,28 @@ camomilla/views/contents.py,sha256=JxvnmgeK8JEmCMLzVG8pVq2DwvmjXtgnIdsDnn74tA4,1
|
|
85
88
|
camomilla/views/decorators.py,sha256=hR--nTGQn2mMKDrWn-0Ildzbsvp11OfoWAtedKEzmiA,982
|
86
89
|
camomilla/views/languages.py,sha256=Rt_X7s3dbDBv4dxsQ9fnav_u0TAzzo8fGKBBx3esDsg,441
|
87
90
|
camomilla/views/medias.py,sha256=S4Ak4JI6XCSu3BCZx8KOotX7TBfrIU82GPXgyP3KcZA,3001
|
88
|
-
camomilla/views/menus.py,sha256=
|
91
|
+
camomilla/views/menus.py,sha256=Kpygnf3tMKJ30gcblUES2NW83A37Vy75ecSGSvExGKM,3301
|
89
92
|
camomilla/views/pages.py,sha256=KrBsKT0Z-rkBha8K3P6fVDt4vArQBxsc9rZ28j8XW9w,994
|
90
93
|
camomilla/views/tags.py,sha256=XcYRlcBFSPPY32lt7POb6fWPJL_8HsTo5JcHcAOiOKw,479
|
91
94
|
camomilla/views/users.py,sha256=_fvsKOEtep4SJLvMva2_q-HdLQT_1KlFNt4wcl3xCJk,3130
|
92
|
-
camomilla/views/base/__init__.py,sha256=
|
93
|
-
camomilla/views/mixins/__init__.py,sha256=
|
95
|
+
camomilla/views/base/__init__.py,sha256=dWiyyRpvOWXMmBeLsfLDOCK_ZesYzBBniH71zAu6hu4,282
|
96
|
+
camomilla/views/mixins/__init__.py,sha256=MEZiQf-wkY80J-f9c6GnLD2o97YefCKyn5PXW9jj8bc,2672
|
94
97
|
camomilla/views/mixins/ordering.py,sha256=mh7fqPyVCVJh84Nl2pYFQouzGxa-ANF3Wqv0pCb7OVU,4779
|
95
|
-
camomilla/views/mixins/pagination.py,sha256=
|
98
|
+
camomilla/views/mixins/pagination.py,sha256=8BY4T5aWw50LSgIbwM4X0_HWm8GFgKWKn1TUrY028No,5519
|
96
99
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
97
|
-
tests/test_api.py,sha256=
|
98
|
-
tests/test_camomilla_filters.py,sha256=
|
100
|
+
tests/test_api.py,sha256=t03EFDezGgm4UJl8RIVvnTUkAGTB6ptm0G2lHBQ7ljc,1833
|
101
|
+
tests/test_camomilla_filters.py,sha256=9tHdxhU-Qk5c3ZWbhLQE3g1VrWKS-cGlRG258D1T9hI,1535
|
102
|
+
tests/test_media.py,sha256=bBFUFWEKWIWgDxclOyAfLW5wUCERSfONtxD_OOz9KBg,2523
|
103
|
+
tests/test_model_api.py,sha256=Ne8YlXTH2cqP5gzOc8UKjJuh0t-NaKHh5Ol9krpVHQg,3768
|
104
|
+
tests/test_model_api_permissions.py,sha256=7CSb4-yIOfycAL_vXvh1dE2whx7k0gNkWl9LO0yzy4I,1801
|
99
105
|
tests/test_models.py,sha256=WJs8lxWZWn1l7X3a_QFVc8fF5LHTsI8bc3uhQe6-o-Q,684
|
100
|
-
tests/
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
django_camomilla_cms-6.0.
|
106
|
+
tests/test_query_parser.py,sha256=ebHQwjGINHy84JZZXUxrdutXtlHBhoqq7keLr5GHFik,2098
|
107
|
+
tests/test_utils.py,sha256=o_FG7XOxLePOBfwBr4sk09gej0onWNw9t2-gSjGmgNg,3741
|
108
|
+
tests/fixtures/__init__.py,sha256=NGj22kLV65v56IpOrOVqSkPhJePTXD4QjuuZhZSMwfQ,460
|
109
|
+
tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
110
|
+
tests/utils/api.py,sha256=vnj6W3PG5a-R_uF43nv3UushpMfyG_HbqlsBXH9Ysg0,910
|
111
|
+
django_camomilla_cms-6.0.0b16.dist-info/LICENSE,sha256=kVS7zDrNkav2hLLXbOJwVdonY2ToApTK3khyJagGQoQ,1063
|
112
|
+
django_camomilla_cms-6.0.0b16.dist-info/METADATA,sha256=pwvWq5hO3tSBO2Qmt0TgoKJy5QY5Kko2yeaXYjubXAw,2390
|
113
|
+
django_camomilla_cms-6.0.0b16.dist-info/WHEEL,sha256=OpXWERl2xLPRHTvd2ZXo_iluPEQd8uSbYkJ53NAER_Y,109
|
114
|
+
django_camomilla_cms-6.0.0b16.dist-info/top_level.txt,sha256=G9VIGBmMMqC7JEckoTgXKmC6T2BR75QRkqRnngw1_lo,16
|
115
|
+
django_camomilla_cms-6.0.0b16.dist-info/RECORD,,
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
4
|
+
|
5
|
+
|
6
|
+
def load_json_fixture(filename):
|
7
|
+
with open(os.path.join(os.path.dirname(__file__), 'json', filename), "r") as f:
|
8
|
+
return json.load(f)
|
9
|
+
|
10
|
+
|
11
|
+
def load_asset(filename):
|
12
|
+
with open(os.path.join(os.path.dirname(__file__), 'assets', filename), "rb") as f:
|
13
|
+
up_file = SimpleUploadedFile(
|
14
|
+
filename,
|
15
|
+
f.read()
|
16
|
+
)
|
17
|
+
return up_file
|
tests/test_api.py
CHANGED
@@ -1,20 +1,11 @@
|
|
1
1
|
import pytest
|
2
|
-
from django.contrib.auth.models import User
|
3
2
|
from rest_framework.test import APIClient
|
4
|
-
|
3
|
+
from .utils.api import login_superuser
|
5
4
|
from camomilla.models import Tag
|
6
5
|
|
7
6
|
client = APIClient()
|
8
7
|
|
9
8
|
|
10
|
-
def login_superuser():
|
11
|
-
User.objects.create_superuser("admin", "myemail@test.com", "adminadmin")
|
12
|
-
response = client.post(
|
13
|
-
"/api/camomilla/token-auth/", {"username": "admin", "password": "adminadmin"}
|
14
|
-
)
|
15
|
-
return response.json()["token"]
|
16
|
-
|
17
|
-
|
18
9
|
@pytest.mark.django_db
|
19
10
|
def test_create_tag_no_access():
|
20
11
|
response = client.post("/api/camomilla/tags/", {"name_en": "First tag"})
|
@@ -27,7 +18,7 @@ def test_crud_tag():
|
|
27
18
|
token = login_superuser()
|
28
19
|
client.credentials(HTTP_AUTHORIZATION="Token " + token)
|
29
20
|
response = client.post("/api/camomilla/tags/", {"name_en": "First tag"})
|
30
|
-
|
21
|
+
|
31
22
|
assert response.json()["name"] == "First tag"
|
32
23
|
assert len(Tag.objects.all()) == 1
|
33
24
|
assert response.status_code == 201
|
tests/test_camomilla_filters.py
CHANGED
@@ -17,31 +17,25 @@ class CamomillaFiltersTestCase(TestCase):
|
|
17
17
|
pass
|
18
18
|
|
19
19
|
def test_filter_content(self):
|
20
|
+
Page.objects.create(identifier="path", title="Path", permalink="/path", status="PUB")
|
20
21
|
request_factory = RequestFactory()
|
21
22
|
request = request_factory.get("/path")
|
22
23
|
request.META["HTTP_HOST"] = "localhost"
|
23
|
-
page = Page.get(request
|
24
|
+
page = Page.get(request)
|
24
25
|
content = filter_content(page, "content1")
|
25
26
|
self.assertEqual(content.identifier, "content1")
|
26
27
|
self.assertEqual(content.content, "")
|
27
28
|
content.content = "Hello World!"
|
28
29
|
content.save()
|
29
|
-
page = Page.get(request
|
30
|
+
page = Page.get(request)
|
30
31
|
content = filter_content(page, "content1")
|
31
32
|
self.assertEqual(content.identifier, "content1")
|
32
33
|
self.assertEqual(content.content, "Hello World!")
|
33
34
|
|
34
35
|
def test_filter_alternate_urls(self):
|
35
|
-
|
36
|
+
Page.objects.create(identifier="path", title="Path", permalink="/path", status="PUB")
|
37
|
+
request = RequestFactory().get("/path")
|
36
38
|
request.META["HTTP_HOST"] = "localhost"
|
37
|
-
page = Page.get(request
|
39
|
+
page = Page.get(request)
|
38
40
|
alt_urls = dict(alternate_urls(page, request))
|
39
|
-
self.assertEqual(alt_urls, {})
|
40
|
-
|
41
|
-
request = RequestFactory().get("/about", HTTP_HOST="localhost:8000")
|
42
|
-
request.META["HTTP_HOST"] = "localhost"
|
43
|
-
page = Page.get(request, identifier="about")
|
44
|
-
alt_urls = dict(alternate_urls(page, request))
|
45
|
-
self.assertEqual(alt_urls["it"], "http://localhost/about")
|
46
|
-
self.assertEqual(alt_urls["en"], "http://localhost/en/about")
|
47
|
-
self.assertEqual(alt_urls["de"], "http://localhost/de/about")
|
41
|
+
self.assertEqual(alt_urls, {'it': None, 'en': '/path/'})
|
tests/test_media.py
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
import pytest
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
from camomilla.models import Media
|
5
|
+
from .fixtures import load_asset
|
6
|
+
from .utils.api import login_superuser
|
7
|
+
from rest_framework.test import APIClient
|
8
|
+
from django.conf import settings
|
9
|
+
|
10
|
+
client = APIClient()
|
11
|
+
|
12
|
+
|
13
|
+
def load_asset_and_remove_media(filename):
|
14
|
+
asset = load_asset(filename)
|
15
|
+
if os.path.exists(f"{settings.MEDIA_ROOT}/{filename}"):
|
16
|
+
os.remove(f"{settings.MEDIA_ROOT}/{filename}")
|
17
|
+
return asset
|
18
|
+
|
19
|
+
|
20
|
+
@pytest.mark.django_db
|
21
|
+
def test_media_api_creation():
|
22
|
+
token = login_superuser()
|
23
|
+
client.credentials(HTTP_AUTHORIZATION='Token ' + token)
|
24
|
+
asset = load_asset_and_remove_media("10595073.png")
|
25
|
+
response = client.post(
|
26
|
+
"/api/camomilla/media/",
|
27
|
+
{
|
28
|
+
"file": asset,
|
29
|
+
"data": json.dumps({"translations": {"en": {"alt_text": "Test", "title": "Test", "description": "Test"}}}),
|
30
|
+
},
|
31
|
+
format="multipart",
|
32
|
+
)
|
33
|
+
assert response.status_code == 201
|
34
|
+
assert Media.objects.count() == 1
|
35
|
+
media = Media.objects.first()
|
36
|
+
assert media.alt_text == "Test"
|
37
|
+
assert media.title == "Test"
|
38
|
+
assert media.description == "Test"
|
39
|
+
assert media.file.name == "10595073.png"
|
40
|
+
|
41
|
+
|
42
|
+
@pytest.mark.django_db
|
43
|
+
def test_media_compression():
|
44
|
+
token = login_superuser()
|
45
|
+
client.credentials(HTTP_AUTHORIZATION='Token ' + token)
|
46
|
+
asset = load_asset_and_remove_media("Sample-jpg-image-10mb.jpg")
|
47
|
+
asset_size = asset.size
|
48
|
+
response = client.post(
|
49
|
+
"/api/camomilla/media/",
|
50
|
+
{
|
51
|
+
"file": asset,
|
52
|
+
"data": json.dumps({"translations": {"en": {"alt_text": "Test", "title": "Test", "description": "Test"}}}),
|
53
|
+
},
|
54
|
+
format="multipart",
|
55
|
+
)
|
56
|
+
assert response.status_code == 201
|
57
|
+
assert Media.objects.count() == 1
|
58
|
+
media = Media.objects.first()
|
59
|
+
assert media.file.size < asset_size
|
60
|
+
assert media.file.size < 1000000 # 1MB
|
61
|
+
|
62
|
+
|
63
|
+
@pytest.mark.django_db
|
64
|
+
def test_inflating_prevent():
|
65
|
+
token = login_superuser()
|
66
|
+
client.credentials(HTTP_AUTHORIZATION='Token ' + token)
|
67
|
+
asset = load_asset_and_remove_media("optimized.jpg")
|
68
|
+
asset_size = asset.size
|
69
|
+
response = client.post(
|
70
|
+
"/api/camomilla/media/",
|
71
|
+
{
|
72
|
+
"file": asset,
|
73
|
+
"data": json.dumps({"translations": {"en": {"alt_text": "Test", "title": "Test", "description": "Test"}}}),
|
74
|
+
},
|
75
|
+
format="multipart",
|
76
|
+
)
|
77
|
+
assert response.status_code == 201
|
78
|
+
assert Media.objects.count() == 1
|
79
|
+
media = Media.objects.first()
|
80
|
+
assert media.file.size < asset_size
|
tests/test_model_api.py
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
import pytest
|
2
|
+
from rest_framework.test import APIClient
|
3
|
+
from .fixtures import load_json_fixture
|
4
|
+
from .utils.api import login_superuser
|
5
|
+
from example.website.models import SimpleRelationModel
|
6
|
+
|
7
|
+
|
8
|
+
client = APIClient()
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture(autouse=True)
|
12
|
+
def init_test():
|
13
|
+
token = login_superuser()
|
14
|
+
client.credentials(HTTP_AUTHORIZATION="Token " + token)
|
15
|
+
SimpleRelationModel.objects.bulk_create([SimpleRelationModel(name=f"test{i}") for i in range(1, 10)])
|
16
|
+
|
17
|
+
|
18
|
+
@pytest.mark.django_db
|
19
|
+
def test_simple_relation_model_api_endpoint():
|
20
|
+
response = client.get("/api/models/simple-relation-model/")
|
21
|
+
assert response.status_code == 200
|
22
|
+
assert len(response.json()) == 9
|
23
|
+
response = client.get("/api/models/simple-relation-model/1/")
|
24
|
+
assert response.status_code == 200
|
25
|
+
assert response.json()["name"] == "test1"
|
26
|
+
response = client.patch("/api/models/simple-relation-model/1/", {"name": "updated"}, format="json")
|
27
|
+
assert response.status_code == 200
|
28
|
+
assert response.json()["name"] == "updated"
|
29
|
+
response = client.delete("/api/models/simple-relation-model/1/")
|
30
|
+
assert response.status_code == 204
|
31
|
+
response = client.get("/api/models/simple-relation-model/")
|
32
|
+
assert response.status_code == 200
|
33
|
+
assert len(response.json()) == 8
|
34
|
+
response = client.get("/api/models/simple-relation-model/1/")
|
35
|
+
assert response.status_code == 404
|
36
|
+
assert response.json() in [{"detail": "Not found."}, {'detail': 'No SimpleRelationModel matches the given query.'}]
|
37
|
+
|
38
|
+
|
39
|
+
@pytest.mark.django_db
|
40
|
+
def test_test_model_api_endpoint():
|
41
|
+
response = client.get("/api/models/test-model/")
|
42
|
+
assert response.status_code == 200
|
43
|
+
assert response.json() == []
|
44
|
+
test_model_data = load_json_fixture("test-model-api.json")
|
45
|
+
response = client.post("/api/models/test-model/", test_model_data, format="json")
|
46
|
+
assert response.status_code == 201
|
47
|
+
assert response.json()["title"] == test_model_data["title"]
|
48
|
+
assert response.json()["structured_data"]["name"] == test_model_data["structured_data"]["name"]
|
49
|
+
assert response.json()["structured_data"]["age"] == test_model_data["structured_data"]["age"]
|
50
|
+
assert response.json()["structured_data"]["child"]["name"] == test_model_data["structured_data"]["child"]["name"]
|
51
|
+
assert response.json()["structured_data"]["childs"][0]["name"] == test_model_data["structured_data"]["childs"][0]["name"]
|
52
|
+
assert response.json()["structured_data"]["fk_field"]["id"] == test_model_data["structured_data"]["fk_field"]["id"]
|
53
|
+
assert response.json()["structured_data"]["qs_field"][0]["id"] == test_model_data["structured_data"]["qs_field"][0]["id"]
|
54
|
+
response = client.get("/api/models/test-model/")
|
55
|
+
assert response.status_code == 200
|
56
|
+
assert len(response.json()) == 1
|
57
|
+
response = client.get("/api/models/test-model/1/")
|
58
|
+
assert response.status_code == 200
|
59
|
+
assert response.json()["title"] == test_model_data["title"]
|
60
|
+
assert response.json()["structured_data"]["name"] == test_model_data["structured_data"]["name"]
|
61
|
+
assert response.json()["structured_data"]["age"] == test_model_data["structured_data"]["age"]
|
62
|
+
assert response.json()["structured_data"]["child"]["name"] == test_model_data["structured_data"]["child"]["name"]
|
63
|
+
assert response.json()["structured_data"]["childs"][0]["name"] == test_model_data["structured_data"]["childs"][0]["name"]
|
64
|
+
assert response.json()["structured_data"]["fk_field"]["id"] == test_model_data["structured_data"]["fk_field"]["id"]
|
65
|
+
assert response.json()["structured_data"]["qs_field"][0]["id"] == test_model_data["structured_data"]["qs_field"][0]["id"]
|
66
|
+
response = client.patch("/api/models/test-model/1/", {"title": "updated"}, format="json")
|
67
|
+
assert response.status_code == 200
|
68
|
+
assert response.json()["title"] == "updated"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import pytest
|
2
|
+
from rest_framework.test import APIClient
|
3
|
+
from .utils.api import login_user, login_superuser, login_staff
|
4
|
+
|
5
|
+
client = APIClient()
|
6
|
+
|
7
|
+
|
8
|
+
@pytest.mark.django_db
|
9
|
+
def test_right_permissions():
|
10
|
+
response = client.post("/api/models/test-model/", {"title": "test"}, format="json")
|
11
|
+
assert response.status_code == 401
|
12
|
+
token = login_user()
|
13
|
+
client.credentials(HTTP_AUTHORIZATION="Token " + token)
|
14
|
+
response = client.post("/api/models/test-model/", {"title": "test"}, format="json")
|
15
|
+
assert response.status_code == 403
|
16
|
+
token = login_staff()
|
17
|
+
client.credentials(HTTP_AUTHORIZATION="Token " + token)
|
18
|
+
response = client.post("/api/models/test-model/", {"title": "test"}, format="json")
|
19
|
+
assert response.status_code == 403
|
20
|
+
token = login_superuser()
|
21
|
+
client.credentials(HTTP_AUTHORIZATION="Token " + token)
|
22
|
+
response = client.post("/api/models/test-model/", {"title": "test"}, format="json")
|
23
|
+
assert response.status_code == 201
|
24
|
+
response = client.get("/api/models/test-model/")
|
25
|
+
assert response.status_code == 200
|
26
|
+
assert len(response.json()) == 1
|
27
|
+
response = client.get("/api/models/test-model/1/")
|
28
|
+
assert response.status_code == 200
|
29
|
+
response = client.patch("/api/models/test-model/1/", {"title": "updated"}, format="json")
|
30
|
+
assert response.status_code == 200
|
31
|
+
assert response.json()["title"] == "updated"
|
32
|
+
response = client.delete("/api/models/test-model/1/")
|
33
|
+
assert response.status_code == 204
|
34
|
+
response = client.get("/api/models/test-model/")
|
35
|
+
assert response.status_code == 200
|
36
|
+
assert len(response.json()) == 0
|
37
|
+
response = client.get("/api/models/test-model/1/")
|
38
|
+
assert response.status_code == 404
|
39
|
+
assert response.json() in [{"detail": "Not found."}, {'detail': 'No TestModel matches the given query.'}]
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import pytest
|
2
|
+
from django.db.models import Q
|
3
|
+
from camomilla.utils.query_parser import (
|
4
|
+
ConditionParser,
|
5
|
+
)
|
6
|
+
|
7
|
+
@pytest.mark.parametrize(
|
8
|
+
"query, expected_q",
|
9
|
+
[
|
10
|
+
# Single condition
|
11
|
+
("name__icontains=foo", Q(name__icontains="foo")),
|
12
|
+
# Multiple conditions with AND
|
13
|
+
(
|
14
|
+
"name__icontains=foo AND age__gt=21",
|
15
|
+
Q(name__icontains="foo") & Q(age__gt=21),
|
16
|
+
),
|
17
|
+
# Multiple conditions with OR
|
18
|
+
(
|
19
|
+
"name__icontains=foo OR name__icontains=bar",
|
20
|
+
Q(name__icontains="foo") | Q(name__icontains="bar"),
|
21
|
+
),
|
22
|
+
# Mixed AND and OR conditions
|
23
|
+
(
|
24
|
+
"name__icontains=foo OR (age__gt=21 AND city__iexact='New York')",
|
25
|
+
Q(name__icontains="foo") | (Q(age__gt=21) & Q(city__iexact="New York")),
|
26
|
+
),
|
27
|
+
# Nested parentheses with OR inside AND
|
28
|
+
(
|
29
|
+
"((name__icontains=foo) OR (name__icontains=bar)) AND name__icontains=baz",
|
30
|
+
(Q(name__icontains="foo") | Q(name__icontains="bar"))
|
31
|
+
& Q(name__icontains="baz"),
|
32
|
+
),
|
33
|
+
# Complex nested conditions with multiple OR and AND
|
34
|
+
(
|
35
|
+
"((name__icontains=foo) OR (name__icontains=bar) OR (name__icontains=buz)) AND age__gt=25 AND city__iexact='LA'",
|
36
|
+
(
|
37
|
+
Q(name__icontains="foo")
|
38
|
+
| Q(name__icontains="bar")
|
39
|
+
| Q(name__icontains="buz")
|
40
|
+
)
|
41
|
+
& Q(age__gt=25)
|
42
|
+
& Q(city__iexact="LA"),
|
43
|
+
),
|
44
|
+
# Single condition with no parentheses
|
45
|
+
("age__lte=30", Q(age__lte=30)),
|
46
|
+
# Simple nested OR and AND with more levels of nesting
|
47
|
+
(
|
48
|
+
"(name__icontains=foo AND (age__gt=21 OR city__iexact='New York')) OR country__iexact='US'",
|
49
|
+
(Q(name__icontains="foo") & (Q(age__gt=21) | Q(city__iexact="New York")))
|
50
|
+
| Q(country__iexact="US"),
|
51
|
+
),
|
52
|
+
],
|
53
|
+
)
|
54
|
+
def test_condition_parser(query, expected_q):
|
55
|
+
parser = ConditionParser(query)
|
56
|
+
q_object = parser.parse_to_q()
|
57
|
+
assert q_object.__str__() == expected_q.__str__()
|
58
|
+
|
59
|
+
|