diff --git a/README.rst b/README.rst
index 4ef644de6..9bb161e09 100644
--- a/README.rst
+++ b/README.rst
@@ -66,12 +66,12 @@ Install from github source for Django 2.0:
 
 .. code:: bash
 
-    pip install git+git://github.com/sshwsfc/xadmin.git@django2
+    pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
 
 Install Requires 
 ----------------
 
--  `django`_ >=1.9
+-  `django`_ >=2
 
 -  `django-crispy-forms`_ >=1.6.0 (For xadmin crispy forms)
 
diff --git a/demo_app/app/adminx.py b/demo_app/app/adminx.py
index f6755962c..7f50f9c06 100644
--- a/demo_app/app/adminx.py
+++ b/demo_app/app/adminx.py
@@ -71,6 +71,7 @@ class IDCAdmin(object):
 
 @xadmin.sites.register(Host)
 class HostAdmin(object):
+
     def open_web(self, instance):
         return """Open""" % instance.ip
 
@@ -87,7 +88,7 @@ def open_web(self, instance):
     raw_id_fields = ("idc",)
     style_fields = {"system": "radio-inline"}
 
-    search_fields = ["name", "ip", "description"]
+    search_fields = ["name", "ip", "description", "idc__name"]
     list_filter = [
         "idc", "guarantee_date", "status", "brand", "model", "cpu", "core_num",
         "hard_disk", "memory", (
@@ -164,6 +165,7 @@ class HostGroupAdmin(object):
     list_display = ("name", "description")
     list_display_links = ("name",)
 
+    list_filter = ["hosts"]
     search_fields = ["name"]
     style_fields = {"hosts": "checkbox-inline"}
 
@@ -200,6 +202,7 @@ class MaintainLogAdmin(object):
 
 @xadmin.sites.register(AccessRecord)
 class AccessRecordAdmin(object):
+
     def avg_count(self, instance):
         return int(instance.view_count / instance.user_count)
 
@@ -223,8 +226,8 @@ def avg_count(self, instance):
                       "option": {
                           "series": {"bars": {"align": "center", "barWidth": 0.8, 'show': True}},
                           "xaxis": {"aggregate": "sum", "mode": "categories"},
-                      },
-                      },
+        },
+        },
     }
 
     def _chart_month(self, obj):
diff --git a/demo_app/app/models.py b/demo_app/app/models.py
index 5f2ddcc74..430b6370a 100644
--- a/demo_app/app/models.py
+++ b/demo_app/app/models.py
@@ -23,6 +23,7 @@
     ('mix', u"Mix"),
 )
 
+
 @python_2_unicode_compatible
 class IDC(models.Model):
     name = models.CharField(max_length=64)
@@ -46,7 +47,7 @@ class Meta:
 
 @python_2_unicode_compatible
 class Host(models.Model):
-    idc = models.ForeignKey(IDC)
+    idc = models.ForeignKey(IDC, on_delete=models.CASCADE)
     name = models.CharField(max_length=64)
     nagios_name = models.CharField(u"Nagios Host ID", max_length=64, blank=True, null=True)
     ip = models.GenericIPAddressField(blank=True, null=True)
@@ -72,7 +73,7 @@ class Host(models.Model):
     service_type = models.CharField(max_length=32, choices=SERVICE_TYPES)
     description = models.TextField()
 
-    administrator = models.ForeignKey(AUTH_USER_MODEL, verbose_name="Admin")
+    administrator = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Admin")
 
     def __str__(self):
         return self.name
@@ -84,7 +85,7 @@ class Meta:
 
 @python_2_unicode_compatible
 class MaintainLog(models.Model):
-    host = models.ForeignKey(Host)
+    host = models.ForeignKey(Host, on_delete=models.CASCADE)
     maintain_type = models.CharField(max_length=32)
     hard_type = models.CharField(max_length=16)
     time = models.DateTimeField()
