0005-auth_saml-move-related-object-code-to-authenticators.patch
src/authentic2/apps/authenticators/journal_event_types.py | ||
---|---|---|
104 | 104 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
105 | 105 |
authenticator = authenticator or event.get_data('authenticator_name') |
106 | 106 |
return _('deletion of authenticator "%s"') % authenticator |
107 | ||
108 | ||
109 |
class AuthenticatorRelatedObjectEvents(AuthenticatorEvents): |
|
110 |
@classmethod |
|
111 |
def record(cls, *, user, session, related_object, data=None): |
|
112 |
data = data or {} |
|
113 |
data.update({'related_object': related_object.get_journal_text()}) |
|
114 |
super().record(user=user, session=session, authenticator=related_object.authenticator, data=data) |
|
115 | ||
116 | ||
117 |
class AuthenticatorRelatedObjectCreation(AuthenticatorRelatedObjectEvents): |
|
118 |
name = 'authenticator.related_object.creation' |
|
119 |
label = _('Authenticator related object creation') |
|
120 | ||
121 |
@classmethod |
|
122 |
def get_message(cls, event, context): |
|
123 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
124 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
125 |
related_object = event.get_data('related_object') |
|
126 |
if context != authenticator: |
|
127 |
return _('creation of object "{related_object}" in authenticator "{authenticator}"').format( |
|
128 |
related_object=related_object, authenticator=authenticator |
|
129 |
) |
|
130 |
else: |
|
131 |
return _('creation of object "%s"') % related_object |
|
132 | ||
133 | ||
134 |
class AuthenticatorRelatedObjectEdit(AuthenticatorRelatedObjectEvents): |
|
135 |
name = 'authenticator.related_object.edit' |
|
136 |
label = _('Authenticator related object edit') |
|
137 | ||
138 |
@classmethod |
|
139 |
def record(cls, *, user, session, form): |
|
140 |
super().record( |
|
141 |
user=user, |
|
142 |
session=session, |
|
143 |
related_object=form.instance, |
|
144 |
data=form_to_old_new(form), |
|
145 |
) |
|
146 | ||
147 |
@classmethod |
|
148 |
def get_message(cls, event, context): |
|
149 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
150 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
151 |
related_object = event.get_data('related_object') |
|
152 |
new = event.get_data('new') or {} |
|
153 |
edited_attributes = ', '.join(new) or '' |
|
154 |
if context != authenticator: |
|
155 |
return _( |
|
156 |
'edit of object "{related_object}" in authenticator "{authenticator}" ({change})' |
|
157 |
).format( |
|
158 |
related_object=related_object, |
|
159 |
authenticator=authenticator, |
|
160 |
change=edited_attributes, |
|
161 |
) |
|
162 |
else: |
|
163 |
return _('edit of object "{related_object}" ({change})').format( |
|
164 |
related_object=related_object, change=edited_attributes |
|
165 |
) |
|
166 | ||
167 | ||
168 |
class AuthenticatorRelatedObjectDeletion(AuthenticatorRelatedObjectEvents): |
|
169 |
name = 'authenticator.related_object.deletion' |
|
170 |
label = _('Authenticator related object deletion') |
|
171 | ||
172 |
@classmethod |
|
173 |
def get_message(cls, event, context): |
|
174 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
175 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
176 |
related_object = event.get_data('related_object') |
|
177 |
if context != authenticator: |
|
178 |
return _('deletion of object "{related_object}" in authenticator "{authenticator}"').format( |
|
179 |
related_object=related_object, authenticator=authenticator |
|
180 |
) |
|
181 |
else: |
|
182 |
return _('deletion of object "%s"') % related_object |
src/authentic2/apps/authenticators/manager_urls.py | ||
---|---|---|
81 | 81 |
views.order, |
82 | 82 |
name='a2-manager-authenticators-order', |
83 | 83 |
), |
84 |
path( |
|
85 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/add/', |
|
86 |
views.add_related_object, |
|
87 |
name='a2-manager-authenticators-add-related-object', |
|
88 |
), |
|
89 |
path( |
|
90 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/edit/', |
|
91 |
views.edit_related_object, |
|
92 |
name='a2-manager-authenticators-edit-related-object', |
|
93 |
), |
|
94 |
path( |
|
95 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/delete/', |
|
96 |
views.delete_related_object, |
|
97 |
name='a2-manager-authenticators-delete-related-object', |
|
98 |
), |
|
84 | 99 |
], |
85 | 100 |
) |
src/authentic2/apps/authenticators/models.py | ||
---|---|---|
141 | 141 |
return True |
142 | 142 | |
143 | 143 | |
144 |
class AuthenticatorRelatedObjectBase(models.Model): |
|
145 |
authenticator = models.ForeignKey(BaseAuthenticator, on_delete=models.CASCADE) |
|
146 | ||
147 |
class Meta: |
|
148 |
abstract = True |
|
149 | ||
150 |
def get_journal_text(self): |
|
151 |
return '%s (%s)' % (self._meta.verbose_name, self.pk) |
|
152 | ||
153 |
@property |
|
154 |
def model_name(self): |
|
155 |
return self._meta.model_name |
|
156 | ||
157 |
@property |
|
158 |
def verbose_name_plural(self): |
|
159 |
return self._meta.verbose_name_plural |
|
160 | ||
161 | ||
144 | 162 |
class LoginPasswordAuthenticator(BaseAuthenticator): |
145 | 163 |
remember_me = models.PositiveIntegerField( |
146 | 164 |
_('Remember me duration'), |
src/authentic2/apps/authenticators/templates/authentic2/authenticators/authenticator_detail.html | ||
---|---|---|
35 | 35 |
<div class='pk-tabs'> |
36 | 36 |
<div class="pk-tabs--tab-list" role="tablist"> |
37 | 37 |
<button aria-controls="panel-description" aria-selected="true" id="tab-description" role="tab" tabindex="0">{% trans "Description" %}</button> |
38 |
{% for model in object.related_models %} |
|
39 |
<button aria-controls="panel-{{ model.model_name }}" aria-selected="false" id="tab-{{ model.model_name }}" role="tab" tabindex="-1">{{ model.verbose_name_plural }}</button> |
|
40 |
{% endfor %} |
|
38 | 41 |
{% block extra-tab-buttons %} |
39 | 42 |
{% endblock %} |
40 | 43 |
</div> |
... | ... | |
47 | 50 |
{% endfor %} |
48 | 51 |
</ul> |
49 | 52 |
</div> |
53 |
{% for model, objects in object.related_models.items %} |
|
54 |
<div aria-labelledby="tab-{{ model.model_name }}" hidden="" id="panel-{{ model.model_name }}" role="tabpanel" tabindex="0"> |
|
55 |
{% include 'authentic2/authenticators/related_object_list.html' with object_list=objects model_name=model.model_name %} |
|
56 |
</div> |
|
57 |
{% endfor %} |
|
50 | 58 |
{% block extra-tab-list %} |
51 | 59 |
{% endblock %} |
52 | 60 |
</div> |
src/authentic2/apps/authenticators/views.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from django.apps import apps |
|
17 | 18 |
from django.contrib import messages |
18 | 19 |
from django.core.exceptions import PermissionDenied |
19 |
from django.http import HttpResponseRedirect |
|
20 |
from django.forms.models import modelform_factory |
|
21 |
from django.http import Http404, HttpResponseRedirect |
|
20 | 22 |
from django.shortcuts import get_object_or_404 |
21 | 23 |
from django.urls import reverse, reverse_lazy |
22 | 24 |
from django.utils.functional import cached_property |
... | ... | |
215 | 217 | |
216 | 218 | |
217 | 219 |
order = AuthenticatorsOrderView.as_view() |
220 | ||
221 | ||
222 |
class AuthenticatorRelatedObjectMixin(MediaMixin, TitleMixin): |
|
223 |
def dispatch(self, request, *args, **kwargs): |
|
224 |
self.authenticator = get_object_or_404( |
|
225 |
BaseAuthenticator.authenticators.all(), pk=kwargs.get('authenticator_pk') |
|
226 |
) |
|
227 | ||
228 |
model_name = kwargs.get('model_name') |
|
229 |
if model_name not in (x._meta.model_name for x in self.authenticator.related_models): |
|
230 |
raise Http404() |
|
231 |
self.model = apps.get_model(self.authenticator._meta.app_label, model_name) |
|
232 | ||
233 |
return super().dispatch(request, *args, **kwargs) |
|
234 | ||
235 |
def get_form_class(self): |
|
236 |
return modelform_factory(self.model, self.authenticator.related_object_form_class) |
|
237 | ||
238 |
def get_form_kwargs(self): |
|
239 |
kwargs = super().get_form_kwargs() |
|
240 |
if not kwargs.get('instance'): |
|
241 |
kwargs['instance'] = self.model() |
|
242 |
kwargs['instance'].authenticator = self.authenticator |
|
243 |
return kwargs |
|
244 | ||
245 |
def get_success_url(self): |
|
246 |
return ( |
|
247 |
reverse('a2-manager-authenticator-detail', kwargs={'pk': self.authenticator.pk}) |
|
248 |
+ '#open:%s' % self.model._meta.model_name |
|
249 |
) |
|
250 | ||
251 |
@property |
|
252 |
def title(self): |
|
253 |
return self.model._meta.verbose_name |
|
254 | ||
255 | ||
256 |
class RelatedObjectAddView(AuthenticatorRelatedObjectMixin, CreateView): |
|
257 |
template_name = 'authentic2/manager/form.html' |
|
258 | ||
259 |
def form_valid(self, form): |
|
260 |
resp = super().form_valid(form) |
|
261 |
self.request.journal.record('authenticator.related_object.creation', related_object=form.instance) |
|
262 |
return resp |
|
263 | ||
264 | ||
265 |
add_related_object = RelatedObjectAddView.as_view() |
|
266 | ||
267 | ||
268 |
class RelatedObjectEditView(AuthenticatorRelatedObjectMixin, UpdateView): |
|
269 |
template_name = 'authentic2/manager/form.html' |
|
270 | ||
271 |
def form_valid(self, form): |
|
272 |
resp = super().form_valid(form) |
|
273 |
self.request.journal.record('authenticator.related_object.edit', form=form) |
|
274 |
return resp |
|
275 | ||
276 | ||
277 |
edit_related_object = RelatedObjectEditView.as_view() |
|
278 | ||
279 | ||
280 |
class RelatedObjectDeleteView(AuthenticatorRelatedObjectMixin, DeleteView): |
|
281 |
template_name = 'authentic2/authenticators/authenticator_delete_form.html' |
|
282 |
title = '' |
|
283 | ||
284 |
def delete(self, *args, **kwargs): |
|
285 |
self.request.journal.record('authenticator.related_object.deletion', related_object=self.get_object()) |
|
286 |
return super().delete(*args, **kwargs) |
|
287 | ||
288 | ||
289 |
delete_related_object = RelatedObjectDeleteView.as_view() |
src/authentic2_auth_saml/journal_event_types.py | ||
---|---|---|
1 |
# authentic2 - versatile identity manager |
|
2 |
# Copyright (C) 2010-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software: you can redistribute it and/or modify it |
|
5 |
# under the terms of the GNU Affero General Public License as published |
|
6 |
# by the Free Software Foundation, either version 3 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU Affero General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU Affero General Public License |
|
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
from django.utils.translation import gettext_lazy as _ |
|
18 | ||
19 |
from authentic2.apps.authenticators.journal_event_types import AuthenticatorEvents |
|
20 |
from authentic2.apps.authenticators.models import BaseAuthenticator |
|
21 |
from authentic2.apps.journal.utils import form_to_old_new |
|
22 | ||
23 | ||
24 |
class AuthenticatorRelatedObjectEvents(AuthenticatorEvents): |
|
25 |
@classmethod |
|
26 |
def record(cls, *, user, session, related_object, data=None): |
|
27 |
data = data or {} |
|
28 |
data.update({'related_object': related_object.get_journal_text()}) |
|
29 |
super().record(user=user, session=session, authenticator=related_object.authenticator, data=data) |
|
30 | ||
31 | ||
32 |
class AuthenticatorRelatedObjectCreation(AuthenticatorRelatedObjectEvents): |
|
33 |
name = 'authenticator.related_object.creation' |
|
34 |
label = _('Authenticator related object creation') |
|
35 | ||
36 |
@classmethod |
|
37 |
def get_message(cls, event, context): |
|
38 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
39 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
40 |
related_object = event.get_data('related_object') |
|
41 |
if context != authenticator: |
|
42 |
return _('creation of object "{related_object}" in authenticator "{authenticator}"').format( |
|
43 |
related_object=related_object, authenticator=authenticator |
|
44 |
) |
|
45 |
else: |
|
46 |
return _('creation of object "%s"') % related_object |
|
47 | ||
48 | ||
49 |
class AuthenticatorRelatedObjectEdit(AuthenticatorRelatedObjectEvents): |
|
50 |
name = 'authenticator.related_object.edit' |
|
51 |
label = _('Authenticator related object edit') |
|
52 | ||
53 |
@classmethod |
|
54 |
def record(cls, *, user, session, form): |
|
55 |
super().record( |
|
56 |
user=user, |
|
57 |
session=session, |
|
58 |
related_object=form.instance, |
|
59 |
data=form_to_old_new(form), |
|
60 |
) |
|
61 | ||
62 |
@classmethod |
|
63 |
def get_message(cls, event, context): |
|
64 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
65 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
66 |
related_object = event.get_data('related_object') |
|
67 |
new = event.get_data('new') or {} |
|
68 |
edited_attributes = ', '.join(new) or '' |
|
69 |
if context != authenticator: |
|
70 |
return _( |
|
71 |
'edit of object "{related_object}" in authenticator "{authenticator}" ({change})' |
|
72 |
).format( |
|
73 |
related_object=related_object, |
|
74 |
authenticator=authenticator, |
|
75 |
change=edited_attributes, |
|
76 |
) |
|
77 |
else: |
|
78 |
return _('edit of object "{related_object}" ({change})').format( |
|
79 |
related_object=related_object, change=edited_attributes |
|
80 |
) |
|
81 | ||
82 | ||
83 |
class AuthenticatorRelatedObjectDeletion(AuthenticatorRelatedObjectEvents): |
|
84 |
name = 'authenticator.related_object.deletion' |
|
85 |
label = _('Authenticator related object deletion') |
|
86 | ||
87 |
@classmethod |
|
88 |
def get_message(cls, event, context): |
|
89 |
(authenticator,) = event.get_typed_references(BaseAuthenticator) |
|
90 |
authenticator = authenticator or event.get_data('authenticator_name') |
|
91 |
related_object = event.get_data('related_object') |
|
92 |
if context != authenticator: |
|
93 |
return _('deletion of object "{related_object}" in authenticator "{authenticator}"').format( |
|
94 |
related_object=related_object, authenticator=authenticator |
|
95 |
) |
|
96 |
else: |
|
97 |
return _('deletion of object "%s"') % related_object |
src/authentic2_auth_saml/models.py | ||
---|---|---|
21 | 21 |
from django.utils.translation import gettext_lazy as _ |
22 | 22 | |
23 | 23 |
from authentic2.a2_rbac.models import Role |
24 |
from authentic2.apps.authenticators.models import BaseAuthenticator |
|
24 |
from authentic2.apps.authenticators.models import AuthenticatorRelatedObjectBase, BaseAuthenticator
|
|
25 | 25 |
from authentic2.manager.utils import label_from_role |
26 | 26 |
from authentic2.utils.misc import redirect_to_login |
27 | 27 | |
... | ... | |
213 | 213 |
return views.profile(request, *args, **kwargs) |
214 | 214 | |
215 | 215 | |
216 |
class AuthenticatorRelatedObjectBase(models.Model): |
|
217 |
authenticator = models.ForeignKey(BaseAuthenticator, on_delete=models.CASCADE) |
|
218 | ||
219 |
class Meta: |
|
220 |
abstract = True |
|
221 | ||
222 |
def get_journal_text(self): |
|
223 |
return '%s (%s)' % (self._meta.verbose_name, self.pk) |
|
224 | ||
225 |
@property |
|
226 |
def model_name(self): |
|
227 |
return self._meta.model_name |
|
228 | ||
229 |
@property |
|
230 |
def verbose_name_plural(self): |
|
231 |
return self._meta.verbose_name_plural |
|
232 | ||
233 | ||
234 | 216 |
class SAMLAttributeLookup(AuthenticatorRelatedObjectBase): |
235 | 217 |
user_field = models.CharField(_('User field'), max_length=256) |
236 | 218 |
saml_attribute = models.CharField(_('SAML attribute'), max_length=1024) |
src/authentic2_auth_saml/templates/authentic2_auth_saml/authenticator_detail.html | ||
---|---|---|
10 | 10 | |
11 | 11 |
{{ block.super }} |
12 | 12 |
{% endblock %} |
13 | ||
14 |
{% block extra-tab-buttons %} |
|
15 |
{% for model in object.related_models %} |
|
16 |
<button aria-controls="panel-{{ model.model_name }}" aria-selected="false" id="tab-{{ model.model_name }}" role="tab" tabindex="-1">{{ model.verbose_name_plural }}</button> |
|
17 |
{% endfor %} |
|
18 |
{% endblock %} |
|
19 | ||
20 |
{% block extra-tab-list %} |
|
21 |
{% for model, objects in object.related_models.items %} |
|
22 |
<div aria-labelledby="tab-{{ model.model_name }}" hidden="" id="panel-{{ model.model_name }}" role="tabpanel" tabindex="0"> |
|
23 |
{% include 'authentic2_auth_saml/related_object_list.html' with object_list=objects model_name=model.model_name %} |
|
24 |
</div> |
|
25 |
{% endfor %} |
|
26 |
{% endblock %} |
src/authentic2_auth_saml/urls.py | ||
---|---|---|
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
from django.conf.urls import include, url |
18 |
from django.urls import path |
|
19 | ||
20 |
from authentic2.apps.authenticators.manager_urls import superuser_login_required |
|
21 |
from authentic2.decorators import required |
|
22 | ||
23 |
from . import views |
|
24 | 18 | |
25 | 19 |
urlpatterns = [ |
26 | 20 |
url(r'^accounts/saml/', include('mellon.urls'), kwargs={'template_base': 'authentic2/base.html'}) |
27 | 21 |
] |
28 | ||
29 |
urlpatterns += required( |
|
30 |
superuser_login_required, |
|
31 |
[ |
|
32 |
path( |
|
33 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/add/', |
|
34 |
views.add_related_object, |
|
35 |
name='a2-manager-authenticators-add-related-object', |
|
36 |
), |
|
37 |
path( |
|
38 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/edit/', |
|
39 |
views.edit_related_object, |
|
40 |
name='a2-manager-authenticators-edit-related-object', |
|
41 |
), |
|
42 |
path( |
|
43 |
'authenticators/<int:authenticator_pk>/<slug:model_name>/<int:pk>/delete/', |
|
44 |
views.delete_related_object, |
|
45 |
name='a2-manager-authenticators-delete-related-object', |
|
46 |
), |
|
47 |
], |
|
48 |
) |
src/authentic2_auth_saml/views.py | ||
---|---|---|
1 |
from django.apps import apps |
|
2 |
from django.forms.models import modelform_factory |
|
3 |
from django.http import Http404 |
|
4 |
from django.shortcuts import get_object_or_404, render |
|
1 |
from django.shortcuts import render |
|
5 | 2 |
from django.template.loader import render_to_string |
6 |
from django.urls import reverse |
|
7 |
from django.views.generic import CreateView, DeleteView, UpdateView |
|
8 | 3 |
from mellon.utils import get_idp |
9 | 4 | |
10 |
from authentic2.apps.authenticators.models import BaseAuthenticator |
|
11 |
from authentic2.manager.views import MediaMixin, TitleMixin |
|
12 | 5 |
from authentic2.utils.misc import redirect_to_login |
13 | 6 | |
14 | 7 | |
... | ... | |
41 | 34 |
user_saml_identifier.idp = get_idp(user_saml_identifier.issuer.entity_id) |
42 | 35 |
context['user_saml_identifiers'] = user_saml_identifiers |
43 | 36 |
return render_to_string('authentic2_auth_saml/profile.html', context, request=request) |
44 | ||
45 | ||
46 |
class AuthenticatorRelatedObjectMixin(MediaMixin, TitleMixin): |
|
47 |
def dispatch(self, request, *args, **kwargs): |
|
48 |
self.authenticator = get_object_or_404( |
|
49 |
BaseAuthenticator.authenticators.all(), pk=kwargs.get('authenticator_pk') |
|
50 |
) |
|
51 | ||
52 |
model_name = kwargs.get('model_name') |
|
53 |
if model_name not in (x._meta.model_name for x in self.authenticator.related_models): |
|
54 |
raise Http404() |
|
55 |
self.model = apps.get_model(self.authenticator._meta.app_label, model_name) |
|
56 | ||
57 |
return super().dispatch(request, *args, **kwargs) |
|
58 | ||
59 |
def get_form_class(self): |
|
60 |
return modelform_factory(self.model, self.authenticator.related_object_form_class) |
|
61 | ||
62 |
def get_form_kwargs(self): |
|
63 |
kwargs = super().get_form_kwargs() |
|
64 |
if not kwargs.get('instance'): |
|
65 |
kwargs['instance'] = self.model() |
|
66 |
kwargs['instance'].authenticator = self.authenticator |
|
67 |
return kwargs |
|
68 | ||
69 |
def get_success_url(self): |
|
70 |
return ( |
|
71 |
reverse('a2-manager-authenticator-detail', kwargs={'pk': self.authenticator.pk}) |
|
72 |
+ '#open:%s' % self.model._meta.model_name |
|
73 |
) |
|
74 | ||
75 |
@property |
|
76 |
def title(self): |
|
77 |
return self.model._meta.verbose_name |
|
78 | ||
79 | ||
80 |
class RelatedObjectAddView(AuthenticatorRelatedObjectMixin, CreateView): |
|
81 |
template_name = 'authentic2/manager/form.html' |
|
82 | ||
83 |
def form_valid(self, form): |
|
84 |
resp = super().form_valid(form) |
|
85 |
self.request.journal.record('authenticator.related_object.creation', related_object=form.instance) |
|
86 |
return resp |
|
87 | ||
88 | ||
89 |
add_related_object = RelatedObjectAddView.as_view() |
|
90 | ||
91 | ||
92 |
class RelatedObjectEditView(AuthenticatorRelatedObjectMixin, UpdateView): |
|
93 |
template_name = 'authentic2/manager/form.html' |
|
94 | ||
95 |
def form_valid(self, form): |
|
96 |
resp = super().form_valid(form) |
|
97 |
self.request.journal.record('authenticator.related_object.edit', form=form) |
|
98 |
return resp |
|
99 | ||
100 | ||
101 |
edit_related_object = RelatedObjectEditView.as_view() |
|
102 | ||
103 | ||
104 |
class RelatedObjectDeleteView(AuthenticatorRelatedObjectMixin, DeleteView): |
|
105 |
template_name = 'authentic2/authenticators/authenticator_delete_form.html' |
|
106 |
title = '' |
|
107 | ||
108 |
def delete(self, *args, **kwargs): |
|
109 |
self.request.journal.record('authenticator.related_object.deletion', related_object=self.get_object()) |
|
110 |
return super().delete(*args, **kwargs) |
|
111 | ||
112 | ||
113 |
delete_related_object = RelatedObjectDeleteView.as_view() |
|
114 |
- |