accrete 0.0.7__tar.gz → 0.0.9__tar.gz

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 (114) hide show
  1. {accrete-0.0.7 → accrete-0.0.9}/PKG-INFO +3 -1
  2. accrete-0.0.9/accrete/config.py +14 -0
  3. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/__init__.py +2 -1
  4. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/config.py +4 -2
  5. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/helper.py +5 -22
  6. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/js/navbar.js +1 -12
  7. accrete-0.0.9/accrete/contrib/ui/templates/ui/layout.html +147 -0
  8. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/actions.html +2 -1
  9. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/filter.html +4 -4
  10. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/form_modal.html +1 -1
  11. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/header.html +1 -1
  12. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templatetags/accrete_ui.py +14 -0
  13. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/urls.py +1 -0
  14. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/views.py +4 -0
  15. accrete-0.0.9/accrete/contrib/user_registration/config.py +31 -0
  16. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/templates/user_registration/registration.html +4 -1
  17. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/views.py +6 -6
  18. accrete-0.0.9/accrete/decorators.py +24 -0
  19. accrete-0.0.9/pyproject.toml +42 -0
  20. accrete-0.0.7/accrete/contrib/ui/templates/ui/layout.html +0 -154
  21. accrete-0.0.7/accrete/contrib/user_registration/settings.py +0 -29
  22. accrete-0.0.7/accrete/decorators.py +0 -34
  23. accrete-0.0.7/pyproject.toml +0 -43
  24. {accrete-0.0.7 → accrete-0.0.9}/.gitignore +0 -0
  25. {accrete-0.0.7 → accrete-0.0.9}/LICENSE +0 -0
  26. {accrete-0.0.7 → accrete-0.0.9}/README.md +0 -0
  27. {accrete-0.0.7 → accrete-0.0.9}/accrete/__init__.py +0 -0
  28. {accrete-0.0.7 → accrete-0.0.9}/accrete/admin.py +0 -0
  29. {accrete-0.0.7 → accrete-0.0.9}/accrete/apps.py +0 -0
  30. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/__init__.py +0 -0
  31. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/__init__.py +0 -0
  32. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/admin.py +0 -0
  33. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/apps.py +0 -0
  34. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/forms.py +0 -0
  35. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/migrations/0001_initial.py +0 -0
  36. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/migrations/__init__.py +0 -0
  37. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/models.py +0 -0
  38. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/queries.py +0 -0
  39. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/tests.py +0 -0
  40. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/sequence/views.py +0 -0
  41. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/__init__.py +0 -0
  42. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/admin.py +0 -0
  43. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/apps.py +0 -0
  44. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/forms.py +0 -0
  45. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/migrations/0001_initial.py +0 -0
  46. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/migrations/__init__.py +0 -0
  47. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/models.py +0 -0
  48. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/tasks.py +0 -0
  49. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/tests.py +0 -0
  50. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/system_mail/views.py +0 -0
  51. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/admin.py +0 -0
  52. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/apps.py +0 -0
  53. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/filter.py +0 -0
  54. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/migrations/__init__.py +0 -0
  55. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/css/accrete.css +0 -0
  56. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/css/icons.css +0 -0
  57. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/icons/Logo.svg +0 -0
  58. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/icons/accrete.svg +0 -0
  59. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/js/filter.js +0 -0
  60. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/js/list.js +0 -0
  61. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/static/js/utils.js +0 -0
  62. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/attrs.html +0 -0
  63. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/email.html +0 -0
  64. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/file.html +0 -0
  65. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/input.html +0 -0
  66. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/select.html +0 -0
  67. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/text.html +0 -0
  68. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/django/forms/widgets/textarea.html +0 -0
  69. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/detail.html +0 -0
  70. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/form.html +0 -0
  71. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/list.html +0 -0
  72. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/form_errors.html +0 -0
  73. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/onchange_form.html +0 -0
  74. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/pagination_detail.html +0 -0
  75. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/partials/pagination_list.html +0 -0
  76. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templates/ui/table.html +0 -0
  77. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/templatetags/__init__.py +0 -0
  78. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/ui/tests.py +0 -0
  79. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/__init__.py +0 -0
  80. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/admin.py +0 -0
  81. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/apps.py +0 -0
  82. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/locale/de/LC_MESSAGES/django.mo +0 -0
  83. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/locale/de/LC_MESSAGES/django.po +0 -0
  84. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/middleware.py +0 -0
  85. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/migrations/0001_initial.py +0 -0
  86. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/migrations/__init__.py +0 -0
  87. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/models.py +0 -0
  88. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/templates/user/login.html +0 -0
  89. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/tests.py +0 -0
  90. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/urls.py +0 -0
  91. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user/views.py +0 -0
  92. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/__init__.py +0 -0
  93. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/admin.py +0 -0
  94. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/apps.py +0 -0
  95. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/forms.py +0 -0
  96. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/migrations/0001_initial.py +0 -0
  97. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/migrations/__init__.py +0 -0
  98. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/models.py +0 -0
  99. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/templates/user_registration/mail_templates/confirmation_mail.html +0 -0
  100. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/tests.py +0 -0
  101. {accrete-0.0.7 → accrete-0.0.9}/accrete/contrib/user_registration/urls.py +0 -0
  102. {accrete-0.0.7 → accrete-0.0.9}/accrete/forms.py +0 -0
  103. {accrete-0.0.7 → accrete-0.0.9}/accrete/middleware.py +0 -0
  104. {accrete-0.0.7 → accrete-0.0.9}/accrete/migrations/0001_initial.py +0 -0
  105. {accrete-0.0.7 → accrete-0.0.9}/accrete/migrations/0002_initial.py +0 -0
  106. {accrete-0.0.7 → accrete-0.0.9}/accrete/migrations/__init__.py +0 -0
  107. {accrete-0.0.7 → accrete-0.0.9}/accrete/models.py +0 -0
  108. {accrete-0.0.7 → accrete-0.0.9}/accrete/queries.py +0 -0
  109. {accrete-0.0.7 → accrete-0.0.9}/accrete/shortcuts.py +0 -0
  110. {accrete-0.0.7 → accrete-0.0.9}/accrete/tenant.py +0 -0
  111. {accrete-0.0.7 → accrete-0.0.9}/accrete/tests.py +0 -0
  112. {accrete-0.0.7 → accrete-0.0.9}/accrete/utils/__init__.py +0 -0
  113. {accrete-0.0.7 → accrete-0.0.9}/accrete/utils/dates.py +0 -0
  114. {accrete-0.0.7 → accrete-0.0.9}/accrete/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: accrete
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -34,7 +34,9 @@ Requires-Python: >=3.10
34
34
  Requires-Dist: django>=4.2
