From 459b12a24a318398cbd95b9cdd4373a0f100833f Mon Sep 17 00:00:00 2001 From: hfwin <475947645@qq.com> Date: Sun, 30 Jan 2022 23:23:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?[style]=20=E6=9B=B4=E6=96=B0ignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 1e9fa8329..c462b737e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# 编辑器缓存文件夹 +.idea +.idea/ + *.py[cod] # C extensions From 6db52860809900779c55b66002fbbcf27ce6dee0 Mon Sep 17 00:00:00 2001 From: hfwin <475947645@qq.com> Date: Sun, 30 Jan 2022 23:26:25 +0800 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20=E7=BB=91=E5=AE=9Asix=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xadmin/__init__.py | 8 +- "\351\200\202\351\205\215django3.md" | 121 +++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 "\351\200\202\351\205\215django3.md" diff --git a/xadmin/__init__.py b/xadmin/__init__.py index 3e1a0522c..18663ef71 100644 --- a/xadmin/__init__.py +++ b/xadmin/__init__.py @@ -1,8 +1,13 @@ +VERSION = (0, 6, 0) -VERSION = (0,6,0) +import six +import django + +setattr(django.utils, 'six', six) from xadmin.sites import AdminSite, site + class Settings(object): pass @@ -67,4 +72,5 @@ def autodiscover(): if module_has_submodule(mod, 'adminx'): raise + default_app_config = 'xadmin.apps.XAdminConfig' diff --git "a/\351\200\202\351\205\215django3.md" "b/\351\200\202\351\205\215django3.md" new file mode 100644 index 000000000..eb680f084 --- /dev/null +++ "b/\351\200\202\351\205\215django3.md" @@ -0,0 +1,121 @@ +# `xadmin修改记录,基于webDjango项目` + +# 20220123 + +* 复制可用版本,后面碰到问题再改 + +主要出错在plugins + +django3中变量导包位置变动 + +* 更新font-awesome + +前端图标文件 xadmin/static/xadmin/vendor/font-awesome + +原来是4.0.3,更新到4.7.0 + +* 3.x版本中django.uitls中移除了six,但是xadmin中大量这样使用,在xadmin.__init__.py顶部运行位置,增加 + +``` +import six +import django +setattr(django.utils, 'six', six) +``` + +# 20220122 + +1 + +``` + File "D:\0web\xadmin-django3\xadmin\views\list.py", line 225, in get_list_queryset + except models.FieldDoesNotExist: +AttributeError: module 'django.db.models' has no attribute 'FieldDoesNotExist' +``` + +``` +from django.core.exceptions import FieldDoesNotExist +``` + +2 + +``` + File "D:\0web\xadmin-django3\xadmin\plugins\filters.py", line 9, in + from django.db.models.fields import FieldDoesNotExist +``` + +``` +# from django.db.models.fields import FieldDoesNotExist +from django.core.exceptions import FieldDoesNotExist +``` + +3 + +``` +File "D:\0web\xadmin-django3\xadmin\models.py", line 19, in + from xadmin.util import quote + File "D:\0web\xadmin-django3\xadmin\util.py", line 7, in + from django.forms.forms import pretty_name +``` + +``` +# from django.forms.forms import pretty_name +from django.forms.utils import pretty_name +``` + +4 + +``` + File "D:\0web\1_muke\xadmin_ueditor_django\django3\xadmin\sites.py", line 7, in + from django.utils import six +``` + +3.x版本中django.uitls中移除了six,但是xadmin中大量这样使用,在xadmin.__init__.py顶部运行位置,增加 + +``` +import six +import django +setattr(django.utils, 'six', six) +``` + +5 + +``` + File "D:\0web\1_muke\xadmin_ueditor_django\django3\xadmin\models.py", line 11, in + from django.utils.encoding import python_2_unicode_compatible, smart_text +``` + +``` +# from django.utils.encoding import python_2_unicode_compatible, smart_text +from django.utils.encoding import smart_text +from six import python_2_unicode_compatible + +``` + +6 + +``` + File "D:\0web\1_muke\xadmin_ueditor_django\django3\xadmin\models.py", line 20, in + from xadmin.util import quote + File "D:\0web\1_muke\xadmin_ueditor_django\django3\xadmin\util.py", line 25, in + from django.contrib.staticfiles.templatetags.staticfiles import static + +``` + +``` + # from django.contrib.staticfiles.templatetags.staticfiles import static + from django.templatetags.static import static +``` + +7 + +``` +File "D:\0web\1_muke\xadmin_ueditor_django\django3\xadmin\plugins\filters.py", line 9, in + from django.db.models.fields import FieldDoesNotExist +ImportError: cannot import name 'FieldDoesNotExist' from 'django.db.models.fields' (D:\Anaconda3\envs\django3\lib\site-packages\django\db\models\fields\__init__.py) + +``` + +``` +# from django.db.models.fields import FieldDoesNotExist +from django.core.exceptions import FieldDoesNotExist +``` From 0362f2320be22f4c6db9112f00f4461af2b2fa89 Mon Sep 17 00:00:00 2001 From: hfwin <475947645@qq.com> Date: Sun, 30 Jan 2022 23:29:04 +0800 Subject: [PATCH 3/3] =?UTF-8?q?[feat]=20django3=E5=9F=BA=E6=9C=AC=E5=8F=AF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xadmin/__init__.py | 7 +- xadmin/filters.py | 140 +- xadmin/migrations/0004_auto_20210425_2223.py | 33 + xadmin/models.py | 19 +- xadmin/plugins/__init__.py | 47 +- xadmin/plugins/actions.py | 28 +- xadmin/plugins/aggregation.py | 9 +- xadmin/plugins/batch.py | 4 +- xadmin/plugins/bookmark.py | 44 +- xadmin/plugins/details.py | 4 +- xadmin/plugins/editable.py | 13 +- xadmin/plugins/filters.py | 18 +- xadmin/plugins/images.py | 4 +- xadmin/plugins/importexport.py | 4 +- xadmin/plugins/inline.py | 12 +- xadmin/plugins/language.py | 3 +- xadmin/plugins/multiselect.py | 7 +- xadmin/plugins/passwords.py | 20 +- xadmin/plugins/quickfilter.py | 87 +- xadmin/plugins/quickform.py | 4 +- xadmin/plugins/relate.py | 19 +- xadmin/plugins/relfield.py | 23 +- xadmin/plugins/sortablelist.py | 2 +- xadmin/plugins/themes.py | 21 +- xadmin/plugins/topnav.py | 2 +- xadmin/plugins/ueditor.py | 43 + xadmin/plugins/wizard.py | 2 - xadmin/plugins/xversion.py | 15 +- xadmin/sites.py | 26 +- xadmin/static/xadmin/css/xadmin.main.css | 3 + .../css/font-awesome.css | 1338 +++++++ .../css/font-awesome.min.css | 4 + .../fonts/FontAwesome.otf | Bin 0 -> 62856 bytes .../fonts/fontawesome-webfont.eot | Bin 0 -> 38205 bytes .../fonts/fontawesome-webfont.svg | 414 +++ .../fonts/fontawesome-webfont.ttf | Bin 0 -> 80652 bytes .../fonts/fontawesome-webfont.woff | Bin 0 -> 44432 bytes .../vendor/font-awesome/css/font-awesome.css | 1129 +++++- .../font-awesome/css/font-awesome.min.css | 4 +- .../vendor/font-awesome/fonts/FontAwesome.otf | Bin 62856 -> 134808 bytes .../fonts/fontawesome-webfont.eot | Bin 38205 -> 165742 bytes .../fonts/fontawesome-webfont.svg | 3079 ++++++++++++++--- .../fonts/fontawesome-webfont.ttf | Bin 80652 -> 165548 bytes .../fonts/fontawesome-webfont.woff | Bin 44432 -> 98024 bytes .../fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../xadmin/blocks/comm.top.setlang.html | 2 +- xadmin/templates/xadmin/views/logged_out.html | 2 +- xadmin/templatetags/xadmin_tags.py | 7 +- xadmin/util.py | 38 +- xadmin/views/__init__.py | 4 +- xadmin/views/base.py | 37 +- xadmin/views/dashboard.py | 20 +- xadmin/views/delete.py | 16 +- xadmin/views/detail.py | 2 +- xadmin/views/edit.py | 18 +- xadmin/views/list.py | 43 +- xadmin/views/website.py | 14 +- xadmin/widgets.py | 33 +- 58 files changed, 6006 insertions(+), 861 deletions(-) create mode 100644 xadmin/migrations/0004_auto_20210425_2223.py create mode 100644 xadmin/plugins/ueditor.py create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/css/font-awesome.css create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/css/font-awesome.min.css create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/fonts/FontAwesome.otf create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/fonts/fontawesome-webfont.eot create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/fonts/fontawesome-webfont.svg create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/fonts/fontawesome-webfont.ttf create mode 100644 xadmin/static/xadmin/vendor/font-awesome.bak4.0.3/fonts/fontawesome-webfont.woff create mode 100644 xadmin/static/xadmin/vendor/font-awesome/fonts/fontawesome-webfont.woff2 diff --git a/xadmin/__init__.py b/xadmin/__init__.py index 18663ef71..24f3e498b 100644 --- a/xadmin/__init__.py +++ b/xadmin/__init__.py @@ -1,13 +1,15 @@ -VERSION = (0, 6, 0) + +VERSION = (0,6,0) import six import django - setattr(django.utils, 'six', six) + from xadmin.sites import AdminSite, site + class Settings(object): pass @@ -72,5 +74,4 @@ def autodiscover(): if module_has_submodule(mod, 'adminx'): raise - default_app_config = 'xadmin.apps.XAdminConfig' diff --git a/xadmin/filters.py b/xadmin/filters.py index 9eba01e99..9ab977a6c 100644 --- a/xadmin/filters.py +++ b/xadmin/filters.py @@ -8,19 +8,19 @@ from django.template.context import Context from django.utils import six from django.utils.safestring import mark_safe -from django.utils.html import escape,format_html +from django.utils.html import escape, format_html from django.utils.text import Truncator from django.core.cache import cache, caches from xadmin.views.list import EMPTY_CHANGELIST_VALUE -from xadmin.util import is_related_field,is_related_field2 +from xadmin.util import is_related_field, is_related_field2 import datetime FILTER_PREFIX = '_p_' SEARCH_VAR = '_q_' from .util import (get_model_from_relation, - reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) + reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) class BaseFilter(object): @@ -125,9 +125,9 @@ def __init__(self, field, request, params, model, admin_view, field_path): self.context_params["%s_val" % name] = '' arr = map( - lambda kv: setattr(self, 'lookup_' + kv[0], kv[1]), - self.context_params.items() - ) + lambda kv: setattr(self, 'lookup_' + kv[0], kv[1]), + self.context_params.items() + ) if six.PY3: list(arr) @@ -169,27 +169,27 @@ def choices(self): ('', _('All')), ('1', _('Yes')), ('0', _('No')), - ): + ): yield { - 'selected': ( - self.lookup_exact_val == lookup - and not self.lookup_isnull_val - ), - 'query_string': self.query_string( - {self.lookup_exact_name: lookup}, - [self.lookup_isnull_name], - ), - 'display': title, - } + 'selected': ( + self.lookup_exact_val == lookup + and not self.lookup_isnull_val + ), + 'query_string': self.query_string( + {self.lookup_exact_name: lookup}, + [self.lookup_isnull_name], + ), + 'display': title, + } if isinstance(self.field, models.NullBooleanField): yield { - 'selected': self.lookup_isnull_val == 'True', - 'query_string': self.query_string( - {self.lookup_isnull_name: 'True'}, - [self.lookup_exact_name], - ), - 'display': _('Unknown'), - } + 'selected': self.lookup_isnull_val == 'True', + 'query_string': self.query_string( + {self.lookup_isnull_name: 'True'}, + [self.lookup_exact_name], + ), + 'display': _('Unknown'), + } @manager.register @@ -222,10 +222,10 @@ class TextFieldListFilter(FieldFilter): @classmethod def test(cls, field, request, params, model, admin_view, field_path): return ( - isinstance(field, models.CharField) - and field.max_length > 20 - or isinstance(field, models.TextField) - ) + isinstance(field, models.CharField) + and field.max_length > 20 + or isinstance(field, models.TextField) + ) @manager.register @@ -320,7 +320,7 @@ def choices(self): yield { 'selected': self.date_params == param_dict, 'query_string': self.query_string( - param_dict, [FILTER_PREFIX + self.field_generic]), + param_dict, [FILTER_PREFIX + self.field_generic]), 'display': title, } @@ -339,12 +339,12 @@ def test(cls, field, request, params, model, admin_view, field_path): def __init__(self, field, request, params, model, model_admin, field_path): other_model = get_model_from_relation(field) - if hasattr(field, 'rel'): - rel_name = field.rel.get_related_field().name + if hasattr(field, 'remote_field'): + rel_name = field.remote_field.get_related_field().name else: rel_name = other_model._meta.pk.name - self.lookup_formats = {'in': '%%s__%s__in' % rel_name,'exact': '%%s__%s__exact' % rel_name} + self.lookup_formats = {'in': '%%s__%s__in' % rel_name, 'exact': '%%s__%s__exact' % rel_name} super(RelatedFieldSearchFilter, self).__init__( field, request, params, model, model_admin, field_path) @@ -360,9 +360,9 @@ def __init__(self, field, request, params, model, model_admin, field_path): other_model._meta.app_label, other_model._meta.model_name)) self.label = self.label_for_value(other_model, rel_name, self.lookup_exact_val) if self.lookup_exact_val else "" self.choices = '?' - if field.rel.limit_choices_to: - for i in list(field.rel.limit_choices_to): - self.choices += "&_p_%s=%s" % (i, field.rel.limit_choices_to[i]) + if field.remote_field.limit_choices_to: + for i in list(field.remote_field.limit_choices_to): + self.choices += "&_p_%s=%s" % (i, field.remote_field.limit_choices_to[i]) self.choices = format_html(self.choices) def label_for_value(self, other_model, rel_name, value): @@ -390,12 +390,12 @@ def test(cls, field, request, params, model, admin_view, field_path): def __init__(self, field, request, params, model, model_admin, field_path): other_model = get_model_from_relation(field) - if hasattr(field, 'rel'): - rel_name = field.rel.get_related_field().name + if hasattr(field, 'remote_field'): + rel_name = field.remote_field.get_related_field().name else: rel_name = other_model._meta.pk.name - self.lookup_formats = {'in': '%%s__%s__in' % rel_name,'exact': '%%s__%s__exact' % + self.lookup_formats = {'in': '%%s__%s__in' % rel_name, 'exact': '%%s__%s__exact' % rel_name, 'isnull': '%s__isnull'} self.lookup_choices = field.get_choices(include_blank=False) super(RelatedFieldListFilter, self).__init__( @@ -409,7 +409,7 @@ def __init__(self, field, request, params, model, model_admin, field_path): def has_output(self): if (is_related_field(self.field) - and self.field.field.null or hasattr(self.field, 'rel') + and self.field.field.null or hasattr(self.field, 'remote_field') and self.field.null): extra = 1 else: @@ -435,7 +435,7 @@ def choices(self): 'display': val, } if (is_related_field(self.field) - and self.field.field.null or hasattr(self.field, 'rel') + and self.field.field.null or hasattr(self.field, 'remote_field') and self.field.null): yield { 'selected': bool(self.lookup_isnull_val), @@ -445,81 +445,83 @@ def choices(self): 'display': EMPTY_CHANGELIST_VALUE, } + @manager.register class MultiSelectFieldListFilter(ListFieldFilter): """ Delegates the filter to the default filter and ors the results of each - + Lists the distinct values of each field as a checkbox Uses the default spec for each - + """ template = 'xadmin/filters/checklist.html' lookup_formats = {'in': '%s__in'} - cache_config = {'enabled':False,'key':'quickfilter_%s','timeout':3600,'cache':'default'} - + cache_config = {'enabled': False, 'key': 'quickfilter_%s', 'timeout': 3600, 'cache': 'default'} + @classmethod def test(cls, field, request, params, model, admin_view, field_path): return True - + def get_cached_choices(self): if not self.cache_config['enabled']: return None c = caches(self.cache_config['cache']) - return c.get(self.cache_config['key']%self.field_path) - - def set_cached_choices(self,choices): + return c.get(self.cache_config['key'] % self.field_path) + + def set_cached_choices(self, choices): if not self.cache_config['enabled']: return c = caches(self.cache_config['cache']) - return c.set(self.cache_config['key']%self.field_path,choices) - - def __init__(self, field, request, params, model, model_admin, field_path,field_order_by=None,field_limit=None,sort_key=None,cache_config=None): - super(MultiSelectFieldListFilter,self).__init__(field, request, params, model, model_admin, field_path) - + return c.set(self.cache_config['key'] % self.field_path, choices) + + def __init__(self, field, request, params, model, model_admin, field_path, field_order_by=None, field_limit=None, sort_key=None, cache_config=None): + super(MultiSelectFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) + # Check for it in the cachce - if cache_config is not None and type(cache_config)==dict: + if cache_config is not None and type(cache_config) == dict: self.cache_config.update(cache_config) - + if self.cache_config['enabled']: self.field_path = field_path choices = self.get_cached_choices() if choices: self.lookup_choices = choices return - + # Else rebuild it - queryset = self.admin_view.queryset().exclude(**{"%s__isnull"%field_path:True}).values_list(field_path, flat=True).distinct() + queryset = self.admin_view.queryset().exclude(**{"%s__isnull" % field_path: True}).values_list(field_path, flat=True).distinct() #queryset = self.admin_view.queryset().distinct(field_path).exclude(**{"%s__isnull"%field_path:True}) - + if field_order_by is not None: # Do a subquery to order the distinct set queryset = self.admin_view.queryset().filter(id__in=queryset).order_by(field_order_by) - - if field_limit is not None and type(field_limit)==int and queryset.count()>field_limit: + + if field_limit is not None and type(field_limit) == int and queryset.count() > field_limit: queryset = queryset[:field_limit] - - self.lookup_choices = [str(it) for it in queryset.values_list(field_path,flat=True) if str(it).strip()!=""] + + self.lookup_choices = [str(it) for it in queryset.values_list(field_path, flat=True) if str(it).strip() != ""] if sort_key is not None: - self.lookup_choices = sorted(self.lookup_choices,key=sort_key) - + self.lookup_choices = sorted(self.lookup_choices, key=sort_key) + if self.cache_config['enabled']: - self.set_cached_choices(self.lookup_choices) + self.set_cached_choices(self.lookup_choices) def choices(self): - self.lookup_in_val = (type(self.lookup_in_val) in (tuple,list)) and self.lookup_in_val or list(self.lookup_in_val) + self.lookup_in_val = (type(self.lookup_in_val) in (tuple, list)) and self.lookup_in_val or list(self.lookup_in_val) yield { 'selected': len(self.lookup_in_val) == 0, - 'query_string': self.query_string({},[self.lookup_in_name]), + 'query_string': self.query_string({}, [self.lookup_in_name]), 'display': _('All'), } for val in self.lookup_choices: yield { 'selected': smart_text(val) in self.lookup_in_val, - 'query_string': self.query_string({self.lookup_in_name: ",".join([val]+self.lookup_in_val),}), - 'remove_query_string': self.query_string({self.lookup_in_name: ",".join([v for v in self.lookup_in_val if v != val]),}), + 'query_string': self.query_string({self.lookup_in_name: ",".join([val] + self.lookup_in_val), }), + 'remove_query_string': self.query_string({self.lookup_in_name: ",".join([v for v in self.lookup_in_val if v != val]), }), 'display': val, } + @manager.register class AllValuesFieldListFilter(ListFieldFilter): lookup_formats = {'exact': '%s__exact', 'isnull': '%s__isnull'} diff --git a/xadmin/migrations/0004_auto_20210425_2223.py b/xadmin/migrations/0004_auto_20210425_2223.py new file mode 100644 index 000000000..8f4bbe649 --- /dev/null +++ b/xadmin/migrations/0004_auto_20210425_2223.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2 on 2021-04-25 14:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('xadmin', '0003_auto_20160715_0100'), + ] + + operations = [ + migrations.AlterField( + model_name='bookmark', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='log', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='usersettings', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + migrations.AlterField( + model_name='userwidget', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/xadmin/models.py b/xadmin/models.py index da5145641..09c169b25 100644 --- a/xadmin/models.py +++ b/xadmin/models.py @@ -5,10 +5,12 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _, ugettext -from django.core.urlresolvers import NoReverseMatch, reverse +from django.urls.base import reverse from django.core.serializers.json import DjangoJSONEncoder from django.db.models.base import ModelBase -from django.utils.encoding import python_2_unicode_compatible, smart_text +# from django.utils.encoding import python_2_unicode_compatible, smart_text +from django.utils.encoding import smart_text +from six import python_2_unicode_compatible from django.db.models.signals import post_migrate from django.contrib.auth.models import Permission @@ -19,6 +21,7 @@ AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') + def add_view_permissions(sender, **kwargs): """ This syncdb hooks takes care of adding a view permission too all our @@ -35,7 +38,7 @@ def add_view_permissions(sender, **kwargs): Permission.objects.create(content_type=content_type, codename=codename, name="Can view %s" % content_type.name) - #print "Added view permission for %s" % content_type.name + # print "Added view permission for %s" % content_type.name # check for all our view permissions after a syncdb post_migrate.connect(add_view_permissions) @@ -44,9 +47,9 @@ def add_view_permissions(sender, **kwargs): @python_2_unicode_compatible class Bookmark(models.Model): title = models.CharField(_(u'Title'), max_length=128) - user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user"), blank=True, null=True) + user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_(u"user"), blank=True, null=True) url_name = models.CharField(_(u'Url Name'), max_length=64) - content_type = models.ForeignKey(ContentType) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) query = models.CharField(_(u'Query String'), max_length=1000, blank=True) is_share = models.BooleanField(_(u'Is Shared'), default=False) @@ -66,6 +69,7 @@ class Meta: class JSONEncoder(DjangoJSONEncoder): + def default(self, o): if isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') @@ -84,7 +88,7 @@ def default(self, o): @python_2_unicode_compatible class UserSettings(models.Model): - user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) + user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_(u"user")) key = models.CharField(_('Settings Key'), max_length=256) value = models.TextField(_('Settings Content')) @@ -104,7 +108,7 @@ class Meta: @python_2_unicode_compatible class UserWidget(models.Model): - user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) + user = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_(u"user")) page_id = models.CharField(_(u"Page"), max_length=256) widget_type = models.CharField(_(u"Widget Type"), max_length=50) value = models.TextField(_(u"Widget Params")) @@ -186,4 +190,3 @@ def __str__(self): def get_edited_object(self): "Returns the edited object represented by this log entry" return self.content_type.get_object_for_this_type(pk=self.object_id) - diff --git a/xadmin/plugins/__init__.py b/xadmin/plugins/__init__.py index e570dfe67..ce3955f94 100644 --- a/xadmin/plugins/__init__.py +++ b/xadmin/plugins/__init__.py @@ -1,34 +1,35 @@ PLUGINS = ( - 'actions', - 'filters', - 'bookmark', - 'export', - 'layout', + 'actions', + 'filters', + 'bookmark', + 'export', + 'layout', 'refresh', 'details', - 'editable', - 'relate', - 'chart', - 'ajax', - 'relfield', - 'inline', - 'topnav', - 'portal', + 'editable', + 'relate', + 'chart', + 'ajax', + 'relfield', + 'inline', + 'topnav', + 'portal', 'quickform', - 'wizard', - 'images', - 'auth', - 'multiselect', - 'themes', - 'aggregation', - 'mobile', + 'wizard', + 'images', + 'auth', + 'multiselect', + 'themes', + 'aggregation', + # 'mobile', 'passwords', - 'sitemenu', - 'language', + 'sitemenu', + 'language', 'quickfilter', 'sortablelist', - 'importexport' + 'importexport', + # 'ueditor', ) diff --git a/xadmin/plugins/actions.py b/xadmin/plugins/actions.py index 02085bf33..d3c244001 100644 --- a/xadmin/plugins/actions.py +++ b/xadmin/plugins/actions.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from django import forms +from django import forms, VERSION as django_version from django.core.exceptions import PermissionDenied from django.db import router from django.http import HttpResponse, HttpResponseRedirect @@ -19,6 +19,7 @@ from xadmin.views import BaseAdminPlugin, ListAdminView from xadmin.views.base import filter_hook, ModelAdminView +from xadmin import views ACTION_CHECKBOX_NAME = '_selected_action' checkbox = forms.CheckboxInput({'class': 'action-select'}, lambda value: False) @@ -26,12 +27,15 @@ def action_checkbox(obj): return checkbox.render(ACTION_CHECKBOX_NAME, force_text(obj.pk)) + + action_checkbox.short_description = mark_safe( '') action_checkbox.allow_tags = True action_checkbox.allow_export = False action_checkbox.is_column = False + class BaseActionView(ModelAdminView): action_name = None description = None @@ -51,6 +55,13 @@ def init_action(self, list_view): def do_action(self, queryset): pass + def __init__(self, request, *args, **kwargs): + super().__init__(request, *args, **kwargs) + if django_version > (2, 0): + for model in self.admin_site._registry: + if not hasattr(self.admin_site._registry[model], 'has_delete_permission'): + setattr(self.admin_site._registry[model], 'has_delete_permission', self.has_delete_permission) + class DeleteSelectedAction(BaseActionView): @@ -70,7 +81,7 @@ def delete_models(self, queryset): n = queryset.count() if n: if self.delete_models_batch: - self.log('delete', _('Batch delete %(count)d %(items)s.') % { "count": n, "items": model_ngettext(self.opts, n) }) + self.log('delete', _('Batch delete %(count)d %(items)s.') % {"count": n, "items": model_ngettext(self.opts, n)}) queryset.delete() else: for obj in queryset: @@ -86,12 +97,17 @@ def do_action(self, queryset): if not self.has_delete_permission(): raise PermissionDenied - using = router.db_for_write(self.model) - # Populate deletable_objects, a data structure of all related objects that # will also be deleted. - deletable_objects, model_count, perms_needed, protected = get_deleted_objects( - queryset, self.opts, self.user, self.admin_site, using) + + if django_version > (2, 1): + deletable_objects, model_count, perms_needed, protected = get_deleted_objects( + queryset, self.opts, self.admin_site) + else: + using = router.db_for_write(self.model) + deletable_objects, model_count, perms_needed, protected = get_deleted_objects( + queryset, self.opts, self.user, self.admin_site, using) + # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. diff --git a/xadmin/plugins/aggregation.py b/xadmin/plugins/aggregation.py index 16114da8d..98cca09b7 100644 --- a/xadmin/plugins/aggregation.py +++ b/xadmin/plugins/aggregation.py @@ -1,5 +1,8 @@ -from django.db.models import FieldDoesNotExist, Avg, Max, Min, Count, Sum + +from django.core.exceptions import FieldDoesNotExist +from django.db.models import Avg, Max, Min, Count, Sum from django.utils.translation import ugettext as _ +from django.forms import Media from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ListAdminView @@ -61,9 +64,7 @@ def results(self, rows): # Media def get_media(self, media): - media.add_css({'screen': [self.static( - 'xadmin/css/xadmin.plugin.aggregation.css'), ]}) - return media + return media + Media(css={'screen': [self.static('xadmin/css/xadmin.plugin.aggregation.css'), ]}) site.register_plugin(AggregationPlugin, ListAdminView) diff --git a/xadmin/plugins/batch.py b/xadmin/plugins/batch.py index b322af98e..9dbf70b2c 100644 --- a/xadmin/plugins/batch.py +++ b/xadmin/plugins/batch.py @@ -36,7 +36,7 @@ def media(self): media = self.widget.media + vendor('xadmin.plugin.batch.js') return media - def render(self, name, value, attrs=None): + def render(self, name, value, attrs=None, renderer=None): output = [] is_required = self.widget.is_required output.append(u'