2015-07-15 10:35:21 +00:00
|
|
|
from urllib import parse
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
from django import forms
|
2023-11-17 12:25:13 +00:00
|
|
|
from django.urls import re_path as url
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.contrib import admin, messages
|
|
|
|
from django.contrib.admin.options import IS_POPUP_VAR
|
2014-09-03 22:01:44 +00:00
|
|
|
from django.contrib.admin.utils import unquote
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.contrib.auth import update_session_auth_hash
|
|
|
|
from django.core.exceptions import PermissionDenied
|
2016-05-11 12:56:10 +00:00
|
|
|
from django.http import HttpResponseRedirect, Http404, HttpResponse
|
2014-05-08 16:59:35 +00:00
|
|
|
from django.forms.models import BaseInlineFormSet
|
2015-04-04 17:44:07 +00:00
|
|
|
from django.shortcuts import get_object_or_404
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.template.response import TemplateResponse
|
|
|
|
from django.utils.decorators import method_decorator
|
2023-11-17 12:25:13 +00:00
|
|
|
from django.utils.encoding import force_str
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.utils.html import escape
|
2023-11-17 12:25:13 +00:00
|
|
|
from django.utils.translation import gettext_lazy as _
|
2014-10-06 14:57:02 +00:00
|
|
|
from django.views.decorators.debug import sensitive_post_parameters
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2016-05-11 12:56:10 +00:00
|
|
|
from orchestra.models.utils import has_db_field
|
|
|
|
|
2015-09-04 10:22:14 +00:00
|
|
|
from ..utils.python import random_ascii, pairwise
|
2015-04-16 13:15:21 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
from .forms import AdminPasswordChangeForm
|
2016-05-11 12:56:10 +00:00
|
|
|
#, AdminRawPasswordChangeForm
|
2014-10-06 14:57:02 +00:00
|
|
|
#from django.contrib.auth.forms import AdminPasswordChangeForm
|
2015-10-05 13:31:08 +00:00
|
|
|
from .utils import action_to_view
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-08-19 18:59:23 +00:00
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
|
|
|
|
|
|
|
|
|
2014-08-19 18:59:23 +00:00
|
|
|
class ChangeListDefaultFilter(object):
|
|
|
|
"""
|
|
|
|
Enables support for default filtering on admin change list pages
|
|
|
|
Your model admin class should define an default_changelist_filters attribute
|
|
|
|
default_changelist_filters = (('my_nodes', 'True'),)
|
|
|
|
"""
|
|
|
|
default_changelist_filters = ()
|
|
|
|
|
|
|
|
def changelist_view(self, request, extra_context=None):
|
2015-07-09 10:44:22 +00:00
|
|
|
# defaults = []
|
2015-07-15 10:35:21 +00:00
|
|
|
# for key, value in self.default_changelist_filters:
|
|
|
|
# set_url_query(request, key, value)
|
|
|
|
# defaults.append(key)
|
|
|
|
# # hack response cl context in order to hook default filter awaearness
|
|
|
|
# # into search_form.html template
|
|
|
|
# response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context)
|
|
|
|
# if hasattr(response, 'context_data') and 'cl' in response.context_data:
|
|
|
|
# response.context_data['cl'].default_changelist_filters = defaults
|
|
|
|
# return response
|
|
|
|
querystring = request.META['QUERY_STRING']
|
|
|
|
querydict = parse.parse_qs(querystring)
|
|
|
|
redirect = False
|
|
|
|
for field, value in self.default_changelist_filters:
|
|
|
|
if field not in querydict:
|
|
|
|
redirect = True
|
|
|
|
querydict[field] = value
|
|
|
|
if redirect:
|
|
|
|
querystring = parse.urlencode(querydict, doseq=True)
|
|
|
|
return HttpResponseRedirect(request.path + '?%s' % querystring)
|
|
|
|
return super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context)
|
2014-08-19 18:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet):
|
|
|
|
def clean(self):
|
|
|
|
"""Check that at least one service has been entered."""
|
|
|
|
super(AtLeastOneRequiredInlineFormSet, self).clean()
|
|
|
|
if any(self.errors):
|
|
|
|
return
|
|
|
|
if not any(cleaned_data and not cleaned_data.get('DELETE', False)
|
|
|
|
for cleaned_data in self.cleaned_data):
|
|
|
|
raise forms.ValidationError('At least one item required.')
|
|
|
|
|
|
|
|
|
2015-09-04 10:22:14 +00:00
|
|
|
class EnhaceSearchMixin(object):
|
|
|
|
def lookup_allowed(self, lookup, value):
|
|
|
|
""" allows any lookup """
|
|
|
|
if 'password' in lookup:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def get_search_results(self, request, queryset, search_term):
|
|
|
|
""" allows to specify field <field_name>:<search_term> """
|
|
|
|
search_fields = self.get_search_fields(request)
|
2015-10-08 13:54:39 +00:00
|
|
|
if '=' in search_term:
|
2015-09-04 10:22:14 +00:00
|
|
|
fields = {field.split('__')[0]: field for field in search_fields}
|
|
|
|
new_search_term = []
|
|
|
|
for part in search_term.split():
|
2015-10-08 13:54:39 +00:00
|
|
|
field = None
|
|
|
|
if '=' in part:
|
|
|
|
field, term = part.split('=')
|
|
|
|
kwarg = '%s__icontains'
|
|
|
|
c_term = term
|
|
|
|
if term.startswith(('"', "'")) and term.endswith(('"', "'")):
|
|
|
|
c_term = term[1:-1]
|
|
|
|
kwarg = '%s__iexact'
|
2015-09-04 10:22:14 +00:00
|
|
|
if field in fields:
|
2015-10-08 13:54:39 +00:00
|
|
|
queryset = queryset.filter(**{kwarg % fields[field]: c_term})
|
2015-09-04 10:22:14 +00:00
|
|
|
else:
|
2015-10-08 13:54:39 +00:00
|
|
|
new_search_term.append('='.join((field, term)))
|
|
|
|
else:
|
|
|
|
new_search_term.append(part)
|
2015-09-04 10:22:14 +00:00
|
|
|
search_term = ' '.join(new_search_term)
|
|
|
|
return super(EnhaceSearchMixin, self).get_search_results(request, queryset, search_term)
|
|
|
|
|
|
|
|
|
2014-08-19 18:59:23 +00:00
|
|
|
class ChangeViewActionsMixin(object):
|
|
|
|
""" Makes actions visible on the admin change view page. """
|
|
|
|
change_view_actions = ()
|
|
|
|
change_form_template = 'orchestra/admin/change_form.html'
|
|
|
|
|
|
|
|
def get_urls(self):
|
|
|
|
"""Returns the additional urls for the change view links"""
|
|
|
|
urls = super(ChangeViewActionsMixin, self).get_urls()
|
|
|
|
admin_site = self.admin_site
|
|
|
|
opts = self.model._meta
|
2015-05-19 13:27:04 +00:00
|
|
|
new_urls = []
|
2014-08-19 18:59:23 +00:00
|
|
|
for action in self.get_change_view_actions():
|
2015-05-19 13:27:04 +00:00
|
|
|
new_urls.append(
|
2014-08-19 18:59:23 +00:00
|
|
|
url('^(\d+)/%s/$' % action.url_name,
|
|
|
|
admin_site.admin_view(action),
|
2014-10-07 13:50:59 +00:00
|
|
|
name='%s_%s_%s' % (opts.app_label, opts.model_name, action.url_name)
|
|
|
|
)
|
|
|
|
)
|
2014-08-19 18:59:23 +00:00
|
|
|
return new_urls + urls
|
|
|
|
|
2014-09-16 17:14:24 +00:00
|
|
|
def get_change_view_actions(self, obj=None):
|
|
|
|
""" allow customization on modelamdin """
|
2014-08-19 18:59:23 +00:00
|
|
|
views = []
|
|
|
|
for action in self.change_view_actions:
|
2015-04-02 16:14:55 +00:00
|
|
|
if isinstance(action, str):
|
2014-08-19 18:59:23 +00:00
|
|
|
action = getattr(self, action)
|
|
|
|
view = action_to_view(action, self)
|
|
|
|
view.url_name = getattr(action, 'url_name', action.__name__)
|
2015-07-20 12:51:30 +00:00
|
|
|
tool_description = getattr(action, 'tool_description', '')
|
|
|
|
if not tool_description:
|
|
|
|
tool_description = getattr(action, 'short_description',
|
|
|
|
view.url_name.capitalize().replace('_', ' '))
|
|
|
|
if hasattr(tool_description, '__call__'):
|
|
|
|
tool_description = tool_description(obj)
|
|
|
|
view.tool_description = tool_description
|
2014-08-19 18:59:23 +00:00
|
|
|
view.css_class = getattr(action, 'css_class', 'historylink')
|
2015-07-09 10:19:30 +00:00
|
|
|
view.help_text = getattr(action, 'help_text', '')
|
2016-05-18 14:08:12 +00:00
|
|
|
view.hidden = getattr(action, 'hidden', False)
|
2014-08-19 18:59:23 +00:00
|
|
|
views.append(view)
|
|
|
|
return views
|
|
|
|
|
2014-09-03 22:01:44 +00:00
|
|
|
def change_view(self, request, object_id, **kwargs):
|
2014-11-20 16:48:50 +00:00
|
|
|
if kwargs.get('extra_context', None) is None:
|
2014-08-19 18:59:23 +00:00
|
|
|
kwargs['extra_context'] = {}
|
2014-09-16 17:14:24 +00:00
|
|
|
obj = self.get_object(request, unquote(object_id))
|
2014-08-19 18:59:23 +00:00
|
|
|
kwargs['extra_context']['object_tools_items'] = [
|
2016-05-18 14:08:12 +00:00
|
|
|
action.__dict__ for action in self.get_change_view_actions(obj) if not action.hidden
|
2014-08-19 18:59:23 +00:00
|
|
|
]
|
2016-03-31 16:02:50 +00:00
|
|
|
return super().change_view(request, object_id, **kwargs)
|
2014-08-19 18:59:23 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ChangeAddFieldsMixin(object):
|
|
|
|
""" Enables to specify different set of fields for change and add views """
|
2014-05-08 16:59:35 +00:00
|
|
|
add_fields = ()
|
|
|
|
add_fieldsets = ()
|
|
|
|
add_form = None
|
2014-10-24 14:19:34 +00:00
|
|
|
add_prepopulated_fields = {}
|
2014-05-08 16:59:35 +00:00
|
|
|
change_readonly_fields = ()
|
2015-05-14 13:28:54 +00:00
|
|
|
change_form = None
|
2015-03-27 19:50:54 +00:00
|
|
|
add_inlines = None
|
2014-05-08 16:59:35 +00:00
|
|
|
|
2014-10-24 14:19:34 +00:00
|
|
|
def get_prepopulated_fields(self, request, obj=None):
|
|
|
|
if not obj:
|
2014-11-14 15:51:18 +00:00
|
|
|
return super(ChangeAddFieldsMixin, self).get_prepopulated_fields(request, obj)
|
2014-10-24 14:19:34 +00:00
|
|
|
return {}
|
|
|
|
|
2015-03-23 15:36:51 +00:00
|
|
|
def get_change_readonly_fields(self, request, obj=None):
|
|
|
|
return self.change_readonly_fields
|
|
|
|
|
2014-05-08 16:59:35 +00:00
|
|
|
def get_readonly_fields(self, request, obj=None):
|
2014-11-14 15:51:18 +00:00
|
|
|
fields = super(ChangeAddFieldsMixin, self).get_readonly_fields(request, obj)
|
2014-05-08 16:59:35 +00:00
|
|
|
if obj:
|
2015-03-26 16:00:30 +00:00
|
|
|
return fields + self.get_change_readonly_fields(request, obj)
|
2014-05-08 16:59:35 +00:00
|
|
|
return fields
|
|
|
|
|
|
|
|
def get_fieldsets(self, request, obj=None):
|
|
|
|
if not obj:
|
|
|
|
if self.add_fieldsets:
|
|
|
|
return self.add_fieldsets
|
|
|
|
elif self.add_fields:
|
|
|
|
return [(None, {'fields': self.add_fields})]
|
2014-11-14 15:51:18 +00:00
|
|
|
return super(ChangeAddFieldsMixin, self).get_fieldsets(request, obj)
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
def get_inline_instances(self, request, obj=None):
|
|
|
|
""" add_inlines and inline.parent_object """
|
|
|
|
if obj:
|
|
|
|
self.inlines = type(self).inlines
|
2014-10-17 10:04:47 +00:00
|
|
|
else:
|
2015-03-27 19:50:54 +00:00
|
|
|
self.inlines = self.inlines if self.add_inlines is None else self.add_inlines
|
2014-11-14 15:51:18 +00:00
|
|
|
inlines = super(ChangeAddFieldsMixin, self).get_inline_instances(request, obj)
|
2014-05-08 16:59:35 +00:00
|
|
|
for inline in inlines:
|
|
|
|
inline.parent_object = obj
|
|
|
|
return inlines
|
|
|
|
|
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
|
|
|
""" Use special form during user creation """
|
|
|
|
defaults = {}
|
2015-05-14 13:28:54 +00:00
|
|
|
if obj is None:
|
|
|
|
if self.add_form:
|
|
|
|
defaults['form'] = self.add_form
|
|
|
|
else:
|
|
|
|
if self.change_form:
|
|
|
|
defaults['form'] = self.change_form
|
2014-05-08 16:59:35 +00:00
|
|
|
defaults.update(kwargs)
|
2014-08-19 18:59:23 +00:00
|
|
|
return super(ChangeAddFieldsMixin, self).get_form(request, obj, **defaults)
|
2014-05-08 16:59:35 +00:00
|
|
|
|
|
|
|
|
2015-09-04 10:22:14 +00:00
|
|
|
class ExtendedModelAdmin(ChangeViewActionsMixin,
|
|
|
|
ChangeAddFieldsMixin,
|
|
|
|
ChangeListDefaultFilter,
|
|
|
|
EnhaceSearchMixin,
|
|
|
|
admin.ModelAdmin):
|
2014-11-20 16:48:50 +00:00
|
|
|
list_prefetch_related = None
|
2014-11-02 14:33:55 +00:00
|
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
|
qs = super(ExtendedModelAdmin, self).get_queryset(request)
|
2014-11-20 16:48:50 +00:00
|
|
|
if self.list_prefetch_related:
|
|
|
|
qs = qs.prefetch_related(*self.list_prefetch_related)
|
2014-11-02 14:33:55 +00:00
|
|
|
return qs
|
2015-04-21 13:12:48 +00:00
|
|
|
|
|
|
|
def get_object(self, request, object_id, from_field=None):
|
|
|
|
obj = super(ExtendedModelAdmin, self).get_object(request, object_id, from_field)
|
|
|
|
if obj is None:
|
|
|
|
opts = self.model._meta
|
|
|
|
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
|
2023-11-17 12:25:13 +00:00
|
|
|
'name': force_str(opts.verbose_name), 'key': escape(object_id)})
|
2015-04-21 13:12:48 +00:00
|
|
|
return obj
|
2014-09-26 19:21:09 +00:00
|
|
|
|
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
class ChangePasswordAdminMixin(object):
|
|
|
|
change_password_form = AdminPasswordChangeForm
|
|
|
|
change_user_password_template = 'admin/orchestra/change_password.html'
|
|
|
|
|
|
|
|
def get_urls(self):
|
|
|
|
opts = self.model._meta
|
|
|
|
info = opts.app_label, opts.model_name
|
2015-05-19 13:27:04 +00:00
|
|
|
return [
|
2014-10-06 14:57:02 +00:00
|
|
|
url(r'^(\d+)/password/$',
|
|
|
|
self.admin_site.admin_view(self.change_password),
|
2016-05-11 12:56:10 +00:00
|
|
|
name='%s_%s_change_password' % info),
|
|
|
|
url(r'^(\d+)/hash/$',
|
|
|
|
self.admin_site.admin_view(self.show_hash),
|
|
|
|
name='%s_%s_show_hash' % info)
|
|
|
|
] + super().get_urls()
|
2014-10-06 14:57:02 +00:00
|
|
|
|
2015-09-21 13:57:15 +00:00
|
|
|
def get_change_password_username(self, obj):
|
|
|
|
return str(obj)
|
|
|
|
|
2014-10-06 14:57:02 +00:00
|
|
|
@sensitive_post_parameters_m
|
|
|
|
def change_password(self, request, id, form_url=''):
|
|
|
|
if not self.has_change_permission(request):
|
|
|
|
raise PermissionDenied
|
2014-10-15 21:18:50 +00:00
|
|
|
# TODO use this insetad of self.get_object(), in other places
|
2015-09-21 13:57:15 +00:00
|
|
|
obj = get_object_or_404(self.get_queryset(request), pk=id)
|
2016-05-11 12:56:10 +00:00
|
|
|
raw = request.GET.get('raw', '0') == '1'
|
|
|
|
can_raw = has_db_field(obj, 'password')
|
|
|
|
if raw and not can_raw:
|
|
|
|
raise TypeError("%s has no password db field for raw password edditing." % obj)
|
2014-10-06 14:57:02 +00:00
|
|
|
related = []
|
2015-09-21 13:57:15 +00:00
|
|
|
for obj_name_attr in ('username', 'name', 'hostname'):
|
|
|
|
try:
|
|
|
|
obj_name = getattr(obj, obj_name_attr)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
if hasattr(obj, 'account'):
|
|
|
|
account = obj.account
|
|
|
|
if obj.account.username == obj_name:
|
|
|
|
related.append(obj.account)
|
2014-10-06 14:57:02 +00:00
|
|
|
else:
|
2015-09-21 13:57:15 +00:00
|
|
|
account = obj
|
|
|
|
if account.username == obj_name:
|
2016-05-11 12:56:10 +00:00
|
|
|
for rel in account.get_related_passwords(db_field=raw):
|
2015-09-21 13:57:15 +00:00
|
|
|
if not isinstance(obj, type(rel)):
|
2014-11-14 23:06:14 +00:00
|
|
|
related.append(rel)
|
2014-10-06 14:57:02 +00:00
|
|
|
|
|
|
|
if request.method == 'POST':
|
2016-05-11 12:56:10 +00:00
|
|
|
form = self.change_password_form(obj, request.POST, related=related, raw=raw)
|
2014-10-06 14:57:02 +00:00
|
|
|
if form.is_valid():
|
|
|
|
form.save()
|
2016-05-13 08:42:58 +00:00
|
|
|
self.log_change(request, obj, _("Password changed."))
|
2014-10-06 14:57:02 +00:00
|
|
|
msg = _('Password changed successfully.')
|
|
|
|
messages.success(request, msg)
|
|
|
|
update_session_auth_hash(request, form.user) # This is safe
|
|
|
|
return HttpResponseRedirect('..')
|
|
|
|
else:
|
2016-05-11 12:56:10 +00:00
|
|
|
form = self.change_password_form(obj, related=related, raw=raw)
|
2014-10-06 14:57:02 +00:00
|
|
|
|
|
|
|
fieldsets = [
|
2015-09-21 13:57:15 +00:00
|
|
|
(obj._meta.verbose_name.capitalize(), {
|
2014-10-06 14:57:02 +00:00
|
|
|
'classes': ('wide',),
|
2016-05-11 12:56:10 +00:00
|
|
|
'fields': ('password',) if raw else ('password1', 'password2'),
|
2014-10-06 14:57:02 +00:00
|
|
|
}),
|
|
|
|
]
|
|
|
|
for ix, rel in enumerate(related):
|
|
|
|
fieldsets.append((rel._meta.verbose_name.capitalize(), {
|
|
|
|
'classes': ('wide',),
|
2016-05-11 12:56:10 +00:00
|
|
|
'fields': ('password_%i' % ix,) if raw else ('password1_%i' % ix, 'password2_%i' % ix)
|
2014-10-06 14:57:02 +00:00
|
|
|
}))
|
|
|
|
|
2015-09-21 13:57:15 +00:00
|
|
|
obj_username = self.get_change_password_username(obj)
|
2014-10-06 14:57:02 +00:00
|
|
|
adminForm = admin.helpers.AdminForm(form, fieldsets, {})
|
|
|
|
context = {
|
2015-09-21 13:57:15 +00:00
|
|
|
'title': _('Change password: %s') % obj_username,
|
2014-10-06 14:57:02 +00:00
|
|
|
'adminform': adminForm,
|
2016-05-11 12:56:10 +00:00
|
|
|
'raw': raw,
|
|
|
|
'can_raw': can_raw,
|
2014-10-06 14:57:02 +00:00
|
|
|
'errors': admin.helpers.AdminErrorList(form, []),
|
|
|
|
'form_url': form_url,
|
|
|
|
'is_popup': (IS_POPUP_VAR in request.POST or
|
|
|
|
IS_POPUP_VAR in request.GET),
|
|
|
|
'add': True,
|
|
|
|
'change': False,
|
|
|
|
'has_delete_permission': False,
|
|
|
|
'has_change_permission': True,
|
|
|
|
'has_absolute_url': False,
|
|
|
|
'opts': self.model._meta,
|
2015-09-21 13:57:15 +00:00
|
|
|
'original': obj,
|
|
|
|
'obj_username': obj_username,
|
2014-10-06 14:57:02 +00:00
|
|
|
'save_as': False,
|
|
|
|
'show_save': True,
|
2015-04-16 13:15:21 +00:00
|
|
|
'password': random_ascii(10),
|
2014-10-06 14:57:02 +00:00
|
|
|
}
|
2015-04-07 15:14:49 +00:00
|
|
|
context.update(admin.site.each_context(request))
|
2016-10-22 07:23:45 +00:00
|
|
|
return TemplateResponse(request, self.change_user_password_template, context)
|
2016-05-11 12:56:10 +00:00
|
|
|
|
|
|
|
def show_hash(self, request, id):
|
|
|
|
if not request.user.is_superuser:
|
|
|
|
raise PermissionDenied
|
|
|
|
obj = get_object_or_404(self.get_queryset(request), pk=id)
|
|
|
|
return HttpResponse(obj.password)
|