diff --git a/.gitignore b/.gitignore index cccac58f3..bae85af2f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ xadmin/static/xadmin/bower_components/ .idea *~ demo_app/data.db + +TAGS* +*.swp diff --git a/demo_app/app/adminx.py b/demo_app/app/adminx.py index 3e3c50de4..43109d25b 100644 --- a/demo_app/app/adminx.py +++ b/demo_app/app/adminx.py @@ -1,6 +1,7 @@ +from __future__ import absolute_import import xadmin from xadmin import views -from models import IDC, Host, MaintainLog, HostGroup, AccessRecord +from .models import IDC, Host, MaintainLog, HostGroup, AccessRecord from xadmin.layout import Main, TabHolder, Tab, Fieldset, Row, Col, AppendedText, Side from xadmin.plugins.inline import Inline from xadmin.plugins.batch import BatchChangeAction @@ -9,12 +10,11 @@ class MainDashboard(object): widgets = [ [ {"type": "html", "title": "Test Widget", "content": "

Welcome to Xadmin!

Join Online Group:
QQ Qun : 282936295

"}, - {"type": "chart", "model": "app.accessrecord", 'chart': 'user_count', 'params': {'_p_date__gte': '2013-01-08', 'p': 1, '_p_date__lt': '2013-01-29'}}, - {"type": "list", "model": "app.host", 'params': { - 'o':'-guarantee_date'}}, + {"type": "chart", "model": "app.accessrecord", "chart": "user_count", "params": {"_p_date__gte": "2013-01-08", "p": 1, "_p_date__lt": "2013-01-29"}}, + {"type": "list", "model": "app.host", "params": {"o":"-guarantee_date"}}, ], [ - {"type": "qbutton", "title": "Quick Start", "btns": [{'model': Host}, {'model':IDC}, {'title': "Google", 'url': "http://www.google.com"}]}, + {"type": "qbutton", "title": "Quick Start", "btns": [{"model": Host}, {"model":IDC}, {"title": "Google", "url": "http://www.google.com"}]}, {"type": "addform", "model": MaintainLog}, ] ] @@ -30,134 +30,160 @@ class BaseSetting(object): class GlobalSetting(object): global_search_models = [Host, IDC] global_models_icon = { - Host: 'fa fa-laptop', IDC: 'fa fa-cloud' + Host: "fa fa-laptop", IDC: "fa fa-cloud" } - menu_style = 'default'#'accordion' + menu_style = "default"#"accordion" xadmin.sites.site.register(views.CommAdminView, GlobalSetting) class MaintainInline(object): model = MaintainLog extra = 1 - style = 'accordion' + style = "accordion" class IDCAdmin(object): - list_display = ('name', 'description', 'create_time') - list_display_links = ('name',) + list_display = ("name", "description", "create_time") + list_display_links = ("name",) wizard_form_list = [ - ('First\'s Form', ('name', 'description')), - ('Second Form', ('contact', 'telphone', 'address')), - ('Thread Form', ('customer_id',)) - ] + ("First's Form", ("name", "description")), + ("Second Form", ("contact", "telphone", "address")), + ("Thread Form", ("customer_id",)) + ] - search_fields = ['name'] - relfield_style = 'fk-select' + search_fields = ["name"] + relfield_style = "fk-select" reversion_enable = True actions = [BatchChangeAction, ] - batch_fields = ('contact', 'groups') + batch_fields = ("contact", "groups") class HostAdmin(object): def open_web(self, instance): - return "Open" % instance.ip + return """Open""" % instance.ip open_web.short_description = "Acts" open_web.allow_tags = True open_web.is_column = True - list_display = ('name', 'idc', 'guarantee_date', 'service_type', - 'status', 'open_web', 'description') - list_display_links = ('name',) + list_display = ( + "name", "idc", "guarantee_date", "service_type", "status", "open_web", + "description", + ) + list_display_links = ("name",) - raw_id_fields = ('idc',) - style_fields = {'system': "radio-inline"} + raw_id_fields = ("idc",) + style_fields = {"system": "radio-inline"} - search_fields = ['name', 'ip', 'description'] - list_filter = ['idc', 'guarantee_date', 'status', 'brand', 'model', - 'cpu', 'core_num', 'hard_disk', 'memory', ('service_type',xadmin.filters.MultiSelectFieldListFilter)] + search_fields = ["name", "ip", "description"] + list_filter = [ + "idc", "guarantee_date", "status", "brand", "model", "cpu", "core_num", + "hard_disk", "memory", ( + "service_type", + xadmin.filters.MultiSelectFieldListFilter, + ), + ] - list_quick_filter = ['service_type',{'field':'idc__name','limit':10}] - list_bookmarks = [{'title': "Need Guarantee", 'query': {'status__exact': 2}, 'order': ('-guarantee_date',), 'cols': ('brand', 'guarantee_date', 'service_type')}] - - show_detail_fields = ('idc',) + list_quick_filter = ["service_type",{"field":"idc__name","limit":10}] + list_bookmarks = [{ + "title": "Need Guarantee", + "query": {"status__exact": 2}, + "order": ("-guarantee_date",), + "cols": ("brand", "guarantee_date", "service_type"), + }] + + show_detail_fields = ("idc",) list_editable = ( - 'name', 'idc', 'guarantee_date', 'service_type', 'description') + "name", "idc", "guarantee_date", "service_type", "description", + ) save_as = True aggregate_fields = {"guarantee_date": "min"} - grid_layouts = ('table', 'thumbnails') + grid_layouts = ("table", "thumbnails") form_layout = ( Main( TabHolder( - Tab('Comm Fields', - Fieldset('Company data', - 'name', 'idc', - description="some comm fields, required" - ), + Tab( + "Comm Fields", + Fieldset( + "Company data", "name", "idc", + description="some comm fields, required", + ), Inline(MaintainLog), ), - Tab('Extend Fields', - Fieldset('Contact details', - 'service_type', - Row('brand', 'model'), - Row('cpu', 'core_num'), - Row(AppendedText( - 'hard_disk', 'G'), AppendedText('memory', "G")), - 'guarantee_date' - ), + Tab( + "Extend Fields", + Fieldset( + "Contact details", + "service_type", + Row("brand", "model"), + Row("cpu", "core_num"), + Row( + AppendedText("hard_disk", "G"), + AppendedText("memory", "G") + ), + "guarantee_date" + ), ), + ), ), - ), Side( - Fieldset('Status data', - 'status', 'ssh_port', 'ip' - ), + Fieldset("Status data", "status", "ssh_port", "ip"), + ) ) - ) inlines = [MaintainInline] reversion_enable = True - data_charts = { - "host_service_type_counts": {'title': u"Host service type count", "x-field": "service_type", "y-field": ("service_type",), - "option": { - "series": {"bars": {"align": "center", "barWidth": 0.8,'show':True}}, - "xaxis": {"aggregate": "count", "mode": "categories"}, - }, - }, - } + data_charts = {"host_service_type_counts": { + "title": u"Host service type count", + "x-field": "service_type", + "y-field": ("service_type",), + "option": { + "series": { + "bars": { + "align": "center", + "barWidth": 0.8, + "show": True, + }, + }, + "xaxis": { + "aggregate": "count", + "mode": "categories", + }, + }, + }} class HostGroupAdmin(object): - list_display = ('name', 'description') - list_display_links = ('name',) + list_display = ("name", "description") + list_display_links = ("name",) - search_fields = ['name'] - style_fields = {'hosts': 'checkbox-inline'} + search_fields = ["name"] + style_fields = {"hosts": "checkbox-inline"} class MaintainLogAdmin(object): list_display = ( - 'host', 'maintain_type', 'hard_type', 'time', 'operator', 'note') - list_display_links = ('host',) + "host", "maintain_type", "hard_type", "time", "operator", "note") + list_display_links = ("host",) - list_filter = ['host', 'maintain_type', 'hard_type', 'time', 'operator'] - search_fields = ['note'] + list_filter = ["host", "maintain_type", "hard_type", "time", "operator"] + search_fields = ["note"] form_layout = ( Col("col2", - Fieldset('Record data', - 'time', 'note', - css_class='unsort short_label no_title' + Fieldset("Record data", + "time", "note", + css_class="unsort short_label no_title" ), span=9, horizontal=True ), Col("col1", - Fieldset('Comm data', - 'host', 'maintain_type' + Fieldset("Comm data", + "host", "maintain_type" ), - Fieldset('Maintain details', - 'hard_type', 'operator' + Fieldset("Maintain details", + "hard_type", "operator" ), span=3 ) @@ -172,20 +198,20 @@ def avg_count(self, instance): avg_count.allow_tags = True avg_count.is_column = True - list_display = ('date', 'user_count', 'view_count', 'avg_count') - list_display_links = ('date',) + list_display = ("date", "user_count", "view_count", "avg_count") + list_display_links = ("date",) - list_filter = ['date', 'user_count', 'view_count'] + list_filter = ["date", "user_count", "view_count"] actions = None - aggregate_fields = {"user_count": "sum", 'view_count': "sum"} + aggregate_fields = {"user_count": "sum", "view_count": "sum"} refresh_times = (3, 5, 10) data_charts = { - "user_count": {'title': u"User Report", "x-field": "date", "y-field": ("user_count", "view_count"), "order": ('date',)}, - "avg_count": {'title': u"Avg Report", "x-field": "date", "y-field": ('avg_count',), "order": ('date',)}, - "per_month": {'title': u"Monthly Users", "x-field": "_chart_month", "y-field": ("user_count", ), + "user_count": {"title": u"User Report", "x-field": "date", "y-field": ("user_count", "view_count"), "order": ("date",)}, + "avg_count": {"title": u"Avg Report", "x-field": "date", "y-field": ("avg_count",), "order": ("date",)}, + "per_month": {"title": u"Monthly Users", "x-field": "_chart_month", "y-field": ("user_count", ), "option": { - "series": {"bars": {"align": "center", "barWidth": 0.8,'show':True}}, + "series": {"bars": {"align": "center", "barWidth": 0.8,"show":True}}, "xaxis": {"aggregate": "sum", "mode": "categories"}, }, }, diff --git a/demo_app/app/models.py b/demo_app/app/models.py index 32fef1113..5f2ddcc74 100644 --- a/demo_app/app/models.py +++ b/demo_app/app/models.py @@ -1,6 +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 AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') @@ -22,7 +23,7 @@ ('mix', u"Mix"), ) - +@python_2_unicode_compatible class IDC(models.Model): name = models.CharField(max_length=64) description = models.TextField() @@ -35,7 +36,7 @@ class IDC(models.Model): create_time = models.DateField(auto_now=True) - def __unicode__(self): + def __str__(self): return self.name class Meta: @@ -43,6 +44,7 @@ class Meta: verbose_name_plural = verbose_name +@python_2_unicode_compatible class Host(models.Model): idc = models.ForeignKey(IDC) name = models.CharField(max_length=64) @@ -72,7 +74,7 @@ class Host(models.Model): administrator = models.ForeignKey(AUTH_USER_MODEL, verbose_name="Admin") - def __unicode__(self): + def __str__(self): return self.name class Meta: @@ -80,6 +82,7 @@ class Meta: verbose_name_plural = verbose_name +@python_2_unicode_compatible class MaintainLog(models.Model): host = models.ForeignKey(Host) maintain_type = models.CharField(max_length=32) @@ -88,7 +91,7 @@ class MaintainLog(models.Model): operator = models.CharField(max_length=16) note = models.TextField() - def __unicode__(self): + def __str__(self): return '%s maintain-log [%s] %s %s' % (self.host.name, self.time.strftime('%Y-%m-%d %H:%M:%S'), self.maintain_type, self.hard_type) @@ -97,6 +100,7 @@ class Meta: verbose_name_plural = verbose_name +@python_2_unicode_compatible class HostGroup(models.Model): name = models.CharField(max_length=32) @@ -108,10 +112,11 @@ class Meta: verbose_name = u"Host Group" verbose_name_plural = verbose_name - def __unicode__(self): + def __str__(self): return self.name +@python_2_unicode_compatible class AccessRecord(models.Model): date = models.DateField() user_count = models.IntegerField() @@ -121,5 +126,5 @@ class Meta: verbose_name = u"Access Record" verbose_name_plural = verbose_name - def __unicode__(self): + def __str__(self): return "%s Access Record" % self.date.strftime('%Y-%m-%d') diff --git a/demo_app/demo/settings.py b/demo_app/demo/settings.py index 87b76b1d3..ba583a1bc 100644 --- a/demo_app/demo/settings.py +++ b/demo_app/demo/settings.py @@ -2,13 +2,18 @@ import sys import os.path +from django.utils import six -reload(sys) -sys.setdefaultencoding('utf-8') -gettext = lambda s: s +if six.PY2 and sys.getdefaultencoding()=='ascii': + import imp + imp.reload(sys) + sys.setdefaultencoding('utf-8') + +from django.utils.translation import ugettext_lazy as _ 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__))) DEBUG = True @@ -30,7 +35,7 @@ } # 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 = '*' +ALLOWED_HOSTS = ['*'] # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name @@ -43,8 +48,8 @@ LANGUAGE_CODE = 'en-us' LANGUAGES = ( - ('en', gettext('English')), - ('zh-hans', gettext('Chinese')), + ('en', _('English')), + ('zh-hans', _('Chinese')), ) SITE_ID = 1 @@ -98,10 +103,12 @@ SECRET_KEY = '5=!nss_+^nvyyc_j(tdcf!7(_una*3gtw+_8v5jaa=)j0g^d_2' MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', + '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: @@ -123,12 +130,15 @@ '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 + 'debug': DEBUG, }, }, ] diff --git a/requirements.txt b/requirements.txt index d49b05b72..cb5940a30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ django~=1.9.0 django-crispy-forms~=1.6.0 django-reversion~=2.0.0 django-formtools==1.0 +future==0.15.2 httplib2==0.9.2 +six==1.10.0 diff --git a/setup.py b/setup.py index c9a504580..63a93debb 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ "Programming Language :: JavaScript", 'Programming Language :: Python', "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/tests/runtests.py b/tests/runtests.py index 3efe90c17..5f8677b25 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +from __future__ import print_function import os import shutil import sys @@ -6,6 +7,7 @@ import django from django.apps import AppConfig,apps +from django.utils.encoding import smart_text TEST_ROOT = os.path.realpath(os.path.dirname(__file__)) @@ -32,9 +34,13 @@ def get_test_modules(): modules = [] for f in os.listdir(RUNTESTS_DIR): - if (f.startswith('__init__') or - f.startswith('.') or - f.startswith('sql') or not os.path.isdir(os.path.join(RUNTESTS_DIR, f))): + if ( + f.startswith('__init__') + or f.startswith('__pycache__') + or f.startswith('.') + or f.startswith('sql') + or not os.path.isdir(os.path.join(RUNTESTS_DIR, f)) + ): continue modules.append(f) return modules @@ -81,7 +87,6 @@ def setup(verbosity, test_labels): # Load all the test model apps. test_labels_set = set([label.split('.')[0] for label in test_labels]) test_modules = get_test_modules() - for module_name in test_modules: module_label = module_name # if the module was named on the command line, or @@ -103,7 +108,7 @@ def teardown(state): # so that it will successfully remove temp trees containing # non-ASCII filenames on Windows. (We're assuming the temp dir # name itself does not contain non-ASCII characters.) - shutil.rmtree(unicode(TEMP_DIR)) + shutil.rmtree(smart_text(TEMP_DIR)) # Restore the old settings. for key, value in state.items(): setattr(settings, key, value) diff --git a/tests/xtests/site/apps.py b/tests/xtests/site/apps.py index 1ab4a5c31..37777566c 100644 --- a/tests/xtests/site/apps.py +++ b/tests/xtests/site/apps.py @@ -1,8 +1,10 @@ #!/usr/bin/env python #coding:utf-8 import sys -if sys.getdefaultencoding()=='ascii': - reload(sys) +from django.utils import six +if six.PY2 and sys.getdefaultencoding()=='ascii': + import imp + imp.reload(sys) sys.setdefaultencoding('utf-8') from django.apps import AppConfig diff --git a/tests/xtests/site/tests.py b/tests/xtests/site/tests.py index db5ccb81a..808997f40 100644 --- a/tests/xtests/site/tests.py +++ b/tests/xtests/site/tests.py @@ -1,10 +1,11 @@ +from __future__ import absolute_import from django.http import HttpResponse from base import BaseTest from xadmin.sites import AdminSite from xadmin.views import BaseAdminView, BaseAdminPlugin, ModelAdminView, filter_hook -from models import ModelA +from .models import ModelA class ModelAAdmin(object): diff --git a/tests/xtests/view_base/adminx.py b/tests/xtests/view_base/adminx.py index 7db81e35f..b75fc8f8a 100644 --- a/tests/xtests/view_base/adminx.py +++ b/tests/xtests/view_base/adminx.py @@ -1,6 +1,7 @@ +from __future__ import absolute_import from xadmin.sites import AdminSite from xadmin.views import BaseAdminView, CommAdminView, ListAdminView -from models import ModelA, ModelB +from .models import ModelA, ModelB site = AdminSite('views_base') diff --git a/tests/xtests/view_base/apps.py b/tests/xtests/view_base/apps.py index 6398388ca..5b59ac85d 100644 --- a/tests/xtests/view_base/apps.py +++ b/tests/xtests/view_base/apps.py @@ -1,8 +1,10 @@ #!/usr/bin/env python #coding:utf-8 import sys -if sys.getdefaultencoding()=='ascii': - reload(sys) +from django.utils import six +if six.PY2 and sys.getdefaultencoding()=='ascii': + import imp + imp.reload(sys) sys.setdefaultencoding('utf-8') from django.apps import AppConfig diff --git a/tests/xtests/view_base/tests.py b/tests/xtests/view_base/tests.py index a34da38cb..a779341f4 100644 --- a/tests/xtests/view_base/tests.py +++ b/tests/xtests/view_base/tests.py @@ -1,11 +1,12 @@ +from __future__ import absolute_import from django.contrib.auth.models import User from base import BaseTest from xadmin.views import BaseAdminView, BaseAdminPlugin, ModelAdminView, ListAdminView -from models import ModelA, ModelB -from adminx import site, ModelAAdmin, TestBaseView, TestCommView, TestAView, OptionA +from .models import ModelA, ModelB +from .adminx import site, ModelAAdmin, TestBaseView, TestCommView, TestAView, OptionA class BaseAdminTest(BaseTest): diff --git a/tests/xtests/view_base/urls.py b/tests/xtests/view_base/urls.py index 5f76a1c80..a6a3601ec 100644 --- a/tests/xtests/view_base/urls.py +++ b/tests/xtests/view_base/urls.py @@ -1,5 +1,6 @@ +from __future__ import absolute_import from django.conf.urls import patterns, include -from adminx import site +from .adminx import site urlpatterns = patterns('', (r'', include(site.urls)), diff --git a/xadmin/adminx.py b/xadmin/adminx.py index a22630107..e7a66e0ff 100644 --- a/xadmin/adminx.py +++ b/xadmin/adminx.py @@ -1,5 +1,6 @@ +from __future__ import absolute_import import xadmin -from models import UserSettings, Log +from .models import UserSettings, Log from xadmin.layout import * from django.utils.translation import ugettext_lazy as _, ugettext diff --git a/xadmin/filters.py b/xadmin/filters.py index 6656578c3..9eba01e99 100644 --- a/xadmin/filters.py +++ b/xadmin/filters.py @@ -1,10 +1,12 @@ +from __future__ import absolute_import from django.db import models from django.core.exceptions import ImproperlyConfigured -from django.utils.encoding import smart_unicode +from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.template.loader import get_template 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.text import Truncator @@ -17,7 +19,7 @@ FILTER_PREFIX = '_p_' SEARCH_VAR = '_q_' -from util import (get_model_from_relation, +from .util import (get_model_from_relation, reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value) @@ -45,8 +47,10 @@ def query_string(self, new_params=None, remove=None): return self.admin_view.get_query_string(new_params, remove) def form_params(self): - return self.admin_view.get_form_params( - remove=map(lambda k: FILTER_PREFIX + k, self.used_params.keys())) + arr = map(lambda k: FILTER_PREFIX + k, self.used_params.keys()) + if six.PY3: + arr = list(arr) + return self.admin_view.get_form_params(remove=arr) def has_output(self): """ @@ -120,14 +124,20 @@ def __init__(self, field, request, params, model, admin_view, field_path): else: self.context_params["%s_val" % name] = '' - map(lambda kv: setattr( - self, 'lookup_' + kv[0], kv[1]), self.context_params.items()) + arr = map( + lambda kv: setattr(self, 'lookup_' + kv[0], kv[1]), + self.context_params.items() + ) + if six.PY3: + list(arr) def get_context(self): context = super(FieldFilter, self).get_context() context.update(self.context_params) - context['remove_url'] = self.query_string( - {}, map(lambda k: FILTER_PREFIX + k, self.used_params.keys())) + obj = map(lambda k: FILTER_PREFIX + k, self.used_params.keys()) + if six.PY3: + obj = list(obj) + context['remove_url'] = self.query_string({}, obj) return context def has_output(self): @@ -158,22 +168,28 @@ def choices(self): for lookup, title in ( ('', _('All')), ('1', _('Yes')), - ('0', _('No'))): + ('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 @@ -192,7 +208,7 @@ def choices(self): } for lookup, title in self.field.flatchoices: yield { - 'selected': smart_unicode(lookup) == self.lookup_exact_val, + 'selected': smart_text(lookup) == self.lookup_exact_val, 'query_string': self.query_string({self.lookup_exact_name: lookup}), 'display': title, } @@ -201,11 +217,15 @@ def choices(self): @manager.register class TextFieldListFilter(FieldFilter): template = 'xadmin/filters/char.html' - lookup_formats = {'in': '%s__in','search': '%s__contains'} + lookup_formats = {'in': '%s__in', 'search': '%s__contains'} @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) + return ( + isinstance(field, models.CharField) + and field.max_length > 20 + or isinstance(field, models.TextField) + ) @manager.register @@ -408,7 +428,7 @@ def choices(self): } for pk_val, val in self.lookup_choices: yield { - 'selected': self.lookup_exact_val == smart_unicode(pk_val), + 'selected': self.lookup_exact_val == smart_text(pk_val), 'query_string': self.query_string({ self.lookup_exact_name: pk_val, }, [self.lookup_isnull_name]), @@ -494,7 +514,7 @@ def choices(self): } for val in self.lookup_choices: yield { - 'selected': smart_unicode(val) in self.lookup_in_val, + '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]),}), 'display': val, @@ -535,7 +555,7 @@ def choices(self): if val is None: include_none = True continue - val = smart_unicode(val) + val = smart_text(val) yield { 'selected': self.lookup_exact_val == val, 'query_string': self.query_string({self.lookup_exact_name: val}, diff --git a/xadmin/models.py b/xadmin/models.py index 32ef94f27..da5145641 100644 --- a/xadmin/models.py +++ b/xadmin/models.py @@ -8,7 +8,7 @@ from django.core.urlresolvers import NoReverseMatch, 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, smart_unicode +from django.utils.encoding import python_2_unicode_compatible, smart_text from django.db.models.signals import post_migrate from django.contrib.auth.models import Permission @@ -40,6 +40,8 @@ def add_view_permissions(sender, **kwargs): # check for all our view permissions after a syncdb post_migrate.connect(add_view_permissions) + +@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) @@ -55,7 +57,7 @@ def url(self): base_url = base_url + '?' + self.query return base_url - def __unicode__(self): + def __str__(self): return self.title class Meta: @@ -65,10 +67,10 @@ class Meta: class JSONEncoder(DjangoJSONEncoder): def default(self, o): - if isinstance(o, datetime.date): - return o.strftime('%Y-%m-%d') - elif isinstance(o, datetime.datetime): + if isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') + elif isinstance(o, datetime.date): + return o.strftime('%Y-%m-%d') elif isinstance(o, decimal.Decimal): return str(o) elif isinstance(o, ModelBase): @@ -77,9 +79,10 @@ def default(self, o): try: return super(JSONEncoder, self).default(o) except Exception: - return smart_unicode(o) + return smart_text(o) +@python_2_unicode_compatible class UserSettings(models.Model): user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) key = models.CharField(_('Settings Key'), max_length=256) @@ -91,7 +94,7 @@ def json_value(self): def set_json(self, obj): self.value = json.dumps(obj, cls=JSONEncoder, ensure_ascii=False) - def __unicode__(self): + def __str__(self): return "%s %s" % (self.user, self.key) class Meta: @@ -99,6 +102,7 @@ class Meta: verbose_name_plural = _('User Settings') +@python_2_unicode_compatible class UserWidget(models.Model): user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) page_id = models.CharField(_(u"Page"), max_length=256) @@ -126,13 +130,15 @@ def save(self, *args, **kwargs): except Exception: pass - def __unicode__(self): + def __str__(self): return "%s %s widget" % (self.user, self.widget_type) class Meta: verbose_name = _(u'User Widget') verbose_name_plural = _('User Widgets') + +@python_2_unicode_compatible class Log(models.Model): action_time = models.DateTimeField( _('action time'), diff --git a/xadmin/plugins/actions.py b/xadmin/plugins/actions.py index f05c1a180..02085bf33 100644 --- a/xadmin/plugins/actions.py +++ b/xadmin/plugins/actions.py @@ -5,7 +5,8 @@ from django.http import HttpResponse, HttpResponseRedirect from django.template import loader from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _, ungettext from django.utils.text import capfirst @@ -24,7 +25,7 @@ def action_checkbox(obj): - return checkbox.render(ACTION_CHECKBOX_NAME, force_unicode(obj.pk)) + return checkbox.render(ACTION_CHECKBOX_NAME, force_text(obj.pk)) action_checkbox.short_description = mark_safe( '') action_checkbox.allow_tags = True @@ -102,9 +103,9 @@ def do_action(self, queryset): return None if len(queryset) == 1: - objects_name = force_unicode(self.opts.verbose_name) + objects_name = force_text(self.opts.verbose_name) else: - objects_name = force_unicode(self.opts.verbose_name_plural) + objects_name = force_text(self.opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} @@ -227,6 +228,8 @@ def get_actions(self): # get_action might have returned None, so filter any of those out. actions = filter(None, actions) + if six.PY3: + actions = list(actions) # Convert the actions into a OrderedDict keyed by name. actions = OrderedDict([ @@ -242,7 +245,7 @@ def get_action_choices(self): tuple (name, description). """ choices = [] - for ac, name, description, icon in self.actions.itervalues(): + for ac, name, description, icon in self.actions.values(): choice = (name, description % model_format_dict(self.opts), icon) choices.append(choice) return choices diff --git a/xadmin/plugins/ajax.py b/xadmin/plugins/ajax.py index f6e21c3d6..b09bea21b 100644 --- a/xadmin/plugins/ajax.py +++ b/xadmin/plugins/ajax.py @@ -1,7 +1,7 @@ from collections import OrderedDict from django.forms.utils import ErrorDict from django.utils.html import escape -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ListAdminView, ModelFormAdminView, DetailAdminView @@ -27,7 +27,7 @@ def get_list_display(self,list_display): def get_result_list(self, response): av = self.admin_view base_fields = self.get_list_display(av.base_list_display) - headers = dict([(c.field_name, force_unicode(c.text)) for c in av.result_headers( + headers = dict([(c.field_name, force_text(c.text)) for c in av.result_headers( ).cells if c.field_name in base_fields]) objects = [dict([(o.field_name, escape(str(o.value))) for i, o in diff --git a/xadmin/plugins/auth.py b/xadmin/plugins/auth.py index fb42fe12d..1ab6f6110 100644 --- a/xadmin/plugins/auth.py +++ b/xadmin/plugins/auth.py @@ -9,6 +9,7 @@ from django.utils.decorators import method_decorator from django.http import HttpResponseRedirect from django.utils.html import escape +from django.utils.encoding import smart_text from django.utils.translation import ugettext as _ from django.views.decorators.debug import sensitive_post_parameters from django.forms import ModelMultipleChoiceField @@ -198,7 +199,7 @@ def get_context(self): helper.include_media = False self.form.helper = helper context.update({ - 'title': _('Change password: %s') % escape(unicode(self.obj)), + 'title': _('Change password: %s') % escape(smart_text(self.obj)), 'form': self.form, 'has_delete_permission': False, 'has_change_permission': True, diff --git a/xadmin/plugins/batch.py b/xadmin/plugins/batch.py index b6030773b..b322af98e 100644 --- a/xadmin/plugins/batch.py +++ b/xadmin/plugins/batch.py @@ -5,7 +5,7 @@ from django.core.exceptions import PermissionDenied from django.forms.models import modelform_factory from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _, ugettext_lazy from xadmin.layout import FormHelper, Layout, Fieldset, Container, Col @@ -129,9 +129,9 @@ def do_action(self, queryset): self.form_obj.helper = helper count = len(queryset) if count == 1: - objects_name = force_unicode(self.opts.verbose_name) + objects_name = force_text(self.opts.verbose_name) else: - objects_name = force_unicode(self.opts.verbose_name_plural) + objects_name = force_text(self.opts.verbose_name_plural) context = self.get_context() context.update({ diff --git a/xadmin/plugins/bookmark.py b/xadmin/plugins/bookmark.py index da1c39ebd..3d612cec8 100644 --- a/xadmin/plugins/bookmark.py +++ b/xadmin/plugins/bookmark.py @@ -1,22 +1,23 @@ -from django.template import loader -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext_lazy as _ -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect from django.contrib.contenttypes.models import ContentType +from django.core.urlresolvers import reverse from django.db import transaction from django.db.models import Q from django.forms import ModelChoiceField from django.http import QueryDict +from django.template import loader +from django.utils.decorators import method_decorator +from django.utils.encoding import smart_text +from django.utils.translation import ugettext_lazy as _ +from django.views.decorators.csrf import csrf_protect +from xadmin.filters import FILTER_PREFIX, SEARCH_VAR +from xadmin.plugins.relate import RELATE_PREFIX from xadmin.plugins.utils import get_context_dict from xadmin.sites import site from xadmin.views import ModelAdminView, BaseAdminPlugin, ListAdminView from xadmin.views.list import COL_LIST_VAR, ORDER_VAR from xadmin.views.dashboard import widget_manager, BaseWidget, PartialBaseWidget -from xadmin.filters import FILTER_PREFIX, SEARCH_VAR -from xadmin.plugins.relate import RELATE_PREFIX from xadmin.models import Bookmark @@ -41,9 +42,17 @@ 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()))]) + 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() + )) + ]) model_info = (self.opts.app_label, self.opts.model_name) has_selected = False @@ -54,8 +63,10 @@ def get_context(self, context): # local bookmarks for bk in self.list_bookmarks: title = bk['title'] - params = dict( - [(FILTER_PREFIX + k, v) for (k, v) in bk['query'].items()]) + params = dict([ + (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: @@ -64,7 +75,10 @@ def get_context(self, context): 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()))]) + 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) @@ -188,7 +202,7 @@ def setup(self): self.bookmark = bookmark if not self.title: - self.title = unicode(bookmark) + self.title = smart_text(bookmark) req = self.make_get_request("", data.items()) self.list_view = self.get_view_class( @@ -207,9 +221,13 @@ 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()] + 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() + ] context['result_count'] = list_view.result_count context['page_url'] = self.bookmark.url diff --git a/xadmin/plugins/chart.py b/xadmin/plugins/chart.py index 36907da65..e2b0a649a 100644 --- a/xadmin/plugins/chart.py +++ b/xadmin/plugins/chart.py @@ -1,22 +1,21 @@ +import calendar import datetime import decimal -import calendar -from django.template import loader -from django.http import HttpResponseNotFound from django.core.serializers.json import DjangoJSONEncoder -from django.http import HttpResponse -from django.utils.encoding import smart_unicode from django.db import models +from django.http import HttpResponse, HttpResponseNotFound +from django.template import loader from django.utils.http import urlencode +from django.utils.encoding import force_text, smart_text from django.utils.translation import ugettext_lazy as _, ugettext from xadmin.plugins.utils import get_context_dict from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ListAdminView from xadmin.views.dashboard import ModelBaseWidget, widget_manager -from xadmin.util import lookup_field, label_for_field, force_unicode, json +from xadmin.util import lookup_field, label_for_field, json @widget_manager.register @@ -77,7 +76,7 @@ def default(self, o): try: return super(JSONEncoder, self).default(o) except Exception: - return smart_unicode(o) + return smart_text(o) class ChartsPlugin(BaseAdminPlugin): @@ -124,7 +123,7 @@ def get(self, request, name): self.y_fields = ( y_fields,) if type(y_fields) not in (list, tuple) else y_fields - datas = [{"data":[], "label": force_unicode(label_for_field( + datas = [{"data":[], "label": force_text(label_for_field( i, self.model, model_admin=self))} for i in self.y_fields] self.make_result_list() diff --git a/xadmin/plugins/editable.py b/xadmin/plugins/editable.py index 00c4c8ae0..1b49feb60 100644 --- a/xadmin/plugins/editable.py +++ b/xadmin/plugins/editable.py @@ -3,7 +3,7 @@ from django.db import models, transaction from django.forms.models import modelform_factory from django.http import Http404, HttpResponse -from django.utils.encoding import force_unicode, smart_unicode +from django.utils.encoding import force_text, smart_text from django.utils.html import escape, conditional_escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ @@ -71,7 +71,7 @@ def init_request(self, object_id, *args, **kwargs): if self.org_obj is None: raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % - {'name': force_unicode(self.opts.verbose_name), 'key': escape(object_id)}) + {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)}) def get_new_field_html(self, f): result = self.result_item(self.org_obj, f, {'is_display_first': @@ -92,7 +92,7 @@ def _get_new_field_html(self, field_name): allow_tags = True text = boolean_icon(value) else: - text = smart_unicode(value) + text = smart_text(value) else: if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(self.org_obj, f.name) diff --git a/xadmin/plugins/export.py b/xadmin/plugins/export.py index 860755640..8ccc0f4e8 100644 --- a/xadmin/plugins/export.py +++ b/xadmin/plugins/export.py @@ -1,10 +1,12 @@ -import StringIO +import io import datetime import sys +from future.utils import iteritems from django.http import HttpResponse from django.template import loader -from django.utils.encoding import force_unicode, smart_unicode +from django.utils import six +from django.utils.encoding import force_text, smart_text from django.utils.html import escape from django.utils.translation import ugettext as _ from django.utils.xmlutils import SimplerXMLGenerator @@ -75,7 +77,7 @@ def _get_objects(self, context): rows = context['results'] return [dict([ - (force_unicode(headers[i].text), self._format_value(o)) for i, o in + (force_text(headers[i].text), self._format_value(o)) for i, o in enumerate(filter(lambda c:getattr(c, 'export', False), r.cells))]) for r in rows] def _get_datas(self, context): @@ -83,19 +85,19 @@ def _get_datas(self, context): new_rows = [[self._format_value(o) for o in filter(lambda c:getattr(c, 'export', False), r.cells)] for r in rows] - new_rows.insert(0, [force_unicode(c.text) for c in context['result_headers'].cells if c.export]) + new_rows.insert(0, [force_text(c.text) for c in context['result_headers'].cells if c.export]) return new_rows def get_xlsx_export(self, context): datas = self._get_datas(context) - output = StringIO.StringIO() + output = io.BytesIO() export_header = ( self.request.GET.get('export_xlsx_header', 'off') == 'on') model_name = self.opts.verbose_name book = xlsxwriter.Workbook(output) sheet = book.add_worksheet( - u"%s %s" % (_(u'Sheet'), force_unicode(model_name))) + u"%s %s" % (_(u'Sheet'), force_text(model_name))) styles = {'datetime': book.add_format({'num_format': 'yyyy-mm-dd hh:mm:ss'}), 'date': book.add_format({'num_format': 'yyyy-mm-dd'}), 'time': book.add_format({'num_format': 'hh:mm:ss'}), @@ -125,14 +127,14 @@ def get_xlsx_export(self, context): def get_xls_export(self, context): datas = self._get_datas(context) - output = StringIO.StringIO() + output = io.BytesIO() export_header = ( self.request.GET.get('export_xls_header', 'off') == 'on') model_name = self.opts.verbose_name book = xlwt.Workbook(encoding='utf8') sheet = book.add_sheet( - u"%s %s" % (_(u'Sheet'), force_unicode(model_name))) + u"%s %s" % (_(u'Sheet'), force_text(model_name))) styles = {'datetime': xlwt.easyxf(num_format_str='yyyy-mm-dd hh:mm:ss'), 'date': xlwt.easyxf(num_format_str='yyyy-mm-dd'), 'time': xlwt.easyxf(num_format_str='hh:mm:ss'), @@ -164,7 +166,8 @@ def _format_csv_text(self, t): if isinstance(t, bool): return _('Yes') if t else _('No') t = t.replace('"', '""').replace(',', '\,') - if isinstance(t, basestring): + cls_str = str if six.PY3 else basestring + if isinstance(t, cls_str): t = '"%s"' % t return t @@ -187,17 +190,17 @@ def _to_xml(self, xml, data): self._to_xml(xml, item) xml.endElement("row") elif isinstance(data, dict): - for key, value in data.iteritems(): + for key, value in iteritems(data): key = key.replace(' ', '_') xml.startElement(key, {}) self._to_xml(xml, value) xml.endElement(key) else: - xml.characters(smart_unicode(data)) + xml.characters(smart_text(data)) def get_xml_export(self, context): results = self._get_objects(context) - stream = StringIO.StringIO() + stream = io.StringIO() xml = SimplerXMLGenerator(stream, "utf-8") xml.startDocument() @@ -230,7 +233,7 @@ def get_response(self, response, context, *args, **kwargs): # View Methods def get_result_list(self, __): if self.request.GET.get('all', 'off') == 'on': - self.admin_view.list_per_page = sys.maxint + self.admin_view.list_per_page = sys.maxsize return __() def result_header(self, item, field_name, row): diff --git a/xadmin/plugins/filters.py b/xadmin/plugins/filters.py index 03e4f7831..0ee332650 100644 --- a/xadmin/plugins/filters.py +++ b/xadmin/plugins/filters.py @@ -1,4 +1,5 @@ import operator +from future.utils import iteritems from xadmin import widgets from xadmin.plugins.utils import get_context_dict @@ -8,6 +9,7 @@ from django.db.models.fields import FieldDoesNotExist from django.db.models.sql.query import LOOKUP_SEP, QUERY_TERMS from django.template import loader +from django.utils import six from django.utils.encoding import smart_str from django.utils.translation import ugettext as _ @@ -15,6 +17,7 @@ from xadmin.sites import site from xadmin.views import BaseAdminPlugin, ListAdminView from xadmin.util import is_related_field +from functools import reduce class IncorrectLookupParameters(Exception): @@ -74,7 +77,7 @@ def lookup_allowed(self, lookup, value): 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 lookup_params.iteritems(): + for p_key, p_val in iteritems(lookup_params): if p_val == "False": lookup_params[p_key] = False use_distinct = False @@ -128,7 +131,7 @@ def get_list_queryset(self, queryset): if spec and spec.has_output(): try: new_qs = spec.do_filte(queryset) - except ValidationError, e: + except ValidationError as e: new_qs = None self.admin_view.message_user(_("Filtering error: %s") % e.messages[0], 'error') if new_qs is not None: @@ -138,21 +141,23 @@ def get_list_queryset(self, queryset): self.has_filters = bool(self.filter_specs) self.admin_view.filter_specs = self.filter_specs - self.admin_view.used_filter_num = len( - filter(lambda f: f.is_used, self.filter_specs)) + obj = filter(lambda f: f.is_used, self.filter_specs) + if six.PY3: + obj = list(obj) + self.admin_view.used_filter_num = len(obj) try: for key, value in lookup_params.items(): use_distinct = ( use_distinct or lookup_needs_distinct(self.opts, key)) - except FieldDoesNotExist, e: + except FieldDoesNotExist as e: raise IncorrectLookupParameters(e) try: queryset = queryset.filter(**lookup_params) except (SuspiciousOperation, ImproperlyConfigured): raise - except Exception, e: + except Exception as e: raise IncorrectLookupParameters(e) query = self.request.GET.get(SEARCH_VAR, '') @@ -189,10 +194,16 @@ def construct_search(field_name): # Media def get_media(self, media): - if bool(filter(lambda s: isinstance(s, DateFieldListFilter), self.filter_specs)): + arr = filter(lambda s: isinstance(s, DateFieldListFilter), self.filter_specs) + if six.PY3: + arr = list(arr) + if bool(arr): media = media + self.vendor('datepicker.css', 'datepicker.js', 'xadmin.widget.datetime.js') - if bool(filter(lambda s: isinstance(s, RelatedFieldSearchFilter), self.filter_specs)): + arr = filter(lambda s: isinstance(s, RelatedFieldSearchFilter), self.filter_specs) + if six.PY3: + arr = list(arr) + if bool(arr): media = media + self.vendor( 'select.js', 'select.css', 'xadmin.widget.select.js') return media + self.vendor('xadmin.plugin.filters.js') diff --git a/xadmin/plugins/inline.py b/xadmin/plugins/inline.py index cf4629156..2fbe5b19d 100644 --- a/xadmin/plugins/inline.py +++ b/xadmin/plugins/inline.py @@ -7,6 +7,8 @@ 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 +from django.utils.encoding import smart_text from crispy_forms.utils import TEMPLATE_PACK from xadmin.layout import FormHelper, Layout, flatatt, Container, Column, Field, Fieldset @@ -113,10 +115,11 @@ def get_attrs(self): def replace_field_to_value(layout, av): if layout: + cls_str = str if six.PY3 else basestring for i, lo in enumerate(layout.fields): if isinstance(lo, Field) or issubclass(lo.__class__, Field): layout.fields[i] = ShowField(av, *lo.fields, **lo.attrs) - elif isinstance(lo, basestring): + elif isinstance(lo, cls_str): layout.fields[i] = ShowField(av, lo) elif hasattr(lo, 'get_field_names'): replace_field_to_value(lo, av) @@ -229,7 +232,7 @@ def instance_form(self, **kwargs): label = None if readonly_field in inst._meta.get_all_field_names(): label = inst._meta.get_field(readonly_field).verbose_name - value = unicode(getattr(inst, readonly_field)) + value = smart_text(getattr(inst, readonly_field)) elif inspect.ismethod(getattr(inst, readonly_field, None)): value = getattr(inst, readonly_field)() label = getattr(getattr(inst, readonly_field), 'short_description', readonly_field) diff --git a/xadmin/plugins/multiselect.py b/xadmin/plugins/multiselect.py index 29426a7fe..4fec7f2be 100644 --- a/xadmin/plugins/multiselect.py +++ b/xadmin/plugins/multiselect.py @@ -6,7 +6,7 @@ from django.db.models import ManyToManyField from django.forms.utils import flatatt from django.template import loader -from django.utils.encoding import force_unicode +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 @@ -25,9 +25,9 @@ def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): super(SelectMultipleTransfer, self).__init__(attrs, choices) def render_opt(self, selected_choices, option_value, option_label): - option_value = force_unicode(option_value) + option_value = force_text(option_value) return u'' % ( - escape(option_value), conditional_escape(force_unicode(option_label))), bool(option_value in selected_choices) + escape(option_value), conditional_escape(force_text(option_label))), bool(option_value in selected_choices) def render(self, name, value, attrs=None, choices=()): if attrs is None: @@ -39,14 +39,14 @@ def render(self, name, value, attrs=None, choices=()): value = [] final_attrs = self.build_attrs(attrs, name=name) - selected_choices = set(force_unicode(v) for v in value) + selected_choices = set(force_text(v) for v in value) available_output = [] chosen_output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): available_output.append(u'' % - escape(force_unicode(option_value))) + escape(force_text(option_value))) for option in option_label: output, selected = self.render_opt( selected_choices, *option) diff --git a/xadmin/plugins/quickfilter.py b/xadmin/plugins/quickfilter.py index b51a401e9..6222c34b4 100644 --- a/xadmin/plugins/quickfilter.py +++ b/xadmin/plugins/quickfilter.py @@ -3,6 +3,8 @@ @author: LAB_ADM ''' +from future.utils import iteritems +from django.utils import six from django.utils.translation import ugettext_lazy as _ from xadmin.filters import manager,MultiSelectFieldListFilter from xadmin.plugins.filters import * @@ -80,7 +82,7 @@ def lookup_allowed(self, lookup, value): 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 lookup_params.iteritems(): + for p_key, p_val in iteritems(lookup_params): if p_val == "False": lookup_params[p_key] = False use_distinct = False @@ -136,7 +138,7 @@ def get_list_queryset(self, queryset): if spec and spec.has_output(): try: new_qs = spec.do_filte(queryset) - except ValidationError, e: + except ValidationError as e: new_qs = None self.admin_view.message_user(_("Filtering error: %s") % e.messages[0], 'error') if new_qs is not None: @@ -146,7 +148,10 @@ def get_list_queryset(self, queryset): self.has_filters = bool(self.filter_specs) self.admin_view.quickfilter['filter_specs'] = self.filter_specs - self.admin_view.quickfilter['used_filter_num'] = len(filter(lambda f: f.is_used, 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() diff --git a/xadmin/plugins/relate.py b/xadmin/plugins/relate.py index feb108b49..4d33977a3 100644 --- a/xadmin/plugins/relate.py +++ b/xadmin/plugins/relate.py @@ -3,7 +3,8 @@ from django.core.urlresolvers import reverse from django.db.models.options import PROXY_PARENTS -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text from django.utils.encoding import smart_str from django.utils.safestring import mark_safe from django.db.models.sql.query import LOOKUP_SEP @@ -78,7 +79,7 @@ def related_link(self, instance): field = rel.field rel_name = rel.get_related_field().name - verbose_name = force_unicode(opts.verbose_name) + verbose_name = force_text(opts.verbose_name) lookup_name = '%s__%s__exact' % (field.name, rel_name) link = ''.join(('
  • ', @@ -145,9 +146,9 @@ def get_brand_name(self): if len(self.to_objs) == 1: to_model_name = str(self.to_objs[0]) else: - to_model_name = force_unicode(self.to_model._meta.verbose_name) + to_model_name = force_text(self.to_model._meta.verbose_name) - return mark_safe(u"%s %s" % (to_model_name, force_unicode(self.opts.verbose_name_plural))) + return mark_safe(u"%s %s" % (to_model_name, force_text(self.opts.verbose_name_plural))) class BaseRelateDisplayPlugin(BaseAdminPlugin): @@ -208,7 +209,8 @@ def get_form_datas(self, datas): return datas def post_response(self, response): - if isinstance(response, basestring) and response != self.get_admin_url('index'): + cls_str = str if six.PY3 else basestring + if isinstance(response, cls_str) and response != self.get_admin_url('index'): return self._get_url(response) return response @@ -224,7 +226,8 @@ def block_after_fieldsets(self, context, nodes): class DeleteRelateDisplayPlugin(BaseRelateDisplayPlugin): def post_response(self, response): - if isinstance(response, basestring) and response != self.get_admin_url('index'): + cls_str = str if six.PY3 else basestring + if isinstance(response, cls_str) and response != self.get_admin_url('index'): return self._get_url(response) return response diff --git a/xadmin/plugins/themes.py b/xadmin/plugins/themes.py index 534429f3a..8ae23c101 100644 --- a/xadmin/plugins/themes.py +++ b/xadmin/plugins/themes.py @@ -1,12 +1,19 @@ #coding:utf-8 -import urllib, httplib2 +from __future__ import print_function +import httplib2 from django.template import loader from django.core.cache import cache +from django.utils import six from django.utils.translation import ugettext as _ from xadmin.sites import site from xadmin.models import UserSettings from xadmin.views import BaseAdminPlugin, BaseAdminView from xadmin.util import static, json +import six +if six.PY2: + import urllib +else: + import urllib.parse THEME_CACHE_KEY = 'xadmin_themes' @@ -30,7 +37,11 @@ def _get_theme(self): except Exception: pass if '_theme' in self.request.COOKIES: - return urllib.unquote(self.request.COOKIES['_theme']) + if six.PY2: + func = urllib.unquote + else: + func = urllib.parse.unquote + return func(self.request.COOKIES['_theme']) return self.default_theme def get_context(self, context): @@ -44,10 +55,10 @@ def get_media(self, media): # Block Views 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}] + 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: @@ -61,15 +72,17 @@ def block_top_navmenu(self, context, nodes): ex_themes = [] try: h = httplib2.Http() - resp, content = h.request("http://bootswatch.com/api/3.json", 'GET', \ - "", headers={"Accept": "application/json", "User-Agent": self.request.META['HTTP_USER_AGENT']}) + resp, content = h.request("http://bootswatch.com/api/3.json", 'GET', '', + headers={"Accept": "application/json", "User-Agent": self.request.META['HTTP_USER_AGENT']}) + if six.PY3: + 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, e: - print e + except Exception as e: + print(e) cache.set(THEME_CACHE_KEY, json.dumps(ex_themes), 24 * 3600) themes.extend(ex_themes) diff --git a/xadmin/plugins/utils.py b/xadmin/plugins/utils.py index 10df8daed..6bc7e7915 100644 --- a/xadmin/plugins/utils.py +++ b/xadmin/plugins/utils.py @@ -9,8 +9,7 @@ def get_context_dict(context): :return: dict """ if isinstance(context, RequestContext): - ctx = {} - map(ctx.update, context.dicts) + ctx = context.flatten() else: ctx = context return ctx diff --git a/xadmin/plugins/wizard.py b/xadmin/plugins/wizard.py index b370b622c..9ddaac00a 100644 --- a/xadmin/plugins/wizard.py +++ b/xadmin/plugins/wizard.py @@ -13,6 +13,8 @@ from django.contrib.formtools.wizard.forms import ManagementForm from django.contrib.formtools.wizard.views import StepsHelper +from django.utils import six +from django.utils.encoding import smart_text from django.utils.module_loading import import_string from django.forms import ValidationError from django.forms.models import modelform_factory @@ -41,7 +43,10 @@ class WizardFormPlugin(BaseAdminPlugin): def _get_form_prefix(self, step=None): if step is None: step = self.steps.current - return 'step_%d' % self.get_form_list().keys().index(step) + obj = self.get_form_list().keys() + if six.PY3: + obj = [s for s in obj] + return 'step_%d' % obj.index(step) def get_form_list(self): if not hasattr(self, '_form_list'): @@ -51,7 +56,7 @@ def get_form_list(self): self.wizard_form_list) > 0, 'at least one form is needed' for i, form in enumerate(self.wizard_form_list): - init_form_list[unicode(form[0])] = form[1] + init_form_list[smart_text(form[0])] = form[1] self._form_list = init_form_list @@ -84,8 +89,10 @@ def prepare_form(self, __): # form. (This makes stepping back a lot easier). wizard_goto_step = self.request.POST.get('wizard_goto_step', None) if wizard_goto_step and int(wizard_goto_step) < len(self.get_form_list()): - self.storage.current_step = self.get_form_list( - ).keys()[int(wizard_goto_step)] + obj = self.get_form_list().keys() + if six.PY3: + obj = [s for s in obj] + self.storage.current_step = obj[int(wizard_goto_step)] self.admin_view.model_form = self.get_step_form() self.wizard_goto_step = True return @@ -279,10 +286,12 @@ def get_next_step(self, step=None): """ if step is None: step = self.steps.current - form_list = self.get_form_list() - key = form_list.keys().index(step) + 1 - if len(form_list.keys()) > key: - return form_list.keys()[key] + obj = self.get_form_list().keys() + if six.PY3: + obj = [s for s in obj] + key = obj.index(step) + 1 + if len(obj) > key: + return obj[key] return None def get_prev_step(self, step=None): @@ -293,10 +302,12 @@ def get_prev_step(self, step=None): """ if step is None: step = self.steps.current - form_list = self.get_form_list() - key = form_list.keys().index(step) - 1 + obj = self.get_form_list().keys() + if six.PY3: + obj = [s for s in obj] + key = obj.index(step) - 1 if key >= 0: - return form_list.keys()[key] + return obj[key] return None def get_step_index(self, step=None): @@ -306,7 +317,10 @@ def get_step_index(self, step=None): """ if step is None: step = self.steps.current - return self.get_form_list().keys().index(step) + obj = self.get_form_list().keys() + if six.PY3: + obj = [s for s in obj] + return obj.index(step) def block_before_fieldsets(self, context, nodes): context.update(dict(self.storage.extra_data)) diff --git a/xadmin/plugins/xversion.py b/xadmin/plugins/xversion.py index e0b186035..2a5cb5069 100644 --- a/xadmin/plugins/xversion.py +++ b/xadmin/plugins/xversion.py @@ -8,7 +8,8 @@ from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text, smart_text from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import ugettext as _ @@ -191,7 +192,7 @@ def get_context(self): "opts": opts, "app_label": opts.app_label, "model_name": capfirst(opts.verbose_name), - "title": _("Recover deleted %(name)s") % {"name": force_unicode(opts.verbose_name_plural)}, + "title": _("Recover deleted %(name)s") % {"name": force_text(opts.verbose_name_plural)}, "deleted": deleted, "changelist_url": self.model_admin_url("changelist"), }) @@ -235,9 +236,9 @@ def get_context(self): ).select_related("revision__user")) ] context.update({ - 'title': _('Change history: %s') % force_unicode(self.obj), + 'title': _('Change history: %s') % force_text(self.obj), 'action_list': action_list, - 'model_name': capfirst(force_unicode(opts.verbose_name_plural)), + 'model_name': capfirst(force_text(opts.verbose_name_plural)), 'object': self.obj, 'app_label': opts.app_label, "changelist_url": self.model_admin_url("changelist"), @@ -393,7 +394,7 @@ def init_request(self, object_id, version_id): DetailAdminView, self.model, object_id) self.org_obj = self.detail.obj self.version = get_object_or_404( - Version, pk=version_id, object_id=unicode(self.org_obj.pk)) + Version, pk=version_id, object_id=smart_text(self.org_obj.pk)) self.prepare_form() @@ -421,7 +422,7 @@ def get_form_helper(self): def get_context(self): context = super(RevisionView, self).get_context() context["title"] = _( - "Revert %s") % force_unicode(self.model._meta.verbose_name) + "Revert %s") % force_text(self.model._meta.verbose_name) return context @filter_hook @@ -438,7 +439,7 @@ def get_response(self): @filter_hook def post_response(self): self.message_user(_('The %(model)s "%(name)s" was reverted successfully. You may edit it again below.') % - {"model": force_unicode(self.opts.verbose_name), "name": unicode(self.new_obj)}, 'success') + {"model": force_text(self.opts.verbose_name), "name": smart_text(self.new_obj)}, 'success') return HttpResponseRedirect(self.model_admin_url('change', self.new_obj.pk)) @@ -475,7 +476,7 @@ def get_response(self): @filter_hook def post_response(self): self.message_user(_('The %(model)s "%(name)s" was recovered successfully. You may edit it again below.') % - {"model": force_unicode(self.opts.verbose_name), "name": unicode(self.new_obj)}, 'success') + {"model": force_text(self.opts.verbose_name), "name": smart_text(self.new_obj)}, 'success') return HttpResponseRedirect(self.model_admin_url('change', self.new_obj.pk)) @@ -521,7 +522,7 @@ def get_related_versions(self, obj, version, formset): related_versions = dict([(related_version.object_id, related_version) for related_version in revision_versions if ContentType.objects.get_for_id(related_version.content_type_id).model_class() == formset.model - and unicode(related_version.field_dict[fk_name]) == unicode(object_id)]) + and smart_text(related_version.field_dict[fk_name]) == smart_text(object_id)]) return related_versions def _hack_inline_formset_initial(self, revision_view, formset): @@ -532,9 +533,9 @@ def _hack_inline_formset_initial(self, revision_view, formset): revision_view.org_obj, revision_view.version, formset) formset.related_versions = related_versions for related_obj in formset.queryset: - if unicode(related_obj.pk) in related_versions: + if smart_text(related_obj.pk) in related_versions: initial.append( - related_versions.pop(unicode(related_obj.pk)).field_dict) + related_versions.pop(smart_text(related_obj.pk)).field_dict) else: initial_data = model_to_dict(related_obj) initial_data["DELETE"] = True @@ -563,7 +564,8 @@ def total_form_count_hack(count): if self.request.method == 'GET' and formset.helper and formset.helper.layout: helper = formset.helper - helper.filter(basestring).wrap(InlineDiffField) + cls_str = str if six.PY3 else basestring + helper.filter(cls_str).wrap(InlineDiffField) fake_admin_class = type(str('%s%sFakeAdmin' % (self.opts.app_label, self.opts.model_name)), (object, ), {'model': self.model}) for form in formset.forms: instance = form.instance diff --git a/xadmin/sites.py b/xadmin/sites.py index 8963b7061..62fababba 100644 --- a/xadmin/sites.py +++ b/xadmin/sites.py @@ -1,14 +1,18 @@ import sys from functools import update_wrapper +from future.utils import iteritems from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db.models.base import ModelBase +from django.utils import six from django.views.decorators.cache import never_cache from django.template.engine import Engine import inspect -reload(sys) -sys.setdefaultencoding("utf-8") +if six.PY2 and sys.getdefaultencoding()=='ascii': + import imp + imp.reload(sys) + sys.setdefaultencoding("utf-8") class AlreadyRegistered(Exception): @@ -299,26 +303,35 @@ def wrapper(*args, **kwargs): # Admin-site-wide views. urlpatterns = [ url(r'^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, 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 - ] + url( + 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 + ] # Add in each model's views. - for model, admin_class in self._registry.iteritems(): - view_urls = [url( - 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 model, admin_class in iteritems(self._registry): + view_urls = [ + url( + 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 + ] urlpatterns += [ - url(r'^%s/%s/' % ( model._meta.app_label, model._meta.model_name), include(view_urls)) - ] + url(r'^%s/%s/' % (model._meta.app_label, model._meta.model_name), include(view_urls)) + ] return urlpatterns diff --git a/xadmin/templatetags/xadmin_tags.py b/xadmin/templatetags/xadmin_tags.py index 80ce1dbd9..ee0fc69c5 100644 --- a/xadmin/templatetags/xadmin_tags.py +++ b/xadmin/templatetags/xadmin_tags.py @@ -1,5 +1,6 @@ from django import template from django.template import Library +from django.utils import six from django.utils.safestring import mark_safe from xadmin.util import static, vendor as util_vendor @@ -15,11 +16,12 @@ def view_block(context, block_name, *args, **kwargs): nodes = [] method_name = 'block_%s' % block_name + cls_str = str if six.PY3 else basestring for view in [admin_view] + admin_view.plugins: if hasattr(view, method_name) and callable(getattr(view, method_name)): block_func = getattr(view, method_name) result = block_func(context, nodes, *args, **kwargs) - if result and type(result) in (str, unicode): + if result and isinstance(result, cls_str): nodes.append(result) if nodes: return mark_safe(''.join(nodes)) @@ -60,4 +62,4 @@ def do_blockcapture(parser, token): nodelist = parser.parse(('endblockcapture',)) parser.delete_first_token() - return BlockcaptureNode(nodelist, args) \ No newline at end of file + return BlockcaptureNode(nodelist, args) diff --git a/xadmin/util.py b/xadmin/util.py index ed923e8a2..5138a97e2 100644 --- a/xadmin/util.py +++ b/xadmin/util.py @@ -1,14 +1,15 @@ +from __future__ import absolute_import import django from django.db import models 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.utils import formats +from django.utils import formats, six from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.text import capfirst -from django.utils.encoding import force_unicode, smart_unicode, smart_str +from django.utils.encoding import force_text, smart_text, smart_str from django.utils.translation import ungettext from django.core.urlresolvers import reverse from django.conf import settings @@ -35,17 +36,18 @@ def xstatic(*tags): - from vendors import vendors + from .vendors import vendors node = vendors fs = [] lang = get_language() + cls_str = str if six.PY3 else basestring for tag in tags: try: for p in tag.split('.'): node = node[p] - except Exception, e: + except Exception as e: if tag.startswith('xadmin'): file_type = tag.split('.')[-1] if file_type in ('css', 'js'): @@ -55,7 +57,7 @@ def xstatic(*tags): else: raise e - if type(node) in (str, unicode): + if isinstance(node, cls_str): files = node else: mode = 'dev' @@ -124,7 +126,8 @@ def quote(s): quoting is slightly different so that it doesn't get automatically unquoted by the Web browser. """ - if not isinstance(s, basestring): + cls_str = str if six.PY3 else basestring + if not isinstance(s, cls_str): return s res = list(s) for i in range(len(res)): @@ -138,7 +141,8 @@ def unquote(s): """ Undo the effects of quote(). Based heavily on urllib.unquote(). """ - if not isinstance(s, basestring): + cls_str = str if six.PY3 else basestring + if not isinstance(s, cls_str): return s mychr = chr myatoi = int @@ -187,7 +191,7 @@ def collect(self, objs, source_attr=None, **kwargs): self.add_edge(None, obj) try: return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) - except models.ProtectedError, e: + except models.ProtectedError as e: self.protected.update(e.protected_objects) def related_objects(self, related, objs): @@ -236,8 +240,8 @@ def model_format_dict(obj): else: opts = obj return { - 'verbose_name': force_unicode(opts.verbose_name), - 'verbose_name_plural': force_unicode(opts.verbose_name_plural) + 'verbose_name': force_text(opts.verbose_name), + 'verbose_name_plural': force_text(opts.verbose_name_plural) } @@ -276,8 +280,11 @@ def lookup_field(name, obj, model_admin=None): if callable(name): attr = name value = attr(obj) - elif (model_admin is not None and hasattr(model_admin, name) and - not name == '__str__' and not name == '__unicode__'): + elif ( + model_admin is not None + and hasattr(model_admin, name) + and name not in ('__str__', '__unicode__') + ): attr = getattr(model_admin, name) value = attr(obj) else: @@ -329,9 +336,9 @@ def display_for_field(value, field): elif isinstance(field, models.FloatField): return formats.number_format(value) elif isinstance(field.rel, models.ManyToManyRel): - return ', '.join([smart_unicode(obj) for obj in value.all()]) + return ', '.join([smart_text(obj) for obj in value.all()]) else: - return smart_unicode(value) + return smart_text(value) def display_for_value(value, boolean=False): @@ -348,7 +355,7 @@ def display_for_value(value, boolean=False): elif isinstance(value, (decimal.Decimal, float)): return formats.number_format(value) else: - return smart_unicode(value) + return smart_text(value) class NotRelationField(Exception): diff --git a/xadmin/views/__init__.py b/xadmin/views/__init__.py index c7db4ff58..9e21b9fc2 100644 --- a/xadmin/views/__init__.py +++ b/xadmin/views/__init__.py @@ -1,13 +1,14 @@ +from __future__ import absolute_import -from base import BaseAdminPlugin, BaseAdminView, CommAdminView, ModelAdminView, filter_hook, csrf_protect_m, BaseAdminObject +from .base import BaseAdminPlugin, BaseAdminView, CommAdminView, ModelAdminView, filter_hook, csrf_protect_m, BaseAdminObject -from list import ListAdminView -from edit import CreateAdminView, UpdateAdminView, ModelFormAdminView -from delete import DeleteAdminView -from detail import DetailAdminView -from form import FormAdminView -from dashboard import Dashboard, BaseWidget, widget_manager, ModelDashboard -from website import IndexView, LoginView, LogoutView, UserSettingView +from .list import ListAdminView +from .edit import CreateAdminView, UpdateAdminView, ModelFormAdminView +from .delete import DeleteAdminView +from .detail import DetailAdminView +from .form import FormAdminView +from .dashboard import Dashboard, BaseWidget, widget_manager, ModelDashboard +from .website import IndexView, LoginView, LogoutView, UserSettingView __all__ = ( 'BaseAdminObject', diff --git a/xadmin/views/base.py b/xadmin/views/base.py index 44cb044fb..a238b80ff 100644 --- a/xadmin/views/base.py +++ b/xadmin/views/base.py @@ -1,4 +1,3 @@ -import sys import copy import functools import datetime @@ -7,7 +6,6 @@ from inspect import getargspec from django import forms -from django.utils.encoding import force_unicode, force_text from django.apps import apps from django.conf import settings from django.contrib import messages @@ -18,8 +16,10 @@ from django.http import HttpResponse from django.template import Context, Template from django.template.response import TemplateResponse +from django.utils import six from django.utils.decorators import method_decorator, classonlymethod -from django.utils.encoding import force_unicode, smart_unicode, smart_str +from django.utils.encoding import force_text, smart_text, smart_str +from django.utils.functional import Promise from django.utils.http import urlencode from django.utils.itercompat import is_iterable from django.utils.safestring import mark_safe @@ -87,9 +87,10 @@ def wrap(func): def method(self, context, nodes, *arg, **kwargs): _dict = func(self, context, nodes, *arg, **kwargs) from django.template.loader import get_template, select_template + cls_str = str if six.PY3 else basestring if isinstance(file_name, Template): t = file_name - elif not isinstance(file_name, basestring) and is_iterable(file_name): + elif not isinstance(file_name, cls_str) and is_iterable(file_name): t = select_template(file_name) else: t = get_template(file_name) @@ -110,17 +111,19 @@ def method(self, context, nodes, *arg, **kwargs): class JSONEncoder(DjangoJSONEncoder): def default(self, o): - if isinstance(o, datetime.date): - return o.strftime('%Y-%m-%d') - elif isinstance(o, datetime.datetime): + if isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') + elif isinstance(o, datetime.date): + return o.strftime('%Y-%m-%d') elif isinstance(o, decimal.Decimal): return str(o) + elif isinstance(o, Promise): + return force_text(o) else: try: return super(JSONEncoder, self).default(o) except Exception: - return smart_unicode(o) + return smart_text(o) class BaseAdminObject(object): @@ -154,8 +157,9 @@ def get_query_string(self, new_params=None, remove=None): if remove is None: remove = [] p = dict(self.request.GET.items()).copy() + arr_keys = list(p.keys()) for r in remove: - for k in p.keys(): + for k in arr_keys: if k.startswith(r): del p[k] for k, v in new_params.items(): @@ -172,8 +176,9 @@ def get_form_params(self, new_params=None, remove=None): if remove is None: remove = [] p = dict(self.request.GET.items()).copy() + arr_keys = list(p.keys()) for r in remove: - for k in p.keys(): + for k in arr_keys: if k.startswith(r): del p[k] for k, v in new_params.items(): @@ -344,7 +349,7 @@ def get_url(menu, had_urls): app_label = model._meta.app_label app_icon = None model_dict = { - 'title': unicode(capfirst(model._meta.verbose_name_plural)), + 'title': smart_text(capfirst(model._meta.verbose_name_plural)), 'url': self.get_model_url(model, "changelist"), 'icon': self.get_model_icon(model), 'perm': self.get_model_perm(model, 'view'), @@ -358,11 +363,11 @@ def get_url(menu, had_urls): nav_menu[app_key]['menus'].append(model_dict) else: # Find app title - app_title = unicode(app_label.title()) + app_title = smart_text(app_label.title()) if app_label.lower() in self.apps_label_title: app_title = self.apps_label_title[app_label.lower()] else: - app_title = unicode(apps.get_app_config(app_label).verbose_name) + app_title = smart_text(apps.get_app_config(app_label).verbose_name) #find app icon if app_label.lower() in self.apps_icons: app_icon = self.apps_icons[app_label.lower()] @@ -385,7 +390,7 @@ def get_url(menu, had_urls): for menu in nav_menu.values(): menu['menus'].sort(key=sortkeypicker(['order', 'title'])) - nav_menu = nav_menu.values() + nav_menu = list(nav_menu.values()) nav_menu.sort(key=lambda x: x['title']) site_menu.extend(nav_menu) @@ -423,10 +428,10 @@ def filter_item(item): return item nav_menu = [filter_item(item) for item in menus if check_menu_permission(item)] - nav_menu = filter(lambda x:x, nav_menu) + nav_menu = list(filter(lambda x:x, nav_menu)) if not settings.DEBUG: - self.request.session['nav_menu'] = json.dumps(nav_menu) + self.request.session['nav_menu'] = json.dumps(nav_menu, cls=JSONEncoder, ensure_ascii=False) self.request.session.modified = True def check_selected(menu, path): @@ -495,7 +500,7 @@ def get_context(self): "opts": self.opts, "app_label": self.app_label, "model_name": self.model_name, - "verbose_name": force_unicode(self.opts.verbose_name), + "verbose_name": force_text(self.opts.verbose_name), 'model_icon': self.get_model_icon(self.model), } context = super(ModelAdminView, self).get_context() diff --git a/xadmin/views/dashboard.py b/xadmin/views/dashboard.py index 569c938d7..7aa9dafd5 100644 --- a/xadmin/views/dashboard.py +++ b/xadmin/views/dashboard.py @@ -9,7 +9,7 @@ from django.template import loader from django.http import Http404 from django.test.client import RequestFactory -from django.utils.encoding import force_unicode, smart_unicode +from django.utils.encoding import force_text, smart_text from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ @@ -39,12 +39,12 @@ def render(self, name, value, attrs=None): final_attrs = self.build_attrs(attrs, name=name) final_attrs['class'] = 'nav nav-pills nav-stacked' output = [u'' % flatatt(final_attrs)] - options = self.render_options(force_unicode(value), final_attrs['id']) + options = self.render_options(force_text(value), final_attrs['id']) if options: output.append(options) output.append(u'') output.append('' % - (final_attrs['id'], name, force_unicode(value))) + (final_attrs['id'], name, force_text(value))) return mark_safe(u'\n'.join(output)) def render_option(self, selected_choice, widget, id): @@ -305,7 +305,7 @@ def prepare_value(self, value): def valid_value(self, value): value = self.prepare_value(value) for k, v in self.choices: - if value == smart_unicode(k): + if value == smart_text(k): return True return False @@ -558,7 +558,7 @@ def get_widgets(self): widget = user_widgets.get(int(wid)) if widget: ws.append(self.get_widget(widget)) - except Exception, e: + except Exception as e: import logging logging.error(e, exc_info=True) widgets.append(ws) @@ -637,7 +637,7 @@ def get_page_id(self): @filter_hook def get_title(self): - return self.title % force_unicode(self.obj) + return self.title % force_text(self.obj) def init_request(self, object_id, *args, **kwargs): self.obj = self.get_object(unquote(object_id)) @@ -647,7 +647,7 @@ def init_request(self, object_id, *args, **kwargs): if self.obj is None: raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % - {'name': force_unicode(self.opts.verbose_name), 'key': escape(object_id)}) + {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)}) @filter_hook def get_context(self): diff --git a/xadmin/views/delete.py b/xadmin/views/delete.py index a6b75d066..77d49f5c3 100644 --- a/xadmin/views/delete.py +++ b/xadmin/views/delete.py @@ -2,7 +2,8 @@ from django.db import transaction, router from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.translation import ugettext as _ from django.contrib.admin.utils import get_deleted_objects @@ -24,7 +25,7 @@ def init_request(self, object_id, *args, **kwargs): raise PermissionDenied if self.obj is None: - raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(self.opts.verbose_name), 'key': escape(object_id)}) + raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)}) using = router.db_for_write(self.model) @@ -51,10 +52,10 @@ def post(self, request, object_id): self.delete_model() response = self.post_response() - if isinstance(response, basestring): - return HttpResponseRedirect(response) - else: - return response + cls_str = str if six.PY3 else basestring + if isinstance(response, cls_str): + response = HttpResponseRedirect(response) + return response @filter_hook def delete_model(self): @@ -68,7 +69,7 @@ def delete_model(self): def get_context(self): if self.perms_needed or self.protected: title = _("Cannot delete %(name)s") % {"name": - force_unicode(self.opts.verbose_name)} + force_text(self.opts.verbose_name)} else: title = _("Are you sure?") @@ -87,7 +88,7 @@ def get_context(self): def get_breadcrumb(self): bcs = super(DeleteAdminView, self).get_breadcrumb() bcs.append({ - 'title': force_unicode(self.obj), + 'title': force_text(self.obj), 'url': self.get_object_url(self.obj) }) item = {'title': _('Delete')} @@ -101,7 +102,7 @@ def get_breadcrumb(self): def post_response(self): self.message_user(_('The %(name)s "%(obj)s" was deleted successfully.') % - {'name': force_unicode(self.opts.verbose_name), 'obj': force_unicode(self.obj)}, 'success') + {'name': force_text(self.opts.verbose_name), 'obj': force_text(self.obj)}, 'success') if not self.has_view_permission(): return self.get_admin_url('index') diff --git a/xadmin/views/detail.py b/xadmin/views/detail.py index bc459f2d9..e9b642111 100644 --- a/xadmin/views/detail.py +++ b/xadmin/views/detail.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import copy from crispy_forms.utils import TEMPLATE_PACK @@ -9,7 +10,8 @@ from django.http import Http404 from django.template import loader from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode, smart_unicode +from django.utils import six +from django.utils.encoding import force_text, smart_text from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext as _ @@ -17,7 +19,7 @@ from xadmin.layout import FormHelper, Layout, Fieldset, Container, Column, Field, Col, TabHolder from xadmin.util import unquote, lookup_field, display_for_field, boolean_icon, label_for_field -from base import ModelAdminView, filter_hook, csrf_protect_m +from .base import ModelAdminView, filter_hook, csrf_protect_m # Text to display within change-list table cells if the value is blank. EMPTY_CHANGELIST_VALUE = _('Null') @@ -90,7 +92,7 @@ def init(self): self.allow_tags = True self.text = boolean_icon(value) else: - self.text = smart_unicode(value) + self.text = smart_text(value) else: if isinstance(f.rel, models.ManyToOneRel): self.text = getattr(self.obj, f.name) @@ -104,7 +106,7 @@ def init(self): def val(self): text = mark_safe( self.text) if self.allow_tags else conditional_escape(self.text) - if force_unicode(text) == '' or text == 'None' or text == EMPTY_CHANGELIST_VALUE: + if force_text(text) == '' or text == 'None' or text == EMPTY_CHANGELIST_VALUE: text = mark_safe( '%s' % EMPTY_CHANGELIST_VALUE) for wrap in self.wraps: @@ -113,11 +115,12 @@ def val(self): def replace_field_to_value(layout, cb): + cls_str = str if six.PY3 else basestring for i, lo in enumerate(layout.fields): if isinstance(lo, Field) or issubclass(lo.__class__, Field): layout.fields[i] = ShowField( cb, *lo.fields, attrs=lo.attrs, wrapper_class=lo.wrapper_class) - elif isinstance(lo, basestring): + elif isinstance(lo, cls_str): layout.fields[i] = ShowField(cb, lo) elif hasattr(lo, 'get_field_names'): replace_field_to_value(lo, cb) @@ -140,7 +143,7 @@ def init_request(self, object_id, *args, **kwargs): if self.obj is None: raise Http404( _('%(name)s object with primary key %(key)r does not exist.') % - {'name': force_unicode(self.opts.verbose_name), 'key': escape(object_id)}) + {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)}) self.org_obj = self.obj @filter_hook @@ -211,6 +214,8 @@ def get_form_helper(self): layout = self.get_form_layout() replace_field_to_value(layout, self.get_field_result) helper.add_layout(layout) + cls_str = str if six.PY3 else basestring + helper.filter(cls_str, max_level=20).wrap(ShowField, admin_view=self) return helper @csrf_protect_m @@ -227,7 +232,7 @@ def get(self, request, *args, **kwargs): @filter_hook def get_context(self): new_context = { - 'title': _('%s Detail') % force_unicode(self.opts.verbose_name), + 'title': _('%s Detail') % force_text(self.opts.verbose_name), 'form': self.form_obj, 'object': self.obj, @@ -245,7 +250,7 @@ def get_context(self): @filter_hook def get_breadcrumb(self): bcs = super(DetailAdminView, self).get_breadcrumb() - item = {'title': force_unicode(self.obj)} + item = {'title': force_text(self.obj)} if self.has_view_permission(): item['url'] = self.model_admin_url('detail', self.obj.pk) bcs.append(item) diff --git a/xadmin/views/edit.py b/xadmin/views/edit.py index 0a25259d6..a52ea4d26 100644 --- a/xadmin/views/edit.py +++ b/xadmin/views/edit.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import copy from crispy_forms.utils import TEMPLATE_PACK @@ -8,7 +9,8 @@ from django.forms.models import modelform_factory, modelform_defines_fields from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.text import capfirst, get_text_list from django.template import loader @@ -18,7 +20,7 @@ from xadmin.util import unquote from xadmin.views.detail import DetailAdminUtil -from base import ModelAdminView, filter_hook, csrf_protect_m +from .base import ModelAdminView, filter_hook, csrf_protect_m FORMFIELD_FOR_DBFIELD_DEFAULTS = { @@ -190,7 +192,10 @@ def get_model_form(self, **kwargs): @filter_hook def get_form_layout(self): layout = copy.deepcopy(self.form_layout) - fields = self.form_obj.fields.keys() + list(self.get_readonly_fields()) + arr = self.form_obj.fields.keys() + if six.PY3: + arr = [k for k in arr] + fields = arr + list(self.get_readonly_fields()) if layout is None: layout = Layout(Container(Col('full', @@ -287,7 +292,8 @@ def post(self, request, *args, **kwargs): self.save_models() self.save_related() response = self.post_response() - if isinstance(response, basestring): + cls_str = str if six.PY3 else basestring + if isinstance(response, cls_str): return HttpResponseRedirect(response) else: return response @@ -384,7 +390,7 @@ def get_form_datas(self): @filter_hook def get_context(self): new_context = { - 'title': _('Add %s') % force_unicode(self.opts.verbose_name), + 'title': _('Add %s') % force_text(self.opts.verbose_name), } context = super(CreateAdminView, self).get_context() context.update(new_context) @@ -393,7 +399,7 @@ def get_context(self): @filter_hook def get_breadcrumb(self): bcs = super(ModelFormAdminView, self).get_breadcrumb() - item = {'title': _('Add %s') % force_unicode(self.opts.verbose_name)} + item = {'title': _('Add %s') % force_text(self.opts.verbose_name)} if self.has_add_permission(): item['url'] = self.model_admin_url('add') bcs.append(item) @@ -417,8 +423,8 @@ def post_response(self): request = self.request msg = _( - 'The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(self.opts.verbose_name), - 'obj': "%s" % (self.model_admin_url('change', self.new_obj._get_pk_val()), force_unicode(self.new_obj))} + 'The %(name)s "%(obj)s" was added successfully.') % {'name': force_text(self.opts.verbose_name), + 'obj': "%s" % (self.model_admin_url('change', self.new_obj._get_pk_val()), force_text(self.new_obj))} if "_continue" in request.POST: self.message_user( @@ -426,7 +432,7 @@ def post_response(self): return self.model_admin_url('change', self.new_obj._get_pk_val()) if "_addanother" in request.POST: - self.message_user(msg + ' ' + (_("You may add another %s below.") % force_unicode(self.opts.verbose_name)), 'success') + self.message_user(msg + ' ' + (_("You may add another %s below.") % force_text(self.opts.verbose_name)), 'success') return request.path else: self.message_user(msg, 'success') @@ -452,7 +458,7 @@ def init_request(self, object_id, *args, **kwargs): if self.org_obj is None: raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % - {'name': force_unicode(self.opts.verbose_name), 'key': escape(object_id)}) + {'name': force_text(self.opts.verbose_name), 'key': escape(object_id)}) # comm method for both get and post self.prepare_form() @@ -468,7 +474,7 @@ def get_form_datas(self): @filter_hook def get_context(self): new_context = { - 'title': _('Change %s') % force_unicode(self.org_obj), + 'title': _('Change %s') % force_text(self.org_obj), 'object_id': str(self.org_obj.pk), } context = super(UpdateAdminView, self).get_context() @@ -479,7 +485,7 @@ def get_context(self): def get_breadcrumb(self): bcs = super(ModelFormAdminView, self).get_breadcrumb() - item = {'title': force_unicode(self.org_obj)} + item = {'title': force_text(self.org_obj)} if self.has_change_permission(): item['url'] = self.model_admin_url('change', self.org_obj.pk) bcs.append(item) @@ -514,14 +520,14 @@ def post_response(self): pk_value = obj._get_pk_val() msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': - force_unicode(verbose_name), 'obj': force_unicode(obj)} + force_text(verbose_name), 'obj': force_text(obj)} if "_continue" in request.POST: self.message_user( msg + ' ' + _("You may edit it again below."), 'success') return request.path elif "_addanother" in request.POST: self.message_user(msg + ' ' + (_("You may add another %s below.") - % force_unicode(verbose_name)), 'success') + % force_text(verbose_name)), 'success') return self.model_admin_url('add') else: self.message_user(msg, 'success') diff --git a/xadmin/views/form.py b/xadmin/views/form.py index 685c4b0af..834c7a63d 100644 --- a/xadmin/views/form.py +++ b/xadmin/views/form.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import copy from django import forms @@ -7,7 +8,8 @@ from django.forms.models import modelform_factory from django.http import Http404, HttpResponseRedirect from django.template.response import TemplateResponse -from django.utils.encoding import force_unicode +from django.utils import six +from django.utils.encoding import force_text from django.utils.html import escape from django.template import loader from django.utils.translation import ugettext as _ @@ -16,7 +18,7 @@ from xadmin.util import unquote from xadmin.views.detail import DetailAdminUtil -from base import CommAdminView, filter_hook, csrf_protect_m +from .base import CommAdminView, filter_hook, csrf_protect_m class FormAdminView(CommAdminView): form = forms.ModelForm @@ -110,7 +112,8 @@ def post(self, request, *args, **kwargs): if self.valid_forms(): self.save_forms() response = self.post_response() - if isinstance(response, basestring): + cls_str = str if six.PY3 else basestring + if isinstance(response, cls_str): return HttpResponseRedirect(response) else: return response diff --git a/xadmin/views/list.py b/xadmin/views/list.py index 3c98435a4..43128c8c3 100644 --- a/xadmin/views/list.py +++ b/xadmin/views/list.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from collections import OrderedDict from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.core.paginator import InvalidPage, Paginator @@ -5,7 +6,8 @@ from django.db import models from django.http import HttpResponseRedirect from django.template.response import SimpleTemplateResponse, TemplateResponse -from django.utils.encoding import force_unicode, smart_unicode +from django.utils import six +from django.utils.encoding import force_text, smart_text from django.utils.html import escape, conditional_escape from django.utils.safestring import mark_safe from django.utils.text import capfirst @@ -13,7 +15,7 @@ from xadmin.util import lookup_field, display_for_field, label_for_field, boolean_icon -from base import ModelAdminView, filter_hook, inclusion_tag, csrf_protect_m +from .base import ModelAdminView, filter_hook, inclusion_tag, csrf_protect_m # List settings ALL_VAR = 'all' @@ -66,7 +68,7 @@ def __init__(self, field_name, row): def label(self): text = mark_safe( self.text) if self.allow_tags else conditional_escape(self.text) - if force_unicode(text) == '': + if force_text(text) == '': text = mark_safe(' ') for wrap in self.wraps: text = mark_safe(wrap % text) @@ -282,11 +284,14 @@ def get_ordering(self): or self._get_default_ordering()) if ORDER_VAR in self.params and self.params[ORDER_VAR]: # Clear ordering and used params - ordering = [pfx + self.get_ordering_field(field_name) for n, pfx, field_name in - map( - lambda p: p.rpartition('-'), - self.params[ORDER_VAR].split('.')) - if self.get_ordering_field(field_name)] + ordering = [ + pfx + self.get_ordering_field(field_name) + for n, pfx, field_name in map( + lambda p: p.rpartition('-'), + self.params[ORDER_VAR].split('.') + ) + if self.get_ordering_field(field_name) + ] # Ensure that the primary key is systematically present in the list of # ordering fields so we can guarantee a deterministic order across all @@ -361,12 +366,12 @@ def get_context(self): """ Prepare the context for templates. """ - self.title = _('%s List') % force_unicode(self.opts.verbose_name) + self.title = _('%s List') % force_text(self.opts.verbose_name) model_fields = [(f, f.name in self.list_display, self.get_check_field_url(f)) for f in (list(self.opts.fields) + self.get_model_method_fields()) if f.name not in self.list_exclude] new_context = { - 'model_name': force_unicode(self.opts.verbose_name_plural), + 'model_name': force_text(self.opts.verbose_name_plural), 'title': self.title, 'cl': self, 'model_fields': model_fields, @@ -450,7 +455,10 @@ def result_header(self, field_name, row): if field_name in ordering_field_columns: sorted = True order_type = ordering_field_columns.get(field_name).lower() - sort_priority = ordering_field_columns.keys().index(field_name) + 1 + arr = ordering_field_columns.keys() + if six.PY3: + arr = list(arr) + sort_priority = arr.index(field_name) + 1 th_classes.append('sorted %sending' % order_type) new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type] @@ -534,7 +542,7 @@ def result_item(self, obj, field_name, row): item.allow_tags = True item.text = boolean_icon(value) else: - item.text = smart_unicode(value) + item.text = smart_text(value) else: if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(obj, f.name) diff --git a/xadmin/views/website.py b/xadmin/views/website.py index eb4bcf020..172b99d90 100644 --- a/xadmin/views/website.py +++ b/xadmin/views/website.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from django.utils.translation import ugettext as _ from django.contrib.auth import REDIRECT_FIELD_NAME from django.views.decorators.cache import never_cache @@ -5,8 +6,8 @@ from django.contrib.auth.views import logout from django.http import HttpResponse -from base import BaseAdminView, filter_hook -from dashboard import Dashboard +from .base import BaseAdminView, filter_hook +from .dashboard import Dashboard from xadmin.forms import AdminAuthenticationForm from xadmin.models import UserSettings from xadmin.layout import FormHelper diff --git a/xadmin/widgets.py b/xadmin/widgets.py index e7301b6eb..6636bfa55 100644 --- a/xadmin/widgets.py +++ b/xadmin/widgets.py @@ -1,15 +1,16 @@ """ Form Widget classes specific to the Django admin site. """ +from __future__ import absolute_import from itertools import chain from django import forms from django.forms.widgets import RadioFieldRenderer, RadioChoiceInput -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.safestring import mark_safe from django.utils.html import conditional_escape from django.utils.translation import ugettext as _ -from util import vendor +from .util import vendor class AdminDateWidget(forms.DateInput): @@ -81,7 +82,7 @@ def render(self, name=None, value=None, attrs=None, choices=()): label_for = ' for="%s_%s"' % (self.attrs['id'], self.index) else: label_for = '' - choice_label = conditional_escape(force_unicode(self.choice_label)) + choice_label = conditional_escape(force_text(self.choice_label)) if attrs.get('inline', False): return mark_safe(u'%s %s' % (label_for, self.tag(), choice_label)) else: @@ -99,7 +100,7 @@ def __getitem__(self, idx): return AdminRadioInput(self.name, self.value, self.attrs.copy(), choice, idx) def render(self): - return mark_safe(u'\n'.join([force_unicode(w) for w in self])) + return mark_safe(u'\n'.join([force_text(w) for w in self])) class AdminRadioSelect(forms.RadioSelect): @@ -114,7 +115,7 @@ def render(self, name, value, attrs=None, choices=()): final_attrs = self.build_attrs(attrs, name=name) output = [] # Normalize to strings - str_values = set([force_unicode(v) for v in value]) + str_values = set([force_text(v) for v in value]) for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): # If an ID attribute was given, add a numeric index as a suffix, # so that the checkboxes don't all have the same ID attribute. @@ -126,9 +127,9 @@ def render(self, name, value, attrs=None, choices=()): cb = forms.CheckboxInput( final_attrs, check_test=lambda value: value in str_values) - option_value = force_unicode(option_value) + option_value = force_text(option_value) rendered_cb = cb.render(name, option_value) - option_label = conditional_escape(force_unicode(option_label)) + option_label = conditional_escape(force_text(option_label)) if final_attrs.get('inline', False): output.append(u'%s %s' % (label_for, rendered_cb, option_label))