accrete 0.0.154__py3-none-any.whl → 0.0.156__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.
- accrete/channels.py +39 -0
- accrete/consumer.py +18 -0
- accrete/contrib/log/admin.py +0 -16
- accrete/contrib/log/migrations/0006_remove_log_accrete_log_model_cf888c_idx_and_more.py +24 -0
- accrete/contrib/log/models.py +3 -1
- accrete/contrib/log/signals.py +1 -3
- accrete/contrib/ui/__init__.py +2 -1
- accrete/contrib/ui/config.py +9 -0
- accrete/contrib/ui/filter.py +1 -1
- accrete/contrib/ui/forms.py +1 -0
- accrete/contrib/ui/migrations/0005_theme_base_theme.py +18 -0
- accrete/contrib/ui/models.py +9 -0
- accrete/contrib/ui/response.py +31 -8
- accrete/contrib/ui/static/js/htmx.min.js +1 -1
- accrete/contrib/ui/static/js/ws.js +1 -0
- accrete/contrib/ui/templates/ui/filter/query_params.html +5 -0
- accrete/contrib/ui/templates/ui/layout.html +35 -16
- accrete/contrib/ui/templates/ui/list.html +4 -2
- accrete/contrib/ui/templates/ui/message.html +5 -3
- accrete/contrib/ui/templates/ui/table.html +2 -0
- accrete/contrib/ui/templates/ui/templatetags/tenant_quick_switch.html +14 -0
- accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +2 -2
- accrete/contrib/ui/templatetags/ui.py +45 -1
- accrete/contrib/user/templates/user/accrete_navbar_end_dropdown.html +1 -1
- accrete/contrib/user/templates/user/user_preferences.html +1 -0
- accrete/fields.py +13 -0
- accrete/middleware.py +1 -0
- accrete/migrations/0009_alter_accessgroup_name.py +18 -2
- accrete/migrations/0010_alter_accessgroup_description.py +46 -0
- accrete/models.py +3 -3
- accrete/tenant.py +1 -0
- accrete/utils/__init__.py +4 -1
- accrete/utils/models.py +18 -0
- accrete/utils/views.py +1 -1
- {accrete-0.0.154.dist-info → accrete-0.0.156.dist-info}/METADATA +1 -1
- {accrete-0.0.154.dist-info → accrete-0.0.156.dist-info}/RECORD +38 -30
- {accrete-0.0.154.dist-info → accrete-0.0.156.dist-info}/WHEEL +0 -0
- {accrete-0.0.154.dist-info → accrete-0.0.156.dist-info}/licenses/LICENSE +0 -0
@@ -4,7 +4,7 @@
|
|
4
4
|
{% load partials %}
|
5
5
|
{% load ui %}
|
6
6
|
|
7
|
-
<html lang="en" style="overflow: hidden;" {% block theme %}
|
7
|
+
<html lang="en" style="overflow: hidden;" {% block theme %}data-theme="{% base_theme request.user %}"{% endblock %}>
|
8
8
|
|
9
9
|
<head>
|
10
10
|
{% block head %}
|
@@ -14,9 +14,11 @@
|
|
14
14
|
<link rel="stylesheet" type="text/css" href="{% static "css/accrete.css" %}?v=0.0.154">
|
15
15
|
<link rel="stylesheet" type="text/css" href="{% static "css/icons.css" %}">
|
16
16
|
<link rel="stylesheet" type="text/css" href="{% static "css/fa.css" %}">
|
17
|
+
{% if style_template %}{% include style_template %}{% endif %}
|
17
18
|
{% custom_theme request.user %}
|
18
19
|
<script src="{% static "js/filter.js" %}" type="text/javascript"></script>
|
19
20
|
<script src="{% static "js/htmx.min.js" %}" defer type="text/javascript"></script>
|
21
|
+
<script src="{% static "js/ws.js" %}" type="text/javascript" defer></script>
|
20
22
|
<script src="{% static "js/alpine-focus-3.14.9.js" %}" defer type="text/javascript"></script>
|
21
23
|
<script src="{% static "js/alpine-sort-3.14.9.js" %}" defer type="text/javascript"></script>
|
22
24
|
<script src="{% static "js/alpine-3.14.9.js" %}" defer type="text/javascript"></script>
|
@@ -41,6 +43,14 @@
|
|
41
43
|
document.body.setAttribute('data-tenant-id', url.searchParams.get('tenant_id'));
|
42
44
|
}
|
43
45
|
})
|
46
|
+
{% if debug_htmx_ws %}
|
47
|
+
document.addEventListener("htmx:wsAfterMessage", function (evt) {
|
48
|
+
console.log("HTMX WS message received:", evt.detail.message);
|
49
|
+
});
|
50
|
+
document.addEventListener("htmx:wsAfterSend", function (evt) {
|
51
|
+
console.log("HTMX WS message sent: ", evt.detail.message);
|
52
|
+
});
|
53
|
+
{% endif %}
|
44
54
|
</script>
|
45
55
|
{% if script_template %}{% include script_template %}{% endif %}
|
46
56
|
<title>{% block title %}{{ title }}{% endblock %}</title>
|
@@ -49,7 +59,7 @@
|
|
49
59
|
|
50
60
|
{% block body %}
|
51
61
|
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' data-tenant-id="{{ tenant.id }}" hx-boost="true" hx-history="false"
|
52
|
-
x-data="{ showPanel: false, openPanel() { if(window.innerWidth >= 1216) {this.showPanel = true} } }"
|
62
|
+
x-data="{ showQuickSwitch: false, showPanel: false, openPanel() { if(window.innerWidth >= 1216) {this.showPanel = true} } }"
|
53
63
|
>
|
54
64
|
<nav id="navbar" class="navbar is-primary is-fixed-top" role="navigation" aria-label="main navigation">
|
55
65
|
<div class="navbar-brand">
|
@@ -61,11 +71,11 @@
|
|
61
71
|
</button>
|
62
72
|
</div>
|
63
73
|
{% endif %}
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
{
|
68
|
-
|
74
|
+
{% tenant_quick_switch request.user %}
|
75
|
+
|
76
|
+
{% if navbar_brand_template %}
|
77
|
+
{% include navbar_brand_template %}
|
78
|
+
{% endif %}
|
69
79
|
|
70
80
|
<a id="navbar-burger" role="button" class="navbar-burger"
|
71
81
|
aria-label="menu" aria-expanded="false"
|
@@ -82,7 +92,12 @@
|
|
82
92
|
|
83
93
|
<div id="navbar-menu" class="navbar-menu is-fixed-top">
|
84
94
|
<div class="navbar-start">
|
85
|
-
{%
|
95
|
+
{% if navbar_start_template %}
|
96
|
+
{% include navbar_start_template %}
|
97
|
+
{% elif navbar_start_template is False %}
|
98
|
+
{% else %}
|
99
|
+
{% combine_templates 'accrete_navbar.html' request %}
|
100
|
+
{% endif %}
|
86
101
|
</div>
|
87
102
|
|
88
103
|
<div class="navbar-end">
|
@@ -113,10 +128,10 @@
|
|
113
128
|
{% if panel_template %}{% include panel_template %}{% endif %}
|
114
129
|
{% if ui_filter %}
|
115
130
|
<div class="mt-4">
|
116
|
-
<div class="
|
131
|
+
<div class="mb-1" style="border-bottom: 1px var(--bulma-grey) solid">
|
117
132
|
<span>{% translate 'Filter' %}</span>
|
118
133
|
</div>
|
119
|
-
<div class="
|
134
|
+
<div class="">
|
120
135
|
{% include 'ui/filter/filter.html' %}
|
121
136
|
</div>
|
122
137
|
</div>
|
@@ -131,9 +146,11 @@
|
|
131
146
|
<div id="message" class="" style="position: fixed; top: calc(var(--bulma-navbar-height) + 5px); left: 50%; transform: translateX(-50%); z-index: 999">
|
132
147
|
{% for message in messages %}
|
133
148
|
<div class="mb-2" style="min-width: 330px; max-width: 330px" x-data="{ show: false }" x-show="show" x-cloak="" x-init="show = true; setTimeout(() => show = false, 4000)" x-transition.duration.200ms>
|
134
|
-
|
149
|
+
<article class="message {{ message|message_class }}" style="box-shadow: 2px 2px 5px">
|
150
|
+
<div class="message-body">
|
135
151
|
{{ message }}
|
136
|
-
|
152
|
+
<button class="delete" x-on:click="show = false;" style="position: absolute; top: 5px; right: 5px"></button>
|
153
|
+
</div></article>
|
137
154
|
</div>
|
138
155
|
{% endfor %}
|
139
156
|
</div>
|
@@ -146,8 +163,9 @@
|
|
146
163
|
>
|
147
164
|
|
148
165
|
<div id="overview-container" class="table-container is-flex is-flex-grow-1 is-flex-direction-column mb-0" x-bind:class="showContentRight ? 'is-hidden-mobile' : ''">
|
149
|
-
|
150
|
-
<div
|
166
|
+
{% if config.display_header %}
|
167
|
+
<div id="overview-header">
|
168
|
+
<div class="is-flex is-justify-content-space-between is-flex-wrap-wrap {% if is_centered %}container{% endif %}">
|
151
169
|
<div class="is-flex is-flex-wrap-wrap is-flex-grow-5 is-align-self-center py-2 mx-1 header-items">
|
152
170
|
{% block overview_header %}
|
153
171
|
<div class="" style="align-self: center">
|
@@ -189,8 +207,9 @@
|
|
189
207
|
{% endpartialdef %}
|
190
208
|
</div>
|
191
209
|
{% endif %}
|
210
|
+
</div>
|
192
211
|
</div>
|
193
|
-
|
212
|
+
{% endif %}
|
194
213
|
<div id="overview" class="is-flex-grow-1" style="overflow-y: auto;">
|
195
214
|
<section class="{% if is_centered %}container{% endif %}">
|
196
215
|
{% if overview_template %}{% include overview_template %}{% endif %}
|
@@ -225,4 +244,4 @@
|
|
225
244
|
</div>
|
226
245
|
</div>
|
227
246
|
</body>
|
228
|
-
{% endblock %}
|
247
|
+
{% endblock %}
|
@@ -2,11 +2,12 @@
|
|
2
2
|
{% load ui %}
|
3
3
|
{% load partials %}
|
4
4
|
|
5
|
+
{% block list %}
|
5
6
|
<div class="fixed-grid has-{{ column_count }}-cols has-1-cols-mobile m-2">
|
6
7
|
<div id="list-grid" class="grid m-0">
|
7
8
|
{% for instance in page.object_list %}
|
8
9
|
{% partialdef entry inline=True %}
|
9
|
-
<div id="list-entry-{{ instance.pk }}" class="list-entry cell pb-0" style="height: {{ column_height }}">
|
10
|
+
<div id="list-entry-{{ instance.pk }}" class="list-entry cell pb-0" style="{% if column_height %}min-height: {{ column_height }}{% endif %}">
|
10
11
|
<div class="box p-3"
|
11
12
|
style="word-break: break-word; height: 100%; overflow-y: auto; {% if instance.get_absolute_url and detail_enabled %}cursor:pointer;{% endif %}"
|
12
13
|
{% if instance.get_absolute_url and detail_enabled %}
|
@@ -15,7 +16,7 @@
|
|
15
16
|
{% endif %}
|
16
17
|
x-data="{selected: false}" @unselect-list-entry.window="selected = false"
|
17
18
|
>
|
18
|
-
<div id="list-data-{{ instance.pk }}">
|
19
|
+
<div id="list-data-{{ instance.pk }}" style="height: 100%">
|
19
20
|
{% if list_entry_template %}
|
20
21
|
{% include list_entry_template %}
|
21
22
|
{% else %}
|
@@ -40,3 +41,4 @@
|
|
40
41
|
<progress id="endless-scroll-indicator" class="htmx-indicator progress is-small is-success" max="100">15%</progress>
|
41
42
|
{% endif %}
|
42
43
|
</div>
|
44
|
+
{% endblock %}
|
@@ -4,9 +4,11 @@
|
|
4
4
|
{% for message in messages %}
|
5
5
|
<div hx-swap-oob="{% if append %}beforeend:#message{% else %}innerHTML:#message{% endif %}">
|
6
6
|
<div class="mb-2" style="min-width: 340px; max-width: 340px" x-data="{ show: false }" x-show="show" x-cloak="" x-init="show = true; {% if not persistent %}setTimeout(() => show = false, 4000){% endif %}" x-transition.duration.200ms>
|
7
|
-
<
|
8
|
-
<
|
9
|
-
|
7
|
+
<article class="message {{ message|message_class }}" style="box-shadow: 2px 2px 5px">
|
8
|
+
<div class="message-body">
|
9
|
+
{{ message }}
|
10
|
+
<button class="delete" x-on:click="show = false;" style="position: absolute; top: 5px; right: 5px"></button>
|
11
|
+
</div></article>
|
10
12
|
</div>
|
11
13
|
</div>
|
12
14
|
</div>
|
@@ -2,6 +2,7 @@
|
|
2
2
|
{% load ui %}
|
3
3
|
{% load partials %}
|
4
4
|
|
5
|
+
{% block table %}
|
5
6
|
<table id="content-table" class="table can-compact is-fullwidth is-hoverable is-narrow my-0" hx-indicator=".htmx-indicator" x-data="">
|
6
7
|
<thead style="position: sticky; top: 0; z-index: 10; background-color: var(--bulma-scheme-main)">
|
7
8
|
<tr>
|
@@ -73,3 +74,4 @@
|
|
73
74
|
>
|
74
75
|
</div>
|
75
76
|
<progress id="endless-scroll-indicator" class="htmx-indicator progress is-small is-primary" max="100">15%</progress>
|
77
|
+
{% endblock %}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div class="navbar-item is-unselectable has-text-weight-bold" style="{% if switch_url %}cursor: pointer;{% endif %}" x-on:click="showQuickSwitch = !showQuickSwitch;">
|
2
|
+
{{ tenant.name }}
|
3
|
+
</div>
|
4
|
+
{% if tenants and switch_url %}
|
5
|
+
<article class="p-1 has-text-weight-bold" style="background-color: var(--bulma-body-background-color); position: fixed; top: var(--bulma-navbar-height); left: 0; box-shadow: 2px 2px 5px; border-bottom-right-radius: var(--bulma-radius); min-width: 150px;"
|
6
|
+
x-show="showQuickSwitch" x-cloak="" x-on:click.outside="showQuickSwitch = false"
|
7
|
+
>
|
8
|
+
<ul class="hoverable mr-1">
|
9
|
+
{% for t in tenants %}
|
10
|
+
<li class="title is-6 mb-1 p-2" style="cursor: pointer; border-radius: var(--bulma-radius);"><a href="{{ switch_url }}?tenant_id={{ t.pk }}" hx-boost="false" style="width: 100%; display: inline-block">{{ t.name }}</a></li>
|
11
|
+
{% endfor %}
|
12
|
+
</ul>
|
13
|
+
</article>
|
14
|
+
{% endif %}
|
@@ -10,8 +10,8 @@
|
|
10
10
|
<option selected value="{{ obj.pk }}"></option>
|
11
11
|
{% endfor %}
|
12
12
|
</select>
|
13
|
-
<div id="{{ widget.attrs.id }}_display" class="input select
|
14
|
-
style="padding-right: 30px; min-height: var(--bulma-control-height); height: fit-content; width: 100%"
|
13
|
+
<div id="{{ widget.attrs.id }}_display" class="input select pt-1 pb-0" tabindex="0" x-ref="inputDisplay" aria-label="inputDisplay"
|
14
|
+
style="padding-left: 5px; padding-right: 30px; min-height: var(--bulma-control-height); height: fit-content; width: 100%"
|
15
15
|
x-on:keydown="if (![9, 27, 38, 40].includes($event.keyCode)) {$refs.searchInput.focus();}"
|
16
16
|
x-on:click.stop="if (!$event.target.classList.contains('delete')) {if (open) {open = false; } else {open = true; $refs.searchInput.focus();}}"
|
17
17
|
>
|
@@ -3,6 +3,7 @@ import re
|
|
3
3
|
from datetime import datetime, date, timedelta
|
4
4
|
|
5
5
|
from django.contrib.auth import get_user_model
|
6
|
+
from django.shortcuts import resolve_url
|
6
7
|
from django.utils.translation import gettext_lazy as _
|
7
8
|
from django import template
|
8
9
|
from django.db.models import (
|
@@ -13,7 +14,9 @@ from django.template.loader import render_to_string
|
|
13
14
|
from django.utils.safestring import mark_safe
|
14
15
|
from django.forms import widgets
|
15
16
|
from accrete.contrib.ui.models import Theme
|
16
|
-
from accrete.tenant import get_tenant
|
17
|
+
from accrete.tenant import get_tenant, unscoped
|
18
|
+
from accrete.models import Tenant, Member
|
19
|
+
from accrete.contrib.ui import config
|
17
20
|
|
18
21
|
_logger = logging.getLogger(__name__)
|
19
22
|
register = template.Library()
|
@@ -123,6 +126,7 @@ def message_class(message):
|
|
123
126
|
return 'is-warning'
|
124
127
|
if message.level == 40:
|
125
128
|
return 'is-danger'
|
129
|
+
return ''
|
126
130
|
|
127
131
|
|
128
132
|
@register.filter(name='timedelta_cast')
|
@@ -211,3 +215,43 @@ def custom_theme(user: User) -> str:
|
|
211
215
|
if user.theme == 'preset' and tenant_theme:
|
212
216
|
return mark_safe(tenant_theme.theme_markup)
|
213
217
|
return ''
|
218
|
+
|
219
|
+
|
220
|
+
@register.simple_tag(name='base_theme')
|
221
|
+
def base_theme(user: User) -> str:
|
222
|
+
if user.is_anonymous:
|
223
|
+
return 'light'
|
224
|
+
tenant = get_tenant()
|
225
|
+
tenant_theme = Theme.objects.filter(
|
226
|
+
tenant=tenant, tenant__isnull=False
|
227
|
+
).first()
|
228
|
+
if tenant_theme and tenant_theme.force_tenant_theme:
|
229
|
+
return tenant_theme.base_theme
|
230
|
+
if user.theme == 'custom':
|
231
|
+
theme = Theme.objects.filter(user=user).first()
|
232
|
+
return theme and theme.base_theme or 'light'
|
233
|
+
if user.theme == 'preset' and tenant_theme:
|
234
|
+
return tenant_theme.base_theme
|
235
|
+
return user.theme
|
236
|
+
|
237
|
+
|
238
|
+
@register.simple_tag(name='tenant_quick_switch')
|
239
|
+
def tenant_quick_switch(user: User):
|
240
|
+
templ = 'ui/templatetags/tenant_quick_switch.html'
|
241
|
+
tenant = get_tenant()
|
242
|
+
with unscoped():
|
243
|
+
tenants = Tenant.objects.filter(
|
244
|
+
members__in=Member.objects.filter(user=user)
|
245
|
+
)
|
246
|
+
if tenant:
|
247
|
+
tenants = tenants.exclude(pk=tenant.pk)
|
248
|
+
switch_url = getattr(config, 'ACCRETE_TENANT_QUICK_SWITCH_URL')
|
249
|
+
if switch_url:
|
250
|
+
switch_url = resolve_url(switch_url)
|
251
|
+
return mark_safe(render_to_string(
|
252
|
+
templ, context={
|
253
|
+
'tenant': tenant,
|
254
|
+
'tenants': tenants,
|
255
|
+
'switch_url': switch_url
|
256
|
+
}
|
257
|
+
))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
{% load i18n %}
|
2
2
|
|
3
|
-
<a class="navbar-item" href="{% url 'user:detail' %}">{% translate 'Preferences' %}</a>
|
3
|
+
<a class="navbar-item" href="{% url 'user:detail' %}" hx-boost="false">{% translate 'Preferences' %}</a>
|
4
4
|
{% if request.user.is_staff %}
|
5
5
|
<a class="navbar-item" hx-boost="false" href="/admin/">Admin</a>
|
6
6
|
{% endif %}
|
@@ -43,6 +43,7 @@
|
|
43
43
|
<form id="theme-form" hx-post="{% url 'user:detail' %}" hx-trigger="change changed" hx-select="#theme-form" hx-swap="outerHTML">
|
44
44
|
{{ theme_form.user }}
|
45
45
|
<div class="columns is-multiline">
|
46
|
+
<div class="column">{{ theme_form.base_theme|wrap_form_field }}</div>
|
46
47
|
<div class="column">{{ theme_form.color_primary|wrap_form_field }}</div>
|
47
48
|
<div class="column">{{ theme_form.color_success|wrap_form_field }}</div>
|
48
49
|
<div class="column">{{ theme_form.color_link|wrap_form_field }}</div>
|
accrete/fields.py
CHANGED
@@ -129,3 +129,16 @@ class TranslatedCharField(JSONField):
|
|
129
129
|
if form_class is None:
|
130
130
|
form_class = forms.CharField
|
131
131
|
return form_class(**defaults)
|
132
|
+
|
133
|
+
|
134
|
+
class TranslatedTextField(TranslatedCharField):
|
135
|
+
|
136
|
+
def formfield(self, form_class=None, choices_form_class=None, **kwargs):
|
137
|
+
if form_class is None:
|
138
|
+
form_class = forms.CharField
|
139
|
+
kwargs.update(widget=forms.Textarea)
|
140
|
+
return super().formfield(
|
141
|
+
form_class=form_class,
|
142
|
+
choices_form_class=choices_form_class,
|
143
|
+
**kwargs
|
144
|
+
)
|
accrete/middleware.py
CHANGED
@@ -10,10 +10,26 @@ def char_to_translated_char(apps, schema):
|
|
10
10
|
AccessGroup = apps.get_model('accrete', 'AccessGroup')
|
11
11
|
default_lang = getattr(settings, 'LANGUAGE_CODE', 'en-us')
|
12
12
|
for group in AccessGroup.objects.all():
|
13
|
-
|
13
|
+
try:
|
14
|
+
name = json.dumps(json.loads(group.name).get(default_lang))
|
15
|
+
if not name:
|
16
|
+
name = None
|
17
|
+
except Exception:
|
18
|
+
name = group.name or None
|
19
|
+
group.name = name
|
14
20
|
group.save()
|
15
21
|
|
16
22
|
|
23
|
+
def translated_to_char(apps, schema):
|
24
|
+
AccessGroup = apps.get_model('accrete', 'AccessGroup')
|
25
|
+
default_lang = getattr(settings, 'LANGUAGE_CODE', 'en-us')
|
26
|
+
for group in AccessGroup.objects.all():
|
27
|
+
try:
|
28
|
+
group.name = json.loads(group.name).get(default_lang, '')
|
29
|
+
except Exception:
|
30
|
+
continue
|
31
|
+
|
32
|
+
|
17
33
|
class Migration(migrations.Migration):
|
18
34
|
|
19
35
|
dependencies = [
|
@@ -21,7 +37,7 @@ class Migration(migrations.Migration):
|
|
21
37
|
]
|
22
38
|
|
23
39
|
operations = [
|
24
|
-
migrations.RunPython(char_to_translated_char),
|
40
|
+
migrations.RunPython(char_to_translated_char, translated_to_char),
|
25
41
|
migrations.AlterField(
|
26
42
|
model_name='accessgroup',
|
27
43
|
name='name',
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-04 06:20
|
2
|
+
|
3
|
+
import json
|
4
|
+
import accrete.fields
|
5
|
+
from django.db import migrations
|
6
|
+
from django.conf import settings
|
7
|
+
|
8
|
+
|
9
|
+
def char_to_translated_char(apps, schema):
|
10
|
+
AccessGroup = apps.get_model('accrete', 'AccessGroup')
|
11
|
+
default_lang = getattr(settings, 'LANGUAGE_CODE', 'en-us')
|
12
|
+
for group in AccessGroup.objects.all():
|
13
|
+
try:
|
14
|
+
description = json.dumps(json.loads(group.description).get(default_lang))
|
15
|
+
if not description:
|
16
|
+
description = None
|
17
|
+
except Exception:
|
18
|
+
description = group.description or None
|
19
|
+
group.description = description
|
20
|
+
group.save()
|
21
|
+
|
22
|
+
|
23
|
+
def translated_to_char(apps, schema):
|
24
|
+
AccessGroup = apps.get_model('accrete', 'AccessGroup')
|
25
|
+
default_lang = getattr(settings, 'LANGUAGE_CODE', 'en-us')
|
26
|
+
for group in AccessGroup.objects.all():
|
27
|
+
try:
|
28
|
+
group.description = json.loads(group.description).get(default_lang, '')
|
29
|
+
except Exception:
|
30
|
+
continue
|
31
|
+
|
32
|
+
|
33
|
+
class Migration(migrations.Migration):
|
34
|
+
|
35
|
+
dependencies = [
|
36
|
+
('accrete', '0009_alter_accessgroup_name'),
|
37
|
+
]
|
38
|
+
|
39
|
+
operations = [
|
40
|
+
migrations.RunPython(char_to_translated_char, translated_to_char),
|
41
|
+
migrations.AlterField(
|
42
|
+
model_name='accessgroup',
|
43
|
+
name='description',
|
44
|
+
field=accrete.fields.TranslatedTextField(blank=True, null=True, verbose_name='Description'),
|
45
|
+
),
|
46
|
+
]
|
accrete/models.py
CHANGED
@@ -4,7 +4,7 @@ from django.conf import settings
|
|
4
4
|
from django.utils.translation import gettext_lazy as _
|
5
5
|
from accrete.tenant import get_tenant
|
6
6
|
from accrete.managers import TenantManager, MemberManager, AccessGroupManager
|
7
|
-
from accrete.fields import TranslatedCharField
|
7
|
+
from accrete.fields import TranslatedCharField, TranslatedTextField
|
8
8
|
|
9
9
|
|
10
10
|
class TenantModel(models.Model):
|
@@ -138,7 +138,7 @@ class AccessGroup(models.Model):
|
|
138
138
|
verbose_name=_('Name')
|
139
139
|
)
|
140
140
|
|
141
|
-
description =
|
141
|
+
description = TranslatedTextField(
|
142
142
|
verbose_name=_('Description'),
|
143
143
|
null=True,
|
144
144
|
blank=True
|
@@ -160,7 +160,7 @@ class AccessGroup(models.Model):
|
|
160
160
|
)
|
161
161
|
|
162
162
|
def __str__(self):
|
163
|
-
return self.name
|
163
|
+
return f'{self.name} ({self.get_apply_on_display()})'
|
164
164
|
|
165
165
|
|
166
166
|
class MemberAccessGroupRel(models.Model):
|
accrete/tenant.py
CHANGED
accrete/utils/__init__.py
CHANGED
accrete/utils/models.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import json
|
2
|
+
from django.db import connection
|
1
3
|
from django.db.models import Model, Field
|
2
4
|
|
3
5
|
|
@@ -25,3 +27,19 @@ def get_related_field(model: type[Model], field_path: str) -> Field:
|
|
25
27
|
rel_path = '__'.join(parts[:-1])
|
26
28
|
rel_model, _ = get_related_model(model, rel_path)
|
27
29
|
return rel_model._meta.get_field(parts[-1])
|
30
|
+
|
31
|
+
|
32
|
+
def translated_db_value(instance: Model, field: str) -> dict:
|
33
|
+
if not instance.pk:
|
34
|
+
return dict()
|
35
|
+
with connection.cursor() as cr:
|
36
|
+
query = """SELECT %s FROM %s WHERE id = %s""" % (
|
37
|
+
field,
|
38
|
+
instance._meta.db_table,
|
39
|
+
instance.pk
|
40
|
+
)
|
41
|
+
cr.execute(query)
|
42
|
+
row = cr.fetchone()
|
43
|
+
row = row and row[0]
|
44
|
+
db_value = row and json.loads(row) or dict()
|
45
|
+
return db_value
|
accrete/utils/views.py
CHANGED
@@ -67,7 +67,7 @@ def parse_querystring(model: type[Model], query_string: str) -> Q:
|
|
67
67
|
"""
|
68
68
|
param: query_string: JSON serializable string
|
69
69
|
Parses
|
70
|
-
[{"term": value}, "&", [{
|
70
|
+
[{"term": value}, "&", [{"term": value}, "|", {"~term": value}]]
|
71
71
|
to
|
72
72
|
Q(term=value) & (Q(term=value) | ~Q(term=value))
|
73
73
|
"""
|