diff --git a/demo_app/demo/settings.py b/demo_app/demo/settings.py
index ba583a1bc..75933f3b1 100644
--- a/demo_app/demo/settings.py
+++ b/demo_app/demo/settings.py
@@ -1,198 +1,130 @@
-# Django settings for wictrl project.
+"""
+Django settings for demo_app project.
 
-import sys
-import os.path
-from django.utils import six
+Generated by 'django-admin startproject' using Django 2.0.
 
-if six.PY2 and sys.getdefaultencoding()=='ascii':
-    import imp
-    imp.reload(sys)
-    sys.setdefaultencoding('utf-8')
+For more information on this file, see
+https://docs.djangoproject.com/en/2.0/topics/settings/
 
-from django.utils.translation import ugettext_lazy as _
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.0/ref/settings/
+"""
 
-PROJECT_ROOT = os.path.join(
-    os.path.realpath(os.path.dirname(__file__)), os.pardir)
-# PROJECT_ROOT  = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+import os
+from django.utils.translation import ugettext_lazy as _
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
-DEBUG = True
 
-ADMINS = (
-    # ('Your Name', 'your_email@example.com'),
-)
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
 
-MANAGERS = ADMINS
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
-        'NAME': os.path.join(PROJECT_ROOT, 'data.db'),                      # Or path to database file if using sqlite3.
-        'USER': '',                      # Not used with sqlite3.
-        'PASSWORD': '',                  # Not used with sqlite3.
-        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
-        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
-    }
-}
-# Hosts/domain names that are valid for this site; required if DEBUG is False
-# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
-ALLOWED_HOSTS = ['*']
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# In a Windows environment this must be set to your system time zone.
-TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en-us'
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'l!fafmjcqyn+j+zz1@2@wt$o8w8k(_dhgub%41l#k3zi2m-b%m'
 
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
 LANGUAGES = (
     ('en', _('English')),
     ('zh-hans', _('Chinese')),
 )
 
-SITE_ID = 1
+ALLOWED_HOSTS = []
 
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# If you set this to False, Django will not format dates, numbers and
-# calendars according to the current locale.
-USE_L10N = True
-
-# If you set this to False, Django will not use timezone-aware datetimes.
-USE_TZ = True
 
-# Absolute filesystem path to the directory that will hold user-uploaded files.
-# Example: "/home/media/media.lawrence.com/media/"
-MEDIA_ROOT = ''
+# Application definition
 
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash.
-# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
-MEDIA_URL = ''
-
-# Absolute path to the directory static files should be collected to.
-# Don't put anything in this directory yourself; store your static files
-# in apps' "static/" subdirectories and in STATICFILES_DIRS.
-# Example: "/home/media/media.lawrence.com/static/"
-STATIC_ROOT = 'static/'
-
-# URL prefix for static files.
-# Example: "http://media.lawrence.com/static/"
-STATIC_URL = '/static/'
-
-# Additional locations of static files
-STATICFILES_DIRS = (
-    # Put strings here, like "/home/html/static" or "C:/www/django/static".
-    # Always use forward slashes, even on Windows.
-    # Don't forget to use absolute paths, not relative paths.
-)
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
 
-# List of finder classes that know how to find static files in
-# various locations.
-STATICFILES_FINDERS = (
-    'django.contrib.staticfiles.finders.FileSystemFinder',
-    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
-    #    'django.contrib.staticfiles.finders.DefaultStorageFinder',
-)
+    'xadmin',
+    'crispy_forms',
+    'reversion',
 
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = '5=!nss_+^nvyyc_j(tdcf!7(_una*3gtw+_8v5jaa=)j0g^d_2'
+    'app',
+]
 
-MIDDLEWARE_CLASSES = (
+MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.locale.LocaleMiddleware',
-    # Uncomment the next line for simple clickjacking protection:
-    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
-)
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
 
 ROOT_URLCONF = 'demo.urls'
 
-# Python dotted path to the WSGI application used by Django's runserver.
-WSGI_APPLICATION = 'demo.wsgi.application'
-
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [
-            os.path.join(PROJECT_ROOT,"templates"),
-            ],
+        'DIRS': [],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
-                'django.template.context_processors.i18n',
-                'django.template.context_processors.media',
-                'django.template.context_processors.static',
-                'django.template.context_processors.tz',
                 'django.template.context_processors.debug',
                 'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
             ],