35
35
  Provides-Extra: contrib
36
36
  Requires-Dist: celery>=5.3.4; extra == 'contrib'
37
+ Requires-Dist: django-celery-beat; extra == 'contrib'
37
38
  Provides-Extra: dev
39
+ Requires-Dist: build; extra == 'dev'
38
40
  Requires-Dist: pytest>=7.0; extra == 'dev'
39
41
  Requires-Dist: twine>=4.0.2; extra == 'dev'
40
42
  Description-Content-Type: text/markdown
@@ -0,0 +1,14 @@
1
+ import logging
2
+ from django.conf import settings
3
+
4
+ _logger = logging.getLogger(__name__)
5
+
6
+ ACCRETE_TENANT_NOT_SET_URL = getattr(
7
+ settings, 'ACCRETE_TENANT_NOT_SET_URL',
8
+ False
9
+ )
10
+
11
+ if not ACCRETE_TENANT_NOT_SET_URL:
12
+ _logger.warning(
13
+ 'Setting ACCRETE_TENANT_NOT_SET_URL missing.'
14
+ )
@@ -1,3 +1,4 @@
1
+ from . import config
1
2
  from .filter import Filter
2
3
  from .helper import (
3
4
  ClientAction,
@@ -8,4 +9,4 @@ from .helper import (
8
9
  build_querystring,
9
10
  parse_client_actions,
10
11
  queryset_from_querystring
11
- )
12
+ )
@@ -1,6 +1,8 @@
1
+ import logging
1
2
  from django.conf import settings
2
- from django.core.exceptions import ImproperlyConfigured
3
- from django.utils.translation import gettext_lazy as _
3
+
4
+ _logger = logging.getLogger(__name__)
5
+
4
6
 
