creme-crm 2.7.1__py3-none-any.whl → 2.7.2__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/commercial/templates/commercial/bricks/approaches.html +1 -1
- creme/creme_config/bricks.py +3 -5
- creme/creme_config/static/creme_config/js/tests/button-menu-editor.js +2 -2
- 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/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/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/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/templatetags/widgets/enumerator.html +1 -1
- 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/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/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/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.2.dist-info}/METADATA +1 -1
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.2.dist-info}/RECORD +48 -48
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.2.dist-info}/WHEEL +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.2.dist-info}/entry_points.txt +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.2.dist-info}/licenses/LICENSE.txt +0 -0
- {creme_crm-2.7.1.dist-info → creme_crm-2.7.2.dist-info}/top_level.txt +0 -0
creme/__init__.py
CHANGED
|
@@ -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)
|
|
@@ -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>'
|
|
@@ -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
|
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
|
|
@@ -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 %}
|
|
@@ -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 %}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% else %}
|
|
6
6
|
<a data-action="popover">
|
|
7
7
|
{% if summary is None %}
|
|
8
|
-
{%
|
|
8
|
+
{% blocktranslate with count=length %}{{count}} items{% endblocktranslate %}
|
|
9
9
|
{% else %}
|
|
10
10
|
{% format_string_brace_named summary count=length %}
|
|
11
11
|
{% endif %}
|
|
@@ -1413,7 +1413,47 @@ class HistoryRenderTestCase(CremeTestCase):
|
|
|
1413
1413
|
self.render_line(hline, user),
|
|
1414
1414
|
)
|
|
1415
1415
|
|
|
1416
|
-
def
|
|
1416
|
+
def test_render_auxiliary_creation__invalid_ctype_id(self):
|
|
1417
|
+
user = self.get_root_user()
|
|
1418
|
+
gainax = FakeOrganisation.objects.create(user=user, name='Gainax')
|
|
1419
|
+
address = FakeAddress.objects.create(
|
|
1420
|
+
entity=gainax, country='Japan', city='Tokyo',
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
hline = self.get_hline()
|
|
1424
|
+
self.assertEqual(history.TYPE_AUX_CREATION, hline.type)
|
|
1425
|
+
# print(hline.value)
|
|
1426
|
+
|
|
1427
|
+
self.assertListEqual(
|
|
1428
|
+
[
|
|
1429
|
+
'Gainax',
|
|
1430
|
+
ContentType.objects.get_for_model(FakeAddress).id,
|
|
1431
|
+
address.id,
|
|
1432
|
+
'Tokyo Japan',
|
|
1433
|
+
],
|
|
1434
|
+
json.loads(hline.value),
|
|
1435
|
+
)
|
|
1436
|
+
hline.value = json.dumps([
|
|
1437
|
+
'Gainax',
|
|
1438
|
+
self.UNUSED_PK, # <==
|
|
1439
|
+
address.id,
|
|
1440
|
+
'Tokyo Japan',
|
|
1441
|
+
])
|
|
1442
|
+
hline.save()
|
|
1443
|
+
|
|
1444
|
+
self.assertHTMLEqual(
|
|
1445
|
+
'<div class="history-line history-line-auxiliary_creation">'
|
|
1446
|
+
'{title}'
|
|
1447
|
+
'<div>'.format(
|
|
1448
|
+
title=_('“%(auxiliary_ctype)s“ added: %(auxiliary_value)s') % {
|
|
1449
|
+
'auxiliary_ctype': '??',
|
|
1450
|
+
'auxiliary_value': escape(address),
|
|
1451
|
+
},
|
|
1452
|
+
),
|
|
1453
|
+
self.render_line(hline, user),
|
|
1454
|
+
)
|
|
1455
|
+
|
|
1456
|
+
def test_render_auxiliary_edition(self):
|
|
1417
1457
|
user = self.get_root_user()
|
|
1418
1458
|
|
|
1419
1459
|
country = 'Japan'
|
|
@@ -1467,7 +1507,7 @@ class HistoryRenderTestCase(CremeTestCase):
|
|
|
1467
1507
|
self.render_line(hline, user),
|
|
1468
1508
|
)
|
|
1469
1509
|
|
|
1470
|
-
def
|
|
1510
|
+
def test_render_auxiliary_edition__entity(self):
|
|
1471
1511
|
"""FakeInvoiceLine:
|
|
1472
1512
|
- an auxiliary + CremeEntity at the same time.
|
|
1473
1513
|
- DecimalField.
|
|
@@ -1542,7 +1582,7 @@ class HistoryRenderTestCase(CremeTestCase):
|
|
|
1542
1582
|
self.render_line(hline, user),
|
|
1543
1583
|
)
|
|
1544
1584
|
|
|
1545
|
-
def
|
|
1585
|
+
def test_render_auxiliary_edition__m2m(self):
|
|
1546
1586
|
user = self.get_root_user()
|
|
1547
1587
|
cat = FakeTodoCategory.objects.create(name='Very <b>Important</b>')
|
|
1548
1588
|
|
|
@@ -1591,6 +1631,66 @@ class HistoryRenderTestCase(CremeTestCase):
|
|
|
1591
1631
|
self.render_line(hline, user),
|
|
1592
1632
|
)
|
|
1593
1633
|
|
|
1634
|
+
def test_render_auxiliary_edition__invalid_ctype_id(self):
|
|
1635
|
+
user = self.get_root_user()
|
|
1636
|
+
|
|
1637
|
+
gainax = FakeOrganisation.objects.create(user=user, name='Gainax')
|
|
1638
|
+
address = FakeAddress.objects.create(entity=gainax, country='Japan')
|
|
1639
|
+
|
|
1640
|
+
address = self.refresh(address)
|
|
1641
|
+
address.department = 'Tokyo'
|
|
1642
|
+
address.save()
|
|
1643
|
+
|
|
1644
|
+
hline = self.get_hline()
|
|
1645
|
+
self.assertEqual(history.TYPE_AUX_EDITION, hline.type)
|
|
1646
|
+
self.assertListEqual(
|
|
1647
|
+
[
|
|
1648
|
+
'Gainax',
|
|
1649
|
+
[
|
|
1650
|
+
ContentType.objects.get_for_model(FakeAddress).id,
|
|
1651
|
+
address.id,
|
|
1652
|
+
'Tokyo Japan',
|
|
1653
|
+
],
|
|
1654
|
+
['department', 'Tokyo'],
|
|
1655
|
+
],
|
|
1656
|
+
json.loads(hline.value),
|
|
1657
|
+
)
|
|
1658
|
+
hline.value = json.dumps([
|
|
1659
|
+
'Gainax',
|
|
1660
|
+
[
|
|
1661
|
+
self.UNUSED_PK, # <==
|
|
1662
|
+
address.id,
|
|
1663
|
+
'Tokyo Japan',
|
|
1664
|
+
],
|
|
1665
|
+
['department', 'Tokyo'],
|
|
1666
|
+
])
|
|
1667
|
+
hline.save()
|
|
1668
|
+
self.maxDiff = None
|
|
1669
|
+
self.assertHTMLEqual(
|
|
1670
|
+
'<div class="history-line history-line-auxiliary_edition'
|
|
1671
|
+
' history-line-collapsable history-line-collapsed">'
|
|
1672
|
+
' <div class="history-line-main">'
|
|
1673
|
+
' <div class="toggle-icon-container toggle-icon-expand" title="{expand_title}">'
|
|
1674
|
+
' <div class="toggle-icon"></div>'
|
|
1675
|
+
' </div>'
|
|
1676
|
+
' <div class="toggle-icon-container toggle-icon-collapse"'
|
|
1677
|
+
' title="{collapse_title}">'
|
|
1678
|
+
' <div class="toggle-icon"></div>'
|
|
1679
|
+
' </div>'
|
|
1680
|
+
' <span class="history-line-title">{title}</span>'
|
|
1681
|
+
' </div>'
|
|
1682
|
+
' <ul class="history-line-details"></ul>'
|
|
1683
|
+
'<div>'.format(
|
|
1684
|
+
title=_('“%(auxiliary_ctype)s“ edited: %(auxiliary_value)s') % {
|
|
1685
|
+
'auxiliary_ctype': '??',
|
|
1686
|
+
'auxiliary_value': address,
|
|
1687
|
+
},
|
|
1688
|
+
expand_title=_('Expand'),
|
|
1689
|
+
collapse_title=_('Close'),
|
|
1690
|
+
),
|
|
1691
|
+
self.render_line(hline, user),
|
|
1692
|
+
)
|
|
1693
|
+
|
|
1594
1694
|
def test_render_auxiliary_deletion(self):
|
|
1595
1695
|
user = self.get_root_user()
|
|
1596
1696
|
gainax = FakeOrganisation.objects.create(user=user, name='Gainax')
|
|
@@ -1615,6 +1715,45 @@ class HistoryRenderTestCase(CremeTestCase):
|
|
|
1615
1715
|
self.render_line(hline, user),
|
|
1616
1716
|
)
|
|
1617
1717
|
|
|
1718
|
+
def test_render_auxiliary_deletion__invalid_ctype_id(self):
|
|
1719
|
+
user = self.get_root_user()
|
|
1720
|
+
gainax = FakeOrganisation.objects.create(user=user, name='Gainax')
|
|
1721
|
+
address = FakeAddress.objects.create(
|
|
1722
|
+
entity=gainax, country='Japan', city='Tokyo',
|
|
1723
|
+
)
|
|
1724
|
+
|
|
1725
|
+
address.delete()
|
|
1726
|
+
|
|
1727
|
+
hline = self.get_hline()
|
|
1728
|
+
self.assertEqual(history.TYPE_AUX_DELETION, hline.type)
|
|
1729
|
+
self.assertListEqual(
|
|
1730
|
+
[
|
|
1731
|
+
'Gainax',
|
|
1732
|
+
ContentType.objects.get_for_model(FakeAddress).id,
|
|
1733
|
+
'Tokyo Japan',
|
|
1734
|
+
],
|
|
1735
|
+
json.loads(hline.value),
|
|
1736
|
+
)
|
|
1737
|
+
|
|
1738
|
+
hline.value = json.dumps([
|
|
1739
|
+
'Gainax',
|
|
1740
|
+
self.UNUSED_PK, # <==
|
|
1741
|
+
'Tokyo Japan',
|
|
1742
|
+
])
|
|
1743
|
+
|
|
1744
|
+
self.assertHTMLEqual(
|
|
1745
|
+
format_html(
|
|
1746
|
+
'<div class="history-line history-line-auxiliary_deletion">'
|
|
1747
|
+
'{title}'
|
|
1748
|
+
'<div>',
|
|
1749
|
+
title=_('“%(auxiliary_ctype)s“ deleted: “%(auxiliary_value)s”') % {
|
|
1750
|
+
'auxiliary_ctype': '??',
|
|
1751
|
+
'auxiliary_value': address,
|
|
1752
|
+
},
|
|
1753
|
+
),
|
|
1754
|
+
self.render_line(hline, user),
|
|
1755
|
+
)
|
|
1756
|
+
|
|
1618
1757
|
def test_render_trash(self):
|
|
1619
1758
|
user = self.get_root_user()
|
|
1620
1759
|
gainax = self.refresh(
|