-            'debug': DEBUG,
         },
     },
 ]
 
-INSTALLED_APPS = (
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'django.contrib.admin',
+WSGI_APPLICATION = 'demo.wsgi.application'
 
-    'xadmin',
-    'crispy_forms',
-    'reversion',
 
-    'app',
-)
+# Database
+# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
 
-TEST_RUNNER = 'django.test.runner.DiscoverRunner'
-
-# A sample logging configuration. The only tangible logging
-# performed by this configuration is to send an email to
-# the site admins on every HTTP 500 error when DEBUG=False.
-# See http://docs.djangoproject.com/en/dev/topics/logging for
-# more details on how to customize your logging configuration.
-LOGGING = {
-    'version': 1,
-    'disable_existing_loggers': False,
-    'filters': {
-        'require_debug_false': {
-            '()': 'django.utils.log.RequireDebugFalse'
-        }
-    },
-    'handlers': {
-        'mail_admins': {
-            'level': 'ERROR',
-            'filters': ['require_debug_false'],
-            'class': 'django.utils.log.AdminEmailHandler'
-        },
-        'console': {
-            'level': 'DEBUG',
-            'class': 'logging.StreamHandler'
-        },
-    },
-    'loggers': {
-        'django.request': {
-            'handlers': ['mail_admins'],
-            'level': 'ERROR',
-            'propagate': True,
-        },
-        # 'django.db.backends': {
-        #     'handlers': ['console'],
-        #     'level': 'DEBUG',
-        # }
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
     }
 }
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.0/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.0/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/demo_app/demo/urls.py b/demo_app/demo/urls.py
index 409cde35d..b84fe25ec 100644
--- a/demo_app/demo/urls.py
+++ b/demo_app/demo/urls.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
-from django.conf.urls import include, url
+# from django.conf.urls import include, url
+from django.urls import include, path
 
 # Uncomment the next two lines to enable the admin:
 import xadmin
@@ -12,6 +13,5 @@
 from django.contrib import admin
 
 urlpatterns = [
-    url(r'^admin/', include(admin.site.urls)),
-    url(r'^', include(xadmin.site.urls))
+    path(r'', xadmin.site.urls)
 ]
diff --git a/requirements.txt b/requirements.txt
index 22c7cf752..5dd4056a8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,8 @@
-django>=1.9.0
+django>=2
 django-crispy-forms>=1.6.0
 django-import-export>=0.5.1
 django-reversion>=2.0.0
-django-formtools==1.0
+django-formtools==2.1
 future==0.15.2
 httplib2==0.9.2
 six==1.10.0
diff --git a/setup.py b/setup.py
index 26bf2e698..ae631e6b3 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 
 setup(
     name='xadmin',
-    version='0.6.1',
+    version='2.0.1',
     description='Drop-in replacement of Django admin comes with lots of goodies, fully extensible with plugin support, pretty UI based on Twitter Bootstrap.',
     long_description=open('README.rst', encoding='utf-8').read(),
     author='sshwsfc',
@@ -18,7 +18,7 @@
     include_package_data=True,
     install_requires=[
         'setuptools',
-        'django>=1.9.0',
+        'django>=2',
         'django-crispy-forms>=1.6.0',
         'django-reversion>=2.0.0',
         'django-formtools>=1.0',
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/models.py b/xadmin/models.py
index da5145641..f389fd3bd 100644
--- a/xadmin/models.py
+++ b/xadmin/models.py
@@ -5,7 +5,7 @@
 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
@@ -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..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..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/filters.py b/xadmin/plugins/filters.py
index dc7e171d8..a17777ff4 100644
--- a/xadmin/plugins/filters.py
+++ b/xadmin/plugins/filters.py
@@ -7,7 +7,8 @@
 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
 from django.utils.encoding import smart_str
@@ -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..c95928553 100644
--- a/xadmin/plugins/inline.py
+++ b/xadmin/plugins/inline.py
@@ -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 '