django-camomilla-cms 6.0.0b15__py2.py3-none-any.whl → 6.0.0b17__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 (72) hide show
  1. camomilla/__init__.py +1 -1
  2. camomilla/contrib/modeltranslation/hvad_migration.py +9 -9
  3. camomilla/dynamic_pages_urls.py +6 -2
  4. camomilla/managers/pages.py +93 -8
  5. camomilla/model_api.py +14 -7
  6. camomilla/models/media.py +1 -1
  7. camomilla/models/menu.py +10 -4
  8. camomilla/models/page.py +189 -127
  9. camomilla/openapi/schema.py +17 -8
  10. camomilla/redirects.py +10 -0
  11. camomilla/serializers/base/__init__.py +6 -4
  12. camomilla/serializers/fields/__init__.py +5 -17
  13. camomilla/serializers/fields/related.py +10 -4
  14. camomilla/serializers/mixins/__init__.py +23 -195
  15. camomilla/serializers/mixins/fields.py +20 -0
  16. camomilla/serializers/mixins/filter_fields.py +57 -0
  17. camomilla/serializers/mixins/json.py +34 -0
  18. camomilla/serializers/mixins/language.py +32 -0
  19. camomilla/serializers/mixins/nesting.py +35 -0
  20. camomilla/serializers/mixins/optimize.py +91 -0
  21. camomilla/serializers/mixins/ordering.py +34 -0
  22. camomilla/serializers/mixins/page.py +58 -0
  23. camomilla/{contrib/rest_framework/serializer.py → serializers/mixins/translation.py} +16 -56
  24. camomilla/serializers/utils.py +5 -3
  25. camomilla/serializers/validators.py +6 -2
  26. camomilla/settings.py +10 -2
  27. camomilla/storages/default.py +12 -0
  28. camomilla/storages/optimize.py +2 -2
  29. camomilla/storages/overwrite.py +2 -2
  30. camomilla/templates/defaults/parts/menu.html +1 -1
  31. camomilla/templatetags/menus.py +3 -0
  32. camomilla/theme/__init__.py +1 -1
  33. camomilla/theme/{admin.py → admin/__init__.py} +22 -20
  34. camomilla/theme/admin/pages.py +46 -0
  35. camomilla/theme/admin/translations.py +13 -0
  36. camomilla/theme/apps.py +1 -5
  37. camomilla/translation.py +7 -1
  38. camomilla/urls.py +2 -5
  39. camomilla/utils/query_parser.py +167 -0
  40. camomilla/utils/translation.py +47 -5
  41. camomilla/views/base/__init__.py +35 -5
  42. camomilla/views/medias.py +1 -1
  43. camomilla/views/menus.py +0 -2
  44. camomilla/views/mixins/__init__.py +17 -69
  45. camomilla/views/mixins/bulk_actions.py +22 -0
  46. camomilla/views/mixins/language.py +33 -0
  47. camomilla/views/mixins/optimize.py +18 -0
  48. camomilla/views/mixins/pagination.py +12 -18
  49. camomilla/views/mixins/permissions.py +6 -0
  50. camomilla/views/pages.py +12 -2
  51. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/METADATA +23 -16
  52. django_camomilla_cms-6.0.0b17.dist-info/RECORD +132 -0
  53. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/WHEEL +1 -1
  54. tests/fixtures/__init__.py +17 -0
  55. tests/test_api.py +2 -11
  56. tests/test_camomilla_filters.py +7 -13
  57. tests/test_media.py +113 -0
  58. tests/test_menu.py +97 -0
  59. tests/test_model_api.py +68 -0
  60. tests/test_model_api_permissions.py +39 -0
  61. tests/test_model_api_register.py +393 -0
  62. tests/test_pages.py +343 -0
  63. tests/test_query_parser.py +58 -0
  64. tests/test_templates_context.py +111 -0
  65. tests/test_utils.py +64 -64
  66. tests/utils/api.py +28 -0
  67. tests/utils/media.py +9 -0
  68. camomilla/serializers/fields/json.py +0 -49
  69. django_camomilla_cms-6.0.0b15.dist-info/RECORD +0 -105
  70. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info/licenses}/LICENSE +0 -0
  71. {django_camomilla_cms-6.0.0b15.dist-info → django_camomilla_cms-6.0.0b17.dist-info}/top_level.txt +0 -0
  72. {camomilla/contrib/rest_framework → tests/utils}/__init__.py +0 -0
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,113 @@
1
+ import pytest
2
+ import json
3
+ from django.test import TestCase
4
+ from camomilla.models import Media
5
+ from .utils.api import login_superuser
6
+ from .utils.media import load_asset_and_remove_media
7
+ from rest_framework.test import APIClient
8
+
9
+ client = APIClient()
10
+
11
+ class MediaTestCase(TestCase):
12
+ def setUp(self):
13
+ self.client = APIClient()
14
+ token = login_superuser()
15
+ self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
16
+
17
+ @pytest.mark.django_db
18
+ def test_media_api_crud(self):
19
+ # Create media 1
20
+ asset = load_asset_and_remove_media("10595073.png")
21
+ response = self.client.post(
22
+ "/api/camomilla/media/",
23
+ {
24
+ "file": asset,
25
+ "data": json.dumps({"translations": {"en": {"alt_text": "Test 1", "title": "Test 1", "description": "Test 1"}}}),
26
+ },
27
+ format="multipart",
28
+ )
29
+ assert response.status_code == 201
30
+ assert Media.objects.count() == 1
31
+ media = Media.objects.first()
32
+ assert media.alt_text == "Test 1"
33
+ assert media.title == "Test 1"
34
+ assert media.description == "Test 1"
35
+ assert media.file.name == "10595073.png"
36
+
37
+ # Create media 2
38
+ asset = load_asset_and_remove_media("37059501.png")
39
+ response = self.client.post(
40
+ "/api/camomilla/media/",
41
+ {
42
+ "file": asset,
43
+ "data": json.dumps({"translations": {"en": {"alt_text": "Test 2", "title": "Test 2", "description": "Test 2"}}}),
44
+ },
45
+ format="multipart",
46
+ )
47
+ assert response.status_code == 201
48
+ assert Media.objects.count() == 2
49
+ media = Media.objects.first() # Ordering in model is descending -pk
50
+ assert media.alt_text == "Test 2"
51
+ assert media.title == "Test 2"
52
+ assert media.description == "Test 2"
53
+ assert media.file.name == "37059501.png"
54
+
55
+ # Read media
56
+ response = self.client.get("/api/camomilla/media/2/")
57
+ assert response.status_code == 200
58
+ assert response.json()['id'] == 2
59
+ assert response.json()['title'] == "Test 2"
60
+ assert response.json()['file'] == "http://testserver/media/37059501.png"
61
+
62
+ # Read medias
63
+ response = self.client.get("/api/camomilla/media/")
64
+ assert response.status_code == 200
65
+ assert response.json()[0]['id'] == 2 # Ordering in model is descending -pk
66
+ assert response.json()[0]['title'] == "Test 2"
67
+ assert response.json()[1]['id'] == 1
68
+ assert response.json()[1]['title'] == "Test 1"
69
+
70
+ # Delete media
71
+ response = self.client.delete("/api/camomilla/media/2/")
72
+ assert response.status_code == 204
73
+ assert len(Media.objects.all()) == 1
74
+ media = Media.objects.last()
75
+ assert media.id == 1
76
+ assert media.title == "Test 1"
77
+
78
+
79
+ @pytest.mark.django_db
80
+ def test_media_compression(self):
81
+ asset = load_asset_and_remove_media("Sample-jpg-image-10mb.jpg")
82
+ asset_size = asset.size
83
+ response = self.client.post(
84
+ "/api/camomilla/media/",
85
+ {
86
+ "file": asset,
87
+ "data": json.dumps({"translations": {"en": {"alt_text": "Test", "title": "Test", "description": "Test"}}}),
88
+ },
89
+ format="multipart",
90
+ )
91
+ assert response.status_code == 201
92
+ assert Media.objects.count() == 1
93
+ media = Media.objects.first()
94
+ assert media.file.size < asset_size
95
+ assert media.file.size < 1000000 # 1MB
96
+
97
+
98
+ @pytest.mark.django_db
99
+ def test_inflating_prevent(self):
100
+ asset = load_asset_and_remove_media("optimized.jpg")
101
+ asset_size = asset.size
102
+ response = self.client.post(
103
+ "/api/camomilla/media/",
104
+ {
105
+ "file": asset,
106
+ "data": json.dumps({"translations": {"en": {"alt_text": "Test", "title": "Test", "description": "Test"}}}),
107
+ },
108
+ format="multipart",
109
+ )
110
+ assert response.status_code == 201
111
+ assert Media.objects.count() == 1
112
+ media = Media.objects.first()
113
+ assert media.file.size < asset_size
tests/test_menu.py ADDED
@@ -0,0 +1,97 @@
1
+ import pytest
2
+ import html
3
+ from django.test import TestCase
4
+ from rest_framework.test import APIClient
5
+ from .utils.api import login_superuser
6
+ from django.template import Template, Context
7
+ from camomilla.models import Menu
8
+
9
+
10
+ class MenuTestCase(TestCase):
11
+ def setUp(self):
12
+ self.client = APIClient()
13
+ token = login_superuser()
14
+ self.client.credentials(HTTP_AUTHORIZATION='Token ' + token)
15
+
16
+ def renderTemplate(self, template, context = None):
17
+ return Template('{% load menus %}' + template).render(Context(context))
18
+
19
+ @pytest.mark.django_db
20
+ def test_template_render_menu(self):
21
+ assert self.renderTemplate('{% render_menu "key_1" %}') == "\n\n"
22
+ assert len(Menu.objects.all()) == 1
23
+ menu = Menu.objects.first()
24
+ assert menu.id == 1
25
+ assert menu.key == "key_1"
26
+
27
+ assert self.renderTemplate('{% render_menu "key_2" %}') == "\n\n"
28
+ assert len(Menu.objects.all()) == 2
29
+ menu = Menu.objects.last()
30
+ assert menu.id == 2
31
+ assert menu.key == "key_2"
32
+
33
+ @pytest.mark.django_db
34
+ def test_template_get_menus(self):
35
+ self.renderTemplate('{% render_menu "key_3" %}')
36
+ self.renderTemplate('{% render_menu "key_4" %}')
37
+
38
+ rendered = html.unescape(self.renderTemplate('{% get_menus %}'))
39
+ assert rendered == "{'key_3': <Menu: key_3>, 'key_4': <Menu: key_4>}"
40
+
41
+ rendered = html.unescape(self.renderTemplate('{% get_menus "arg" %}'))
42
+ assert rendered == "{}"
43
+
44
+ rendered = html.unescape(self.renderTemplate('{% get_menus "key_3" %}'))
45
+ assert rendered == "{'key_3': <Menu: key_3>}"
46
+
47
+ menus = 'test "menus" in context'
48
+ rendered = html.unescape(self.renderTemplate('{% get_menus %}', {"menus": menus}))
49
+ assert rendered == menus
50
+
51
+ @pytest.mark.django_db
52
+ def test_template_get_menu_node_url(self):
53
+ self.renderTemplate('{% render_menu "key_5" %}')
54
+
55
+ menu = Menu.objects.first()
56
+ menu.nodes = [{"title": "key_5_node_title", "link":{"static": "key_5_url_static"}}]
57
+ menu.save()
58
+
59
+ rendered = html.unescape(self.renderTemplate('{% render_menu "key_5" %}'))
60
+ assert {'<a href="key_5_url_static">key_5_node_title</a>' in rendered}
61
+
62
+ @pytest.mark.django_db
63
+ def test_menu_custom_template(self):
64
+ self.renderTemplate('{% render_menu "key_6_custom" %}')
65
+
66
+ menu = Menu.objects.first()
67
+ menu.nodes = [{"title": "key_6_node_title", "link":{"static": "key_6_url_static"}}]
68
+ menu.save()
69
+
70
+ rendered = html.unescape(self.renderTemplate('{% render_menu "key_6_custom" "website/menu_custom.html" %}'))
71
+ assert {'This is custom menu: key_6_node_title' in rendered}
72
+
73
+ @pytest.mark.django_db
74
+ def test_menu_in_page_template(self):
75
+ self.renderTemplate('{% render_menu "key_7" %}')
76
+
77
+ response = self.client.post(
78
+ "/api/camomilla/pages/",
79
+ {
80
+ "translations": {
81
+ "en": {
82
+ "title": "title_page_menu_1",
83
+ "permalink": "permalink_page_menu_en_1",
84
+ "autopermalink": False
85
+ }
86
+ }
87
+ },
88
+ format='json'
89
+ )
90
+ assert response.status_code == 201
91
+
92
+ menu = Menu.objects.first()
93
+ menu.nodes = [{"title": "key_7_node_title", "link":{"page": { "id": 1, "model":"camomilla.page" }}}]
94
+ menu.save()
95
+
96
+ rendered = html.unescape(self.renderTemplate('{% render_menu "key_7" %}'))
97
+ assert {'href="permalink_page_menu_en_1"' in rendered}
@@ -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.'}]