5
7
  ACCRETE_UI_ACTIONS_TEMPLATE = getattr(
6
8
  settings, 'ACCRETE_UI_ACTIONS_TEMPLATE',
@@ -19,7 +19,9 @@ ACTIONS_TEMPLATE = config.ACCRETE_UI_ACTIONS_TEMPLATE
19
19
  class ClientAction:
20
20
 
21
21
  name: str
22
- url: str = None
22
+ url: str = ''
23
+ query_params: str = ''
24
+ attrs: list[str] = field(default_factory=list)
23
25
  submit: bool = False
24
26
  form_id: str = 'form'
25
27
  class_list: list = field(default_factory=list)
@@ -41,7 +43,7 @@ def parse_client_actions(actions: list[ClientAction], actions_template: str = No
41
43
  class BreadCrumb:
42
44
 
43
45
  name: str
44
- url:str
46
+ url: str
45
47
 
46
48
 
47
49
  @dataclass
@@ -348,27 +350,8 @@ def get_related_model(model, path):
348
350
  for part in path:
349
351
  try:
350
352
  related_model = related_model._meta.fields_map[part].related_model
351
- continue
352
353
  except (AttributeError, KeyError):
353
- pass
354
- # try:
355
- # related_model = getattr(related_model, part).model
356
- # continue
357
- # except AttributeError:
358
- # pass
359
- # try:
360
- # related_model = getattr(related_model.rel.related_model, part)
361
- # continue
362
- # except AttributeError:
363
- # pass
364
- # try:
365
- # related_model = getattr(related_model, part).field.related_model
366
- # continue
367
- # except AttributeError:
368
- # pass
369
- raise AttributeError(
370
- f'Could not resolve relation for model: {model} with path "{path}" at {part}'
371
- )
354
+ continue
372
355
  return related_model
373
356
 
374
357
 
@@ -1,23 +1,12 @@
1
1
  document.addEventListener('DOMContentLoaded', () => {
2
- const navElements = document.querySelectorAll('.accrete-navbar-menu');
3
- const burger = document.getElementById('navbar-burger');
2
+ const navElements = document.querySelectorAll('.navbar-item, .has-dropdown');
4
3
  navElements.forEach(el => {
5
4
  el.addEventListener('mousedown', toggleNavDropDown);
6
5
  el.addEventListener('blur', deactivateNavDropDown);
7
- el.classList.add('is-hoverable');
8
6
  })
9
- burger.addEventListener('click', toggleHamburger)
10
7
  })
11
8
 
12
9
 
13
- function toggleHamburger() {
14
- const burgerElements = document.querySelectorAll('.accrete-burger');
15
- burgerElements.forEach(el => {
16
- el.classList.toggle('is-active');
17
- })
18
- }
19
-
20
-
21
10
  function toggleNavDropDown() {
22
11
  const active = this.classList.toggle('is-active');
23
12
  if (active) {
@@ -0,0 +1,147 @@
1
+ <!doctype html>
2
+ {% load static %}
3
+ {% load i18n %}
4
+ {% load accrete_ui %}
5
+
6
+ <html lang="en">
7
+
8
+ <head>
9
+ {% block head %}
10
+ <meta charset="utf-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1">
12
+ {% block favicon %}<link rel="icon" type="image/svg" href="{% static 'icons/accrete.svg' %}"/>{% endblock %}
13
+ {% block bulma %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">{% endblock %}
14
+ {% block style %}
15
+ <link rel="stylesheet" type="text/css" href="{% static "css/accrete.css" %}">
16
+ <link rel="stylesheet" type="text/css" href="{% static "css/icons.css" %}">
17
+ {% endblock %}
18
+ {% block htmx %}
19
+ <script src="https://unpkg.com/htmx.org@1.9.6"></script>
20
+ <script src="https://unpkg.com/hyperscript.org@0.9.11"></script>
21
+ {% endblock %}
22
+ {% block script %}
23
+ <script src="{% static "js/navbar.js" %}" defer type="text/javascript"></script>
24
+ <script src="{% static "js/utils.js" %}" defer type="text/javascript"></script>
25
+ {% endblock %}
26
+ <title>{% block title %}{{ title }}{% endblock %}</title>
27
+ {% endblock %}
28
+ </head>
29
+
30
+
31
+ <body {% block body_attrs %}hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'{% endblock %}>
32
+ {% block navbar %}
33
+ <nav id="navbar" class="navbar is-success is-fixed-top" role="navigation" aria-label="main navigation">
34
+ <div class="navbar-brand">
35
+ {% block navbar_brand %}
36
+ <div class="navbar-item is-unselectable">{{ request.tenant.name }}</div>
37
+ {% endblock %}
38
+ <a id="navbar-burger" role="button" class="navbar-burger"
39
+ aria-label="menu" aria-expanded="false"
40
+ _="on click toggle .is-active on me then toggle .is-active on .navbar-menu"
41
+ >
42
+ {# Display hamburger menu on mobile #}
43
+ <span aria-hidden="true"></span>
44
+ <span aria-hidden="true"></span>
45
+ <span aria-hidden="true"></span>
46
+ </a>
47
+ </div>
48
+
49
+ <div class="navbar-menu is-fixed-top">
50
+ <div class="navbar-start">
51
+ {% block navbar_start %}
52
+ {% combine_templates 'accrete_menu.html' %}
53
+ {% endblock %}
54
+ </div>
55
+
56
+ <div class="navbar-end">
57
+ {% block navbar_end %}
58
+ {% endblock %}
59
+ </div>
60
+ </div>
61
+ </nav>
62
+ {% endblock %}
63
+
64
+ <div class="main-columns columns m-0 is-mobile">
65
+ {% block main_columns %}
66
+ {% block side_panel %}
67
+ <div class="side-panel column is-3-widescreen is-2-fullhd is-hidden-touch is-hidden-desktop-only is-hidden-widescreen-only" style="height: 100%">
68
+ <nav class="panel is-shadowless pb-5">
69
+ {% if list_pagination %}
70
+ <div class="panel-block">
71
+ {% include 'ui/partials/pagination_list.html' %}
72
+ </div>
73
+ {% endif %}
74
+ {% if detail_pagination %}
75
+ <div class="panel-block">
76
+ {% include 'ui/partials/pagination_detail.html' %}
77
+ </div>
78
+ {% endif %}
79
+ <div id="panel-actions">
80
+ {% block side_panel_items %}{% if actions and actions_template %}{% include actions_template with all=True %}{% endif %}{% endblock %}
81
+ </div>
82
+ {% if filter_terms %}
83
+ <div class="panel-list px-3 has-text-centered">
84
+ <span>{% translate 'Filter' %}</span>
85
+ </div>
86
+ <div class="panel-block" style="position: sticky">
87
+ <button id="reset-filter-button"
88
+ class="button is-fullwidth mr-1">{% translate 'Reset' %}
89
+ </button>
90
+ <button id="apply-filter-button"
91
+ class="button is-fullwidth ml-1">{% translate 'Filter' %}
92
+ </button>
93
+ </div>
94
+ <div id="filter-panel">
95
+ {% include 'ui/partials/filter.html' %}
96
+ </div>
97
+ {% endif %}
98
+ </nav>
99
+ </div>
100
+ {% endblock %}
101
+
102
+ <div id="content" class="column content-column p-0" style="height: 100%; overflow-y: auto">
103
+ {% block content %}{% endblock %}
104
+ </div>
105
+ {% endblock %}
106
+ </div>
107
+
108
+ {% if filter_terms %}
109
+ <div id="filter-modal" class="modal">
110
+ <div class="modal-background filter-modal-close"></div>
111
+ <div class="modal-card" style="height: 100%">
112
+ <header class="modal-card-head">
113
+ <p class="modal-card-title">
114
+ {% block search_modal_title %}{% translate 'Filter' %}{% endblock %}</p>
115
+ <button class="delete filter-modal-close" aria-label="close"></button>
116
+ </header>
117
+ <section id="modal-content" class="modal-card-body">
118
+ </section>
119
+ <footer class="modal-card-foot">
120
+ <button class="button is-fullwidth mr-1"
121
+ onclick="resetFilter()">{% translate 'Reset' %}</button>
122
+ <button id="applyFilterFromModalButton"
123
+ class="button is-fullwidth is-success ml-1">{% translate 'Filter' %}</button>
124
+ </footer>
125
+ </div>
126
+ </div>
127
+ {% endif %}
128
+
129
+ {% if actions %}
130
+ <div id="action-modal" class="modal">
131
+ <div class="modal-background action-modal-close"></div>
132
+ <div class="modal-card">
133
+ <header class="modal-card-head">
134
+ <p class="modal-card-title">
135
+ {% block action_modal_title %}{% translate 'Actions' %}{% endblock %}</p>
136
+ <button class="delete action-modal-close" aria-label="close"></button>
137
+ </header>
138
+ <section class="modal-card-body">
139
+ <div id="modal-actions">
140
+ {% block action_modal_body %}{% if actions_template %}{% include actions_template with all=True %}{% endif %}{% endblock %}
141
+ </div>
142
+ </section>
143
+ </div>
144
+ </div>
145
+ {% endif %}
146
+ <div id="form-modal" class="modal"></div>
147
+ </body>
@@ -13,7 +13,8 @@
13
13
  {{ action.attrs|join:' ' }}>
14
14
  {% else %}
15
15
  <a class="button is-fullwidth {{ action.class|join:' ' }}"
16
- href="{{ action.url }}{{ querystring }}{% if page %}&page={{ page.number }}{% endif %}" {{ action.attrs }}
16
+ href="{{ action.url }}{{ querystring|default_if_none:'?' }}{% if page %}&page={{ page.number }}{% endif %}{{ action.query_params }}"
17
+ {% for attr in action.attrs %}{{ attr }}{% endfor %}
17
18
  style="word-break: break-word">{{ action.name }}
18
19
  </a>
19
20
  {% endif %}
@@ -1,5 +1,6 @@
1
1
  {% load i18n %}
2
2
  {% load static %}
3
+ {% load cache %}
3
4
  {% load accrete_ui %}
4
5
 
5
6
  <div id="query-block" class="panel-block pt-0" style="position: relative; display: inline-block; width: 100%">
@@ -36,9 +37,8 @@
36
37
 
37
38
 
38
39
  <div id="query-params-dropdown" class="box mt-1 mx-0 p-1 is-hidden" tabindex="-1" style="z-index: 10; background: white; word-break: break-word; position: inherit">
39
- {% for term in filter_terms %}
40
-
41
- {% endfor %}
42
- {{ filter_terms|render_query_params|safe }}
40
+ {% cache 500 filter_params request.path request.GET request.tenant %}
41
+ {{ filter_terms|render_query_params|safe }}
42
+ {% endcache %}
43
43
  </div>
44
44
  </div>
@@ -16,4 +16,4 @@
16
16
  <button class="button" hx-get="{% url 'ui:form_modal' %}" hx-swap="outerHTML" hx-target="#form-modal">{% translate 'Discard' %}</button>
17
17
  </footer>
18
18
  </div>
19
- </div>
19
+ </div>
@@ -18,7 +18,7 @@
18
18
  <nav class="breadcrumb" aria-label="breadcrumbs" style="white-space: unset; word-break: break-word">
19
19
  <ul>
20
20
  {% for crumb in breadcrumbs %}
21
- <li><a href="{{ crumb.url|default_if_none:'#' }}{{ querystring }}" aria-current="page">{{ crumb.name }}</a></li>
21
+ <li><a class="is-underlined" href="{{ crumb.url|default_if_none:'#' }}{{ querystring }}" aria-current="page">{{ crumb.name }}</a></li>
22
22
  {% endfor %}
23
23
  <li class="is-active"><a aria-current="page">{{ title }}</a></li>
24
24
  </ul>
@@ -1,8 +1,22 @@
1
1
  from django import template
2
+ from django.conf import settings
3
+ from django.template.loader import render_to_string
4
+ from django.utils.safestring import mark_safe
2
5
 
3
6
  register = template.Library()
4
7
 
5
8
 
9
+ @register.simple_tag(name='combine_templates')
10
+ def combine_templates(template_name):
11
+ html = ''
12
+ for app in settings.INSTALLED_APPS:
13
+ try:
14
+ html += render_to_string(f'{app.split(".")[-1]}/{template_name}')
15
+ except template.TemplateDoesNotExist:
16
+ continue
17
+ return mark_safe(html)
18
+
19
+
6
20
  @register.filter(name='render_query_params')
7
21
  def query_params_to_html(params):
8
22
  html = ''
@@ -5,5 +5,6 @@ from . import views
5
5
  app_name = 'ui'
6
6
 
7
7
  urlpatterns = [
8
+ path('', views.home, name='home'),
8
9
  path('formmodal/', views.form_modal, name='form_modal'),
9
10
  ]
@@ -1,5 +1,9 @@
1
1
  from django.shortcuts import render
2
2
 
3
3
 
4
+ def home(request):
5
+ return render(request, 'ui/layout.html')
6
+
7
+
4
8
  def form_modal(request):
5
9
  return render(request, 'ui/partials/form_modal.html')
@@ -0,0 +1,31 @@
1
+ import logging
2
+ from django.conf import settings
3
+ from django.utils.translation import gettext_lazy as _
4
+
5
+ _logger = logging.getLogger(__name__)
6
+
7
+ ACCRETE_USER_REGISTRATION_MAIL_FROM_NAME = getattr(
8
+ settings, 'ACCRETE_USER_REGISTRATION_MAIL_FROM_NAME',
9
+ False
10
+ )
11
+
12
+ ACCRETE_USER_REGISTRATION_TEMPLATE_NAME = getattr(
13
+ settings, 'ACCRETE_USER_REGISTRATION_TEMPLATE_NAME',
14
+ 'user_registration/mail_templates/confirmation_mail.html'
15
+ )
16
+
17
+ ACCRETE_USER_REGISTRATION_MAIL_SUBJECT = getattr(
18
+ settings, 'ACCRETE_USER_REGISTRATION_MAIL_SUBJECT',
19
+ _('Registration Confirmation')
20
+ )
21
+
22
+ ACCRETE_USER_REGISTRATION_ALLOWED = getattr(
23
+ settings, 'ACCRETE_USER_REGISTRATION_ALLOWED', True
24
+ )
25
+
26
+ if not ACCRETE_USER_REGISTRATION_MAIL_FROM_NAME:
27
+ _logger.warning(
28
+ 'Setting "ACCRETE_USER_REGISTRATION_MAIL_FROM_NAME" missing.\n'
29
+ 'User Registration won\'t work. Set it or remove '
30
+ 'the app "accrete.contrib.user_registration".'
31
+ )
@@ -6,7 +6,10 @@
6
6
  <head>
7
7
  <meta charset="utf-8">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1">
9
- <link rel="stylesheet" type="text/css" href="{% static "css/bulma.min.css" %}">
9
+ {% block bulma %}
10
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
11
+ {% endblock %}
12
+ {# <link rel="stylesheet" type="text/css" href="{% static "css/bulma.min.css" %}">#}
10
13
  <link rel="shortcut icon" type="image/png" href="{% static 'icons/accrete.svg' %}"/>
11
14
  <title>Registration | Accrete</title>
12
15
  </head>
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from django.db import transaction
2
+
3
3
  from django.views.generic import FormView, View
4
4
  from django.urls import reverse_lazy, reverse
5
5
  from django.contrib.auth import get_user_model
@@ -8,7 +8,7 @@ from django.shortcuts import render
8
8
 
9
9
  from accrete.contrib.system_mail.forms import SystemMailCreateForm
10
10
  from . forms import UserRegistrationForm, ConfirmRegistrationForm, ResendConfirmationMailForm
11
- from . import settings
11
+ from . import config
12
12
 
13
13
  _logger = logging.getLogger(__name__)
14
14
  User = get_user_model()
@@ -22,14 +22,14 @@ def send_confirmation_mail(user, request):
22
22
  confirmation_url = f"{base}{endpoint}"
23
23
 
24
24
  body = render_to_string(
25
- settings.USER_REGISTRATION_TEMPLATE_NAME,
25
+ config.ACCRETE_USER_REGISTRATION_TEMPLATE_NAME,
26
26
  context={'confirmation_url': confirmation_url}
27
27
  )
28
28
 
29
29
  mail = SystemMailCreateForm({
30
- 'from_name': settings.USER_REGISTRATION_MAIL_FROM_NAME,
30
+ 'from_name': config.ACCRETE_USER_REGISTRATION_MAIL_FROM_NAME,
31
31
  'to_addr': user.email,
32
- 'subject': settings.USER_REGISTRATION_MAIL_SUBJECT,
32
+ 'subject': config.ACCRETE_USER_REGISTRATION_MAIL_SUBJECT,
33
33
  'body': body
34
34
  })
35
35
 
@@ -49,7 +49,7 @@ class RegistrationView(FormView):
49
49
  success_url = reverse_lazy('user_registration:registration_mail_sent')
50
50
 
51
51
  def dispatch(self, request, *args, **kwargs):
52
- if not settings.USER_REGISTRATION_ALLOWED:
52
+ if not config.ACCRETE_USER_REGISTRATION_ALLOWED:
53
53
  return self.http_method_not_allowed(request, *args, **kwargs)
54
54
  return super().dispatch(request, *args, **kwargs)
55
55
 
@@ -0,0 +1,24 @@
1
+ from functools import wraps
2
+
3
+ from django.shortcuts import redirect
4
+ from django.contrib.auth.views import login_required
5
+ from . import config
6
+
7
+
8
+ def tenant_required(
9
+ redirect_field_name: str = None,
10
+ login_url: str = None
11
+ ):
12
+ def decorator(f):
13
+ @wraps(f)
14
+ @login_required(
15
+ redirect_field_name=redirect_field_name,
16
+ login_url=login_url
17
+ )
18
+ def _wrapped_view(request, *args, **kwargs):
19
+ tenant = request.tenant
20
+ if not tenant:
21
+ return redirect(config.ACCRETE_TENANT_NOT_SET_URL)
22
+ return f(request, *args, **kwargs)
23
+ return _wrapped_view
24
+ return decorator
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ['hatchling']
3
+ build-backend = 'hatchling.build'
4
+
5
+ [project]
6
+ name = 'accrete'
7
+ version = '0.0.9'
8
+ authors = [
9
+ { name='Benedikt Jilek', email='benedikt.jilek@pm.me' },
10
+ ]
11
+ description = 'Django Shared Schema Multi Tenant'
12
+ readme = 'README.md'
13
+ license = {file = 'LICENSE'}
14
+ requires-python = '>=3.10'
15
+ classifiers = [
16
+ 'Programming Language :: Python :: 3',
17
+ 'License :: OSI Approved :: MIT License',
18
+ 'Operating System :: OS Independent',
19
+ 'Environment :: Web Environment',
20
+ 'Framework :: Django',
21
+ 'Intended Audience :: Developers',
22
+ 'Topic :: Internet :: WWW/HTTP'
23
+ ]
24
+ dependencies = [
25
+ 'django >= 4.2'
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ 'twine >= 4.0.2',
31
+ 'pytest >= 7.0',
32
+ 'build'
33
+ ]
34
+ contrib = [
35
+ 'celery >= 5.3.4',
36
+ 'django_celery_beat'
37
+ ]
38
+
39
+ [tool.hatch.build]
40
+ exclude = [
41
+ '.git/'
42
+ ]
@@ -1,154 +0,0 @@
1
- <!doctype html>
2
- {% load static %}
3
- {% load i18n %}
4
- {% load cache %}
5
-
6
- <html lang="en">
7
- <head>
8
- {% block head %}
9
- <meta charset="utf-8">
10
- <meta name="viewport" content="width=device-width, initial-scale=1">
11
- {% block favicon %}
12
- <link rel="icon" type="image/svg" href="{% static 'icons/accrete.svg' %}"/>
13
- {% endblock %}
14
- {% block bulma %}
15
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
16
- {% endblock %}
17
- {% block style %}
18
- <link rel="stylesheet" type="text/css" href="{% static "css/accrete.css" %}">
19
- <link rel="stylesheet" type="text/css" href="{% static "css/icons.css" %}">
20
- {% endblock %}
21
- {% block htmx %}
22
- <script src="https://unpkg.com/htmx.org@1.9.6"></script>
23
- <script src="https://unpkg.com/hyperscript.org@0.9.11"></script>
24
- {% endblock %}
25
- {% block script %}
26
- <script src="{% static "js/navbar.js" %}" defer type="text/javascript"></script>
27
- <script src="{% static "js/utils.js" %}" defer type="text/javascript"></script>
28
- {% endblock %}
29
- {% endblock %}
30
- {% block title_tag %}
31
- <title>{% block title %}{{ title }}{% endblock %}</title>
32
- {% endblock %}
33
- </head>
34
-
35
- <body id="body" {% block body_attrs %}hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'{% endblock %}>
36
- <nav id="navbar" class="navbar is-success is-info is-fixed-top" role="navigation" aria-label="main navigation">
37
- <div class="navbar-brand">
38
- {% block navbar_brand %}
39
- {% if request.tenant %}
40
- <p class="navbar-item is-unselectable">
41
- {{ request.tenant.name }}
42
- </p>
43
- {% endif %}
44
- {% block navbar_brand_inside %}{% endblock %}
45
- <a id="navbar-burger" role="button" class="navbar-burger accrete-burger"
46
- aria-label="menu" aria-expanded="false">
47
- {# Display hamburger menu on mobile #}
48
- <span aria-hidden="true"></span>
49
- <span aria-hidden="true"></span>
50
- <span aria-hidden="true"></span>
51
- </a>
52
- {% endblock %}
53
- </div>
54
-
55
- <div class="navbar-menu is-fixed-top accrete-burger">
56
- <div class="navbar-start">
57
- {% block navbar_start %}
58
- {% endblock %}
59
- </div>
60
-
61
- <div class="navbar-end">
62
- <div class="navbar-item has-dropdown accrete-navbar-menu">
63
- {% block navbar_end %}
64
- {% endblock %}
65
- </div>
66
- </div>
67
- </div>
68
- </nav>
69
-
70
- <div class="main-columns columns m-0 is-mobile">
71
- {% block main_columns %}
72
- <div class="side-panel column is-3-widescreen is-2-fullhd is-hidden-touch is-hidden-desktop-only is-hidden-widescreen-only" style="height: 100%">
73
- <nav class="panel is-shadowless pb-5">
74
- {% if list_pagination %}
75
- <div class="panel-block">
76
- {% include 'ui/partials/pagination_list.html' %}
77
- </div>
78
- {% endif %}
79
- {% if detail_pagination %}
80
- <div class="panel-block">
81
- {% include 'ui/partials/pagination_detail.html' %}
82
- </div>
83
- {% endif %}
84
- <div id="panel-actions">
85
- {% block side_panel_items %}{% if actions and actions_template %}{% include actions_template with all=True %}{% endif %}{% endblock %}
86
- </div>
87
- {% if filter_terms %}
88
- <div class="panel-list px-3 has-text-centered">
89
- <span>{% translate 'Filter' %}</span>
90
- </div>
91
- <div class="panel-block" style="position: sticky">
92
- <button id="reset-filter-button"
93
- class="button is-fullwidth mr-1">{% translate 'Reset' %}
94
- </button>
95
- <button id="apply-filter-button"
96
- class="button is-fullwidth ml-1">{% translate 'Filter' %}
97
- </button>
98
- </div>
99
- <div id="filter-panel">
100
- {% cache 500 filter_panel request.path request.GET request.tenant %}
101
- {% include 'ui/partials/filter.html' %}
102
- {% endcache %}
103
- </div>
104
- {% endif %}
105
- </nav>
106
- </div>
107
-
108
-
109
- <div id="content" class="column content-column p-0" style="height: 100%; overflow-y: auto">
110
- {% block content %}{% endblock %}
111
- </div>
112
- {% endblock %}
113
- </div>
114
-
115
- {% if filter_terms %}
116
- <div id="filter-modal" class="modal">
117
- <div class="modal-background filter-modal-close"></div>
118
- <div class="modal-card" style="height: 100%">
119
- <header class="modal-card-head">
120
- <p class="modal-card-title">
121
- {% block search_modal_title %}{% translate 'Filter' %}{% endblock %}</p>
122
- <button class="delete filter-modal-close" aria-label="close"></button>
123
- </header>
124
- <section id="modal-content" class="modal-card-body">
125
- </section>
126
- <footer class="modal-card-foot">
127
- <button class="button is-fullwidth mr-1"
128
- onclick="resetFilter()">{% translate 'Reset' %}</button>
129
- <button id="applyFilterFromModalButton"
130
- class="button is-fullwidth is-success ml-1">{% translate 'Filter' %}</button>
131
- </footer>
132
- </div>
133
- </div>
134
- {% endif %}
135
-
136
- {% if actions %}
137
- <div id="action-modal" class="modal">
138
- <div class="modal-background action-modal-close"></div>
139
- <div class="modal-card">
140
- <header class="modal-card-head">
141
- <p class="modal-card-title">
142
- {% block action_modal_title %}{% translate 'Actions' %}{% endblock %}</p>
143
- <button class="delete action-modal-close" aria-label="close"></button>
144
- </header>
145
- <section class="modal-card-body">
146
- <div id="modal-actions">
147
- {% block action_modal_body %}{% if actions_template %}{% include actions_template with all=True %}{% endif %}{% endblock %}
148
- </div>
149
- </section>
150
- </div>
151
- </div>
152
- {% endif %}
153
- <div id="form-modal" class="modal"></div>
154
- </body>
@@ -1,29 +0,0 @@
1
- from django.conf import settings
2
- from django.core.exceptions import ImproperlyConfigured
3
- from django.utils.translation import gettext_lazy as _
4
-
5
- USER_REGISTRATION_MAIL_FROM_NAME = getattr(
6
- settings, 'USER_REGISTRATION_MAIL_FROM_NAME',
7
- False
8
- )
9
-
10
- USER_REGISTRATION_TEMPLATE_NAME = getattr(
11
- settings, 'USER_REGISTRATION_TEMPLATE_NAME',
12
- 'user_registration/mail_templates/confirmation_mail.html'
13
- )
14
-
15
- USER_REGISTRATION_MAIL_SUBJECT = getattr(
16
- settings, 'USER_REGISTRATION_MAIL_SUBJECT',
17
- _('Registration Confirmation')
18
- )
19
-
20
- USER_REGISTRATION_ALLOWED = getattr(
21
- settings, 'USER_REGISTRATION_ALLOWED', False
22
- )
23
-
24
- if not USER_REGISTRATION_MAIL_FROM_NAME:
25
- raise ImproperlyConfigured(
26
- 'Setting "USER_REGISTRATION_MAIL_FROM_NAME" missing.\n'
27
- 'User Registration won\'t work. Set it or remove '
28
- 'the app "user_registration" from INSTALLED_APPS and your urls.'
29
- )
@@ -1,34 +0,0 @@
1
- from functools import wraps
2
-
3
- from django.conf import settings
4
- from django.shortcuts import redirect
5
- from django.contrib.auth.views import login_required
6
- from django.core.exceptions import ImproperlyConfigured
7
-
8
- TENANT_NOT_SET_URL = settings.ACCRETE_TENANT_NOT_SET_URL
9
-
10
- def tenant_required(
11
- tenant_not_set_url: str = None,
12
- redirect_field_name: str = None,
13
- login_url: str = None
14
- ):
15
- def decorator(f):
16
- @wraps(f)
17
- @login_required(
18
- redirect_field_name=redirect_field_name,
19
- login_url=login_url
20
- )
21
- def _wrapped_view(request, *args, **kwargs):
22
- tenant = request.tenant
23
- if not tenant:
24
- url = tenant_not_set_url or TENANT_NOT_SET_URL
25
- if not url:
26
- raise ImproperlyConfigured(
27
- f'Redirect URL not set. '
28
- f'Define settings.ACCRETE_TENANT_NOT_SET_URL or pass '
29
- f'the redirect URL to the tenant_required decorator.'
30
- )
31
- return redirect(url)
32
- return f(request, *args, **kwargs)
33
- return _wrapped_view
34
- return decorator
@@ -1,43 +0,0 @@
1
- [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
4
-
5
- [project]
6
- name = "accrete"
7
- version = "0.0.7"
8
- authors = [
9
- { name="Benedikt Jilek", email="benedikt.jilek@pm.me" },
10
- ]
11
- description = "Django Shared Schema Multi Tenant"
12
- readme = "README.md"
13
- license = {file = "LICENSE"}
14
- requires-python = ">=3.10"
15
- classifiers = [
16
- "Programming Language :: Python :: 3",
17
- "License :: OSI Approved :: MIT License",
18
- "Operating System :: OS Independent",
19
- "Environment :: Web Environment",
20
- "Framework :: Django",
21
- "Intended Audience :: Developers",
22
- "Topic :: Internet :: WWW/HTTP"
23
- ]
24
- dependencies = [
25
- "django >= 4.2"
26
- ]
27
-
28
- [project.optional-dependencies]
29
- dev = [
30
- 'twine >= 4.0.2',
31
- 'pytest >= 7.0'
32
- ]
33
- contrib = [
34
- 'celery >= 5.3.4'
35
- ]
36
-
37
- [tool.hatch.build]
38
- exclude = [
39
- 'project/',
40
- '.idea/',
41
- 'gitignore',
42
- 'manage.py'
43
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes