# -*- coding: utf-8 -*-
import re
from functools import update_wrapper
from weakref import WeakSet
from django.apps import apps
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q
from django.db.models.base import ModelBase
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import NoReverseMatch, reverse, reverse_lazy, resolve
from django.utils.functional import LazyObject
from django.utils.module_loading import import_string
from django.utils.text import capfirst
from django.utils.translation import gettext as _, gettext_lazy
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django_webix.admin_webix import ModelWebixAdmin
all_sites = WeakSet()
[docs]class AlreadyRegistered(Exception):
pass
[docs]class NotRegistered(Exception):
pass
[docs]class AdminWebixSite:
# Text to put at the end of each page's <title>.
site_title = gettext_lazy('Django webix site admin')
# Text to put in each page's <h1>.
site_header = gettext_lazy('Django webix administration')
# Text to put at the top of the admin index page.
index_title = gettext_lazy('Site administration')
site_url = '/'
login_form = None
webix_container_id = 'content_right'
index_template = None
login_template = None
logout_template = None
dashboard_template = 'admin_webix/dashboard.js'
webgis_template = None
password_change_template = None
password_change_done_template = None
def __init__(self, name='admin_webix'):
self._registry = {}
self.name = name
all_sites.add(self)
def is_webgis_enable(self):
return apps.is_installed("django_webix_leaflet")
def is_webix_filter_enable(self):
return apps.is_installed("django_webix_filter")
[docs] def has_permission(self, request):
"""
Return True if the given HttpRequest has permission to view
*at least one* page in the admin site.
"""
return request.user.is_active
# def check(self, app_configs): # TODO
# if app_configs is None:
# app_configs = apps.get_app_configs()
# app_configs = set(app_configs) # Speed up lookups below
#
# errors = []
# modeladmins = (o for o in self._registry.values() if o.__class__ is not ModelWebixAdmin)
# for modeladmin in modeladmins:
# if modeladmin.model._meta.app_config in app_configs:
# errors.extend(modeladmin.check())
# return errors
[docs] def register(self, model_or_iterable, admin_class=None, **options):
"""
Register the given model(s) with the given admin class.
The model(s) should be Model classes, not instances.
If an admin class isn't given, use ModelWebixAdmin (the default admin
options). If keyword arguments are given -- e.g., list_display --
apply them as options to the admin class.
If a model is already registered, raise AlreadyRegistered.
If a model is abstract, raise ImproperlyConfigured.
"""
admin_class = admin_class or ModelWebixAdmin
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model._meta.abstract:
raise ImproperlyConfigured(
'The model %s is abstract, so it cannot be registered with admin.' % model.__name__
)
if model in self._registry:
registered_admin = str(self._registry[model])
msg = 'The model %s is already registered ' % model.__name__
if registered_admin.endswith('.ModelWebixAdmin'):
# Most likely registered without a ModelWebixAdmin subclass.
msg += 'in app %r.' % re.sub(r'\.ModelWebixAdmin$', '', registered_admin)
else:
msg += 'with %r.' % registered_admin
raise AlreadyRegistered(msg)
# Ignore the registration if the model has been
# swapped out.
if not model._meta.swapped:
# If we got **options then dynamically construct a subclass of
# admin_class with those **options.
if options:
# For reasons I don't quite understand, without a __module__
# the created class appears to "live" in the wrong place,
# which causes issues later on.
options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
# else:
# raise Exception(model, 'errore swap')
[docs] def unregister(self, model_or_iterable):
"""
Unregister the given model(s).
If a model isn't already registered, raise NotRegistered.
"""
if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable]
for model in model_or_iterable:
if model not in self._registry:
raise NotRegistered('The model %s is not registered' % model.__name__)
del self._registry[model]
[docs] def is_registered(self, model):
"""
Check if a model class is registered with this `AdminWebixSite`.
"""
return model in self._registry
def _build_app_dict(self, request, label=None):
"""
Build the app dictionary. The optional `label` parameter filters models
of a specific app.
"""
app_dict = {}
if label:
models = {
m: m_a for m, m_a in self._registry.items()
if m._meta.app_label == label
}
else:
models = self._registry
for model, model_admin in models.items():
app_label = model._meta.app_label
has_module_perms = model_admin.has_module_permission(request)
if not has_module_perms:
continue
perms = model_admin.get_model_perms(request)
# Check whether user has any perm for this module.
# If so, add the module to the model_list.
if True not in perms.values():
continue
info = (app_label, model._meta.model_name)
model_dict = {
'name': capfirst(model._meta.verbose_name_plural),
'object_name': model._meta.object_name,
'model_name': model._meta.model_name,
'perms': perms,
'admin_url': None,
'add_url': None,
}
if perms.get('change') or perms.get('view'):
model_dict['view_only'] = not perms.get('change')
try:
model_dict['admin_url'] = reverse('admin_webix:%s.%s.list' % info, current_app=self.name)
except NoReverseMatch:
pass
if perms.get('add'):
try:
model_dict['add_url'] = reverse('admin_webix:%s.%s.add' % info, current_app=self.name)
except NoReverseMatch:
pass
if app_label in app_dict:
app_dict[app_label]['models'].append(model_dict)
else:
app_dict[app_label] = {
'name': apps.get_app_config(app_label).verbose_name,
'app_label': app_label,
'has_module_perms': has_module_perms,
'models': [model_dict],
}
if label:
return app_dict.get(label)
return app_dict
[docs] def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: x['name'])
return app_list
def available_menu_items(self, user):
from django_webix.admin_webix.models import WebixAdminMenu
queryset = WebixAdminMenu.objects.all()
if user.is_superuser:
out = queryset.values_list('id', flat=True)
else:
out = []
for el in queryset.filter(enabled=True).filter(Q(active_all=True) | Q(groups__in=user.groups.all())):
if el.model is not None:
if user.has_perm(el.model.app_label + '.view_' + el.model.model):
out.append(el.id)
else:
out.append(el.id)
return out
def get_tree(self, items, available_items):
from django_webix.admin_webix.models import WebixAdminMenu
out = []
new_level = True
for item in items:
if item.id in available_items:
menu_item = {
"id": "menu_{}".format(item),
"value": "{}".format(item),
"icon": item.icon if item.icon not in ['', None] else "fas fa-archive",
}
soons = WebixAdminMenu.objects.filter(parent=item, id__in=available_items).order_by('tree_id', 'lft')
children = self.get_tree(soons, available_items)
if children != []:
menu_item["submenu"] = children
elif (item.model is not None) or (item.url not in ['', None]):
if item.get_url is None:
URL = ""
else:
URL = item.get_url
menu_item["url"] = URL
menu_item["loading_type"] = "js_script"
out.append(menu_item)
return out
def get_menu_list(self, request):
from django_webix.admin_webix.models import WebixAdminMenu
if request.user.is_anonymous:
return {}
available = self.available_menu_items(request.user)
return self.get_tree(WebixAdminMenu.objects.filter(level=0, id__in=available).order_by('tree_id', 'lft'),
available)
[docs] def admin_view(self, view, cacheable=False):
"""
Decorator to create an admin view attached to this ``AdminWebixSite``. This
wraps the view and provides permission checking by calling
``self.has_permission``.
You'll want to use this from within ``AdminWebixSite.get_urls()``:
class MyAdminWebixSite(AdminWebixSite):
def get_urls(self):
from django.urls import path
urls = super().get_urls()
urls += [
path('my_view/', self.admin_view(some_view))
]
return urls
By default, admin_views are marked non-cacheable using the
``never_cache`` decorator. If the view can be safely cached, set
cacheable=True.
"""
def inner(request, *args, **kwargs):
if not self.has_permission(request):
if request.path == reverse('admin_webix:logout', current_app=self.name):
index_path = reverse('admin_webix:index', current_app=self.name)
return HttpResponseRedirect(index_path)
# Inner import to prevent django.contrib.admin (app) from
# importing django.contrib.auth.models.User (unrelated model).
from django.contrib.auth.views import redirect_to_login
return redirect_to_login(
request.get_full_path(),
reverse('admin_webix:login', current_app=self.name)
)
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
# We add csrf_protect here so this function can be used as a utility
# function for any view, without having to repeat 'csrf_protect'.
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
return update_wrapper(inner, view)
def get_urls(self):
from django.urls import include, path
# Since this module gets imported in the application's root package,
# it cannot import models from other applications at the module level,
# and django.contrib.contenttypes.views imports ContentType.
from django_webix.admin_webix import forms
from django_webix.admin_webix import views
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return self.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = self
return update_wrapper(wrapper, view)
# Admin-site-wide views.
urlpatterns = [ # prefix = admin_webix:XXXX
path('', wrap(self.index), name='index'),
path('login/', self.login, name='login'),
path('logout/', wrap(self.logout), name='logout'),
path('dashboard/', wrap(self.dashboard), name='dashboard'),
path('index/', wrap(self.index), name='index'),
path('password_change/', wrap(self.password_change, cacheable=True), name='password_change'),
path('password_change/done/', wrap(self.password_change_done, cacheable=True), name='password_change_done'),
# ################################################ user update ############################################
path('account/update/<int:pk>/', views.UserUpdate.as_view(), name='account_update'),
# ############################################ Reset password by email ####################################
# nessuna di queste view deve essere sotto wrap perche si deve poter accedere anche non essendo loggati
path('password_reset/', self.password_reset, name='password_reset'),
# OLD
# path('reset/<uidb64>/<token>/', self.password_reset_confirm, name='password_reset_confirm'),
# path('reset/<uidb64>/<token>/', PasswordResetConfirmView.as_view(
# form_class=forms.WebixSetPasswordForm,
# success_url=reverse_lazy('admin_webix:password_reset_complete'),
# template_name='admin_webix/account/password_reset_confirm.html'
# ), name='password_reset_confirm'),
path('reset/<uidb64>/<token>/', views.PasswordResetConfirmViewCustom.as_view(
form_class=forms.WebixSetPasswordForm,
success_url=reverse_lazy('admin_webix:password_reset_complete'),
template_name='admin_webix/account/password_reset_confirm.html'
), name='password_reset_confirm'),
path('password_reset/done/', self.password_reset_done, name='password_reset_done'),
path('reset/done/', self.password_reset_complete, name='password_reset_complete'),
]
if apps.is_installed("two_factor"):
urlpatterns += [
path('two_factor/', wrap(self.two_factor_profile), name='two_factor_profile'),
]
# Add in each model's views, and create a list of valid URLS for the
# app_index
valid_app_labels = []
for model, model_admin in self._registry.items():
urlpatterns += [
path('%s/%s/' % (model._meta.app_label, model._meta.model_name), include(model_admin.urls)),
]
# if model._meta.app_label not in valid_app_labels:
# valid_app_labels.append(model._meta.app_label)
# If there were ModelAdmins registered, we should have a list of app
# labels for which we need to allow access to the app_index view,
# if valid_app_labels:
# regex = r'^(?P<app_label>' + '|'.join(valid_app_labels) + ')/$'
# urlpatterns += [
# re_path(regex, wrap(self.app_index), name='app_list'),
# ]
return urlpatterns
@property
def urls(self):
return self.get_urls(), 'admin_webix', self.name
[docs] def each_context(self, request):
"""
Return a dictionary of variables to put in the template context for
*every* page in the admin site.
For sites running on a subpath, use the SCRIPT_NAME value if site_url
hasn't been customized.
"""
script_name = request.META['SCRIPT_NAME']
site_url = script_name if self.site_url == '/' and script_name else self.site_url
return {
'site_title': self.site_title,
'site_header': self.site_header,
'site_url': site_url,
'is_webgis_enable': self.is_webgis_enable(),
'is_webix_filter_enable': self.is_webix_filter_enable(),
'has_permission': self.has_permission(request), # utils for menu
'menu_list': self.get_menu_list(request), # utils for menu
'available_apps': self.get_app_list(request), # utils for menu
'webix_container_id': self.webix_container_id,
}
@never_cache
def dashboard(self, request, extra_context=None):
from django.views.generic import TemplateView
defaults = {
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
if self.dashboard_template is not None:
defaults['template_name'] = self.dashboard_template
return TemplateView.as_view(**defaults)(request)
[docs] def password_change(self, request, extra_context=None):
"""
Handle the "change password" task -- both form display and validation.
"""
from django.contrib.admin.forms import AdminPasswordChangeForm
# from django.contrib.auth.views import PasswordChangeView
from django_webix.admin_webix.views import PasswordChangeViewCustom
url = reverse('admin_webix:password_change_done', current_app=self.name)
defaults = {
'form_class': AdminPasswordChangeForm,
'success_url': url,
'extra_context': {**self.each_context(request), **(extra_context or {})},
'template_name': self.password_change_template or 'admin_webix/account/password_change.js',
}
request.current_app = self.name
return PasswordChangeViewCustom.as_view(**defaults)(request)
[docs] def password_change_done(self, request, extra_context=None):
"""
Display the "success" page after a password change.
"""
from django.contrib.auth.views import PasswordChangeDoneView
defaults = {
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
if self.password_change_done_template is not None:
defaults['template_name'] = self.password_change_done_template
request.current_app = self.name
return PasswordChangeDoneView.as_view(**defaults)(request)
[docs] def password_reset(self, request, extra_context=None):
"""
Handle the "reset password" task -- both form display and validation.
"""
from django.contrib.auth.views import PasswordResetView
from django_webix.admin_webix import forms
from django.contrib.sites.shortcuts import get_current_site
current_site = get_current_site(request)
site_name = current_site.name
domain = current_site.domain
if extra_context is None:
extra_context = {}
extra_context['domain'] = domain
extra_context['site_name'] = site_name
template = 'admin_webix/account/password_reset_form.js'
if not self.has_permission(request):
template = 'admin_webix/account/password_reset_form.html'
defaults = {
'template_name': template,
'email_template_name': 'admin_webix/account/password_reset_email.html',
'form_class': forms.WebixPasswordResetForm,
'success_url': reverse_lazy('admin_webix:password_reset_done'),
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
request.current_app = self.name
return PasswordResetView.as_view(**defaults)(request)
[docs] def password_reset_done(self, request, extra_context=None):
"""
Handle the "reset password" task -- both form display and validation.
"""
from django.contrib.auth.views import PasswordResetDoneView
template = 'admin_webix/account/password_reset_done.js'
if not self.has_permission(request):
template = 'admin_webix/account/password_reset_done.html'
defaults = {
'template_name': template,
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
request.current_app = self.name
return PasswordResetDoneView.as_view(**defaults)(request)
# NON so perche non funziona se lo metto nella function
# def password_reset_confirm(self, request, extra_context=None):
#
# from django.contrib.auth.views import PasswordResetConfirmView
# from django_webix.admin_webix import forms
#
# defaults = {
# 'template_name': 'admin_webix/account/password_reset_confirm.js',
# 'form_class': forms.WebixSetPasswordForm,
# 'success_url': reverse_lazy('admin_webix:password_reset_complete'),
# # 'extra_context': {**self.each_context(request)},
# }
# # raise Exception(extra_context, defaults)
# # request.current_app = self.name
# return PasswordResetConfirmView.as_view(
# form_class=forms.WebixSetPasswordForm,
# success_url=reverse_lazy('admin_webix:password_reset_complete'),
# template_name='admin_webix/account/password_reset_confirm.js'
# )(request)
def password_reset_complete(self, request, extra_context=None):
from django.contrib.auth.views import PasswordResetCompleteView
defaults = {
'template_name': 'admin_webix/account/password_reset_complete.html',
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
request.current_app = self.name
return PasswordResetCompleteView.as_view(**defaults)(request)
def two_factor_profile(self, request, extra_context=None):
if apps.is_installed("two_factor"):
from django_webix.views import WebixTemplateView
defaults = {
'template_name': 'admin_webix/account/two_factor.js',
'extra_context': {**self.each_context(request), **(extra_context or {})},
}
request.current_app = self.name
return WebixTemplateView.as_view(**defaults)(request)
else:
return None
[docs] @never_cache
def logout(self, request, extra_context=None):
"""
Log out the user for the given HttpRequest.
This should *not* assume the user is already logged in.
"""
from django.contrib.auth.views import LogoutView
defaults = {
'extra_context': {
**self.each_context(request),
# Since the user isn't logged out at this point, the value of
# has_permission must be overridden.
'has_permission': False,
'template_name': self.logout_template or 'admin_webix/logged_out.html',
'site_title': self.site_title,
**(extra_context or {})
},
}
request.current_app = self.name
LogoutView.template_name = self.logout_template or 'admin_webix/logged_out.html'
return LogoutView.as_view(**defaults)(request)
[docs] @never_cache
def login(self, request, extra_context=None):
"""
Display the login form for the given HttpRequest.
"""
if request.method == 'GET' and self.has_permission(request):
# Already logged-in, redirect to admin index
index_path = reverse('admin_webix:index', current_app=self.name)
return HttpResponseRedirect(index_path)
from django.contrib.auth.views import LoginView
# Since this module gets imported in the application's root package,
# it cannot import models from other applications at the module level,
# and django.contrib.admin.forms eventually imports User.
# from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.forms import AuthenticationForm
class AdminAuthenticationForm(AuthenticationForm):
"""
A custom authentication form used in the admin app.
"""
error_messages = {
**AuthenticationForm.error_messages,
'invalid_login': _(
"Please enter the correct %(username)s and password for a staff "
"account. Note that both fields may be case-sensitive."
),
}
required_css_class = 'required'
def confirm_login_allowed(self, user):
super().confirm_login_allowed(user)
# if not user.is_staff:
# raise forms.ValidationError(
# self.error_messages['invalid_login'],
# code='invalid_login',
# params={'username': self.username_field.verbose_name}
# )
context = {
**self.each_context(request),
'title': _('Log in'),
'app_path': request.get_full_path(),
'username': request.user.get_username(),
'site_title': self.site_title,
}
if (REDIRECT_FIELD_NAME not in request.GET and
REDIRECT_FIELD_NAME not in request.POST):
context[REDIRECT_FIELD_NAME] = reverse('admin_webix:index', current_app=self.name)
context.update(extra_context or {})
defaults = {
'extra_context': context,
'authentication_form': self.login_form or AdminAuthenticationForm,
'template_name': self.login_template or 'admin_webix/login.html',
}
request.current_app = self.name
return LoginView.as_view(**defaults)(request)
def extra_index_context(self, request):
return {}
[docs] @never_cache
def index(self, request, extra_context=None): # TODO da terminare la parte del template in modo carino circa
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
if self.is_webgis_enable() and self.webgis_template is None:
raise ImproperlyConfigured('Webgis template is not set')
history_url = request.GET.get('state', None)
try:
resolve(history_url)
except:
history_url = None
active_tab = request.GET.get('tab', None)
if active_tab not in ['webgis_leaflet', self.webix_container_id]:
active_tab = self.webix_container_id
context = {
**self.each_context(request),
**self.extra_index_context(request),
'history_url': history_url,
'title': self.index_title,
'app_list': self.get_app_list(request),
'active_tab': active_tab,
**(extra_context or {}),
}
request.current_app = self.name
return TemplateResponse(request, self.index_template or 'admin_webix/index.html', context)
[docs]class DefaultAdminWebixSite(LazyObject):
def _setup(self):
AdminWebixSiteClass = import_string(apps.get_app_config('admin_webix').default_site)
self._wrapped = AdminWebixSiteClass()
# This global object represents the default admin site, for the common case.
# You can provide your own AdminWebixSite using the (Simple)AdminConfig.default_site
# attribute. You can also instantiate AdminWebixSite in your own code to create a
# custom admin site.
site = DefaultAdminWebixSite()