django-camomilla-cms 5.8.5__py2.py3-none-any.whl → 6.0.0__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 (143) hide show
  1. camomilla/__init__.py +8 -2
  2. camomilla/apps.py +9 -1
  3. camomilla/context_processors.py +6 -0
  4. camomilla/contrib/modeltranslation/__init__.py +0 -0
  5. camomilla/contrib/modeltranslation/hvad_migration.py +126 -0
  6. camomilla/dynamic_pages_urls.py +33 -0
  7. camomilla/fields/__init__.py +13 -0
  8. camomilla/{fields.py → fields/json.py} +15 -18
  9. camomilla/management/commands/regenerate_thumbnails.py +0 -1
  10. camomilla/managers/__init__.py +3 -0
  11. camomilla/managers/pages.py +116 -0
  12. camomilla/model_api.py +86 -0
  13. camomilla/models/__init__.py +5 -6
  14. camomilla/models/article.py +26 -44
  15. camomilla/models/content.py +8 -15
  16. camomilla/models/media.py +70 -97
  17. camomilla/models/menu.py +106 -0
  18. camomilla/models/mixins/__init__.py +10 -48
  19. camomilla/models/page.py +521 -20
  20. camomilla/openapi/__init__.py +0 -0
  21. camomilla/openapi/schema.py +67 -0
  22. camomilla/parsers.py +0 -1
  23. camomilla/redirects.py +10 -0
  24. camomilla/serializers/__init__.py +2 -0
  25. camomilla/serializers/article.py +5 -10
  26. camomilla/serializers/base/__init__.py +21 -17
  27. camomilla/serializers/content_type.py +17 -0
  28. camomilla/serializers/fields/__init__.py +6 -20
  29. camomilla/serializers/fields/file.py +5 -0
  30. camomilla/serializers/fields/related.py +24 -4
  31. camomilla/serializers/media.py +6 -8
  32. camomilla/serializers/menu.py +17 -0
  33. camomilla/serializers/mixins/__init__.py +23 -187
  34. camomilla/serializers/mixins/fields.py +20 -0
  35. camomilla/serializers/mixins/filter_fields.py +57 -0
  36. camomilla/serializers/mixins/json.py +34 -0
  37. camomilla/serializers/mixins/language.py +32 -0
  38. camomilla/serializers/mixins/nesting.py +35 -0
  39. camomilla/serializers/mixins/optimize.py +91 -0
  40. camomilla/serializers/mixins/ordering.py +34 -0
  41. camomilla/serializers/mixins/page.py +58 -0
  42. camomilla/serializers/mixins/translation.py +103 -0
  43. camomilla/serializers/page.py +53 -4
  44. camomilla/serializers/user.py +5 -4
  45. camomilla/serializers/utils.py +38 -0
  46. camomilla/serializers/validators.py +51 -0
  47. camomilla/settings.py +118 -0
  48. camomilla/sitemap.py +30 -0
  49. camomilla/storages/__init__.py +4 -0
  50. camomilla/storages/default.py +12 -0
  51. camomilla/storages/optimize.py +71 -0
  52. camomilla/{storages.py → storages/overwrite.py} +2 -2
  53. camomilla/templates/admin/camomilla/page/change_form.html +10 -0
  54. camomilla/templates/defaults/articles/default.html +7 -0
  55. camomilla/templates/defaults/base.html +170 -0
  56. camomilla/templates/defaults/pages/default.html +3 -0
  57. camomilla/templates/defaults/parts/langswitch.html +83 -0
  58. camomilla/templates/defaults/parts/menu.html +15 -0
  59. camomilla/templates_context/__init__.py +0 -0
  60. camomilla/templates_context/autodiscover.py +51 -0
  61. camomilla/templates_context/rendering.py +89 -0
  62. camomilla/templatetags/camomilla_filters.py +6 -5
  63. camomilla/templatetags/menus.py +37 -0
  64. camomilla/templatetags/model_extras.py +77 -0
  65. camomilla/theme/__init__.py +1 -1
  66. camomilla/theme/admin/__init__.py +99 -0
  67. camomilla/theme/admin/pages.py +46 -0
  68. camomilla/theme/admin/translations.py +13 -0
  69. camomilla/theme/apps.py +38 -0
  70. camomilla/theme/static/admin/css/responsive.css +5 -1021
  71. camomilla/theme/static/admin/img/favicon.ico +0 -0
  72. camomilla/theme/static/admin/img/logo.svg +31 -0
  73. camomilla/theme/templates/admin/base.html +7 -0
  74. camomilla/theme/templates/rosetta/base.html +196 -0
  75. camomilla/translation.py +61 -0
  76. camomilla/urls.py +38 -17
  77. camomilla/utils/__init__.py +4 -0
  78. camomilla/utils/getters.py +27 -0
  79. camomilla/utils/normalization.py +7 -0
  80. camomilla/utils/query_parser.py +167 -0
  81. camomilla/{utils.py → utils/seo.py} +13 -15
  82. camomilla/utils/setters.py +37 -0
  83. camomilla/utils/templates.py +32 -0
  84. camomilla/utils/translation.py +114 -0
  85. camomilla/views/__init__.py +1 -1
  86. camomilla/views/articles.py +5 -7
  87. camomilla/views/base/__init__.py +35 -5
  88. camomilla/views/contents.py +6 -11
  89. camomilla/views/decorators.py +26 -0
  90. camomilla/views/medias.py +24 -19
  91. camomilla/views/menus.py +81 -0
  92. camomilla/views/mixins/__init__.py +17 -73
  93. camomilla/views/mixins/bulk_actions.py +22 -0
  94. camomilla/views/mixins/language.py +33 -0
  95. camomilla/views/mixins/optimize.py +18 -0
  96. camomilla/views/mixins/ordering.py +2 -2
  97. camomilla/views/mixins/pagination.py +12 -18
  98. camomilla/views/mixins/permissions.py +6 -0
  99. camomilla/views/pages.py +28 -6
  100. camomilla/views/tags.py +5 -6
  101. camomilla/views/users.py +7 -12
  102. django_camomilla_cms-6.0.0.dist-info/METADATA +123 -0
  103. django_camomilla_cms-6.0.0.dist-info/RECORD +133 -0
  104. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info}/WHEEL +1 -1
  105. tests/fixtures/__init__.py +14 -0
  106. tests/test_api.py +22 -39
  107. tests/test_camomilla_filters.py +11 -13
  108. tests/test_media.py +152 -0
  109. tests/test_menu.py +112 -0
  110. tests/test_model_api.py +113 -0
  111. tests/test_model_api_permissions.py +44 -0
  112. tests/test_model_api_register.py +355 -0
  113. tests/test_pages.py +351 -0
  114. tests/test_query_parser.py +58 -0
  115. tests/test_templates_context.py +149 -0
  116. tests/test_utils.py +64 -64
  117. tests/utils/__init__.py +0 -0
  118. tests/utils/api.py +28 -0
  119. tests/utils/media.py +10 -0
  120. camomilla/admin.py +0 -98
  121. camomilla/migrations/0001_initial.py +0 -577
  122. camomilla/migrations/0002_auto_20200214_1127.py +0 -33
  123. camomilla/migrations/0003_auto_20210130_1610.py +0 -30
  124. camomilla/migrations/0004_auto_20210511_0937.py +0 -25
  125. camomilla/migrations/0005_media_image_props.py +0 -19
  126. camomilla/migrations/0006_auto_20220103_1845.py +0 -35
  127. camomilla/migrations/0007_auto_20220211_1622.py +0 -18
  128. camomilla/migrations/0008_auto_20220309_1616.py +0 -60
  129. camomilla/migrations/0009_article__hvad_query_category__hvad_query_and_more.py +0 -165
  130. camomilla/migrations/0010_auto_20220802_1406.py +0 -83
  131. camomilla/migrations/0011_auto_20220902_1000.py +0 -15
  132. camomilla/models/category.py +0 -25
  133. camomilla/models/tag.py +0 -19
  134. camomilla/theme/static/admin/img/logo.png +0 -0
  135. camomilla/theme/templates/admin/base_site.html +0 -18
  136. camomilla/views/categories.py +0 -13
  137. django_camomilla_cms-5.8.5.dist-info/METADATA +0 -62
  138. django_camomilla_cms-5.8.5.dist-info/RECORD +0 -76
  139. tests/urls.py +0 -21
  140. /camomilla/{migrations → contrib}/__init__.py +0 -0
  141. /camomilla/templates/{camomilla → defaults}/widgets/media_select_multiple.html +0 -0
  142. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info/licenses}/LICENSE +0 -0
  143. {django_camomilla_cms-5.8.5.dist-info → django_camomilla_cms-6.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="camomilla_logo" data-name="Camomilla Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 50.88">
3
+ <defs>
4
+ <style>
5
+ .logo-part-1 {
6
+ fill: #6ab946;
7
+ }
8
+ .logo-part-2 {
9
+ fill: #fcb92c;
10
+ fill-rule: evenodd;
11
+ }
12
+ .letter {
13
+ fill: #ebebeb;
14
+ }
15
+ </style>
16
+ </defs>
17
+ <g id="camomilla_logo-group" data-name="Camomilla Logo group">
18
+ <path class="logo-part-2" d="M110.84,39.52l4.42-2.06s-2.14-2.33-2.14-5.57c0-3.04,2.09-5.33,6.04-5.33,4.38,0,6.23,2.76,6.23,5.8,0,2.66-1.95,4.9-1.95,4.9l4.85,2.24-8.73,4.14-8.73-4.14Z"/>
19
+ <path class="logo-part-1" d="M118.02,0c-11.08,0-20.06,8.46-20.06,22.33,0,10.64,5.06,15.17,5.06,15.17l8.63-4.01s-3.92-4.27-3.92-10.2c0-5.58,3.84-9.77,11.08-9.77,8.02,0,11.42,5.06,11.42,10.64,0,4.88-3.58,8.98-3.58,8.98l9.42,4.36s3.92-5.76,3.92-14.91c0-13.34-10.29-22.59-21.98-22.59Z"/>
20
+ <path class="letter" d="M38.03,19.47l1.12-4.93c-2.12,.48-2.2-.56-2.2-2.6V7.53c0-5.17-2.12-7.53-7.85-7.53-6.09,0-8.98,2.64-8.57,5.89l6.21,1.16c-.2-1.8,.48-2.48,1.92-2.48,1.52,0,2.04,.76,2.04,2.88v.56c-6.53,.04-10.82,2-10.82,6.33,0,3.57,2.93,5.49,6.33,5.49,2.92,0,4.37-1.44,5.33-2.8,.52,1.4,2.2,3.13,6.49,2.44Zm-7.33-6.45c-.16,1.72-1.6,2.36-2.8,2.36-1.04,0-1.72-.48-1.72-1.52,0-1.84,2.08-2.28,4.53-2.32v1.48Z"/>
21
+ <path class="letter" d="M83.68,19.83c7.37,0,9.66-4.49,9.66-9.98,0-5.17-2.04-9.86-9.78-9.86s-9.74,4.53-9.74,9.78,2.28,10.06,9.86,10.06Zm-.08-4.65c-2.76,0-3.29-2.64-3.29-5.33s.52-5.21,3.25-5.21,3.29,2.56,3.29,5.29-.52,5.25-3.25,5.25Z"/>
22
+ <path class="letter" d="M11.9,12.62c0,1.6-.92,2.56-2.44,2.56-2.16,0-2.97-1.64-2.97-4.97,0-2.68,.28-5.57,2.97-5.57,1.12,0,2.36,.76,2,3.29l6.13-1.24c.44-4.25-2.92-6.69-8.05-6.69C4.09,0,0,2.44,0,9.86c0,6.33,2.88,9.98,9.38,9.98,5.17,0,7.25-2.48,8.33-6.05l-5.81-1.16Z"/>
23
+ <path class="letter" d="M92.21,41.78l1.12-4.93c-2.12,.48-2.2-.56-2.2-2.6v-4.41c0-5.17-2.12-7.53-7.85-7.53-6.09,0-8.98,2.64-8.57,5.89l6.21,1.16c-.2-1.8,.48-2.48,1.92-2.48,1.52,0,2.04,.76,2.04,2.89v.56c-6.53,.04-10.82,2-10.82,6.33,0,3.57,2.93,5.49,6.33,5.49,2.93,0,4.37-1.44,5.33-2.8,.52,1.4,2.2,3.12,6.49,2.44Zm-7.33-6.45c-.16,1.72-1.6,2.36-2.8,2.36-1.04,0-1.72-.48-1.72-1.52,0-1.84,2.08-2.28,4.53-2.32v1.48Z"/>
24
+ <path class="letter" d="M41.35,41.58h6.25V22.87h-6.25v18.71Z"/>
25
+ <path class="letter" d="M44.48,50.88c-1.92,0-3.53-1.6-3.53-3.53s1.6-3.49,3.53-3.49,3.53,1.56,3.53,3.49-1.56,3.53-3.53,3.53Z"/>
26
+ <path class="letter" d="M30.35,41.58h6.25v-12.3c0-5.85-3.21-6.97-5.97-6.97-4.33,0-5.85,2.64-6.29,3.65-.8-2.72-3.09-3.65-5.49-3.65-4.05,0-5.61,2.4-6.13,3.53v-2.97H6.91v18.71h6.25v-9.34c.32-1.24,1.44-4.65,3.69-4.65,1.28,0,1.8,1.12,1.8,3.29v10.7h6.25v-9.46c.36-1.4,1.48-4.53,3.65-4.53,1.28,0,1.8,1.08,1.8,3.29v10.7Z"/>
27
+ <path class="letter" d="M64.75,15.68h6.25V6.97c0-5.85-3.21-6.97-5.97-6.97-4.33,0-5.85,2.64-6.29,3.65-.8-2.72-3.09-3.65-5.49-3.65-4.05,0-5.61,2.4-6.13,3.53V.56h-5.81V19.27h6.25V9.94c.32-1.24,1.44-4.65,3.69-4.65,1.28,0,1.8,1.12,1.8,3.29v7.1h6.25v-5.86c.36-1.4,1.48-4.53,3.65-4.53,1.28,0,1.8,1.08,1.8,3.29v7.1Z"/>
28
+ <path class="letter" d="M53.07,19.27v22.31h6.25V19.27h-6.25Z"/>
29
+ <path class="letter" d="M64.75,19.27v22.31h6.25V19.27h-6.25Z"/>
30
+ </g>
31
+ </svg>
@@ -0,0 +1,7 @@
1
+ {% extends "admin/base.html" %}
2
+ {% load static %}
3
+
4
+ {% block extrahead %}
5
+ <link rel="icon" href="{% static "admin/img/favicon.ico" %}" sizes="48x48" />
6
+ <link rel="stylesheet" type="text/css" href="{% static "admin/css/responsive.css" %}" />
7
+ {% endblock %}
@@ -0,0 +1,196 @@
1
+ <!DOCTYPE html>
2
+ {% load i18n static admin_interface_tags %}
3
+ {% get_admin_interface_theme as theme %}
4
+ {% get_admin_interface_nocache as version_md5_cache %}
5
+ {% get_current_language as current_lang %}
6
+
7
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
8
+ <head>
9
+ <title>{% block pagetitle %}Rosetta{% endblock %}</title>
10
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
11
+ <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0">
12
+ <link rel="stylesheet" type="text/css" href="{% static 'admin/css/responsive.css' %}?nocache={{ version_md5_cache }}">
13
+ <link rel="stylesheet" type="text/css" href="{% static 'admin/css/responsive_rtl.css' %}?nocache={{ version_md5_cache }}">
14
+ {% include "admin_interface/favicon.html" %}
15
+ {% include "admin_interface/foldable-apps.html" %}
16
+ {% include "admin_interface/related-modal.html" %}
17
+ {% include "admin_interface/collapsible-inlines.html" %}
18
+ <link rel="stylesheet" href="{% static "admin/css/base.css" %}" type="text/css"/>
19
+ <link rel="stylesheet" href="{% static "admin/css/forms.css" %}" type="text/css"/>
20
+ <link rel="stylesheet" href="{% static "admin/css/changelists.css" %}" type="text/css"/>
21
+ <style type="text/css">
22
+ :root .admin-interface {
23
+ --admin-interface-title-color: {{ theme.title_color }};
24
+ --admin-interface-logo-color: {{ theme.logo_color }};
25
+ --admin-interface-logo-default-background-image: url("data:image/svg+xml;utf8,<?xml version='1.0' encoding='utf-8'?><svg width='104px' height='36px' viewBox='0 0 104 36' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:sketch='http://www.bohemiancoding.com/sketch/ns'><g id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' sketch:type='MSPage'><g id='logo-django' sketch:type='MSArtboardGroup' transform='translate(-8.000000, -5.000000)' fill='{{ theme.logo_color|urlencode }}'><path d='M20.2602817,5 L25.9509859,5 L25.9509859,31.0824248 C23.0360563,31.6338042 20.8901408,31.8507285 18.5684507,31.8507285 C11.6180282,31.8434735 8,28.7383366 8,22.7747325 C8,17.0287781 11.8377465,13.2997118 17.7847887,13.2997118 C18.7076056,13.2997118 19.4107042,13.3722617 20.2602817,13.5899115 L20.2602817,5 L20.2602817,5 Z M20.2602817,18.1242821 C19.5938028,17.9066323 19.044507,17.8340823 18.3414085,17.8340823 C15.4630986,17.8340823 13.8005634,19.5897906 13.8005634,22.6666331 C13.8005634,25.6622196 15.3898592,27.316358 18.3047887,27.316358 C18.9346479,27.316358 19.4473239,27.2808085 20.2602817,27.1719836 L20.2602817,18.1242821 L20.2602817,18.1242821 Z M34.9960563,13.6987364 L34.9960563,26.7577235 C34.9960563,31.2550936 34.6591549,33.417807 33.6704225,35.2823401 C32.7476056,37.0750489 31.531831,38.2053768 29.0197183,39.453961 L23.7391549,36.9654985 C26.2512676,35.7981701 27.4670423,34.7665101 28.2433803,33.1921767 C29.056338,31.5822938 29.3126761,29.7177606 29.3126761,24.8133855 L29.3126761,13.6987364 L34.9960563,13.6987364 Z M29.3126761,5.02901997 L35.0033803,5.02901997 L35.0033803,10.8112493 L29.3126761,10.8112493 L29.3126761,5.02901997 Z M38.4302535,14.9828702 C40.9430986,13.8148163 43.3453521,13.2997118 45.9673239,13.2997118 C48.8895775,13.2997118 50.8077183,14.0687411 51.6580282,15.5705246 C52.1340845,16.4121037 52.2878873,17.5076077 52.2878873,19.8509704 L52.2878873,31.2993491 C49.7398873,31.6620987 46.5239437,31.922553 44.1649014,31.922553 C39.3970141,31.922553 37.2584225,30.2756696 37.2584225,26.6198787 C37.2584225,22.6659076 40.1008451,20.8376494 47.079831,20.2565245 L47.079831,19.0159207 C47.079831,17.9929667 46.559831,17.6229621 45.124338,17.6229621 C43.0223662,17.6229621 40.6567324,18.2106165 38.4375775,19.3423954 L38.4302535,14.9828702 Z M47.336169,23.9420608 C43.571662,24.3048105 42.3485634,24.8931904 42.3485634,26.3579734 C42.3485634,27.4549284 43.051662,27.9693073 44.604338,27.9693073 C45.4539155,27.9693073 46.2302535,27.8967574 47.3354366,27.7153826 L47.3354366,23.9420608 L47.336169,23.9420608 Z M55.056338,14.5765906 C58.4180282,13.6987364 61.1857465,13.2997118 63.9908169,13.2997118 C66.9057465,13.2997118 69.0157746,13.9599162 70.2674366,15.2367949 C71.4458592,16.4411237 71.8208451,17.7615324 71.8208451,20.5764696 L71.8208451,31.6258237 L66.1294085,31.6258237 L66.1294085,20.8013744 C66.1294085,18.6393866 65.3896901,17.8340823 63.3616901,17.8340823 C62.5846197,17.8340823 61.8822535,17.9066323 60.7397183,18.2403619 L60.7397183,31.6265492 L55.056338,31.6265492 L55.056338,14.5765906 Z M74.0326761,34.7012152 C76.0240563,35.7241692 78.0169014,36.1964692 80.1261972,36.1964692 C83.8540845,36.1964692 85.4433803,34.6946857 85.4433803,31.1107193 L85.4433803,31.0018944 C84.3374648,31.5460188 83.223493,31.7716491 81.7513803,31.7716491 C76.764507,31.7716491 73.5932394,28.5141573 73.5932394,23.3558574 C73.5932394,16.9496987 78.2878873,13.3294573 86.5932394,13.3294573 C89.0321127,13.3294573 91.2878873,13.583382 94.0189859,14.1347615 L92.0708169,18.1975575 C90.5562254,17.9073578 91.9463099,18.1540275 90.804507,18.0452026 L90.804507,18.6328571 L90.8777465,21.0124947 L90.9136338,24.0886117 C90.9509859,24.8583664 90.9509859,25.6259447 90.988338,26.3956994 L90.988338,27.9330324 C90.988338,32.7648576 90.5774648,35.0291409 89.3616901,36.900929 C87.5892958,39.6425908 84.5212958,41 80.1620845,41 C77.943662,41 76.0240563,40.6727998 74.0326761,39.9030451 L74.0326761,34.7012152 L74.0326761,34.7012152 Z M85.3335211,17.8703573 L85.1504225,17.8703573 L84.7395493,17.8703573 C83.6336338,17.8340823 82.3380282,18.1242821 81.4510986,18.6756615 C80.0895775,19.4446908 79.3872113,20.8376494 79.3872113,22.8110074 C79.3872113,25.6252192 80.7934085,27.2365531 83.3055211,27.2365531 C84.0811268,27.2365531 84.7117183,27.0921787 85.4441127,26.8738034 L85.4441127,26.4667983 L85.4441127,24.9294653 C85.4441127,24.269261 85.4067606,23.5365067 85.4067606,22.7674775 L85.3708732,20.17019 L85.3335211,18.3056569 L85.3335211,17.8703573 Z M102.84507,13.2271619 C108.528451,13.2271619 112,16.7748534 112,22.5208077 C112,28.4118619 108.382704,32.1039278 102.617296,32.1039278 C96.9265915,32.1039278 93.4176901,28.5569618 93.4176901,22.8480079 C93.4272113,16.9199532 97.044507,13.2271619 102.84507,13.2271619 Z M102.727887,27.5623023 C104.910423,27.5623023 106.199437,25.7710445 106.199437,22.6586526 C106.199437,19.5825356 104.94631,17.7542774 102.765239,17.7542774 C100.509465,17.7542774 99.2189859,19.5462607 99.2189859,22.6586526 C99.2189859,25.7710445 100.516056,27.5623023 102.727887,27.5623023 L102.727887,27.5623023 Z M102.727887,27.5623023' id='Shape' sketch:type='MSShapeGroup'></path></g></g></svg>");
26
+ --admin-interface-env-color: {{ theme.env_color }};
27
+ --admin-interface-header-background-color: {{ theme.css_header_background_color }};
28
+ --admin-interface-header-text-color: {{ theme.css_header_text_color }};
29
+ --admin-interface-header-link-color: {{ theme.css_header_link_color }};
30
+ --admin-interface-header-link-hover-color: {{ theme.css_header_link_hover_color }};
31
+ --admin-interface-module-background-color: {{ theme.css_module_background_color }};
32
+ --admin-interface-module-background-selected-color: {{ theme.css_module_background_selected_color }};
33
+ --admin-interface-module-text-color: {{ theme.css_module_text_color }};
34
+ --admin-interface-module-link-color: {{ theme.css_module_link_color }};
35
+ --admin-interface-module-link-selected-color: {{ theme.css_module_link_selected_color }};
36
+ --admin-interface-module-link-hover-color: {{ theme.css_module_link_hover_color }};
37
+ --admin-interface-generic-link-color: {{ theme.css_generic_link_color }};
38
+ --admin-interface-generic-link-hover-color: {{ theme.css_generic_link_hover_color }};
39
+ --admin-interface-generic-link-active-color: {{ theme.css_generic_link_active_color }};
40
+ --admin-interface-save-button-background-color: {{ theme.css_save_button_background_color }};
41
+ --admin-interface-save-button-background-hover-color: {{ theme.css_save_button_background_hover_color }};
42
+ --admin-interface-save-button-text-color: {{ theme.css_save_button_text_color }};
43
+ --admin-interface-delete-button-background-color: {{ theme.css_delete_button_background_color }};
44
+ --admin-interface-delete-button-background-hover-color: {{ theme.css_delete_button_background_hover_color }};
45
+ --admin-interface-delete-button-text-color: {{ theme.css_delete_button_text_color }};
46
+ --admin-interface-related-modal-background-color: {{ theme.related_modal_background_color }};
47
+ --admin-interface-related-modal-background-opacity: {{ theme.related_modal_background_opacity }};
48
+
49
+ {% if theme.css_header_background_color != theme.css_module_background_color %}
50
+ --admin-interface-main-border-top: 10px solid var(--admin-interface-module-background-color);
51
+ {% else %}
52
+ --admin-interface-main-border-top: 0px;
53
+ {% endif %}
54
+
55
+ {% if theme.logo_max_width > 0 %}
56
+ --admin-interface-logo-max-width: min({{ theme.logo_max_width }}px, 100%);
57
+ {% else %}
58
+ --admin-interface-logo-max-width: 100%;
59
+ {% endif %}
60
+
61
+ {% if theme.logo_max_height > 0 %}
62
+ --admin-interface-logo-max-height: {{ theme.logo_max_height }}px;
63
+ {% else %}
64
+ --admin-interface-logo-max-height: unset;
65
+ {% endif %}
66
+
67
+ {% if theme.related_modal_rounded_corners %}
68
+ --admin-interface-related-modal-border-radius: 4px;
69
+ {% else %}
70
+ --admin-interface-related-modal-border-radius: 0px;
71
+ {% endif %}
72
+
73
+ {% if theme.css_module_rounded_corners %}
74
+ --admin-interface-module-border-radius: 4px;
75
+ --admin-interface-jsoneditor-border-radius: var(--admin-interface-module-border-radius);
76
+ --admin-interface-jsoneditor-overflow: hidden;
77
+ {% else %}
78
+ --admin-interface-module-border-radius: 0px;
79
+ --admin-interface-jsoneditor-border-radius: var(--admin-interface-module-border-radius);
80
+ --admin-interface-jsoneditor-overflow: unset;
81
+ {% endif %}
82
+
83
+ {% if not theme.related_modal_close_button_visible %}
84
+ --admin-interface-related-modal-close-button-display: none;
85
+ {% else %}
86
+ --admin-interface-related-modal-close-button-display: unset;
87
+ {% endif %}
88
+ }
89
+ </style>
90
+ <link rel="stylesheet" type="text/css"
91
+ href="{% static 'admin_interface/css/admin-interface.css' %}?v={{ version_md5_cache }}">
92
+ <link rel="stylesheet" type="text/css"
93
+ href="{% static 'admin_interface/css/admin-interface-fix.css' %}?v={{ version_md5_cache }}">
94
+ <link rel="stylesheet" type="text/css"
95
+ href="{% static 'admin_interface/css/form-controls.css' %}?v={{ version_md5_cache }}">
96
+ <link rel="stylesheet" type="text/css"
97
+ href="{% static 'admin_interface/css/language-chooser.css' %}?v={{ version_md5_cache }}">
98
+ <link rel="stylesheet" type="text/css"
99
+ href="{% static 'admin_interface/css/list-filter-dropdown.css' %}?v={{ version_md5_cache }}">
100
+ <link rel="stylesheet" type="text/css"
101
+ href="{% static 'admin_interface/css/rangefilter.css' %}?v={{ version_md5_cache }}">
102
+ {% if not theme.recent_actions_visible %}
103
+ <link rel="stylesheet" type="text/css"
104
+ href="{% static 'admin_interface/css/recent-actions.css' %}?v={{ version_md5_cache }}">
105
+ {% endif %}
106
+ <link rel="stylesheet" type="text/css"
107
+ href="{% static 'admin_interface/css/related-modal.css' %}?v={{ version_md5_cache }}">
108
+ <link rel="stylesheet" type="text/css"
109
+ href="{% static 'admin_interface/css/streamfield.css' %}?v={{ version_md5_cache }}">
110
+ <link rel="stylesheet" type="text/css"
111
+ href="{% static 'admin_interface/css/jquery.ui.tabs.css' %}?v={{ version_md5_cache }}">
112
+ <link rel="stylesheet" type="text/css"
113
+ href="{% static 'admin_interface/css/modeltranslation.css' %}?v={{ version_md5_cache }}">
114
+ <link rel="stylesheet" type="text/css"
115
+ href="{% static 'admin_interface/css/sorl-thumbnail.css' %}?v={{ version_md5_cache }}">
116
+ <link rel="stylesheet" type="text/css"
117
+ href="{% static 'admin_interface/css/ckeditor.css' %}?v={{ version_md5_cache }}">
118
+ <link rel="stylesheet" type="text/css"
119
+ href="{% static 'admin_interface/css/tinymce.css' %}?v={{ version_md5_cache }}">
120
+ <link rel="stylesheet" type="text/css"
121
+ href="{% static 'admin_interface/css/json-widget.css' %}?v={{ version_md5_cache }}">
122
+ <link rel="stylesheet" type="text/css"
123
+ href="{% static 'admin_interface/css/import-export.css' %}?v={{ version_md5_cache }}">
124
+ <link rel="stylesheet" type="text/css"
125
+ href="{% static 'admin_interface/css/rtl.css' %}?v={{ version_md5_cache }}">
126
+ <link rel="stylesheet" type="text/css"
127
+ href="{% static 'admin_interface/css/tabbed-changeform.css' %}?v={{ version_md5_cache }}">
128
+
129
+ {% if current_lang == 'fa' %}
130
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v27.2.2/dist/font-face.css">
131
+ {% endif %}
132
+ <style type="text/css" media="screen">
133
+ {% include 'rosetta/css/rosetta.css' %}
134
+ .breadcrumbs { display: flex; }
135
+ .rosetta-title {
136
+ font-size: 10px;
137
+ text-transform: uppercase;
138
+ color: var(--admin-interface-header-link-color) !important;
139
+ font-weight: 700;
140
+ };
141
+ </style>
142
+ {% block extra_styles %}{% endblock %}
143
+ <script src="{% static "admin/js/vendor/jquery/jquery.min.js" %}"></script>
144
+ <script type="text/javascript">
145
+ {% include 'rosetta/js/rosetta.js' %}
146
+ </script>
147
+ </head>
148
+ <body class="{% get_admin_interface_theme as theme %}
149
+ flat-theme admin-interface
150
+ {% if theme.name %} {{ theme.name|slugify }}-theme {% endif %}
151
+ {% if theme.foldable_apps %} foldable-apps {% endif %}
152
+ {% if theme.form_submit_sticky %} sticky-submit {% endif %}
153
+ {% if theme.form_pagination_sticky %} sticky-pagination {% endif %}
154
+ {% if theme.list_filter_highlight %} list-filter-highlight {% endif %}
155
+ {% if theme.list_filter_sticky %} list-filter-sticky {% endif %}
156
+ {% if theme.collapsible_stacked_inlines %} collapsible-stacked-inlines
157
+ {% if theme.collapsible_stacked_inlines_collapsed %} collapsible-stacked-inlines-collapsed {% endif %}
158
+ {% endif %}
159
+ {% if theme.collapsible_tabular_inlines %} collapsible-tabular-inlines
160
+ {% if theme.collapsible_tabular_inlines_collapsed %} collapsible-tabular-inlines-collapsed {% endif %}
161
+ {% endif %}">
162
+ <div id="container">
163
+ <div id="header">
164
+ {% block header %}
165
+ <div id="branding">
166
+ {% comment %} <h1 id="site-name"><a href="{% url 'rosetta-file-list' po_filter='project' %}">Rosetta</a> </h1> {% endcomment %}
167
+ <h1 id="site-name">
168
+ <a href="{% url 'admin:index' %}">
169
+ {% if theme.logo_visible %}
170
+ {% if theme.logo %}
171
+ <img class="logo" style="display:none;" src="{{ theme.logo.url }}" {% if theme.logo.width %}width="{{ theme.logo.width }}"{% endif %} {% if theme.logo.height %}height="{{ theme.logo.height }}"{% endif %}>
172
+ {% else %}
173
+ <img class="logo default" style="display:none;" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" width="104" height="36">
174
+ {% endif %}
175
+ {% endif %}
176
+ {% if theme.title_visible %}
177
+ <span>{% if theme.title %}{% trans theme.title %}{% else %}{{ site_header|default:_('Django administration') }}{% endif %} </span>
178
+ {% endif %}
179
+ </a><a class="rosetta-title" href="{% url 'rosetta-file-list' po_filter=po_filter %}">Rosetta panel</a>
180
+ </h1>
181
+ </div>
182
+ {% endblock %}
183
+ </div>
184
+ <div class="breadcrumbs">
185
+ <a href="{% url 'admin:index' %}">{% if theme.title %}{% trans theme.title %}{% else %}{{ site_header|default:_('Django administration') }}{% endif %}</a>&nbsp;&rsaquo;&nbsp;
186
+ {% block breadcumbs %}{% endblock %}</div>
187
+ <div id="content" class="flex">
188
+ {% block main %}{% endblock %}
189
+ </div>
190
+ <div id="footer" class="breadcumbs">
191
+ <a href="https://github.com/mbi/django-rosetta">Rosetta</a> <span class="version">{{version}}</span>
192
+ </div>
193
+ </div>
194
+ </body>
195
+ </html>
196
+
@@ -0,0 +1,61 @@
1
+ from modeltranslation.translator import TranslationOptions, register
2
+
3
+ from camomilla.models import Article, Content, Media, Page, Tag, UrlNode, Menu
4
+
5
+
6
+ class SeoMixinTranslationOptions(TranslationOptions):
7
+ fields = (
8
+ "title",
9
+ "description",
10
+ "og_description",
11
+ "og_title",
12
+ "og_type",
13
+ "og_url",
14
+ "canonical",
15
+ )
16
+
17
+
18
+ class AbstractPageTranslationOptions(SeoMixinTranslationOptions):
19
+ fields = (
20
+ "breadcrumbs_title",
21
+ "autopermalink",
22
+ "status",
23
+ "indexable",
24
+ "template_data",
25
+ )
26
+
27
+
28
+ @register(Article)
29
+ class ArticleTranslationOptions(AbstractPageTranslationOptions):
30
+ fields = ("content",)
31
+
32
+
33
+ @register(Tag)
34
+ class TagTranslationOptions(TranslationOptions):
35
+ fields = ("name",)
36
+
37
+
38
+ @register(Content)
39
+ class ContentTranslationOptions(TranslationOptions):
40
+ fields = ("content",)
41
+ empty_values = {"title": None}
42
+
43
+
44
+ @register(Media)
45
+ class MediaTranslationOptions(TranslationOptions):
46
+ fields = ("title", "alt_text", "description")
47
+
48
+
49
+ @register(Page)
50
+ class PageTranslationOptions(AbstractPageTranslationOptions):
51
+ pass
52
+
53
+
54
+ @register(UrlNode)
55
+ class UrlNodeTranslationOptions(TranslationOptions):
56
+ fields = ("permalink",)
57
+
58
+
59
+ @register(Menu)
60
+ class MenuTranslationOptions(TranslationOptions):
61
+ fields = ("nodes",)
camomilla/urls.py CHANGED
@@ -1,40 +1,61 @@
1
- from .views import (
1
+ from django.urls import include, path
2
+ from rest_framework import routers
3
+ from importlib.util import find_spec
4
+ from rest_framework.schemas import get_schema_view
5
+
6
+ from camomilla.openapi.schema import SchemaGenerator
7
+ from camomilla.views import (
2
8
  ArticleViewSet,
3
- CamomillaObtainAuthToken,
4
- CamomillaAuthLogout,
5
9
  CamomillaAuthLogin,
6
- CategoryViewSet,
10
+ CamomillaAuthLogout,
11
+ CamomillaObtainAuthToken,
12
+ ContentViewSet,
13
+ LanguageViewSet,
7
14
  MediaFolderViewSet,
15
+ MediaViewSet,
16
+ PageViewSet,
17
+ PermissionViewSet,
18
+ TagViewSet,
19
+ UserViewSet,
20
+ MenuViewSet,
8
21
  )
9
- from .views import TagViewSet, ContentViewSet, MediaViewSet, PermissionViewSet
10
- from .views import PageViewSet, LanguageViewSet, UserViewSet
11
-
12
- from django.urls import include, path
13
- from django.shortcuts import redirect
14
-
15
- from rest_framework import routers
22
+ from camomilla.views.pages import fetch_page
23
+ from camomilla.redirects import url_patterns as old_redirects
16
24
 
17
25
  router = routers.DefaultRouter()
18
26
 
19
27
  router.register(r"tags", TagViewSet, "camomilla-tags")
20
- router.register(r"categories", CategoryViewSet, "camomilla-categories")
21
28
  router.register(r"articles", ArticleViewSet, "camomilla-articles")
22
29
  router.register(r"contents", ContentViewSet, "camomilla-content")
23
30
  router.register(r"media", MediaViewSet, "camomilla-media")
24
31
  router.register(r"media-folders", MediaFolderViewSet, "camomilla-media_folders")
25
32
  router.register(r"pages", PageViewSet, "camomilla-pages")
26
- router.register(r"sitemap", PageViewSet, "camomilla-sitemap")
27
33
  router.register(r"users", UserViewSet, "camomilla-users")
28
34
  router.register(r"permissions", PermissionViewSet, "camomilla-permissions")
29
-
35
+ router.register(r"menus", MenuViewSet, "camomilla-menus")
30
36
 
31
37
  urlpatterns = [
38
+ *old_redirects,
32
39
  path("", include(router.urls)),
33
- path(
34
- "profiles/me/", lambda _: redirect("../../users/current/"), name="profiles-me"
35
- ),
40
+ path("pages-router/", fetch_page),
41
+ path("pages-router/<path:permalink>", fetch_page),
36
42
  path("token-auth/", CamomillaObtainAuthToken.as_view(), name="api_token"),
37
43
  path("auth/login/", CamomillaAuthLogin.as_view(), name="login"),
38
44
  path("auth/logout/", CamomillaAuthLogout.as_view(), name="logout"),
39
45
  path("languages/", LanguageViewSet.as_view(), name="get_languages"),
46
+ path(
47
+ "openapi",
48
+ get_schema_view(
49
+ title="Camomilla",
50
+ description="API for all things …",
51
+ version="1.0.0",
52
+ generator_class=SchemaGenerator,
53
+ ),
54
+ name="openapi-schema",
55
+ ),
40
56
  ]
57
+
58
+ if find_spec("djsuperadmin.urls") is not None:
59
+ urlpatterns += [
60
+ path("djsuperadmin/", include("djsuperadmin.urls")),
61
+ ]
@@ -0,0 +1,4 @@
1
+ from .normalization import *
2
+ from .seo import *
3
+ from .translation import *
4
+ from .templates import *
@@ -0,0 +1,27 @@
1
+ from typing import Any, Callable, Union
2
+
3
+
4
+ def safe_getter(instance: Union[dict, object], key: str, default: Any = None) -> Any:
5
+ if isinstance(instance, dict):
6
+ return instance.get(key, default)
7
+ return getattr(instance, key, default)
8
+
9
+
10
+ def pointed_getter(
11
+ instance: Union[dict, object], pointed: str, default: Any = None
12
+ ) -> Any:
13
+ attrs = pointed.split(".", 1)
14
+ data = safe_getter(instance, attrs[0], default)
15
+ if len(attrs) == 2:
16
+ data = pointed_getter(data, attrs[1], default)
17
+ return data
18
+
19
+
20
+ def find_and_replace_dict(obj: dict, predicate: Callable):
21
+ result = {}
22
+ for k, v in obj.items():
23
+ v = predicate(key=k, value=v)
24
+ if isinstance(v, dict):
25
+ v = find_and_replace_dict(v, predicate)
26
+ result[k] = v
27
+ return result
@@ -0,0 +1,7 @@
1
+ def dict_merge(dct: dict, merge_dct: dict) -> dict:
2
+ for k, v in merge_dct.items():
3
+ if k in dct and isinstance(dct[k], dict) and isinstance(v, dict): # noqa
4
+ dict_merge(dct[k], v)
5
+ else:
6
+ dct[k] = v
7
+ return dct
@@ -0,0 +1,167 @@
1
+ import re
2
+ from django.db.models import Q
3
+ from typing import List, Dict, Optional
4
+
5
+
6
+ class ConditionParser:
7
+ CONDITION_PATTERN = re.compile(
8
+ r"(\w+__\w+='[^']+'|\w+__\w+=\S+|\w+='[^']+'|\w+=\S+)"
9
+ )
10
+ LOGICAL_OPERATORS = {"AND", "OR"}
11
+
12
+ __db_query: Optional[Q] = None
13
+
14
+ def __init__(self, query: str):
15
+ self.__db_query = None
16
+ self.query = query
17
+
18
+ def parse(self, query: str = None) -> Dict:
19
+ """Parse the query or subquery. If no query is provided, use the instance's query."""
20
+ if query is None:
21
+ query = self.query
22
+
23
+ tokens = self.tokenize(query)
24
+ # If there's just one token and it's a dictionary (single condition), return it
25
+ if len(tokens) == 1 and isinstance(tokens[0], dict):
26
+ return tokens[0]
27
+ return self.build_tree(tokens)
28
+
29
+ def tokenize(self, query: str) -> List:
30
+ tokens = []
31
+ i = 0
32
+ while i < len(query):
33
+ if query[i] == "(":
34
+ j = i + 1
35
+ open_parens = 1
36
+ while j < len(query) and open_parens > 0:
37
+ if query[j] == "(":
38
+ open_parens += 1
39
+ elif query[j] == ")":
40
+ open_parens -= 1
41
+ j += 1
42
+ if open_parens == 0:
43
+ subquery = query[i + 1 : j - 1]
44
+ tokens.append(self.parse(subquery)) # Pass the subquery here
45
+ i = j
46
+ else:
47
+ raise ValueError("Mismatched parentheses")
48
+ elif query[i : i + 3] == "AND" or query[i : i + 2] == "OR":
49
+ operator = "AND" if query[i : i + 3] == "AND" else "OR"
50
+ tokens.append(operator)
51
+ i += 3 if operator == "AND" else 2
52
+ else:
53
+ match = self.CONDITION_PATTERN.match(query[i:])
54
+ if match:
55
+ condition = self.parse_condition(match.group())
56
+ tokens.append(condition)
57
+ i += match.end()
58
+ else:
59
+ i += 1
60
+ return tokens
61
+
62
+ def parse_condition(self, condition: str) -> Optional[Dict]:
63
+ """Parse a single condition into field lookup and value."""
64
+ if "=" in condition:
65
+ field_lookup, value = condition.split("=")
66
+ value = value.strip("'").strip('"') # Remove single or double quotes
67
+ value = self.parse_value(value) # Parse the value
68
+ return {"field_lookup": field_lookup, "value": value}
69
+ return None
70
+
71
+ def parse_value(self, string: str):
72
+ """Parse single condition values based on specific rules."""
73
+ if string and string.startswith("[") and string.endswith("]"):
74
+ string = [self.parse_value(substr) for substr in string[1:-1].split(",")]
75
+ elif string and string.lower() in ["true", "false"]:
76
+ string = string.lower() == "true"
77
+ elif string and string.isdigit():
78
+ string = int(string)
79
+ return string
80
+
81
+ def build_tree(self, tokens: List[str]) -> Dict:
82
+ """Build a tree-like structure with operators and conditions."""
83
+ if not tokens:
84
+ return None
85
+
86
+ output_stack = []
87
+ operator_stack = []
88
+
89
+ # Process each token in the query
90
+ for token in tokens:
91
+ if isinstance(token, dict):
92
+ # Handle a single condition
93
+ if operator_stack:
94
+ operator = operator_stack.pop()
95
+ if isinstance(output_stack[-1], dict):
96
+ output_stack[-1] = {
97
+ "operator": operator,
98
+ "conditions": [output_stack[-1], token],
99
+ }
100
+ else:
101
+ output_stack[-1]["conditions"].append(token)
102
+ else:
103
+ output_stack.append(token)
104
+
105
+ elif token in self.LOGICAL_OPERATORS:
106
+ # Operator found (AND/OR), handle precedence
107
+ operator_stack.append(token)
108
+
109
+ # If only one item in output_stack, return it directly
110
+ if len(output_stack) == 1:
111
+ return output_stack[0]
112
+ return {
113
+ "operator": "AND",
114
+ "conditions": output_stack,
115
+ } # Default to AND if no operators
116
+
117
+ def to_q(self, parsed_tree: Dict) -> Q:
118
+ """Convert parsed tree structure into Q objects."""
119
+ if isinstance(parsed_tree, list):
120
+ # If parsed_tree is a list, combine all conditions with AND by default
121
+ q_objects = [self.to_q(cond) for cond in parsed_tree]
122
+ combined_q = Q()
123
+ for q_obj in q_objects:
124
+ combined_q &= q_obj
125
+ return combined_q
126
+
127
+ if isinstance(parsed_tree, dict):
128
+ if "field_lookup" in parsed_tree:
129
+ # Base case: a single condition
130
+ return Q(**{parsed_tree["field_lookup"]: parsed_tree["value"]})
131
+
132
+ elif "operator" in parsed_tree and "conditions" in parsed_tree:
133
+ operator = parsed_tree["operator"]
134
+ conditions = parsed_tree["conditions"]
135
+
136
+ q_objects = [self.to_q(cond) for cond in conditions]
137
+
138
+ if operator == "AND":
139
+ combined_q = Q()
140
+ for q_obj in q_objects:
141
+ combined_q &= q_obj
142
+ return combined_q
143
+ elif operator == "OR":
144
+ combined_q = Q()
145
+ for q_obj in q_objects:
146
+ combined_q |= q_obj
147
+ return combined_q
148
+ else:
149
+ raise ValueError(f"Unknown operator: {operator}")
150
+
151
+ raise ValueError("Parsed tree structure is invalid")
152
+
153
+ def parse_to_q(self) -> Q:
154
+ """Parse the query and convert to Q object."""
155
+ parsed_tree = self.parse()
156
+ if not parsed_tree:
157
+ return Q() # Return an empty Q if parsing fails
158
+ return self.to_q(parsed_tree)
159
+
160
+ @property
161
+ def db_query(self) -> Q:
162
+ if self.__db_query is None:
163
+ self.__db_query = self.parse_to_q()
164
+ return self.__db_query
165
+
166
+ def __str__(self) -> str:
167
+ return f"ConditionParser({self.db_query})"
@@ -3,10 +3,18 @@ import urllib.parse
3
3
  from django.apps import apps
4
4
  from django.conf import settings
5
5
  from django.http import Http404
6
- from django.utils.translation import activate, get_language
7
6
  from django.urls import resolve, reverse
7
+ from django.utils.translation import activate, get_language
8
+
9
+ from camomilla.exceptions import NeedARedirect
10
+
8
11
 
9
- from .exceptions import NeedARedirect
12
+ def is_page(model):
13
+ from camomilla.models import AbstractPage
14
+
15
+ return next(
16
+ (True for base in model.__bases__ if issubclass(base, AbstractPage)), False
17
+ )
10
18
 
11
19
 
12
20
  def get_host_url(request):
@@ -42,7 +50,6 @@ def get_page(
42
50
  page = model_page.objects.prefetch_related("contents").get(**kwargs)
43
51
  except model_page.DoesNotExist:
44
52
  page, _ = model_page.objects.get_or_create(**kwargs)
45
- page.translate(lang)
46
53
  page = compile_seo(request, page, lang)
47
54
  return page
48
55
 
@@ -73,16 +80,16 @@ def compile_seo(request, seo_obj, lang=""):
73
80
 
74
81
  def find_or_redirect(request, obj_class, **kwargs):
75
82
  try:
76
- obj = obj_class.objects.language().get(**kwargs)
83
+ obj = obj_class.objects.get(**kwargs)
77
84
  return obj
78
85
  except obj_class.DoesNotExist:
79
86
  cur_language = get_language()
80
87
  for language in settings.LANGUAGES:
81
88
  try:
82
89
  activate(language[0])
83
- obj = obj_class.objects.language().get(**kwargs)
90
+ obj = obj_class.objects.get(**kwargs)
84
91
  activate(cur_language)
85
- obj = obj_class.objects.language().get(pk=obj.pk)
92
+ obj = obj_class.objects.get(pk=obj.pk)
86
93
  args = []
87
94
  for kwarg_key, kwarg_val in kwargs.items():
88
95
  args.append(getattr(obj, kwarg_key))
@@ -93,12 +100,3 @@ def find_or_redirect(request, obj_class, **kwargs):
93
100
  pass
94
101
  activate(cur_language)
95
102
  raise Http404()
96
-
97
-
98
- def dict_merge(dct, merge_dct):
99
- for k, v in merge_dct.items():
100
- if k in dct and isinstance(dct[k], dict) and isinstance(v, dict): # noqa
101
- dict_merge(dct[k], v)
102
- else:
103
- dct[k] = v
104
- return dct