Fix for typeclass app_label, and admin fix. Resolves #2112.

This commit is contained in:
Griatch 2020-06-26 21:17:07 +02:00
parent e8b99175ad
commit 5e6c52e2e6
2 changed files with 108 additions and 13 deletions

View file

@ -4,13 +4,26 @@
# #
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin, messages
from django.contrib.admin.options import IS_POPUP_VAR
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.contrib.admin.utils import unquote
from django.template.response import TemplateResponse
from django.http import Http404, HttpResponseRedirect
from django.core.exceptions import PermissionDenied
from django.views.decorators.debug import sensitive_post_parameters
from django.utils.decorators import method_decorator
from django.utils.html import escape
from django.urls import path, reverse
from django.contrib.auth import update_session_auth_hash
from evennia.accounts.models import AccountDB from evennia.accounts.models import AccountDB
from evennia.typeclasses.admin import AttributeInline, TagInline from evennia.typeclasses.admin import AttributeInline, TagInline
from evennia.utils import create from evennia.utils import create
sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
# handle the custom User editor # handle the custom User editor
class AccountDBChangeForm(UserChangeForm): class AccountDBChangeForm(UserChangeForm):
@ -260,6 +273,71 @@ class AccountDBAdmin(BaseUserAdmin):
), ),
) )
@sensitive_post_parameters_m
def user_change_password(self, request, id, form_url=''):
user = self.get_object(request, unquote(id))
if not self.has_change_permission(request, user):
raise PermissionDenied
if user is None:
raise Http404('%(name)s object with primary key %(key)r does not exist.') % {
'name': self.model._meta.verbose_name,
'key': escape(id),
}
if request.method == 'POST':
form = self.change_password_form(user, request.POST)
if form.is_valid():
form.save()
change_message = self.construct_change_message(request, form, None)
self.log_change(request, user, change_message)
msg = 'Password changed successfully.'
messages.success(request, msg)
update_session_auth_hash(request, form.user)
return HttpResponseRedirect(
reverse(
'%s:%s_%s_change' % (
self.admin_site.name,
user._meta.app_label,
# the model_name is something we need to hardcode
# since our accountdb is a proxy:
"accountdb",
),
args=(user.pk,),
)
)
else:
form = self.change_password_form(user)
fieldsets = [(None, {'fields': list(form.base_fields)})]
adminForm = admin.helpers.AdminForm(form, fieldsets, {})
context = {
'title': 'Change password: %s' % escape(user.get_username()),
'adminForm': adminForm,
'form_url': form_url,
'form': form,
'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,
'original': user,
'save_as': False,
'show_save': True,
**self.admin_site.each_context(request),
}
request.current_app = self.admin_site.name
return TemplateResponse(
request,
self.change_user_password_template or
'admin/auth/user/change_password.html',
context,
)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" """
Custom save actions. Custom save actions.

View file

@ -104,18 +104,24 @@ class TypeclassBase(SharedMemoryModelBase):
attrs["typename"] = name attrs["typename"] = name
attrs["path"] = "%s.%s" % (attrs["__module__"], name) attrs["path"] = "%s.%s" % (attrs["__module__"], name)
# typeclass proxy setup def _get_dbmodel(bases):
app_label = None """Recursively get the dbmodel"""
# first check explicit __applabel__ on the typeclass if not hasattr(bases, "__iter__"):
if "__applabel__" not in attrs: bases = [bases]
# find the app-label in one of the bases, usually the dbmodel
for base in bases: for base in bases:
try: if base._meta.proxy or base._meta.abstract:
attrs["__applabel__"] = base.__applabel__ for kls in base._meta.parents:
except AttributeError: return _get_dbmodel(kls)
pass return base
else:
break dbmodel = _get_dbmodel(bases)
# typeclass proxy setup
# first check explicit __applabel__ on the typeclass, then figure
# it out from the dbmodel
if dbmodel and "__applabel__" not in attrs:
# find the app-label in one of the bases, usually the dbmodel
attrs["__applabel__"] = dbmodel._meta.app_label
if "Meta" not in attrs: if "Meta" not in attrs:
class Meta: class Meta:
@ -127,9 +133,20 @@ class TypeclassBase(SharedMemoryModelBase):
new_class = ModelBase.__new__(cls, name, bases, attrs) new_class = ModelBase.__new__(cls, name, bases, attrs)
# django doesn't support inheriting proxy models so we hack support for
# it here by injecting `proxy_for_model` to the actual dbmodel.
# Unfortunately we cannot also set the correct model_name, because this
# would block multiple-inheritance of typeclasses (Django doesn't allow
# multiple bases of the same model).
if dbmodel:
new_class._meta.proxy_for_model = dbmodel
# Maybe Django will eventually handle this in the future:
# new_class._meta.model_name = dbmodel._meta.model_name
# attach signals # attach signals
signals.post_save.connect(call_at_first_save, sender=new_class) signals.post_save.connect(call_at_first_save, sender=new_class)
signals.pre_delete.connect(remove_attributes_on_delete, sender=new_class) signals.pre_delete.connect(
remove_attributes_on_delete, sender=new_class)
return new_class return new_class