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.
Files changed (38) hide show
  1. camomilla/__init__.py +1 -1
  2. camomilla/managers/pages.py +7 -7
  3. camomilla/model_api.py +9 -4
  4. camomilla/models/media.py +1 -1
  5. camomilla/models/menu.py +3 -2
  6. camomilla/models/page.py +12 -11
  7. camomilla/openapi/schema.py +4 -0
  8. camomilla/serializers/base/__init__.py +3 -1
  9. camomilla/serializers/fields/json.py +0 -1
  10. camomilla/serializers/fields/related.py +5 -1
  11. camomilla/serializers/mixins/__init__.py +51 -6
  12. camomilla/serializers/mixins/filter_fields.py +56 -0
  13. camomilla/serializers/utils.py +3 -1
  14. camomilla/storages/default.py +6 -0
  15. camomilla/storages/optimize.py +2 -2
  16. camomilla/storages/overwrite.py +2 -2
  17. camomilla/templates/defaults/parts/menu.html +1 -1
  18. camomilla/theme/__init__.py +1 -1
  19. camomilla/utils/query_parser.py +148 -0
  20. camomilla/views/base/__init__.py +2 -2
  21. camomilla/views/menus.py +0 -2
  22. camomilla/views/mixins/__init__.py +9 -2
  23. camomilla/views/mixins/pagination.py +4 -13
  24. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/METADATA +1 -1
  25. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/RECORD +38 -28
  26. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/WHEEL +1 -1
  27. tests/fixtures/__init__.py +17 -0
  28. tests/test_api.py +2 -11
  29. tests/test_camomilla_filters.py +7 -13
  30. tests/test_media.py +80 -0
  31. tests/test_model_api.py +68 -0
  32. tests/test_model_api_permissions.py +39 -0
  33. tests/test_query_parser.py +59 -0
  34. tests/test_utils.py +64 -64
  35. tests/utils/__init__.py +0 -0
  36. tests/utils/api.py +29 -0
  37. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b16.dist-info}/LICENSE +0 -0
  38. {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
- filter_name, value = filter.split("=")
46
- return filter_name, self.parse_qs_value(value)
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
- filter_name, value = self.parse_filter(filter)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-camomilla-cms
3
- Version: 6.0.0b15
3
+ Version: 6.0.0b16
4
4
  Summary: Django powered cms
5
5
  Author-email: Lotrèk <dimmitutto@lotrek.it>
6
6
  License: MIT
@@ -1,11 +1,11 @@
1
- camomilla/__init__.py,sha256=NkhtGSnemCX19_YBz80aKvqoqwSnTGEtwPHHnB-JjcI,251
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=VC3tNQucS3-KVtdZNWkkbkJn8vtl_cBKYTcC3QU6PpU,2253
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=8SXd0iUt2UA59lEn9DzJC41SkeU7Vc_tFe9sJhvfyww,1144
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=7TD_0nhNoi8P-Gmsr0Kag4eGaxMXuTfQOFl53Mb03cQ,6854
31
- camomilla/models/menu.py,sha256=1eUOjBTZQklPgHXrQTpPsRKjT4QNFSoQ8diUVNRymfc,3589
32
- camomilla/models/page.py,sha256=O4EGWjzk4cHGzmtkJr2nSWG51qLAlqt5B9fkoyEatrs,17287
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=kYNtIKnLx4SIj893umIE6u-q3HH9hYH9O4zdZCSOe7U,2100
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=sE6Fe8vgGuEWYTox7_ATC6zwcUZ7t2EqkemuP5tWp00,1001
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=HSDBPXQ3ldX0pixDbNLna5LSUQ_TfFZ4JWKRm6OpWcU,793
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=LzSVjVXjRR5mbZ94tVq-wdX3_B5yzv0h7pPEFZVOL1s,1874
49
- camomilla/serializers/fields/related.py,sha256=Lofgk582KAb0EIK20jtds538O_y-yRHo644ND_VlXrM,4747
50
- camomilla/serializers/mixins/__init__.py,sha256=prhA91hMrLezm-mIYc3yHJXefM346okZQ8OS8IxxqhI,7114
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/optimize.py,sha256=YKbhSbTaWNbTFIRTRWdilD3w8b_LjMLOmE6yk2lrbo4,2745
53
- camomilla/storages/overwrite.py,sha256=CO_zSZxr321eohYFuzPjDMK7WMFnvvwlIigIPabU_eE,343
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=KBgXv23dCWW0XM16R5fT0_g-w_qudVfcF-CYSdwl83s,338
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=0q-mhm8VNgYidC6IDdFUN4eArRIb2Wf1Wf1vymdu_Fg,30
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=o2W6oQNiB__jHw5rbGaitEENxuRk1_mRoLCKyb30qPk,3352
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=S7KGhVzHM7f0j8XD7cbBY5__66zvKYkphRD9cfl0O1E,222
93
- camomilla/views/mixins/__init__.py,sha256=GeRoznX3wTm6XydOltAIbx_WU7hVS70R-7jyAck0UpE,2438
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=vUkfhqhTabeDdoiMwjeVpO6gl534EI0JMrr4TlZ8MRA,5956
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=DTXtvNNRwvZvxGKi6LyCzhpyU1OzPWGSbBUfRL3-Nck,2101
98
- tests/test_camomilla_filters.py,sha256=_W9CcwsEyewMBvnNYKk4CVqrkXxRYejle3mjiBS7hqk,1848
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/test_utils.py,sha256=0tdEDBaBkyjAXpbqP0SpFcfgO56MV2TWZmk9xpjR7J0,3613
101
- django_camomilla_cms-6.0.0b15.dist-info/LICENSE,sha256=kVS7zDrNkav2hLLXbOJwVdonY2ToApTK3khyJagGQoQ,1063
102
- django_camomilla_cms-6.0.0b15.dist-info/METADATA,sha256=_k6mnVPePkm5V8-NNr4LKbft-k1_vte_CM6lEgTB32U,2390
103
- django_camomilla_cms-6.0.0b15.dist-info/WHEEL,sha256=TJ49d73sNs10F0aze1W_bTW2P_X7-F4YXOlBqoqA-jY,109
104
- django_camomilla_cms-6.0.0b15.dist-info/top_level.txt,sha256=G9VIGBmMMqC7JEckoTgXKmC6T2BR75QRkqRnngw1_lo,16
105
- django_camomilla_cms-6.0.0b15.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -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
@@ -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, identifier="home")
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, identifier="home")
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
- request = RequestFactory().get("/path", HTTP_HOST="localhost:8000")
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, identifier="home")
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
@@ -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
+