Source code for oioioi.base.admin

import urllib.parse
from django.contrib import admin, messages
from django.contrib.admin import helpers
from django.contrib.admin.sites import AdminSite as DjangoAdminSite
from django.contrib.admin.utils import NestedObjects, model_ngettext, unquote
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db import router
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.html import escape
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _

from oioioi.base.forms import OioioiUserChangeForm, OioioiUserCreationForm
from oioioi.base.menu import MenuRegistry, side_pane_menus_registry
from oioioi.base.models import Consents
from oioioi.base.permissions import is_superuser
from oioioi.base.utils import ClassInitMeta, ObjectWithMixins
from oioioi.base.utils.redirect import safe_redirect

[docs]NO_CATEGORY = '__no_category__'
[docs]class TabularInline(admin.TabularInline): # by default we assume that if item is added to specific # admin menu mixin it should be visible and editable
[docs] def has_add_permission(self, request, obj=None): return True
[docs] def has_change_permission(self, request, obj=None): return True
[docs] def has_delete_permission(self, request, obj=None): return True
[docs] def has_view_permission(self, request, obj=None): return self.has_change_permission(request, obj)
[docs]class StackedInline(admin.StackedInline): # by default we assume that if item is added to specific # admin menu mixin it should be visible and editable
[docs] def has_add_permission(self, request, obj=None): return True
[docs] def has_change_permission(self, request, obj=None): return True
[docs] def has_delete_permission(self, request, obj=None): return True
[docs] def has_view_permission(self, request, obj=None): return self.has_change_permission(request, obj)
[docs]class ModelAdminMeta(admin.ModelAdmin.__class__, ClassInitMeta): pass
[docs]class ModelAdmin( admin.ModelAdmin, ObjectWithMixins, metaclass=ModelAdminMeta ): # This is handled by AdminSite._reinit_model_admins
[docs] allow_too_late_mixins = True
[docs] def response_change(self, request, obj): not_popup = '_popup' not in request.GET and '_popup' not in request.POST if '_continue' in request.POST and not_popup: return HttpResponseRedirect(request.get_full_path()) if ( 'came_from' in request.GET and '_continue' not in request.POST and '_saveasnew' not in request.POST and '_addanother' not in request.POST ): return safe_redirect(request, request.GET.get('came_from')) return super(ModelAdmin, self).response_change(request, obj)
[docs] def change_view(self, request, object_id, form_url='', extra_context=None): response = super(ModelAdmin, self).change_view( request, object_id, form_url, extra_context ) if isinstance(response, TemplateResponse) and 'came_from' in request.GET: response.context_data['form_url'] += '?' + urllib.parse.urlencode( {'came_from': request.GET.get('came_from')} ) return response
[docs] def response_delete(self, request): opts = self.model._meta if 'came_from' in request.GET: return safe_redirect(request, request.GET.get('came_from')) if not self.has_change_permission(request): return HttpResponseRedirect( reverse('admin:index', current_app=self.admin_site.name) ) return HttpResponseRedirect( reverse( 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name), current_app=self.admin_site.name, ) )
[docs] def delete_view(self, request, object_id, extra_context=None): opts = self.model._meta app_label = opts.app_label obj = self.get_object(request, unquote(object_id)) if not self.has_delete_permission(request, obj): raise PermissionDenied if obj is None: raise Http404( _("%(name)s object with primary key %(key)r does not exist.") % {'name': force_str(opts.verbose_name), 'key': escape(object_id)} ) if request.POST: # The user has already confirmed the deletion. obj_display = force_str(obj) self.log_deletion(request, obj, obj_display) self.delete_model(request, obj) self.message_user( request, _("The %(name)s \"%(obj)s\" was deleted successfully.") % { 'name': force_str(opts.verbose_name), 'obj': force_str(obj_display), }, ) return self.response_delete(request) object_name = force_str(opts.verbose_name) context = { "object_name": object_name, "object": obj, "opts": opts, "app_label": app_label, } context.update(extra_context or {}) request.current_app = self.admin_site.name return TemplateResponse( request, self.delete_confirmation_template or [ "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), "admin/%s/delete_confirmation.html" % app_label, "admin/delete_confirmation.html", ], context, )
[docs] def get_queryset(self, request): qs = super(ModelAdmin, self).get_queryset(request) list_select_related = self.get_custom_list_select_related() if list_select_related: return qs.select_related(*list_select_related) else: return qs
[docs] def has_view_permission(self, request, obj=None): return self.has_change_permission(request, obj)
[docs]def delete_selected(modeladmin, request, queryset, **kwargs): """Default ModelAdmin action that deletes the selected objects. Django's default handler doesn't even check the has_delete_permission() of corresponding ModelAdmin with specific instances (only the general permission), and requires django's User model permissions as well. Theese aren't currently used in OIOIOI, so this custom method doesn't care about them. This implementation checks if deleted model is registered in the current AdminSite and if it is, then uses has_delete_permission() with it's instance. It first displays a confirmation page that shows all the deleteable objects, or, if the user has no permission for one of the related objects (foreignkeys), a "permission denied" message. Next, it deletes all selected objects and redirects back to the change list. """ opts = modeladmin.model._meta app_label = opts.app_label # Find related objects and check their permissions # This is a custom method as well to_delete, perms_needed, protected = collect_deleted_objects( modeladmin, request, queryset ) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): if perms_needed: raise PermissionDenied n = queryset.count() if n: for obj in queryset: obj_display = force_str(obj) modeladmin.log_deletion(request, obj, obj_display) queryset.delete() message_text = _("Successfully deleted %(count)d %(items)s.") % { "count": n, "items": model_ngettext(modeladmin.opts, n), } modeladmin.message_user(request, message_text, messages.SUCCESS) # If specific_redirect was not given in kwargs, return None to display the change list page again. if kwargs.get('specific_redirect') is None: return None else: return redirect(kwargs['specific_redirect']) if len(queryset) == 1: objects_name = force_str(opts.verbose_name) else: objects_name = force_str(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") context = dict( modeladmin.admin_site.each_context(request), title=title, objects_name=objects_name, deletable_objects=[to_delete], queryset=queryset, perms_lacking=perms_needed, protected=protected, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = modeladmin.admin_site.name custom_template = modeladmin.delete_selected_confirmation_template # Display the confirmation page return TemplateResponse( request, custom_template or [ "admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.model_name), "admin/%s/delete_selected_confirmation.html" % app_label, "admin/delete_selected_confirmation.html", ], context, )
[docs]delete_selected.short_description = _("Delete selected %(verbose_name_plural)s")
[docs]def collect_deleted_objects(modeladmin, request, queryset): """Collects objects that are related to queryset items and checks their permissions. This method checks if the user has permissions to delete items that are anyhow related to theese in the queryset (regardless of the depth). ``modeladmin`` is expected to be a ModelAdmin instance corresponding to the class of items contained in the ``queryset``. """ db_backend = router.db_for_write(queryset.first().__class__) # NestedObjects is undocumented API, can blow up at any time collector = NestedObjects(using=db_backend) collector.collect(queryset) # Check permissions and return a human-readable string representation perms_needed = set() def format_callback(obj): admin_site = modeladmin.admin_site has_admin = obj.__class__ in admin_site._registry opts = obj._meta if has_admin: model_admin = admin_site._registry[obj.__class__] if not request.user.is_superuser and not model_admin.has_delete_permission( request, obj ): perms_needed.add(opts.verbose_name) return '%s: %s' % (capfirst(force_str(opts.verbose_name)), force_str(obj)) # Get a nested list of dependent objects to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
[docs]class AdminSite(DjangoAdminSite): def __init__(self, *args, **kwargs): super(AdminSite, self).__init__(*args, **kwargs) # Override default delete_selected action handler # See delete_selected() docstring for further information self._actions['delete_selected'] = delete_selected self._global_actions['delete_selected'] = delete_selected
[docs] def has_permission(self, request): return request.user.is_active
[docs] def _reinit_model_admins(self): for model, model_admin in self._registry.items(): cls = model_admin.__class__ cls = getattr(cls, '__unmixed_class__', cls) self._registry[model] = cls(model, self)
[docs] def get_urls(self): self._reinit_model_admins() return super(AdminSite, self).get_urls()
[docs] def login(self, request, extra_context=None): next_url = request.GET.get('next', None) suffix = ( '?' + urllib.parse.urlencode({'next': next_url}) if next_url else '' ) return HttpResponseRedirect(reverse('auth_login') + suffix)
[docs]site = AdminSite(name='oioioiadmin')
[docs]system_admin_menu_registry = MenuRegistry(_("System Administration"), is_superuser)
side_pane_menus_registry.register(system_admin_menu_registry, order=10)
[docs]class OioioiUserAdmin(UserAdmin, ObjectWithMixins, metaclass=ModelAdminMeta):
[docs] form = OioioiUserChangeForm
[docs] add_form = OioioiUserCreationForm
# This is handled by AdminSite._reinit_model_admins
[docs] allow_too_late_mixins = True
[docs] fieldsets = ( (None, {'fields': ('username', 'password')}), (_("Personal info"), {'fields': ('first_name', 'last_name', 'email')}), (_("Permissions"), {'fields': ('is_active', 'is_superuser', 'groups')}), (_("Important dates"), {'fields': ('last_login', 'date_joined')}), )
[docs] list_filter = ['is_superuser', 'is_active']
[docs] list_display = ['username', 'email', 'first_name', 'last_name', 'is_active']
[docs] filter_horizontal = ()
[docs] actions = ['activate_user']
[docs] def activate_user(self, request, qs): qs.update(is_active=True)
[docs] activate_user.short_description = _("Mark users as active")
site.register(User, OioioiUserAdmin) system_admin_menu_registry.register( 'users', _("Users"), lambda request: reverse('oioioiadmin:auth_user_changelist'), order=10, )
[docs]class InstanceDependentAdmin(admin.ModelAdmin):
[docs] default_model_admin = admin.ModelAdmin
[docs] def _model_admin_for_instance(self, request, instance=None): raise NotImplementedError
[docs] def _find_model_admin(self, request, object_id): if object_id is None: obj = None else: obj = self.get_object(request, unquote(object_id)) model_admin = self._model_admin_for_instance(request, obj) if not model_admin: model_admin = self.default_model_admin(self.model, self.admin_site) return model_admin
[docs] def add_view(self, request, form_url='', extra_context=None): model_admin = self._find_model_admin(request, None) return model_admin.add_view(request, form_url, extra_context)
[docs] def change_view(self, request, object_id, form_url='', extra_context=None): model_admin = self._find_model_admin(request, object_id) return model_admin.change_view(request, object_id, form_url, extra_context)
[docs] def delete_view(self, request, object_id, extra_context=None): model_admin = self._find_model_admin(request, object_id) return model_admin.delete_view(request, object_id, extra_context)
[docs] def changelist_view(self, request, extra_context=None): model_admin = self._find_model_admin(request, None) return model_admin.changelist_view(request, extra_context)
[docs] def history_view(self, request, object_id, extra_context=None): model_admin = self._find_model_admin(request, object_id) return model_admin.history_view(request, object_id, extra_context)
[docs] def has_view_permission(self, request, obj=None): return self.has_change_permission(request, obj)
[docs]class MixinsAdmin(InstanceDependentAdmin): def __init__(self, *args, **kwargs): super(InstanceDependentAdmin, self).__init__(*args, **kwargs) if not issubclass(self.default_model_admin, ObjectWithMixins): raise AssertionError( 'MixinsAdmin.default_model_admin must ' 'be a subclass of ObjectWithMixins' )
[docs] def _model_admin_for_instance(self, request, instance=None): mixins = self._mixins_for_instance(request, instance) if mixins: return self.default_model_admin(self.model, self.admin_site, mixins=mixins)
[docs] def _mixins_for_instance(self, request, instance=None): raise NotImplementedError
[docs]class ConsentsInline(StackedInline):
[docs] model = Consents
[docs] extra = 0
[docs] def has_add_permission(self, request, obj=None): return False
[docs] def has_change_permission(self, request, obj=None): # Protected by parent ModelAdmin return True
[docs] def has_delete_permission(self, request, obj=None): return False
[docs]class UserWithConsentsAdminMixin(object): def __init__(self, *args, **kwargs): super(UserWithConsentsAdminMixin, self).__init__(*args, **kwargs) self.inlines = tuple(self.inlines) + (ConsentsInline,)
OioioiUserAdmin.mix_in(UserWithConsentsAdminMixin)