creme-crm 2.7.1__py3-none-any.whl → 2.7.3__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.
- creme/__init__.py +1 -1
- creme/activities/bricks.py +1 -1
- creme/commercial/templates/commercial/bricks/approaches.html +1 -1
- creme/creme_config/bricks.py +3 -5
- creme/creme_config/static/chantilly/creme_config/css/creme_config.css +5 -1
- creme/creme_config/static/creme_config/js/tests/button-menu-editor.js +2 -2
- creme/creme_config/static/icecream/creme_config/css/creme_config.css +5 -1
- creme/creme_config/templates/creme_config/bricks/users.html +1 -1
- creme/creme_config/templates/creme_config/bricks/workflows.html +1 -1
- creme/creme_core/gui/button_menu.py +2 -2
- creme/creme_core/gui/history.py +18 -4
- creme/creme_core/gui/listview/smart_columns.py +67 -8
- creme/creme_core/gui/menu.py +1 -1
- creme/creme_core/locale/fr/LC_MESSAGES/django.mo +0 -0
- creme/creme_core/locale/fr/LC_MESSAGES/django.po +21 -9
- creme/creme_core/models/entity_filter.py +79 -18
- creme/creme_core/models/setting_value.py +3 -2
- creme/creme_core/static/chantilly/creme_core/css/list_view.css +2 -1
- creme/creme_core/static/icecream/creme_core/css/list_view.css +8 -4
- creme/creme_core/templates/authent/creme_login.html +1 -1
- creme/creme_core/templates/creme_core/auth/password_reset/base.html +1 -1
- creme/creme_core/templates/creme_core/auth/password_reset/complete.html +2 -2
- creme/creme_core/templates/creme_core/auth/password_reset/done.html +2 -2
- creme/creme_core/templates/creme_core/auth/password_reset/email/body.txt +2 -2
- creme/creme_core/templates/creme_core/bricks/base/hat-card.html +1 -1
- creme/creme_core/templates/creme_core/history/html/auxiliary-creation.html +1 -1
- creme/creme_core/templates/creme_core/history/html/auxiliary-deletion.html +1 -1
- creme/creme_core/templates/creme_core/history/html/auxiliary-edition.html +1 -1
- creme/creme_core/templates/creme_core/listview/content.html +69 -72
- creme/creme_core/templates/creme_core/templatetags/widgets/enumerator.html +1 -1
- creme/creme_core/tests/base.py +3 -2
- creme/creme_core/tests/gui/listview/test_smart_columns.py +129 -0
- creme/creme_core/tests/gui/test_history.py +142 -3
- creme/creme_core/tests/models/test_entity_filter.py +147 -2
- creme/creme_core/tests/models/test_setting_value.py +151 -129
- creme/creme_core/tests/templatetags/test_creme_core_tags.py +2 -0
- creme/creme_core/tests/templatetags/test_creme_listview.py +4 -2
- creme/creme_core/tests/templatetags/test_entity_filter.py +2 -1
- creme/creme_core/tests/views/test_creme_property.py +3 -1
- creme/creme_core/tests/views/test_search.py +1 -1
- creme/emails/forms/mail.py +22 -6
- creme/emails/templates/emails/bricks/sending-config-items.html +1 -1
- creme/emails/templates/emails/bricks/sync-config-items.html +1 -1
- creme/emails/tests/test_mail.py +50 -30
- creme/emails/tests/test_sending.py +1 -0
- creme/emails/views/mail.py +1 -0
- creme/opportunities/locale/fr/LC_MESSAGES/django.mo +0 -0
- creme/opportunities/locale/fr/LC_MESSAGES/django.po +2 -2
- creme/opportunities/populate.py +1 -1
- creme/polls/bricks.py +1 -1
- creme/reports/bricks.py +4 -3
- creme/reports/tests/test_bricks.py +108 -73
- creme/reports/tests/test_report.py +11 -2
- creme/reports/tests/test_views.py +35 -46
- creme/reports/urls.py +1 -0
- creme/reports/views/graph.py +7 -2
- creme/sketch/templates/sketch/bricks/chart.html +2 -2
- creme/sms/tests/test_campaign.py +2 -0
- creme/sms/tests/test_messaging_list.py +2 -0
- creme/sms/tests/test_template.py +2 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/METADATA +3 -2
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/RECORD +66 -65
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/WHEEL +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/entry_points.txt +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/licenses/LICENSE.txt +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.3.dist-info}/top_level.txt +0 -0
creme/__init__.py
CHANGED
creme/activities/bricks.py
CHANGED
|
@@ -156,7 +156,7 @@ class SubjectsBrick(QuerysetBrick):
|
|
|
156
156
|
id = QuerysetBrick.generate_id('activities', 'subjects')
|
|
157
157
|
verbose_name = _('Subjects')
|
|
158
158
|
|
|
159
|
-
dependencies = (Relation, Organisation) # See
|
|
159
|
+
dependencies = (Relation, Organisation) # See ParticipantsBrick.dependencies
|
|
160
160
|
relation_type_deps = (constants.REL_OBJ_ACTIVITY_SUBJECT,)
|
|
161
161
|
|
|
162
162
|
template_name = 'activities/bricks/subjects.html'
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
{% brick_table_column title=_('Related entity') %}
|
|
22
22
|
{% endif %}
|
|
23
23
|
|
|
24
|
-
{%
|
|
24
|
+
{% translate 'Created on' context 'commercial-approach' as creation_label %}
|
|
25
25
|
{% brick_table_column_for_field ctype=objects_ctype field='creation_date' status='nowrap' title=creation_label %}
|
|
26
26
|
|
|
27
27
|
{% brick_table_column_for_field ctype=objects_ctype field='description' title=_('Details') class='approaches-details' %}
|
creme/creme_config/bricks.py
CHANGED
|
@@ -1339,12 +1339,12 @@ class EntityFiltersBrick(PaginatedBrick):
|
|
|
1339
1339
|
|
|
1340
1340
|
# NB: efilters[content_type.id][user.id] -> List[EntityFilter]
|
|
1341
1341
|
efilters = defaultdict(lambda: defaultdict(list))
|
|
1342
|
-
|
|
1342
|
+
users = {} # Dict[user_id, user]
|
|
1343
1343
|
|
|
1344
1344
|
for efilter in core_models.EntityFilter.objects.filter(
|
|
1345
1345
|
filter_type=self.filter_type,
|
|
1346
1346
|
entity_type__in=[ctw.ctype for ctw in ctypes_wrappers],
|
|
1347
|
-
):
|
|
1347
|
+
).prefetch_related('user', 'user__teammates_set'):
|
|
1348
1348
|
# TODO: templatetags instead? (+ reason in tooltip if forbidden)
|
|
1349
1349
|
# efilter.view_perm = efilter.can_view(user)[0]
|
|
1350
1350
|
efilter.edition_url = reverse(self.edition_url_name, args=(efilter.id,))
|
|
@@ -1353,9 +1353,7 @@ class EntityFiltersBrick(PaginatedBrick):
|
|
|
1353
1353
|
|
|
1354
1354
|
user_id = efilter.user_id
|
|
1355
1355
|
efilters[efilter.entity_type_id][user_id].append(efilter)
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
users = get_user_model().objects.in_bulk(user_ids)
|
|
1356
|
+
users[user_id] = efilter.user
|
|
1359
1357
|
|
|
1360
1358
|
def efilter_key(efilter):
|
|
1361
1359
|
return sort_key(efilter.name)
|
|
@@ -96,7 +96,11 @@ li.ui-creme-navigation-item-id_creme_config-main > svg:nth-child(2) {
|
|
|
96
96
|
color: #818485;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
/*
|
|
99
|
+
/* Registered model config */
|
|
100
|
+
|
|
101
|
+
.creme_config-configurable-model-brick .brick-content {
|
|
102
|
+
overflow-x: auto;
|
|
103
|
+
}
|
|
100
104
|
|
|
101
105
|
.creme_config-configurable-model-brick th.configmodel-column-order {
|
|
102
106
|
width: 50px;
|
|
@@ -19,11 +19,11 @@ QUnit.module("creme.ButtonMenuEditor", new QUnitMixin(QUnitEventMixin,
|
|
|
19
19
|
var html = (
|
|
20
20
|
'<div class="buttonmenu-edit-widget" id="${id}">' +
|
|
21
21
|
'<div class="widget-available buttons-list instance-buttons">' +
|
|
22
|
-
'<div class="buttons-list-header">
|
|
22
|
+
'<div class="buttons-list-header">Available buttons</div>' +
|
|
23
23
|
'<div class="widget-container"></div>' +
|
|
24
24
|
'</div>' +
|
|
25
25
|
'<div class="widget-selected buttons-list instance-buttons">' +
|
|
26
|
-
'<div class="buttons-list-header">
|
|
26
|
+
'<div class="buttons-list-header">Selected buttons</div>' +
|
|
27
27
|
'<div class="widget-container" style="width: 100px;height: 100px;"></div>' +
|
|
28
28
|
'</div>' +
|
|
29
29
|
'</div>'
|
|
@@ -111,7 +111,11 @@ li.ui-creme-navigation-item-id_creme_config-main > svg:nth-child(2) {
|
|
|
111
111
|
color: #777;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/*
|
|
114
|
+
/* Registered model config */
|
|
115
|
+
|
|
116
|
+
.creme_config-configurable-model-brick .brick-content {
|
|
117
|
+
overflow-x: auto;
|
|
118
|
+
}
|
|
115
119
|
|
|
116
120
|
.creme_config-configurable-model-brick th.configmodel-column-order {
|
|
117
121
|
width: 50px;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{% endcomment %}
|
|
11
11
|
{% block brick_before %}
|
|
12
12
|
{% if search_fields and not request|is_ajax %}
|
|
13
|
-
<div class="creme_config-users-brick-search">{%
|
|
13
|
+
<div class="creme_config-users-brick-search">{% translate 'Filter the users' as search_label %}
|
|
14
14
|
{% widget_icon name='search' size='form-widget' label=search_label %}
|
|
15
15
|
<input type="search" class="creme_config-users-brick-search-input" placeholder="{{search_label}}" title="{% blocktranslate with fields=search_fields|join:', ' %}Filter the displayed users. Fields used: {{fields}}.{% endblocktranslate %}" />
|
|
16
16
|
</div>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{% widget_icon ctype=ctype size='brick-list' class='workflow-config-type-icon' %} {{ctype}}
|
|
19
19
|
</div>
|
|
20
20
|
<div class="workflow-config-group-action">
|
|
21
|
-
{%
|
|
21
|
+
{% blocktranslate asvar creation_label %}Create a Workflow for «{{ctype}}»{% endblocktranslate %}
|
|
22
22
|
{% brick_action id='add' url='creme_config__create_workflow'|url:ctype.id label=creation_label enabled=admin_perm %}
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
@@ -73,8 +73,8 @@ class Button:
|
|
|
73
73
|
|
|
74
74
|
# Permission string(s) ; an empty value means no permission is needed.
|
|
75
75
|
# Example: <'myapp.add_mymodel'>
|
|
76
|
-
# BEWARE: you have to use the template context variable "button.
|
|
77
|
-
# (computed from 'permissions' -- see '
|
|
76
|
+
# BEWARE: you have to use the template context variable "button.permission_error"
|
|
77
|
+
# (computed from 'permissions' -- see 'check_permissions()' ) yourself.
|
|
78
78
|
permissions: str | Sequence[str] = ''
|
|
79
79
|
|
|
80
80
|
def __eq__(self, other):
|
creme/creme_core/gui/history.py
CHANGED
|
@@ -664,7 +664,12 @@ class HTMLAuxCreationExplainer(HistoryLineExplainer):
|
|
|
664
664
|
|
|
665
665
|
# TODO: use aux_id to display an up-to-date value ??
|
|
666
666
|
ct_id, aux_id, str_obj = self.hline.modifications
|
|
667
|
-
|
|
667
|
+
try:
|
|
668
|
+
ctype = ContentType.objects.get_for_id(ct_id)
|
|
669
|
+
except ContentType.DoesNotExist:
|
|
670
|
+
ctype = None
|
|
671
|
+
|
|
672
|
+
context['auxiliary_ctype'] = ctype
|
|
668
673
|
context['auxiliary_value'] = str_obj
|
|
669
674
|
|
|
670
675
|
return context
|
|
@@ -678,7 +683,10 @@ class _AuxiliaryEditionExplainer(HistoryLineExplainer):
|
|
|
678
683
|
|
|
679
684
|
# TODO: use aux_id to display an up-to-date value ??
|
|
680
685
|
ct_id, __aux_id, str_obj = modifications[0]
|
|
681
|
-
|
|
686
|
+
try:
|
|
687
|
+
ctype = ContentType.objects.get_for_id(ct_id)
|
|
688
|
+
except ContentType.DoesNotExist:
|
|
689
|
+
ctype = None
|
|
682
690
|
|
|
683
691
|
self._aux_ctype = ctype
|
|
684
692
|
self._aux_value = str_obj
|
|
@@ -687,7 +695,7 @@ class _AuxiliaryEditionExplainer(HistoryLineExplainer):
|
|
|
687
695
|
model_class=ctype.model_class(),
|
|
688
696
|
modifications=modifications[1:],
|
|
689
697
|
),
|
|
690
|
-
]
|
|
698
|
+
] if ctype else []
|
|
691
699
|
|
|
692
700
|
def get_context(self):
|
|
693
701
|
context = super().get_context()
|
|
@@ -714,7 +722,13 @@ class HTMLAuxDeletionExplainer(HistoryLineExplainer):
|
|
|
714
722
|
context = super().get_context()
|
|
715
723
|
|
|
716
724
|
ct_id, str_obj = self.hline.modifications
|
|
717
|
-
|
|
725
|
+
|
|
726
|
+
try:
|
|
727
|
+
ctype = ContentType.objects.get_for_id(ct_id)
|
|
728
|
+
except ContentType.DoesNotExist:
|
|
729
|
+
ctype = None
|
|
730
|
+
|
|
731
|
+
context['auxiliary_ctype'] = ctype
|
|
718
732
|
context['auxiliary_value'] = str_obj
|
|
719
733
|
|
|
720
734
|
return context
|
|
@@ -84,30 +84,89 @@ class _ModelSmartColumnsRegistry:
|
|
|
84
84
|
|
|
85
85
|
return rtype
|
|
86
86
|
|
|
87
|
-
def register_function_field(self, func_field_name: str) -> _ModelSmartColumnsRegistry:
|
|
88
|
-
self._cells.append((EntityCellFunctionField, func_field_name))
|
|
89
|
-
return self
|
|
90
|
-
|
|
91
87
|
def register_field(self, field_name: str) -> _ModelSmartColumnsRegistry:
|
|
88
|
+
"""Register a field by its name."""
|
|
92
89
|
self._cells.append((EntityCellRegularField, field_name))
|
|
93
90
|
return self
|
|
94
91
|
|
|
92
|
+
def register_function_field(self, func_field_name: str) -> _ModelSmartColumnsRegistry:
|
|
93
|
+
"""Register a function field by its name."""
|
|
94
|
+
self._cells.append((EntityCellFunctionField, func_field_name))
|
|
95
|
+
return self
|
|
96
|
+
|
|
95
97
|
def register_relationtype(self, rtype_id: str) -> _ModelSmartColumnsRegistry:
|
|
98
|
+
"""Register a RelationType by its ID."""
|
|
96
99
|
self._cells.append((EntityCellRelation, rtype_id))
|
|
97
100
|
return self
|
|
98
101
|
|
|
102
|
+
def _unregister(self, cell_type, cell_name, error_message) -> _ModelSmartColumnsRegistry:
|
|
103
|
+
try:
|
|
104
|
+
self._cells.remove((cell_type, cell_name))
|
|
105
|
+
except ValueError as e:
|
|
106
|
+
raise ValueError(error_message.format(cell_name)) from e
|
|
107
|
+
|
|
108
|
+
return self
|
|
109
|
+
|
|
110
|
+
def unregister_field(self, field_name: str) -> _ModelSmartColumnsRegistry:
|
|
111
|
+
"""Unregister a field by its name."""
|
|
112
|
+
return self._unregister(
|
|
113
|
+
cell_type=EntityCellRegularField, cell_name=field_name,
|
|
114
|
+
error_message='The field "{}" in not registered.',
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def unregister_function_field(self, func_field_name: str) -> _ModelSmartColumnsRegistry:
|
|
118
|
+
"""Unregister a function field by its name."""
|
|
119
|
+
return self._unregister(
|
|
120
|
+
cell_type=EntityCellFunctionField, cell_name=func_field_name,
|
|
121
|
+
error_message='The function field "{}" in not registered.',
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def unregister_relationtype(self, rtype_id: str) -> _ModelSmartColumnsRegistry:
|
|
125
|
+
"""Unregister a RelationType by its ID."""
|
|
126
|
+
return self._unregister(
|
|
127
|
+
cell_type=EntityCellRelation, cell_name=rtype_id,
|
|
128
|
+
error_message='The relation type "{}" in not registered.',
|
|
129
|
+
)
|
|
130
|
+
|
|
99
131
|
|
|
100
132
|
class SmartColumnsRegistry:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
"""Registry to indicate with EntityCells should be selected by default by
|
|
134
|
+
the form for HeaderFilters (because these columns are often selected for
|
|
135
|
+
this model).
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
registry = SmartColumnsRegistry()
|
|
139
|
+
registry.register_model(
|
|
140
|
+
Contact
|
|
141
|
+
).register_field('last_name').register_field('first_name')
|
|
142
|
+
registry.register_model(
|
|
143
|
+
Organisation
|
|
144
|
+
).register_field('name').register_relationtype(REL_SUB_PROVIDER)
|
|
145
|
+
|
|
146
|
+
Hint: you'll probably use <CremeAppConfig.register_smart_columns()>.
|
|
147
|
+
"""
|
|
148
|
+
_model_registries: DefaultDict[type[CremeEntity], _ModelSmartColumnsRegistry]
|
|
149
|
+
|
|
150
|
+
def __init__(self):
|
|
151
|
+
self._model_registries = defaultdict(_ModelSmartColumnsRegistry)
|
|
105
152
|
|
|
106
153
|
def get_cells(self, model: type[CremeEntity]) -> list[EntityCell]:
|
|
154
|
+
"""Get the "smart" cells for a given model."""
|
|
107
155
|
return self._model_registries[model]._get_cells(model)
|
|
108
156
|
|
|
157
|
+
# TODO: rename just "model" (because it's used to retrieve existing too)
|
|
109
158
|
def register_model(self, model: type[CremeEntity]) -> _ModelSmartColumnsRegistry:
|
|
159
|
+
"""Get the sub-registry containing the registered cells for a given model.
|
|
160
|
+
Useful to register & unregister cells by chaining with:
|
|
161
|
+
- [un]register_field()
|
|
162
|
+
- [un]register_function_field()
|
|
163
|
+
- [un]register_relationtype()
|
|
164
|
+
"""
|
|
110
165
|
return self._model_registries[model]
|
|
111
166
|
|
|
167
|
+
def clear_model(self, model: type[CremeEntity]) -> None:
|
|
168
|
+
"""Remove the sub-registry related to a madel (& so all related cells)."""
|
|
169
|
+
del self._model_registries[model]
|
|
170
|
+
|
|
112
171
|
|
|
113
172
|
smart_columns_registry = SmartColumnsRegistry()
|
creme/creme_core/gui/menu.py
CHANGED
|
@@ -409,7 +409,7 @@ class MenuRegistry:
|
|
|
409
409
|
# TODO: to be removed in creme2.8
|
|
410
410
|
if hasattr(entry_cls, '_has_perm'):
|
|
411
411
|
logger.critical(
|
|
412
|
-
'The class %s still defines a method "
|
|
412
|
+
'The class %s still defines a method "_has_perm()"; '
|
|
413
413
|
'define the new method "check_permissions()" instead.',
|
|
414
414
|
entry_cls,
|
|
415
415
|
)
|
|
Binary file
|
|
@@ -8,7 +8,7 @@ msgid ""
|
|
|
8
8
|
msgstr ""
|
|
9
9
|
"Project-Id-Version: Creme Creme-Core 2.7\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: \n"
|
|
11
|
-
"POT-Creation-Date: 2025-
|
|
11
|
+
"POT-Creation-Date: 2025-10-20 17:01+0200\n"
|
|
12
12
|
"Last-Translator: Hybird <contact@hybird.org>\n"
|
|
13
13
|
"Language: fr\n"
|
|
14
14
|
"MIME-Version: 1.0\n"
|
|
@@ -2445,22 +2445,34 @@ msgstr "Filtre de fiche"
|
|
|
2445
2445
|
msgid "Filters of Entity"
|
|
2446
2446
|
msgstr "Filtres de fiche"
|
|
2447
2447
|
|
|
2448
|
-
msgid "This filter can't be
|
|
2449
|
-
msgstr "Ce filtre ne peut être
|
|
2448
|
+
msgid "This filter can't be deleted (system filter)"
|
|
2449
|
+
msgstr "Ce filtre ne peut être supprimé (filtre système)"
|
|
2450
2450
|
|
|
2451
2451
|
msgid "You are not allowed to access to this app"
|
|
2452
2452
|
msgstr "Vous n'avez pas la permission d'accéder à cette application"
|
|
2453
2453
|
|
|
2454
|
-
msgid "Only superusers can
|
|
2454
|
+
msgid "Only superusers can delete this filter (no owner)"
|
|
2455
2455
|
msgstr ""
|
|
2456
|
-
"Seuls les super-utilisateurs peuvent
|
|
2456
|
+
"Seuls les super-utilisateurs peuvent supprimer ce filtre (pas de "
|
|
2457
2457
|
"propriétaire)"
|
|
2458
2458
|
|
|
2459
|
-
msgid ""
|
|
2460
|
-
|
|
2459
|
+
msgid "You are not allowed to delete this filter (you are not the owner)"
|
|
2460
|
+
msgstr ""
|
|
2461
|
+
"Vous n'avez pas la permission de supprimer ce filtre (il ne vous appartient "
|
|
2462
|
+
"pas)"
|
|
2463
|
+
|
|
2464
|
+
msgid "Only superusers can edit this filter (no owner)"
|
|
2465
|
+
msgstr ""
|
|
2466
|
+
"Seuls les super-utilisateurs peuvent modifier ce filtre (pas de propriétaire)"
|
|
2467
|
+
|
|
2468
|
+
msgid "You are not allowed to edit this filter (you are not the owner)"
|
|
2469
|
+
msgstr ""
|
|
2470
|
+
"Vous n'avez pas la permission de modifier ce filtre (il ne vous appartient "
|
|
2471
|
+
"pas)"
|
|
2472
|
+
|
|
2473
|
+
msgid "You are not allowed to view this filter (you are not the owner)"
|
|
2461
2474
|
msgstr ""
|
|
2462
|
-
"Vous n'avez pas la permission de voir
|
|
2463
|
-
"vous appartient pas)"
|
|
2475
|
+
"Vous n'avez pas la permission de voir ce filtre (il ne vous appartient pas)"
|
|
2464
2476
|
|
|
2465
2477
|
msgid "A condition can not reference its own filter."
|
|
2466
2478
|
msgstr "Une condition ne peut pas référencer son propre filtre."
|
|
@@ -423,15 +423,24 @@ class EntityFilter(models.Model): # TODO: CremeModel? MinionModel?
|
|
|
423
423
|
"""
|
|
424
424
|
return all(c.handler.applicable_on_entity_base for c in self.get_conditions())
|
|
425
425
|
|
|
426
|
+
# TODO: can_*() methods:
|
|
427
|
+
# - move to a registry?
|
|
428
|
+
# - factorise
|
|
426
429
|
def can_delete(self, user: CremeUser) -> tuple[bool, str]:
|
|
430
|
+
# if not self.is_custom:
|
|
431
|
+
# return False, gettext("This filter can't be edited/deleted")
|
|
432
|
+
#
|
|
433
|
+
# return self.can_edit(user)
|
|
434
|
+
assert not user.is_team
|
|
435
|
+
|
|
427
436
|
if not self.is_custom:
|
|
428
|
-
return False, gettext("This filter can't be
|
|
437
|
+
return False, gettext("This filter can't be deleted (system filter)")
|
|
429
438
|
|
|
430
|
-
|
|
439
|
+
if user.is_staff:
|
|
440
|
+
return True, 'OK'
|
|
431
441
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
assert not user.is_team
|
|
442
|
+
if user.is_superuser and not self.is_private:
|
|
443
|
+
return True, 'OK'
|
|
435
444
|
|
|
436
445
|
if not user.has_perm(self.entity_type.app_label):
|
|
437
446
|
return False, gettext('You are not allowed to access to this app')
|
|
@@ -444,42 +453,94 @@ class EntityFilter(models.Model): # TODO: CremeModel? MinionModel?
|
|
|
444
453
|
(True, 'OK')
|
|
445
454
|
if user.is_superuser
|
|
446
455
|
or SettingValue.objects.get_4_key(global_filters_edition_key).value else
|
|
447
|
-
|
|
448
|
-
(False, gettext('Only superusers can edit/delete this filter (no owner)'))
|
|
456
|
+
(False, gettext('Only superusers can delete this filter (no owner)'))
|
|
449
457
|
)
|
|
450
458
|
|
|
459
|
+
if not self.user.is_team:
|
|
460
|
+
if self.user_id == user.id:
|
|
461
|
+
return True, 'OK'
|
|
462
|
+
elif user.id in self.user.teammates: # TODO: move in a User method ??
|
|
463
|
+
return True, 'OK'
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
False,
|
|
467
|
+
gettext('You are not allowed to delete this filter (you are not the owner)'),
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def can_edit(self, user: CremeUser) -> tuple[bool, str]:
|
|
471
|
+
assert not user.is_team
|
|
472
|
+
|
|
473
|
+
if not user.has_perm(self.entity_type.app_label):
|
|
474
|
+
return False, gettext('You are not allowed to access to this app')
|
|
475
|
+
|
|
451
476
|
if user.is_staff:
|
|
452
477
|
return True, 'OK'
|
|
453
478
|
|
|
454
479
|
if user.is_superuser and not self.is_private:
|
|
455
480
|
return True, 'OK'
|
|
456
481
|
|
|
482
|
+
if not self.user_id: # All users allowed
|
|
483
|
+
from .setting_value import SettingValue
|
|
484
|
+
|
|
485
|
+
return (
|
|
486
|
+
(True, 'OK')
|
|
487
|
+
if user.is_superuser
|
|
488
|
+
or SettingValue.objects.get_4_key(global_filters_edition_key).value else
|
|
489
|
+
# (False, gettext('Only superusers can edit/delete this filter (no owner)'))
|
|
490
|
+
(False, gettext('Only superusers can edit this filter (no owner)'))
|
|
491
|
+
)
|
|
492
|
+
|
|
457
493
|
if not self.user.is_team:
|
|
458
494
|
if self.user_id == user.id:
|
|
459
495
|
return True, 'OK'
|
|
460
|
-
elif user.id in self.user.teammates:
|
|
496
|
+
elif user.id in self.user.teammates:
|
|
461
497
|
return True, 'OK'
|
|
462
498
|
|
|
463
499
|
return (
|
|
464
500
|
False,
|
|
465
501
|
gettext(
|
|
466
|
-
'You are not allowed to view/edit/delete this filter '
|
|
502
|
+
# 'You are not allowed to view/edit/delete this filter '
|
|
503
|
+
'You are not allowed to edit this filter '
|
|
467
504
|
'(you are not the owner)'
|
|
468
505
|
)
|
|
469
506
|
)
|
|
470
507
|
|
|
471
508
|
# def can_view(self, user: CremeUser, content_type=_NOT_PASSED) -> tuple[bool, str]:
|
|
472
509
|
def can_view(self, user: CremeUser) -> tuple[bool, str]:
|
|
473
|
-
# if content_type is not _NOT_PASSED:
|
|
474
|
-
# warnings.warn(
|
|
475
|
-
# 'In EntityFilter.can_view(), the argument "content_type" is deprecated.',
|
|
476
|
-
# DeprecationWarning,
|
|
477
|
-
# )
|
|
478
|
-
#
|
|
479
|
-
# if content_type and content_type != self.entity_type:
|
|
480
|
-
# return False, 'Invalid entity type'
|
|
510
|
+
# # if content_type is not _NOT_PASSED:
|
|
511
|
+
# # warnings.warn(
|
|
512
|
+
# # 'In EntityFilter.can_view(), the argument "content_type" is deprecated.',
|
|
513
|
+
# # DeprecationWarning,
|
|
514
|
+
# # )
|
|
515
|
+
# #
|
|
516
|
+
# # if content_type and content_type != self.entity_type:
|
|
517
|
+
# # return False, 'Invalid entity type'
|
|
518
|
+
# return self.can_edit(user)
|
|
519
|
+
assert not user.is_team
|
|
481
520
|
|
|
482
|
-
|
|
521
|
+
if user.is_staff:
|
|
522
|
+
return True, 'OK'
|
|
523
|
+
|
|
524
|
+
if not self.is_private:
|
|
525
|
+
return (
|
|
526
|
+
(True, 'OK')
|
|
527
|
+
if user.is_superuser or user.has_perm(self.entity_type.app_label) else
|
|
528
|
+
(False, gettext('You are not allowed to access to this app'))
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
if not self.user.is_team:
|
|
532
|
+
if self.user_id == user.id:
|
|
533
|
+
return True, 'OK'
|
|
534
|
+
elif user.id in self.user.teammates:
|
|
535
|
+
return True, 'OK'
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
False,
|
|
539
|
+
gettext(
|
|
540
|
+
'You are not allowed to view this filter '
|
|
541
|
+
'(you are not the owner)'
|
|
542
|
+
),
|
|
543
|
+
)
|
|
483
544
|
|
|
484
545
|
def check_cycle(self, conditions: Iterable[EntityFilterCondition]) -> None:
|
|
485
546
|
assert self.id
|
|
@@ -80,7 +80,7 @@ class SettingValueManager(models.Manager):
|
|
|
80
80
|
"""Get the SettingValue value or default if not filled.
|
|
81
81
|
|
|
82
82
|
@param key: A SettingKey instance, or an ID of SettingKey (string).
|
|
83
|
-
@param default: (optional) If given & the SettingValue does not exist
|
|
83
|
+
@param default: (optional) If given & the SettingValue does not exist.
|
|
84
84
|
"""
|
|
85
85
|
try:
|
|
86
86
|
return self.get_4_key(key, default=default).value
|
|
@@ -96,7 +96,8 @@ class SettingValueManager(models.Manager):
|
|
|
96
96
|
BEWARE: you should probably just use the "value" attribute of this dummy object
|
|
97
97
|
(it's not a true SettingValue instance -- see 'DummySettingValue').
|
|
98
98
|
@return: A SettingValue instance.
|
|
99
|
-
@raise: SettingValue.DoesNotExist
|
|
99
|
+
@raise: SettingValue.DoesNotExist if the SettingValue does not exist &
|
|
100
|
+
no default value has been given.
|
|
100
101
|
@raise: KeyError if the SettingKey is not registered.
|
|
101
102
|
"""
|
|
102
103
|
return next(iter(self.get_4_keys({'key': key, **kwargs}).values()))
|
|
@@ -259,7 +259,8 @@
|
|
|
259
259
|
vertical-align: top;
|
|
260
260
|
|
|
261
261
|
border-left: 0 !important;
|
|
262
|
-
border-right: 0 !important;
|
|
262
|
+
/* border-right: 0 !important; */
|
|
263
|
+
border-right: 0;
|
|
263
264
|
|
|
264
265
|
color: #222;
|
|
265
266
|
}
|
|
@@ -882,9 +883,12 @@
|
|
|
882
883
|
}
|
|
883
884
|
|
|
884
885
|
/* right border fix for header rows, and body rows */
|
|
885
|
-
|
|
886
|
-
.listview thead .lv-
|
|
887
|
-
.listview
|
|
886
|
+
/*.listview thead .lv-columns-header th:last-child, */
|
|
887
|
+
.listview thead .lv-columns-header th:nth-last-child(1 of :not(.lv-column-hidden)),
|
|
888
|
+
/* .listview thead .lv-search-header th:last-child, */
|
|
889
|
+
.listview thead .lv-search-header th:nth-last-child(1 of :not(.lv-column-hidden)),
|
|
890
|
+
/*.listview > tbody > tr > td:last-child { */
|
|
891
|
+
.listview > tbody > tr > td:nth-last-child(1 of :not(.lv-cell-hidden)) {
|
|
888
892
|
border-right: 1px solid rgba(84, 130, 149, 0.3);
|
|
889
893
|
}
|
|
890
894
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
{% if world_settings.password_reset_enabled %}
|
|
26
26
|
<div class="lost-password">
|
|
27
|
-
<a href="{% url 'creme_core__reset_password' %}">{%
|
|
27
|
+
<a href="{% url 'creme_core__reset_password' %}">{% translate 'You lost your password?' %}</a>
|
|
28
28
|
</div>
|
|
29
29
|
{% endif %}
|
|
30
30
|
{% endblock %}
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
}
|
|
110
110
|
{% endblock %}
|
|
111
111
|
</style>
|
|
112
|
-
<title>{%
|
|
112
|
+
<title>{% translate 'Reset your password' %} - Creme CRM</title>
|
|
113
113
|
<link rel="shortcut icon" href="{% media_url 'common/images/favicon.ico' %}" type="image/x-icon" />
|
|
114
114
|
</head>
|
|
115
115
|
<body>
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
{% endblock %}
|
|
29
29
|
|
|
30
30
|
{% block main %}
|
|
31
|
-
<p>{%
|
|
31
|
+
<p>{% translate 'Your new password has been saved.' %}</p>
|
|
32
32
|
<div class="login">
|
|
33
|
-
<a href="{% url 'creme_login' %}">{%
|
|
33
|
+
<a href="{% url 'creme_login' %}">{% translate 'Log in' %}</a>
|
|
34
34
|
</div>
|
|
35
35
|
{% endblock %}
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
{% endblock %}
|
|
11
11
|
|
|
12
12
|
{% block main %}
|
|
13
|
-
<p>{%
|
|
14
|
-
<p>{%
|
|
13
|
+
<p>{% translate 'If the given email address exists in Creme CRM database then instructions to reset your password has been sent.' %}</p>
|
|
14
|
+
<p>{% translate 'They should not be long!' %}</p>
|
|
15
15
|
{% endblock %}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% load i18n %}{% load templatize from creme_core_tags %}{% url 'creme_core__password_reset_confirm' uidb64=uid token=token as rel_url %}{% templatize '{{protocol}}://{{domain}}{{rel_url}}' as url %}{%
|
|
1
|
+
{% load i18n %}{% load templatize from creme_core_tags %}{% url 'creme_core__password_reset_confirm' uidb64=uid token=token as rel_url %}{% templatize '{{protocol}}://{{domain}}{{rel_url}}' as url %}{% blocktranslate with username=user.username %}Hi,
|
|
2
2
|
|
|
3
3
|
You receive this email because a reset of your password for {{software}} has been requested.
|
|
4
4
|
|
|
@@ -9,4 +9,4 @@ Here your username in case you forgot it too: {{username}}
|
|
|
9
9
|
Thanks for show an interest in {{software}}.
|
|
10
10
|
|
|
11
11
|
Your {{software}} administrator
|
|
12
|
-
{%
|
|
12
|
+
{% endblocktranslate %}
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
{% block clone_button %}
|
|
101
101
|
{% get_cloning_info entity=object user=user as cloning %}
|
|
102
102
|
{% if cloning.enabled %}
|
|
103
|
-
<div class='
|
|
103
|
+
<div class='card-action'>
|
|
104
104
|
{% if cloning.error %}
|
|
105
105
|
{% blocktranslate asvar clone_button_help with error=cloning.error %}Cloning is forbidden ({{error}}){% endblocktranslate %}
|
|
106
106
|
{% brick_card_button action='' label=_('Clone') help_text=clone_button_help url='#' icon='clone' enabled=False %}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% extends 'creme_core/history/html/base.html' %}
|
|
2
2
|
{% load i18n %}
|
|
3
3
|
{% block content %}
|
|
4
|
-
{% blocktranslate %}“{{auxiliary_ctype}}“ added: {{auxiliary_value}}{% endblocktranslate %}
|
|
4
|
+
{% blocktranslate with auxiliary_ctype=auxiliary_ctype|default_if_none:'??' %}“{{auxiliary_ctype}}“ added: {{auxiliary_value}}{% endblocktranslate %}
|
|
5
5
|
{% endblock %}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% extends 'creme_core/history/html/base.html' %}
|
|
2
2
|
{% load i18n %}
|
|
3
3
|
{% block content %}
|
|
4
|
-
{% blocktranslate %}“{{auxiliary_ctype}}“ deleted: “{{auxiliary_value}}”{% endblocktranslate %}
|
|
4
|
+
{% blocktranslate with auxiliary_ctype=auxiliary_ctype|default_if_none:'??' %}“{{auxiliary_ctype}}“ deleted: “{{auxiliary_value}}”{% endblocktranslate %}
|
|
5
5
|
{% endblock %}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<div class="toggle-icon-container toggle-icon-expand" title="{% translate 'Expand' %}"><div class="toggle-icon"></div></div>
|
|
9
9
|
<div class="toggle-icon-container toggle-icon-collapse" title="{% translate 'Close' %}"><div class="toggle-icon"></div></div>
|
|
10
10
|
|
|
11
|
-
<span class="history-line-title">{% blocktranslate %}“{{auxiliary_ctype}}“ edited: {{auxiliary_value}}{% endblocktranslate %}</span>
|
|
11
|
+
<span class="history-line-title">{% blocktranslate with auxiliary_ctype=auxiliary_ctype|default_if_none:'??' %}“{{auxiliary_ctype}}“ edited: {{auxiliary_value}}{% endblocktranslate %}</span>
|
|
12
12
|
</div>
|
|
13
13
|
<ul class="history-line-details">{% for modif in modifications %}<li>{{modif}}</li>{% endfor %}</ul>
|
|
14
14
|
{% endblock %}
|