From 30cc2f86b3e07d07283c605f81cef3aa2f291aa3 Mon Sep 17 00:00:00 2001 From: Hamim Al Mahdi Russell Date: Sun, 12 Jan 2020 00:08:20 +0600 Subject: [PATCH 01/11] project compatible for django 3.x --- xadmin/filters.py | 142 +++++++++--------- xadmin/models.py | 19 +-- xadmin/plugins/__init__.py | 46 +++--- xadmin/plugins/actions.py | 30 +++- xadmin/plugins/aggregation.py | 5 +- xadmin/plugins/bookmark.py | 44 +++--- xadmin/plugins/details.py | 4 +- xadmin/plugins/editable.py | 13 +- xadmin/plugins/export.py | 2 +- xadmin/plugins/filters.py | 17 ++- xadmin/plugins/images.py | 4 +- xadmin/plugins/importexport.py | 4 +- xadmin/plugins/inline.py | 14 +- xadmin/plugins/language.py | 3 +- xadmin/plugins/multiselect.py | 7 +- xadmin/plugins/passwords.py | 20 ++- xadmin/plugins/quickfilter.py | 89 +++++------ xadmin/plugins/quickform.py | 4 +- xadmin/plugins/relate.py | 21 ++- xadmin/plugins/relfield.py | 21 +-- xadmin/plugins/sortablelist.py | 2 +- xadmin/plugins/themes.py | 23 +-- xadmin/plugins/topnav.py | 2 +- xadmin/plugins/wizard.py | 4 +- xadmin/plugins/xversion.py | 17 ++- xadmin/sites.py | 28 ++-- xadmin/static/xadmin/css/xadmin.main.css | 3 + .../xadmin/blocks/comm.top.setlang.html | 2 +- xadmin/templates/xadmin/views/logged_out.html | 2 +- xadmin/templatetags/xadmin_tags.py | 9 +- xadmin/util.py | 39 +++-- xadmin/views/__init__.py | 4 +- xadmin/views/base.py | 39 ++--- xadmin/views/dashboard.py | 20 ++- xadmin/views/delete.py | 18 ++- xadmin/views/detail.py | 4 +- xadmin/views/edit.py | 20 ++- xadmin/views/form.py | 2 +- xadmin/views/list.py | 40 ++--- xadmin/views/website.py | 14 +- xadmin/widgets.py | 33 ++-- 41 files changed, 441 insertions(+), 393 deletions(-) diff --git a/xadmin/filters.py b/xadmin/filters.py index 9eba01e99..4e719bf1b 100644 --- a/xadmin/filters.py +++ b/xadmin/filters.py @@ -6,21 +6,21 @@ from django.utils import timezone from django.template.loader import get_template from django.template.context import Context -from django.utils import six +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/models.py b/xadmin/models.py index da5145641..c65eca1fd 100644 --- a/xadmin/models.py +++ b/xadmin/models.py @@ -5,11 +5,11 @@ 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 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 +19,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 +36,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 +45,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 +67,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 +86,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 +106,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 +188,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..d0611656f 100644 --- a/xadmin/plugins/__init__.py +++ b/xadmin/plugins/__init__.py @@ -1,34 +1,34 @@ 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' ) diff --git a/xadmin/plugins/actions.py b/xadmin/plugins/actions.py index 02085bf33..01f69ac43 100644 --- a/xadmin/plugins/actions.py +++ b/xadmin/plugins/actions.py @@ -1,11 +1,11 @@ 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 from django.template import loader from django.template.response import TemplateResponse -from django.utils import six +import six from django.utils.encoding import force_text from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _, ungettext @@ -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..1e7553fad 100644 --- a/xadmin/plugins/aggregation.py +++ b/xadmin/plugins/aggregation.py @@ -1,5 +1,6 @@ from django.db.models import FieldDoesNotExist, 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 +62,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/bookmark.py b/xadmin/plugins/bookmark.py index 3d612cec8..f6e0708e8 100644 --- a/xadmin/plugins/bookmark.py +++ b/xadmin/plugins/bookmark.py @@ -1,6 +1,6 @@ from django.contrib.contenttypes.models import ContentType -from django.core.urlresolvers import reverse +from django.urls.base import reverse from django.db import transaction from django.db.models import Q from django.forms import ModelChoiceField @@ -43,16 +43,16 @@ def get_context(self, context): bookmarks = [] current_qs = '&'.join([ - '%s=%s' % (k, v) - for k, v in sorted(filter( - lambda i: bool(i[1] and ( - i[0] in (COL_LIST_VAR, ORDER_VAR, SEARCH_VAR) - or i[0].startswith(FILTER_PREFIX) - or i[0].startswith(RELATE_PREFIX) - )), - self.request.GET.items() - )) - ]) + '%s=%s' % (k, v) + for k, v in sorted(filter( + lambda i: bool(i[1] and ( + i[0] in (COL_LIST_VAR, ORDER_VAR, SEARCH_VAR) + or i[0].startswith(FILTER_PREFIX) + or i[0].startswith(RELATE_PREFIX) + )), + self.request.GET.items() + )) + ]) model_info = (self.opts.app_label, self.opts.model_name) has_selected = False @@ -64,21 +64,22 @@ def get_context(self, context): for bk in self.list_bookmarks: title = bk['title'] params = dict([ - (FILTER_PREFIX + k, v) - for (k, v) in bk['query'].items() - ]) + (FILTER_PREFIX + k, v) + for (k, v) in bk['query'].items() + ]) if 'order' in bk: params[ORDER_VAR] = '.'.join(bk['order']) if 'cols' in bk: params[COL_LIST_VAR] = '.'.join(bk['cols']) if 'search' in bk: params[SEARCH_VAR] = bk['search'] + def check_item(i): return bool(i[1]) or i[1] == False bk_qs = '&'.join([ '%s=%s' % (k, v) for k, v in sorted(filter(check_item, params.items())) - ]) + ]) url = list_base_url + '?' + bk_qs selected = (current_qs == bk_qs) @@ -174,7 +175,6 @@ def get_list_display(self): list_display.remove('user') return list_display - def has_change_permission(self, obj=None): if not obj or self.user.is_superuser: return True @@ -222,12 +222,12 @@ def context(self, context): context['result_headers'] = [c for c in list_view.result_headers( ).cells if c.field_name in base_fields] context['results'] = [ - [o for i, o in enumerate(filter( - lambda c: c.field_name in base_fields, - r.cells - ))] - for r in list_view.results() - ] + [o for i, o in enumerate(filter( + lambda c: c.field_name in base_fields, + r.cells + ))] + for r in list_view.results() + ] context['result_count'] = list_view.result_count context['page_url'] = self.bookmark.url diff --git a/xadmin/plugins/details.py b/xadmin/plugins/details.py index 0b5ed919e..ba167fdc3 100644 --- a/xadmin/plugins/details.py +++ b/xadmin/plugins/details.py @@ -1,7 +1,7 @@ from django.utils.translation import ugettext as _ -from django.core.urlresolvers import reverse, NoReverseMatch +from django.urls.base import reverse, NoReverseMatch from django.db import models from xadmin.sites import site @@ -16,7 +16,7 @@ class DetailsPlugin(BaseAdminPlugin): def result_item(self, item, obj, field_name, row): if (self.show_all_rel_details or (field_name in self.show_detail_fields)): rel_obj = None - if hasattr(item.field, 'rel') and isinstance(item.field.rel, models.ManyToOneRel): + if hasattr(item.field, 'remote_field') and isinstance(item.field.remote_field, models.ManyToOneRel): rel_obj = getattr(obj, field_name) elif field_name in self.show_detail_fields: rel_obj = obj diff --git a/xadmin/plugins/editable.py b/xadmin/plugins/editable.py index 1b49feb60..68a596004 100644 --- a/xadmin/plugins/editable.py +++ b/xadmin/plugins/editable.py @@ -2,6 +2,7 @@ from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.db import models, transaction from django.forms.models import modelform_factory +from django.forms import Media from django.http import Http404, HttpResponse from django.utils.encoding import force_text, smart_text from django.utils.html import escape, conditional_escape @@ -32,7 +33,7 @@ def init_request(self, *args, **kwargs): return active def result_item(self, item, obj, field_name, row): - if self.list_editable and item.field and item.field.editable and (field_name in self.list_editable): + if self.list_editable and item.field and item.field.editable and (field_name in self.list_editable): pk = getattr(obj, obj._meta.pk.attname) field_label = label_for_field(field_name, obj, model_admin=self.admin_view, @@ -52,7 +53,12 @@ def result_item(self, item, obj, field_name, row): # Media def get_media(self, media): if self.editable_need_fields: - media = media + self.model_form.media + \ + + try: + m = self.model_form.media + except: + m = Media() + media = media + m +\ self.vendor( 'xadmin.plugin.editable.js', 'xadmin.widget.editable.css') return media @@ -75,7 +81,7 @@ def init_request(self, object_id, *args, **kwargs): def get_new_field_html(self, f): result = self.result_item(self.org_obj, f, {'is_display_first': - False, 'object': self.org_obj}) + False, 'object': self.org_obj}) return mark_safe(result.text) if result.allow_tags else conditional_escape(result.text) def _get_new_field_html(self, field_name): @@ -156,5 +162,6 @@ def post(self, request, object_id): return self.render_response(result) + site.register_plugin(EditablePlugin, ListAdminView) site.register_modelview(r'^(.+)/patch/$', EditPatchView, name='%s_%s_patch') diff --git a/xadmin/plugins/export.py b/xadmin/plugins/export.py index 8ccc0f4e8..12d94e262 100644 --- a/xadmin/plugins/export.py +++ b/xadmin/plugins/export.py @@ -5,7 +5,7 @@ from django.http import HttpResponse from django.template import loader -from django.utils import six +import six from django.utils.encoding import force_text, smart_text from django.utils.html import escape from django.utils.translation import ugettext as _ diff --git a/xadmin/plugins/filters.py b/xadmin/plugins/filters.py index dc7e171d8..f9a308f6f 100644 --- a/xadmin/plugins/filters.py +++ b/xadmin/plugins/filters.py @@ -7,9 +7,10 @@ from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured, ValidationError from django.db import models from django.db.models.fields import FieldDoesNotExist -from django.db.models.sql.query import LOOKUP_SEP, QUERY_TERMS +from django.db.models.constants import LOOKUP_SEP +# from django.db.models.sql.constants import QUERY_TERMS from django.template import loader -from django.utils import six +import six from django.utils.encoding import smart_str from django.utils.translation import ugettext as _ @@ -44,8 +45,8 @@ def lookup_allowed(self, lookup, value): # Last term in lookup is a query term (__exact, __startswith etc) # This term can be ignored. - if len(parts) > 1 and parts[-1] in QUERY_TERMS: - parts.pop() + # if len(parts) > 1 and parts[-1] in QUERY_TERMS: + # parts.pop() # Special case -- foo__id__exact and foo__id queries are implied # if foo has been specificially included in the lookup list; so @@ -59,9 +60,9 @@ def lookup_allowed(self, lookup, value): # Lookups on non-existants fields are ok, since they're ignored # later. return True - if hasattr(field, 'rel'): - model = field.rel.to - rel_name = field.rel.get_related_field().name + if hasattr(field, 'remote_field'): + model = field.remote_field.to + rel_name = field.remote_field.get_related_field().name elif is_related_field(field): model = field.model rel_name = model._meta.pk.name @@ -158,7 +159,7 @@ def get_list_queryset(self, queryset): # fix a bug by david: In demo, quick filter by IDC Name() cannot be used. if isinstance(queryset, models.query.QuerySet) and lookup_params: new_lookup_parames = dict() - for k, v in lookup_params.iteritems(): + for k, v in lookup_params.items(): list_v = v.split(',') if len(list_v) > 0: new_lookup_parames.update({k: list_v}) diff --git a/xadmin/plugins/images.py b/xadmin/plugins/images.py index 1c92bca55..7fea2b3b1 100644 --- a/xadmin/plugins/images.py +++ b/xadmin/plugins/images.py @@ -42,13 +42,13 @@ class AdminImageWidget(forms.FileInput): def __init__(self, attrs={}): super(AdminImageWidget, self).__init__(attrs) - def render(self, name, value, attrs=None): + def render(self, name, value, attrs=None, renderer=None): output = [] if value and hasattr(value, "url"): label = self.attrs.get('label', name) output.append('
%s ' % (value.url, label, value.url, _('Change:'))) - output.append(super(AdminImageWidget, self).render(name, value, attrs)) + output.append(super(AdminImageWidget, self).render(name, value, attrs, renderer)) return mark_safe(u''.join(output)) diff --git a/xadmin/plugins/importexport.py b/xadmin/plugins/importexport.py index 90fe10c47..507ae5e1e 100644 --- a/xadmin/plugins/importexport.py +++ b/xadmin/plugins/importexport.py @@ -63,7 +63,7 @@ class FooAdmin(object): from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.contrib.contenttypes.models import ContentType from django.contrib import messages -from django.core.urlresolvers import reverse +from django.urls.base import reverse from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect, HttpResponse @@ -153,6 +153,7 @@ def get_import_formats(self): class ImportView(ImportBaseView): + def get_media(self): media = super(ImportView, self).get_media() media = media + self.vendor('xadmin.plugin.importexport.css') @@ -254,6 +255,7 @@ def post(self, request, *args, **kwargs): class ImportProcessView(ImportBaseView): + @filter_hook @csrf_protect_m @transaction.atomic diff --git a/xadmin/plugins/inline.py b/xadmin/plugins/inline.py index 2fbe5b19d..fd4d18ac8 100644 --- a/xadmin/plugins/inline.py +++ b/xadmin/plugins/inline.py @@ -7,7 +7,7 @@ from django.template import loader from django.template.loader import render_to_string from django.contrib.auth import get_permission_codename -from django.utils import six +import six from django.utils.encoding import smart_text from crispy_forms.utils import TEMPLATE_PACK @@ -210,7 +210,7 @@ def instance_form(self, **kwargs): rendered_fields = [i[1] for i in layout.get_field_names()] layout.extend([f for f in instance[0] - .fields.keys() if f not in rendered_fields]) + .fields.keys() if f not in rendered_fields]) helper.add_layout(layout) style.update_layout(helper) @@ -227,10 +227,11 @@ def instance_form(self, **kwargs): form.readonly_fields = [] inst = form.save(commit=False) if inst: + meta_field_names = [field.name for field in inst._meta.get_fields()] for readonly_field in readonly_fields: value = None label = None - if readonly_field in inst._meta.get_all_field_names(): + if readonly_field in meta_field_names: label = inst._meta.get_field(readonly_field).verbose_name value = smart_text(getattr(inst, readonly_field)) elif inspect.ismethod(getattr(inst, readonly_field, None)): @@ -268,8 +269,8 @@ def has_change_permission(self): opts = self.opts if opts.auto_created: for field in opts.fields: - if field.rel and field.rel.to != self.parent_model: - opts = field.rel.to._meta + if field.remote_field and field.remote_field.model != self.parent_model: + opts = field.remote_field.model._meta break codename = get_permission_codename('change', opts) @@ -352,7 +353,7 @@ class Inline(Fieldset): def __init__(self, rel_model): self.model = rel_model self.fields = [] - super(Inline,self).__init__(legend="") + super(Inline, self).__init__(legend="") def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs): return "" @@ -471,6 +472,7 @@ def _get_detail_formset_instance(self, inline): DetailAdminUtil, fake_admin_class, instance) return formset + class DetailAdminUtil(DetailAdminView): def init_request(self, obj): diff --git a/xadmin/plugins/language.py b/xadmin/plugins/language.py index 3814771e4..7c73b9d7d 100644 --- a/xadmin/plugins/language.py +++ b/xadmin/plugins/language.py @@ -14,6 +14,7 @@ def block_top_navmenu(self, context, nodes): context['redirect_to'] = self.request.get_full_path() nodes.append(loader.render_to_string('xadmin/blocks/comm.top.setlang.html', context=context)) + class SetLangView(BaseAdminView): def post(self, request, *args, **kwargs): @@ -21,6 +22,6 @@ def post(self, request, *args, **kwargs): del request.session['nav_menu'] return set_language(request) -if settings.LANGUAGES and 'django.middleware.locale.LocaleMiddleware' in settings.MIDDLEWARE_CLASSES: +if settings.LANGUAGES and 'django.middleware.locale.LocaleMiddleware' in settings.MIDDLEWARE: site.register_plugin(SetLangNavPlugin, CommAdminView) site.register_view(r'^i18n/setlang/$', SetLangView, 'set_language') diff --git a/xadmin/plugins/multiselect.py b/xadmin/plugins/multiselect.py index 38e0c917d..0681af8ab 100644 --- a/xadmin/plugins/multiselect.py +++ b/xadmin/plugins/multiselect.py @@ -9,7 +9,7 @@ from django.utils.encoding import force_text from django.utils.html import escape, conditional_escape from django.utils.safestring import mark_safe -from xadmin.util import vendor, DJANGO_11 +from xadmin.util import vendor from xadmin.views import BaseAdminPlugin, ModelFormAdminView @@ -37,10 +37,7 @@ def render(self, name, value, attrs=None, choices=()): attrs['class'] += 'stacked' if value is None: value = [] - if DJANGO_11: - final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) - else: - final_attrs = self.build_attrs(attrs, name=name) + final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) selected_choices = set(force_text(v) for v in value) available_output = [] diff --git a/xadmin/plugins/passwords.py b/xadmin/plugins/passwords.py index ac7852714..a5f3eebe5 100644 --- a/xadmin/plugins/passwords.py +++ b/xadmin/plugins/passwords.py @@ -1,7 +1,7 @@ # coding=utf-8 from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm from django.contrib.auth.tokens import default_token_generator -from django.contrib.auth.views import password_reset_confirm +from django.contrib.auth.views import PasswordResetConfirmView as password_reset_confirm from django.template.response import TemplateResponse from django.utils.translation import ugettext as _ @@ -53,14 +53,17 @@ def post(self, request, *args, **kwargs): else: return self.get(request, form=form) + site.register_view(r'^xadmin/password_reset/$', ResetPasswordSendView, name='xadmin_password_reset') + class ResetLinkPlugin(BaseAdminPlugin): def block_form_bottom(self, context, nodes): reset_link = self.get_admin_url('xadmin_password_reset') return '
%s
' % (reset_link, _('Forgotten your password or username?')) + site.register_plugin(ResetLinkPlugin, LoginView) @@ -75,11 +78,11 @@ class ResetPasswordComfirmView(BaseAdminView): def do_view(self, request, uidb36, token, *args, **kwargs): context = super(ResetPasswordComfirmView, self).get_context() return password_reset_confirm(request, uidb36, token, - template_name=self.password_reset_confirm_template, - token_generator=self.password_reset_token_generator, - set_password_form=self.password_reset_set_form, - post_reset_redirect=self.get_admin_url('xadmin_password_reset_complete'), - current_app=self.admin_site.name, extra_context=context) + template_name=self.password_reset_confirm_template, + token_generator=self.password_reset_token_generator, + set_password_form=self.password_reset_set_form, + post_reset_redirect=self.get_admin_url('xadmin_password_reset_complete'), + current_app=self.admin_site.name, extra_context=context) def get(self, request, uidb36, token, *args, **kwargs): return self.do_view(request, uidb36, token) @@ -91,8 +94,9 @@ def get_media(self): return super(ResetPasswordComfirmView, self).get_media() + \ self.vendor('xadmin.page.form.js', 'xadmin.form.css') + site.register_view(r'^xadmin/password_reset/(?P[0-9A-Za-z]{1,13})-(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - ResetPasswordComfirmView, name='xadmin_password_reset_confirm') + ResetPasswordComfirmView, name='xadmin_password_reset_confirm') class ResetPasswordCompleteView(BaseAdminView): @@ -107,5 +111,5 @@ def get(self, request, *args, **kwargs): return TemplateResponse(request, self.password_reset_complete_template, context) -site.register_view(r'^xadmin/password_reset/complete/$', ResetPasswordCompleteView, name='xadmin_password_reset_complete') +site.register_view(r'^xadmin/password_reset/complete/$', ResetPasswordCompleteView, name='xadmin_password_reset_complete') diff --git a/xadmin/plugins/quickfilter.py b/xadmin/plugins/quickfilter.py index 6222c34b4..4de7c38cd 100644 --- a/xadmin/plugins/quickfilter.py +++ b/xadmin/plugins/quickfilter.py @@ -4,37 +4,39 @@ @author: LAB_ADM ''' from future.utils import iteritems -from django.utils import six +import six from django.utils.translation import ugettext_lazy as _ -from xadmin.filters import manager,MultiSelectFieldListFilter +from xadmin.filters import manager, MultiSelectFieldListFilter from xadmin.plugins.filters import * from xadmin.util import is_related_field + @manager.register class QuickFilterMultiSelectFieldListFilter(MultiSelectFieldListFilter): """ 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/quickfilter.html' + class QuickFilterPlugin(BaseAdminPlugin): """ Add a filter menu to the left column of the page """ - list_quick_filter = () # these must be a subset of list_filter to work - quickfilter = {} + list_quick_filter = () # these must be a subset of list_filter to work + quickfilter = {} search_fields = () free_query_filter = True - + def init_request(self, *args, **kwargs): - menu_style_accordian = hasattr(self.admin_view,'menu_style') and self.admin_view.menu_style == 'accordion' + menu_style_accordian = hasattr(self.admin_view, 'menu_style') and self.admin_view.menu_style == 'accordion' return bool(self.list_quick_filter) and not menu_style_accordian - + # Media def get_media(self, media): - return media + self.vendor('xadmin.plugin.quickfilter.js','xadmin.plugin.quickfilter.css') - + return media + self.vendor('xadmin.plugin.quickfilter.js', 'xadmin.plugin.quickfilter.css') + def lookup_allowed(self, lookup, value): model = self.model # Check FKey lookups that are allowed, so that popups produced by @@ -44,14 +46,14 @@ def lookup_allowed(self, lookup, value): for k, v in widgets.url_params_from_lookup_dict(l).items(): if k == lookup and v == value: return True - + parts = lookup.split(LOOKUP_SEP) - + # Last term in lookup is a query term (__exact, __startswith etc) # This term can be ignored. if len(parts) > 1 and parts[-1] in QUERY_TERMS: parts.pop() - + # Special case -- foo__id__exact and foo__id queries are implied # if foo has been specificially included in the lookup list; so # drop __id if it is the last part. However, first we need to find @@ -64,9 +66,9 @@ def lookup_allowed(self, lookup, value): # Lookups on non-existants fields are ok, since they're ignored # later. return True - if hasattr(field, 'rel'): - model = field.rel.to - rel_name = field.rel.get_related_field().name + if hasattr(field, 'remote_field'): + model = field.remote_field.model + rel_name = field.remote_field.get_related_field().name elif is_related_field(field): model = field.model rel_name = model._meta.pk.name @@ -74,32 +76,32 @@ def lookup_allowed(self, lookup, value): rel_name = None if rel_name and len(parts) > 1 and parts[-1] == rel_name: parts.pop() - + if len(parts) == 1: return True clean_lookup = LOOKUP_SEP.join(parts) return clean_lookup in self.list_quick_filter - + def get_list_queryset(self, queryset): lookup_params = dict([(smart_str(k)[len(FILTER_PREFIX):], v) for k, v in self.admin_view.params.items() if smart_str(k).startswith(FILTER_PREFIX) and v != '']) for p_key, p_val in iteritems(lookup_params): if p_val == "False": lookup_params[p_key] = False use_distinct = False - - if not hasattr(self.admin_view,'quickfilter'): + + if not hasattr(self.admin_view, 'quickfilter'): self.admin_view.quickfilter = {} - + # for clean filters self.admin_view.quickfilter['has_query_param'] = bool(lookup_params) self.admin_view.quickfilter['clean_query_url'] = self.admin_view.get_query_string(remove=[k for k in self.request.GET.keys() if k.startswith(FILTER_PREFIX)]) - + # Normalize the types of keys if not self.free_query_filter: for key, value in lookup_params.items(): if not self.lookup_allowed(key, value): raise SuspiciousOperation("Filtering by %s not allowed" % key) - + self.filter_specs = [] if self.list_quick_filter: for list_quick_filter in self.list_quick_filter: @@ -107,10 +109,10 @@ def get_list_queryset(self, queryset): field_order_by = None field_limit = None field_parts = [] - sort_key = None + sort_key = None cache_config = None - - if type(list_quick_filter)==dict and 'field' in list_quick_filter: + + if type(list_quick_filter) == dict and 'field' in list_quick_filter: field = list_quick_filter['field'] if 'order_by' in list_quick_filter: field_order_by = list_quick_filter['order_by'] @@ -118,23 +120,24 @@ def get_list_queryset(self, queryset): field_limit = list_quick_filter['limit'] if 'sort' in list_quick_filter and callable(list_quick_filter['sort']): sort_key = list_quick_filter['sort'] - if 'cache' in list_quick_filter and type(list_quick_filter)==dict: + if 'cache' in list_quick_filter and type(list_quick_filter) == dict: cache_config = list_quick_filter['cache'] - - else: - field = list_quick_filter # This plugin only uses MultiselectFieldListFilter - + + else: + field = list_quick_filter # This plugin only uses MultiselectFieldListFilter + if not isinstance(field, models.Field): field_path = field field_parts = get_fields_from_path(self.model, field_path) field = field_parts[-1] - spec = QuickFilterMultiSelectFieldListFilter(field, self.request, lookup_params,self.model, self.admin_view, field_path=field_path,field_order_by=field_order_by,field_limit=field_limit,sort_key=sort_key,cache_config=cache_config) - - if len(field_parts)>1: - spec.title = "%s %s"%(field_parts[-2].name,spec.title) - + spec = QuickFilterMultiSelectFieldListFilter(field, self.request, lookup_params, self.model, self.admin_view, field_path=field_path, + field_order_by=field_order_by, field_limit=field_limit, sort_key=sort_key, cache_config=cache_config) + + if len(field_parts) > 1: + spec.title = "%s %s" % (field_parts[-2].name, spec.title) + # Check if we need to use distinct() - use_distinct = True#(use_distinct orlookup_needs_distinct(self.opts, field_path)) + use_distinct = True # (use_distinct orlookup_needs_distinct(self.opts, field_path)) if spec and spec.has_output(): try: new_qs = spec.do_filte(queryset) @@ -143,23 +146,23 @@ def get_list_queryset(self, queryset): self.admin_view.message_user(_("Filtering error: %s") % e.messages[0], 'error') if new_qs is not None: queryset = new_qs - + self.filter_specs.append(spec) - + self.has_filters = bool(self.filter_specs) self.admin_view.quickfilter['filter_specs'] = self.filter_specs obj = filter(lambda f: f.is_used, self.filter_specs) if six.PY3: obj = list(obj) self.admin_view.quickfilter['used_filter_num'] = len(obj) - + if use_distinct: return queryset.distinct() else: return queryset - + def block_left_navbar(self, context, nodes): nodes.append(loader.render_to_string('xadmin/blocks/modal_list.left_navbar.quickfilter.html', get_context_dict(context))) - + site.register_plugin(QuickFilterPlugin, ListAdminView) diff --git a/xadmin/plugins/quickform.py b/xadmin/plugins/quickform.py index d93e2c647..7516f688f 100644 --- a/xadmin/plugins/quickform.py +++ b/xadmin/plugins/quickform.py @@ -77,7 +77,7 @@ def render(self, name, value, renderer=None, *args, **kwargs): if self.add_url: output.append(u'' % ( - self.add_url, (_('Create New %s') % self.rel.to._meta.verbose_name), name, + self.add_url, (_('Create New %s') % self.rel.model._meta.verbose_name), name, "%s?_field=%s&%s=" % (self.rel_add_url, name, name))) output.extend(['
' % name, self.widget.render(name, value, *args, **kwargs), '
']) @@ -103,7 +103,7 @@ def formfield_for_dbfield(self, formfield, db_field, **kwargs): if rel_model in self.admin_site._registry and self.has_model_perm(rel_model, 'add'): add_url = self.get_model_url(rel_model, 'add') formfield.widget = RelatedFieldWidgetWrapper( - formfield.widget, db_field.rel, add_url, self.get_model_url(self.model, 'add')) + formfield.widget, db_field.remote_field, add_url, self.get_model_url(self.model, 'add')) return formfield site.register_plugin(QuickFormPlugin, ModelFormAdminView) diff --git a/xadmin/plugins/relate.py b/xadmin/plugins/relate.py index 4d33977a3..e682df652 100644 --- a/xadmin/plugins/relate.py +++ b/xadmin/plugins/relate.py @@ -1,9 +1,9 @@ # coding=UTF-8 from itertools import chain -from django.core.urlresolvers import reverse +from django.urls.base import reverse from django.db.models.options import PROXY_PARENTS -from django.utils import six +import six from django.utils.encoding import force_text from django.utils.encoding import smart_str from django.utils.safestring import mark_safe @@ -47,7 +47,6 @@ def _get_all_related_objects(self, local_only=False, include_hidden=False, fields = chain(fields, relations) return list(fields) - def get_related_list(self): if hasattr(self, '_related_acts'): return self._related_acts @@ -85,20 +84,20 @@ def related_link(self, instance): link = ''.join(('
  • ', ' %s' % - ( - reverse('%s:%s_%s_changelist' % ( + ( + reverse('%s:%s_%s_changelist' % ( self.admin_site.app_name, label, model_name)), - RELATE_PREFIX + lookup_name, str(instance.pk), verbose_name, verbose_name) if view_perm else + RELATE_PREFIX + lookup_name, str(instance.pk), verbose_name, verbose_name) if view_perm else ' %s' % verbose_name, '' % - ( - reverse('%s:%s_%s_add' % ( + ( + reverse('%s:%s_%s_add' % ( self.admin_site.app_name, label, model_name)), - RELATE_PREFIX + lookup_name, str( - instance.pk)) if add_perm else "", + RELATE_PREFIX + lookup_name, str( + instance.pk)) if add_perm else "", - '
  • ')) + '')) links.append(link) ul_html = '' % ''.join( links) diff --git a/xadmin/plugins/relfield.py b/xadmin/plugins/relfield.py index 506222e4c..19b403e43 100644 --- a/xadmin/plugins/relfield.py +++ b/xadmin/plugins/relfield.py @@ -7,7 +7,7 @@ from django import forms from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ModelFormAdminView -from xadmin.util import vendor, DJANGO_11 +from xadmin.util import vendor class ForeignKeySearchWidget(forms.Widget): @@ -19,7 +19,7 @@ def __init__(self, rel, admin_view, attrs=None, using=None): super(ForeignKeySearchWidget, self).__init__(attrs) def build_attrs(self, attrs={}, extra_attrs=None, **kwargs): - to_opts = self.rel.to._meta + to_opts = self.rel.model._meta if "class" not in attrs: attrs['class'] = 'select-search' else: @@ -32,20 +32,11 @@ def build_attrs(self, attrs={}, extra_attrs=None, **kwargs): for i in list(self.rel.limit_choices_to): attrs['data-choices'] += "&_p_%s=%s" % (i, self.rel.limit_choices_to[i]) attrs['data-choices'] = format_html(attrs['data-choices']) - if DJANGO_11: - attrs.update(kwargs) - return super(ForeignKeySearchWidget, self).build_attrs(attrs, extra_attrs=extra_attrs) - else: - if extra_attrs: - attrs.update(extra_attrs) - return super(ForeignKeySearchWidget, self).build_attrs(attrs, **kwargs) + attrs.update(kwargs) + return super(ForeignKeySearchWidget, self).build_attrs(attrs, extra_attrs=extra_attrs) def render(self, name, value, attrs=None): - if DJANGO_11: - final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) - else: - final_attrs = self.build_attrs(attrs, name=name) - + final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) output = [format_html('', flatatt(final_attrs))] if value: output.append(format_html('', value, self.label_for_value(value))) @@ -74,7 +65,7 @@ def build_attrs(self, attrs={}, **kwargs): attrs['class'] = 'select-preload' else: attrs['class'] = attrs['class'] + ' select-preload' - attrs['data-placeholder'] = _('Select %s') % self.rel.to._meta.verbose_name + attrs['data-placeholder'] = _('Select %s') % self.rel.model._meta.verbose_name return attrs diff --git a/xadmin/plugins/sortablelist.py b/xadmin/plugins/sortablelist.py index 8b9942f6e..17d34b615 100644 --- a/xadmin/plugins/sortablelist.py +++ b/xadmin/plugins/sortablelist.py @@ -8,7 +8,7 @@ from __future__ import unicode_literals from django.template.loader import render_to_string -from django.core.urlresolvers import reverse +from django.urls.base import reverse from django.db import transaction from xadmin.views import ( diff --git a/xadmin/plugins/themes.py b/xadmin/plugins/themes.py index d83dd9cc3..86b98abd1 100644 --- a/xadmin/plugins/themes.py +++ b/xadmin/plugins/themes.py @@ -1,9 +1,9 @@ -#coding:utf-8 +# coding:utf-8 from __future__ import print_function -import requests +import httplib2 from django.template import loader from django.core.cache import cache -from django.utils import six +import six from django.utils.translation import ugettext as _ from xadmin.sites import site from xadmin.models import UserSettings @@ -58,7 +58,7 @@ def block_top_navmenu(self, context, nodes): themes = [ {'name': _(u"Default"), 'description': _(u"Default bootstrap theme"), 'css': self.default_theme}, {'name': _(u"Bootstrap2"), 'description': _(u"Bootstrap 2.x theme"), 'css': self.bootstrap2_theme}, - ] + ] select_css = context.get('site_theme', self.default_theme) if self.user_themes: @@ -71,13 +71,16 @@ def block_top_navmenu(self, context, nodes): else: ex_themes = [] try: - headers = {"Accept": "application/json", "User-Agent": self.request.META['HTTP_USER_AGENT']} - content = requests.get("https://bootswatch.com/api/3.json", headers=headers) + h = httplib2.Http() + resp, content = h.request("https://bootswatch.com/api/3.json", 'GET', '', + headers={"Accept": "application/json", "User-Agent": self.request.META['HTTP_USER_AGENT']}) if six.PY3: - content = content.text.decode() - watch_themes = json.loads(content.text)['themes'] - ex_themes.extend([{'name': t['name'], 'description': t['description'], 'css': t['cssMin'], - 'thumbnail': t['thumbnail']} for t in watch_themes]) + content = content.decode() + watch_themes = json.loads(content)['themes'] + ex_themes.extend([ + {'name': t['name'], 'description': t['description'], + 'css': t['cssMin'], 'thumbnail': t['thumbnail']} + for t in watch_themes]) except Exception as e: print(e) diff --git a/xadmin/plugins/topnav.py b/xadmin/plugins/topnav.py index c92fce549..fdf458b42 100644 --- a/xadmin/plugins/topnav.py +++ b/xadmin/plugins/topnav.py @@ -1,7 +1,7 @@ from django.template import loader from django.utils.text import capfirst -from django.core.urlresolvers import reverse, NoReverseMatch +from django.urls.base import reverse, NoReverseMatch from django.utils.translation import ugettext as _ from xadmin.sites import site diff --git a/xadmin/plugins/wizard.py b/xadmin/plugins/wizard.py index 38d21d406..222a505db 100644 --- a/xadmin/plugins/wizard.py +++ b/xadmin/plugins/wizard.py @@ -13,7 +13,7 @@ from django.contrib.formtools.wizard.forms import ManagementForm from django.contrib.formtools.wizard.views import StepsHelper -from django.utils import six +import six from django.utils.encoding import smart_text from django.utils.module_loading import import_string from django.forms import ValidationError @@ -22,8 +22,6 @@ from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ModelFormAdminView -from xadmin.util import DJANGO_11 - def normalize_name(name): new = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', name) diff --git a/xadmin/plugins/xversion.py b/xadmin/plugins/xversion.py index 6e337a72b..bfa21b3e0 100644 --- a/xadmin/plugins/xversion.py +++ b/xadmin/plugins/xversion.py @@ -8,7 +8,7 @@ from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse -from django.utils import six +import six from django.utils.encoding import force_text, smart_text from django.utils.safestring import mark_safe from django.utils.text import capfirst @@ -62,10 +62,10 @@ def _register_model(admin, model): fk_name = getattr(inline, 'fk_name', None) if not fk_name: for field in inline_model._meta.fields: - if isinstance(field, (models.ForeignKey, models.OneToOneField)) and issubclass(model, field.rel.to): + if isinstance(field, (models.ForeignKey, models.OneToOneField)) and issubclass(model, field.remote_field.model): fk_name = field.name _autoregister(admin, inline_model, follow=[fk_name]) - if not inline_model._meta.get_field(fk_name).rel.is_hidden(): + if not inline_model._meta.get_field(fk_name).remote_field.is_hidden(): accessor = inline_model._meta.get_field(fk_name).remote_field.get_accessor_name() inline_fields.append(accessor) _autoregister(admin, model, inline_fields) @@ -79,12 +79,14 @@ def register_models(admin_site=None): if getattr(admin, 'reversion_enable', False): _register_model(admin, model) + @contextmanager def do_create_revision(request): with create_revision(): set_user(request.user) yield + class ReversionPlugin(BaseAdminPlugin): # The serialization format to use when registering models with reversion. @@ -144,6 +146,8 @@ def block_nav_btns(self, context, nodes): nodes.append(mark_safe(' %s' % (revisionlist_url, _(u'History')))) # action revision + + class ActionRevisionPlugin(BaseAdminPlugin): reversion_enable = False @@ -204,7 +208,7 @@ def get(self, request, *args, **kwargs): return TemplateResponse( request, self.recover_list_template or self.get_template_list( - "views/recover_list.html"), + "views/recover_list.html"), context) @@ -381,7 +385,7 @@ def render(self, form, form_style, context, template_pack=TEMPLATE_PACK, **kwarg html = '' for field in self.fields: html += ('
    %s
    ' % - (_('Current: %s') % self.attrs.pop('orgdata', ''), render_field(field, form, form_style, context, template_pack=template_pack, attrs=self.attrs))) + (_('Current: %s') % self.attrs.pop('orgdata', ''), render_field(field, form, form_style, context, template_pack=template_pack, attrs=self.attrs))) return html @@ -402,7 +406,7 @@ def get_form_helper(self): helper = super(RevisionView, self).get_form_helper() diff_fields = {} version_data = self.version.field_dict - + for f in self.opts.fields: fvalue = f.value_from_object(self.org_obj) vvalue = version_data.get(f.name, None) @@ -585,6 +589,7 @@ class VersionInline(object): extra = 0 style = 'accordion' + class ReversionAdmin(object): model_icon = 'fa fa-exchange' diff --git a/xadmin/sites.py b/xadmin/sites.py index f5bff8de8..2bceee612 100644 --- a/xadmin/sites.py +++ b/xadmin/sites.py @@ -4,7 +4,7 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db.models.base import ModelBase -from django.utils import six +import six from django.views.decorators.cache import never_cache from django.template.engine import Engine import inspect @@ -289,7 +289,7 @@ def create_model_admin_view(self, admin_view_class, model, option_class): return self.get_view_class(admin_view_class, option_class).as_view() def get_urls(self): - from django.conf.urls import url, include + from django.urls import include, path, re_path from xadmin.views.base import BaseAdminView if settings.DEBUG: @@ -298,39 +298,40 @@ def get_urls(self): 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 = [ - url(r'^jsi18n/$', wrap(self.i18n_javascript, cacheable=True), name='jsi18n') + path('jsi18n/', wrap(self.i18n_javascript, cacheable=True), name='jsi18n') ] # Registed admin views # inspect[isclass]: Only checks if the object is a class. With it lets you create an custom view that # inherits from multiple views and have more of a metaclass. urlpatterns += [ - url( - path, + re_path( + _path, wrap(self.create_admin_view(clz_or_func)) if inspect.isclass(clz_or_func) and issubclass(clz_or_func, BaseAdminView) else include(clz_or_func(self)), name=name ) - for path, clz_or_func, name in self._registry_views + for _path, clz_or_func, name in self._registry_views ] # Add in each model's views. for model, admin_class in iteritems(self._registry): view_urls = [ - url( - path, + re_path( + _path, wrap(self.create_model_admin_view(clz, model, admin_class)), name=name % (model._meta.app_label, model._meta.model_name) ) - for path, clz, name in self._registry_modelviews + for _path, clz, name in self._registry_modelviews ] urlpatterns += [ - url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(view_urls)) + re_path(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(view_urls)) ] return urlpatterns @@ -339,17 +340,14 @@ def urls(self): return self.get_urls(), self.name, self.app_name def i18n_javascript(self, request): + from django.views.i18n import JavaScriptCatalog """ Displays the i18n JavaScript that the Django admin requires. This takes into account the USE_I18N setting. If it's set to False, the generated JavaScript will be leaner and faster. """ - if settings.USE_I18N: - from django.views.i18n import javascript_catalog - else: - from django.views.i18n import null_javascript_catalog as javascript_catalog - return javascript_catalog(request, packages=['django.conf', 'xadmin']) + return JavaScriptCatalog.as_view(packages=['django.contrib.admin'])(request) # This global object represents the default admin site, for the common case. # You can instantiate AdminSite in your own code to create a custom admin site. diff --git a/xadmin/static/xadmin/css/xadmin.main.css b/xadmin/static/xadmin/css/xadmin.main.css index 2113276e2..66f891607 100644 --- a/xadmin/static/xadmin/css/xadmin.main.css +++ b/xadmin/static/xadmin/css/xadmin.main.css @@ -243,6 +243,9 @@ ul.nav-sitemenu li a { @media (min-width: 768px) { .panel-group.nav-sitemenu { + top: 65px; + bottom: 0; + overflow-y: auto; position: fixed; margin-left: -15px; } diff --git a/xadmin/templates/xadmin/blocks/comm.top.setlang.html b/xadmin/templates/xadmin/blocks/comm.top.setlang.html index 81a1303b9..0e53770c4 100644 --- a/xadmin/templates/xadmin/blocks/comm.top.setlang.html +++ b/xadmin/templates/xadmin/blocks/comm.top.setlang.html @@ -3,7 +3,7 @@ ' % - ( - (' class="active"' if sorted and order_type == i[ - 0] else ''), - self.get_query_string({ORDER_VAR: '.'.join(i[1])}), i[2], i[3]) for i in menus]) + ( + (' class="active"' if sorted and order_type == i[ + 0] else ''), + self.get_query_string({ORDER_VAR: '.'.join(i[1])}), i[2], i[3]) for i in menus]) item.classes.extend(th_classes) return item @@ -544,7 +544,7 @@ def result_item(self, obj, field_name, row): else: item.text = smart_text(value) else: - if isinstance(f.rel, models.ManyToOneRel): + if isinstance(f.remote_field, models.ManyToOneRel): field_val = getattr(obj, f.name) if field_val is None: item.text = mark_safe("%s" % EMPTY_CHANGELIST_VALUE) @@ -574,7 +574,7 @@ def result_item(self, obj, field_name, row): else: edit_url = "" item.wraps.append('%%s' - % (item_res_uri, edit_url, _(u'Details of %s') % str(obj))) + % (item_res_uri, edit_url, _(u'Details of %s') % str(obj))) else: url = self.url_for_result(obj) item.wraps.append(u'%%s' % url) diff --git a/xadmin/views/website.py b/xadmin/views/website.py index 172b99d90..c8b338c5f 100644 --- a/xadmin/views/website.py +++ b/xadmin/views/website.py @@ -2,8 +2,8 @@ from django.utils.translation import ugettext as _ from django.contrib.auth import REDIRECT_FIELD_NAME from django.views.decorators.cache import never_cache -from django.contrib.auth.views import login -from django.contrib.auth.views import logout +from django.contrib.auth.views import LoginView as login +from django.contrib.auth.views import LogoutView as logout from django.http import HttpResponse from .base import BaseAdminView, filter_hook @@ -58,12 +58,13 @@ def get(self, request, *args, **kwargs): }) defaults = { 'extra_context': context, - 'current_app': self.admin_site.name, + # 'current_app': self.admin_site.name, 'authentication_form': self.login_form or AdminAuthenticationForm, 'template_name': self.login_template or 'xadmin/views/login.html', } self.update_params(defaults) - return login(request, **defaults) + # return login(request, **defaults) + return login.as_view(**defaults)(request) @never_cache def post(self, request, *args, **kwargs): @@ -84,14 +85,15 @@ def get(self, request, *args, **kwargs): context = self.get_context() defaults = { 'extra_context': context, - 'current_app': self.admin_site.name, + # 'current_app': self.admin_site.name, 'template_name': self.logout_template or 'xadmin/views/logged_out.html', } if self.logout_template is not None: defaults['template_name'] = self.logout_template self.update_params(defaults) - return logout(request, **defaults) + # return logout(request, **defaults) + return logout.as_view(**defaults)(request) @never_cache def post(self, request, *args, **kwargs): diff --git a/xadmin/widgets.py b/xadmin/widgets.py index 3263ddc2a..7cdda51de 100644 --- a/xadmin/widgets.py +++ b/xadmin/widgets.py @@ -14,7 +14,7 @@ from django.utils.html import conditional_escape from django.utils.translation import ugettext as _ -from .util import vendor, DJANGO_11 +from .util import vendor class AdminDateWidget(forms.DateInput): @@ -29,8 +29,8 @@ def __init__(self, attrs=None, format=None): final_attrs.update(attrs) super(AdminDateWidget, self).__init__(attrs=final_attrs, format=format) - def render(self, name, value, attrs=None): - input_html = super(AdminDateWidget, self).render(name, value, attrs) + def render(self, name, value, attrs=None, renderer=None): + input_html = super(AdminDateWidget, self).render(name, value, attrs, renderer) return mark_safe('
    %s' '
    ' % (input_html, _(u'Today'))) @@ -47,8 +47,8 @@ def __init__(self, attrs=None, format=None): final_attrs.update(attrs) super(AdminTimeWidget, self).__init__(attrs=final_attrs, format=format) - def render(self, name, value, attrs=None): - input_html = super(AdminTimeWidget, self).render(name, value, attrs) + def render(self, name, value, attrs=None, renderer=None): + input_html = super(AdminTimeWidget, self).render(name, value, attrs, renderer) return mark_safe('
    ' '%s
    ' % (input_html, _(u'Now'))) @@ -71,17 +71,13 @@ def __init__(self, attrs=None): # we want to define widgets. forms.MultiWidget.__init__(self, widgets, attrs) - def render(self, name, value, attrs=None): - if DJANGO_11: - input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs).replace( - '/>\n
    %s' - '
    ' - '
    ' - '%s
    ' % (input_html[0], _(u'Today'), input_html[1], _(u'Now'))) - else: - return super(AdminSplitDateTime, self).render(name, value, attrs) + def render(self, name, value, attrs=None, renderer=None): + input_html = [ht for ht in super(AdminSplitDateTime, self).render(name, value, attrs, renderer).replace('>\n
    %s' + '
    ' + '
    ' + '%s
    ' % (input_html[0], _(u'Today'), input_html[1], _(u'Now'))) def format_output(self, rendered_widgets): return mark_safe(u'
    %s%s
    ' % @@ -130,10 +126,7 @@ def render(self, name, value, attrs=None, choices=()): if value is None: value = [] has_id = attrs and 'id' in attrs - if DJANGO_11: - final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) - else: - final_attrs = self.build_attrs(attrs, name=name) + final_attrs = self.build_attrs(attrs, extra_attrs={'name': name}) output = [] # Normalize to strings str_values = set([force_text(v) for v in value]) From 851dff7625e6c745def9fd4d1fe83a59a56943f6 Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 13:58:37 +0800 Subject: [PATCH 02/11] update: Django3.x compatible --- demo_app/app/models.py | 2 +- requirements.txt | 2 +- tests/xtests/site/apps.py | 3 ++- tests/xtests/view_base/apps.py | 3 ++- xadmin/filters.py | 4 ++-- xadmin/plugins/aggregation.py | 4 +++- xadmin/plugins/filters.py | 3 ++- xadmin/plugins/importexport.py | 3 ++- xadmin/util.py | 9 +++++---- xadmin/views/dashboard.py | 3 ++- 10 files changed, 22 insertions(+), 14 deletions(-) diff --git a/demo_app/app/models.py b/demo_app/app/models.py index 5f2ddcc74..f46731673 100644 --- a/demo_app/app/models.py +++ b/demo_app/app/models.py @@ -1,7 +1,7 @@ from django.db import models from django.contrib.auth.models import Group from django.conf import settings -from django.utils.encoding import python_2_unicode_compatible +from six import python_2_unicode_compatible AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') diff --git a/requirements.txt b/requirements.txt index 22c7cf752..14c0bc7f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ django>=1.9.0 django-crispy-forms>=1.6.0 django-import-export>=0.5.1 django-reversion>=2.0.0 -django-formtools==1.0 +django-formtools==2.2 future==0.15.2 httplib2==0.9.2 six==1.10.0 diff --git a/tests/xtests/site/apps.py b/tests/xtests/site/apps.py index 37777566c..890f5d599 100644 --- a/tests/xtests/site/apps.py +++ b/tests/xtests/site/apps.py @@ -1,7 +1,8 @@ #!/usr/bin/env python #coding:utf-8 import sys -from django.utils import six +import six + if six.PY2 and sys.getdefaultencoding()=='ascii': import imp imp.reload(sys) diff --git a/tests/xtests/view_base/apps.py b/tests/xtests/view_base/apps.py index 5b59ac85d..bd79cce50 100644 --- a/tests/xtests/view_base/apps.py +++ b/tests/xtests/view_base/apps.py @@ -1,7 +1,8 @@ #!/usr/bin/env python #coding:utf-8 import sys -from django.utils import six +import six + if six.PY2 and sys.getdefaultencoding()=='ascii': import imp imp.reload(sys) diff --git a/xadmin/filters.py b/xadmin/filters.py index 4e719bf1b..75e896c2c 100644 --- a/xadmin/filters.py +++ b/xadmin/filters.py @@ -202,7 +202,7 @@ def test(cls, field, request, params, model, admin_view, field_path): def choices(self): yield { - 'selected': self.lookup_exact_val is '', + 'selected': self.lookup_exact_val == '', 'query_string': self.query_string({}, [self.lookup_exact_name]), 'display': _('All') } @@ -548,7 +548,7 @@ def __init__(self, field, request, params, model, admin_view, field_path): def choices(self): yield { - 'selected': (self.lookup_exact_val is '' and self.lookup_isnull_val is ''), + 'selected': (self.lookup_exact_val == '' and self.lookup_isnull_val == ''), 'query_string': self.query_string({}, [self.lookup_exact_name, self.lookup_isnull_name]), 'display': _('All'), } diff --git a/xadmin/plugins/aggregation.py b/xadmin/plugins/aggregation.py index 1e7553fad..b5061b412 100644 --- a/xadmin/plugins/aggregation.py +++ b/xadmin/plugins/aggregation.py @@ -1,4 +1,6 @@ -from django.db.models import FieldDoesNotExist, Avg, Max, Min, Count, Sum +# from django.db.models import FieldDoesNotExist, Avg, Max, Min, Count, Sum +from django.db.models import Avg, Max, Min, Count, Sum +from django.core.exceptions import FieldDoesNotExis from django.utils.translation import ugettext as _ from django.forms import Media diff --git a/xadmin/plugins/filters.py b/xadmin/plugins/filters.py index f9a308f6f..afd448317 100644 --- a/xadmin/plugins/filters.py +++ b/xadmin/plugins/filters.py @@ -6,7 +6,8 @@ from django.contrib.admin.utils import get_fields_from_path, lookup_needs_distinct from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured, ValidationError from django.db import models -from django.db.models.fields import FieldDoesNotExist +# from django.db.models.fields import FieldDoesNotExist +from django.core.exceptions import FieldDoesNotExist from django.db.models.constants import LOOKUP_SEP # from django.db.models.sql.constants import QUERY_TERMS from django.template import loader diff --git a/xadmin/plugins/importexport.py b/xadmin/plugins/importexport.py index 507ae5e1e..d16475346 100644 --- a/xadmin/plugins/importexport.py +++ b/xadmin/plugins/importexport.py @@ -45,7 +45,8 @@ class FooAdmin(object): from xadmin.views import BaseAdminPlugin, ListAdminView, ModelAdminView from xadmin.views.base import csrf_protect_m, filter_hook from django.db import transaction -from import_export.admin import DEFAULT_FORMATS, SKIP_ADMIN_LOG, TMP_STORAGE_CLASS +from import_export.admin import DEFAULT_FORMATS +from import_export.admin import ImportMixin, ImportExportMixinBase from import_export.resources import modelresource_factory from import_export.forms import ( ImportForm, diff --git a/xadmin/util.py b/xadmin/util.py index 333affe2b..4173987fc 100644 --- a/xadmin/util.py +++ b/xadmin/util.py @@ -21,10 +21,11 @@ import datetime import decimal -try: - from django.contrib.staticfiles.templatetags.staticfiles import static -except ModuleNotFoundError: - from django.templatetags.static import static +# try: +# from django.contrib.staticfiles.templatetags.staticfiles import static +# except ModuleNotFoundError: +# from django.templatetags.static import static +from django.templatetags.static import static try: import json diff --git a/xadmin/views/dashboard.py b/xadmin/views/dashboard.py index 4e734324e..9a386202c 100644 --- a/xadmin/views/dashboard.py +++ b/xadmin/views/dashboard.py @@ -279,7 +279,8 @@ def __init__(self, *, required=True, widget=None, label=None, initial=None, help_text=None, **kwargs): # Call Field instead of ChoiceField __init__() because we don't need # ChoiceField.__init__(). - forms.Field.__init__(self, **kwargs) + # forms.Field.__init__(self, **kwargs) + forms.Field.__init__(self) self.widget.choices = self.choices def __deepcopy__(self, memo): From d0599aebf8902a03da755d0a8685e48b33863b0c Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 14:03:13 +0800 Subject: [PATCH 03/11] update readme --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 4ef644de6..99c3765b1 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,10 @@ +Xadmin-Django3 +-------------- +Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 +参考: +https://github.com/sshwsfc/xadmin/pull/706 +https://github.com/sshwsfc/xadmin/pull/716/files + Xadmin |Build Status| ============================================ From 51bad006380f68c0a2ddeea3e994756846eb000a Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 14:05:31 +0800 Subject: [PATCH 04/11] update readme --- README.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 99c3765b1..18b1a481d 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,3 @@ -Xadmin-Django3 --------------- -Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 -参考: -https://github.com/sshwsfc/xadmin/pull/706 -https://github.com/sshwsfc/xadmin/pull/716/files - Xadmin |Build Status| ============================================ @@ -13,6 +6,14 @@ Xadmin |Build Status| Drop-in replacement of Django admin comes with lots of goodies, fully extensible with plugin support, pretty UI based on Twitter Bootstrap. +Xadmin-Django3 +-------------- +Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 +参考: +https://github.com/sshwsfc/xadmin/pull/706 +https://github.com/sshwsfc/xadmin/pull/716/files +https://blog.csdn.net/weixin_43865334/article/details/115071848 + Live Demo --------- From e3703cc57f7bc0fb9448662b5c2e0bb1c5f75c38 Mon Sep 17 00:00:00 2001 From: szhou Date: Tue, 6 Sep 2022 14:07:13 +0800 Subject: [PATCH 05/11] Update README.rst --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 18b1a481d..7261dc9fa 100644 --- a/README.rst +++ b/README.rst @@ -8,11 +8,11 @@ Drop-in replacement of Django admin comes with lots of goodies, fully extensible Xadmin-Django3 -------------- -Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 -参考: -https://github.com/sshwsfc/xadmin/pull/706 -https://github.com/sshwsfc/xadmin/pull/716/files -https://blog.csdn.net/weixin_43865334/article/details/115071848 +- Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 +- 参考: +- https://github.com/sshwsfc/xadmin/pull/706/files +- https://github.com/sshwsfc/xadmin/pull/716/files +- https://blog.csdn.net/weixin_43865334/article/details/115071848 Live Demo --------- From 7d9eef75585521e62614c379d286708d6ba35128 Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 16:32:53 +0800 Subject: [PATCH 06/11] fix: import dependenies --- xadmin/plugins/aggregation.py | 2 +- xadmin/plugins/importexport.py | 2 +- xadmin/util.py | 2 +- xadmin/views/list.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/xadmin/plugins/aggregation.py b/xadmin/plugins/aggregation.py index b5061b412..18351223e 100644 --- a/xadmin/plugins/aggregation.py +++ b/xadmin/plugins/aggregation.py @@ -1,6 +1,6 @@ # from django.db.models import FieldDoesNotExist, Avg, Max, Min, Count, Sum from django.db.models import Avg, Max, Min, Count, Sum -from django.core.exceptions import FieldDoesNotExis +from django.core.exceptions import FieldDoesNotExist from django.utils.translation import ugettext as _ from django.forms import Media diff --git a/xadmin/plugins/importexport.py b/xadmin/plugins/importexport.py index d16475346..1ad880421 100644 --- a/xadmin/plugins/importexport.py +++ b/xadmin/plugins/importexport.py @@ -45,7 +45,7 @@ class FooAdmin(object): from xadmin.views import BaseAdminPlugin, ListAdminView, ModelAdminView from xadmin.views.base import csrf_protect_m, filter_hook from django.db import transaction -from import_export.admin import DEFAULT_FORMATS +from import_export.formats.base_formats import DEFAULT_FORMATS from import_export.admin import ImportMixin, ImportExportMixinBase from import_export.resources import modelresource_factory from import_export.forms import ( diff --git a/xadmin/util.py b/xadmin/util.py index 4173987fc..fedd0efc9 100644 --- a/xadmin/util.py +++ b/xadmin/util.py @@ -4,7 +4,7 @@ from django.db.models.sql.query import LOOKUP_SEP from django.db.models.deletion import Collector from django.db.models.fields.related import ForeignObjectRel -from django.forms.forms import pretty_name +# from django.forms.forms import pretty_name from django.utils import formats import six from django.utils.html import escape diff --git a/xadmin/views/list.py b/xadmin/views/list.py index a16268947..44f4c8916 100644 --- a/xadmin/views/list.py +++ b/xadmin/views/list.py @@ -12,6 +12,7 @@ from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import ugettext as _ +from django.core.exceptions import FieldDoesNotExist from xadmin.util import lookup_field, display_for_field, label_for_field, boolean_icon @@ -222,7 +223,7 @@ def get_list_queryset(self): for field_name in self.list_display: try: field = self.opts.get_field(field_name) - except models.FieldDoesNotExist: + except FieldDoesNotExist: pass else: if isinstance(field.remote_field, models.ManyToOneRel): @@ -259,7 +260,7 @@ def get_ordering_field(self, field_name): try: field = self.opts.get_field(field_name) return field.name - except models.FieldDoesNotExist: + except FieldDoesNotExist: # See whether field_name is a name of a non-field # that allows sorting. if callable(field_name): From 55586740982aa054fe6ec875ded260d351fe7369 Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 16:34:42 +0800 Subject: [PATCH 07/11] update readme --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 7261dc9fa..469a315b2 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ Drop-in replacement of Django admin comes with lots of goodies, fully extensible Xadmin-Django3 -------------- - Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 + 在django-3.2.15上测试暂无问题 - 参考: - https://github.com/sshwsfc/xadmin/pull/706/files - https://github.com/sshwsfc/xadmin/pull/716/files From 1012f98f88165a8a98a325c6464c89a747d1fc01 Mon Sep 17 00:00:00 2001 From: szhou Date: Tue, 6 Sep 2022 16:35:25 +0800 Subject: [PATCH 08/11] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 469a315b2..8ff570547 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ Drop-in replacement of Django admin comes with lots of goodies, fully extensible Xadmin-Django3 -------------- - Tips: 这个项目合并了原项目的#706和#716 PR请求,并做了部分修改适应Django3 - 在django-3.2.15上测试暂无问题 +- 在django-3.2.15上测试暂无问题 - 参考: - https://github.com/sshwsfc/xadmin/pull/706/files - https://github.com/sshwsfc/xadmin/pull/716/files From 86b5f5ae1ec1e4f5f97a3880f0e09cec962c18fa Mon Sep 17 00:00:00 2001 From: szhou Date: Tue, 6 Sep 2022 16:53:14 +0800 Subject: [PATCH 09/11] Update README.rst --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 8ff570547..6c377b20e 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,15 @@ Xadmin-Django3 - https://github.com/sshwsfc/xadmin/pull/716/files - https://blog.csdn.net/weixin_43865334/article/details/115071848 +安装以下两个依赖: + +.. code:: bash + + pip install requests + + pip install cryptography + + Live Demo --------- From c4e7ad13a41fc44da904a617a217db0d5c7d32cb Mon Sep 17 00:00:00 2001 From: szhou Date: Tue, 6 Sep 2022 17:03:00 +0800 Subject: [PATCH 10/11] Update README.rst --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 6c377b20e..d12727d6a 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,12 @@ Xadmin-Django3 pip install cryptography +使用pip安装这个分支的xadmin: + +.. code:: bash + + pip install https://github.com/SzLeaves/xadmin-django3/archive/refs/heads/master.zip + Live Demo --------- From c8be7e02c24059285dda8c601e552b8e14b0bb29 Mon Sep 17 00:00:00 2001 From: szleaves Date: Tue, 6 Sep 2022 17:25:35 +0800 Subject: [PATCH 11/11] fix: FieldDoesNotExist --- xadmin/views/edit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xadmin/views/edit.py b/xadmin/views/edit.py index 9cd8e0349..52ed9708b 100644 --- a/xadmin/views/edit.py +++ b/xadmin/views/edit.py @@ -20,6 +20,7 @@ from xadmin.layout import FormHelper, Layout, Fieldset, TabHolder, Container, Column, Col, Field from xadmin.util import unquote from xadmin.views.detail import DetailAdminUtil +from django.core.exceptions import FieldDoesNotExist from .base import ModelAdminView, filter_hook, csrf_protect_m @@ -383,7 +384,7 @@ def get_form_datas(self): for k in initial: try: f = self.opts.get_field(k) - except models.FieldDoesNotExist: + except FieldDoesNotExist: continue if isinstance(f, models.ManyToManyField): initial[k] = initial[k].split(",")