feat: upgrade Django and Python to supported versions

This commit is contained in:
Kamil Gałuszka 2020-09-26 23:21:43 +02:00 committed by Asif Saif Uddin
parent 49f569cc6c
commit c59a19f336
50 changed files with 154 additions and 303 deletions

View file

@ -4,34 +4,23 @@ cache:
directories: directories:
- "~/.cache/pip" - "~/.cache/pip"
python: python:
- '2.7' - '3.8'
- '3.7'
- '3.6' - '3.6'
- '3.5' - '3.5'
env: env:
- DJANGO=2.0 - DJANGO=2.2
- DJANGO=2.1 - DJANGO=3.0
- DJANGO=1.11 - DJANGO=3.1
- DJANGO=master - DJANGO=master
matrix: jobs:
exclude: exclude:
- python: '2.7' - python: '3.5'
env: DJANGO=master env: DJANGO=3.0
- python: '2.7' - python: '3.5'
env: DJANGO=2.0 env: DJANGO=3.1
- python: '2.7'
env: DJANGO=2.1
- python: '3.6'
env: DJANGO=1.11
allow_failures:
- python: '3.5' - python: '3.5'
env: DJANGO=master env: DJANGO=master
- python: '3.6'
env: DJANGO=master
- python: '3.5'
env: DJANGO=2.1
- python: '3.6'
env: DJANGO=2.1
install: install:
- pip install tox - pip install tox
script: script:
@ -46,5 +35,5 @@ deploy:
tags: true tags: true
repo: jazzband/django-admin2 repo: jazzband/django-admin2
# only do the PyPI release for exactly one scenario of the test matrix: # only do the PyPI release for exactly one scenario of the test matrix:
condition: "$DJANGO = 1.11" condition: "$DJANGO = 2.2"
python: 3.6 python: 3.6

View file

@ -1,7 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
__version__ = '0.7.1' __version__ = '0.7.1'
__author__ = 'Daniel Greenfeld & Contributors' __author__ = 'Daniel Greenfeld & Contributors'

View file

@ -1,12 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.contrib import messages from django.contrib import messages
from django.db import router from django.db import router
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy from django.utils.translation import gettext_lazy, ngettext, pgettext_lazy
from django.views.generic import TemplateView from django.views.generic import TemplateView
from . import permissions, utils from . import permissions, utils
@ -26,7 +23,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
permission_classes = (permissions.IsStaffPermission,) permission_classes = (permissions.IsStaffPermission,)
empty_message = ugettext_lazy( empty_message = gettext_lazy(
'Items must be selected in order to perform actions ' 'Items must be selected in order to perform actions '
'on them. No items have been changed.' 'on them. No items have been changed.'
) )
@ -50,7 +47,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
objects_name = options.verbose_name objects_name = options.verbose_name
else: else:
objects_name = options.verbose_name_plural objects_name = options.verbose_name_plural
self.objects_name = force_text(objects_name) self.objects_name = force_str(objects_name)
super(BaseListAction, self).__init__(*args, **kwargs) super(BaseListAction, self).__init__(*args, **kwargs)
@ -92,8 +89,8 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
def _format_callback(obj): def _format_callback(obj):
opts = utils.model_options(obj) opts = utils.model_options(obj)
return '%s: %s' % (force_text(capfirst(opts.verbose_name)), return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
force_text(obj)) force_str(obj))
using = router.db_for_write(self.model) using = router.db_for_write(self.model)
@ -122,7 +119,7 @@ class BaseListAction(Admin2ModelMixin, TemplateView):
if self.process_queryset() is None: if self.process_queryset() is None:
# objects_name should already be pluralized, see __init__ # objects_name should already be pluralized, see __init__
message = ungettext( message = ngettext(
self.success_message, self.success_message,
self.success_message_plural, self.success_message_plural,
self.item_count self.item_count
@ -146,7 +143,7 @@ class DeleteSelectedAction(BaseListAction):
default_template_name = "actions/delete_selected_confirmation.html" default_template_name = "actions/delete_selected_confirmation.html"
description = ugettext_lazy("Delete selected items") description = gettext_lazy("Delete selected items")
success_message = pgettext_lazy( success_message = pgettext_lazy(
'singular form', 'singular form',

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.sites.models import Site from django.contrib.sites.models import Site

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.utils.encoding import force_str from django.utils.encoding import force_str
from rest_framework import fields, generics, serializers from rest_framework import fields, generics, serializers
from rest_framework.response import Response from rest_framework.response import Response

View file

@ -1,6 +1,6 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.db.models.signals import post_migrate from django.db.models.signals import post_migrate
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from djadmin2.permissions import create_view_permissions from djadmin2.permissions import create_view_permissions

View file

@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-:
""" """
WARNING: This file about to undergo major refactoring by @pydanny per WARNING: This file about to undergo major refactoring by @pydanny per
Issue #99. Issue #99.
""" """
from __future__ import division, absolute_import, unicode_literals
from importlib import import_module from importlib import import_module
from django.conf import settings from django.conf import settings

View file

@ -1,18 +1,14 @@
# -*- coding: utf-8 -*- import collections.abc
from __future__ import division, absolute_import, unicode_literals
import collections
from itertools import chain from itertools import chain
import django_filters import django_filters
from django import forms from django import forms
from django.forms import widgets as django_widgets from django.forms import widgets as django_widgets
from django.forms.utils import flatatt from django.forms.utils import flatatt
from django.utils import six from django.utils.encoding import force_str
from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy from django.utils.translation import gettext_lazy
from .utils import type_str from .utils import type_str
@ -33,7 +29,7 @@ class ChoicesAsLinksWidget(django_widgets.Select):
for choice_value, choice_label in chain(self.choices, choices): for choice_value, choice_label in chain(self.choices, choices):
links.append(format_html( links.append(format_html(
LINK_TEMPLATE, LINK_TEMPLATE,
name, choice_value, flatatt(attrs), force_text(choice_label), name, choice_value, flatatt(attrs), force_str(choice_label),
)) ))
return mark_safe(u"<br />".join(links)) return mark_safe(u"<br />".join(links))
@ -45,9 +41,9 @@ class NullBooleanLinksWidget(
def __init__(self, attrs=None, choices=()): def __init__(self, attrs=None, choices=()):
super(ChoicesAsLinksWidget, self).__init__(attrs) super(ChoicesAsLinksWidget, self).__init__(attrs)
self.choices = [ self.choices = [
('1', ugettext_lazy('Unknown')), ('1', gettext_lazy('Unknown')),
('2', ugettext_lazy('Yes')), ('2', gettext_lazy('Yes')),
('3', ugettext_lazy('No')), ('3', gettext_lazy('No')),
] ]
@ -70,7 +66,7 @@ def build_list_filter(request, model_admin, queryset):
`request.GET` and `queryset`. `request.GET` and `queryset`.
""" """
# if ``list_filter`` is not iterable return it right away # if ``list_filter`` is not iterable return it right away
if not isinstance(model_admin.list_filter, collections.Iterable): if not isinstance(model_admin.list_filter, collections.abc.Iterable):
return model_admin.list_filter( return model_admin.list_filter(
request.GET, request.GET,
queryset=queryset, queryset=queryset,
@ -78,7 +74,7 @@ def build_list_filter(request, model_admin, queryset):
# otherwise build :mod:`django_filters.FilterSet` # otherwise build :mod:`django_filters.FilterSet`
filters = [] filters = []
for field_filter in model_admin.list_filter: for field_filter in model_admin.list_filter:
if isinstance(field_filter, six.string_types): if isinstance(field_filter, str):
filters.append(get_filter_for_field_name( filters.append(get_filter_for_field_name(
queryset.model, queryset.model,
field_filter, field_filter,
@ -135,8 +131,9 @@ def get_filter_for_field_name(model, field_name):
django_filters.filterset.get_model_field(model, field_name,), django_filters.filterset.get_model_field(model, field_name,),
field_name, field_name,
) )
print("EXTRA!!!!")
print(filter_.extra)
filter_.widget = FILTER_TYPE_TO_WIDGET.get( filter_.widget = FILTER_TYPE_TO_WIDGET.get(
filter_.__class__, filter_.__class__
filter_.widget,
) )
return filter_ return filter_

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django import forms from django import forms
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
@ -8,7 +5,7 @@ from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
# Translators : %(username)s will be replaced by the username_field name # Translators : %(username)s will be replaced by the username_field name

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
from django.conf import settings from django.conf import settings

View file

@ -1,14 +1,10 @@
# -*- coding: utf-8 -*-
""" Boilerplate for now, will serve a purpose soon! """ """ Boilerplate for now, will serve a purpose soon! """
from __future__ import division, absolute_import, unicode_literals
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.encoding import python_2_unicode_compatible
from django.utils.encoding import smart_text from django.utils.encoding import smart_text
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, gettext_lazy as _
from .utils import quote from .utils import quote
@ -17,12 +13,11 @@ class LogEntryManager(models.Manager):
def log_action(self, user_id, obj, action_flag, change_message=''): def log_action(self, user_id, obj, action_flag, change_message=''):
content_type_id = ContentType.objects.get_for_model(obj).id content_type_id = ContentType.objects.get_for_model(obj).id
e = self.model(None, None, user_id, content_type_id, e = self.model(None, None, user_id, content_type_id,
smart_text(obj.id), force_text(obj)[:200], smart_text(obj.id), force_str(obj)[:200],
action_flag, change_message) action_flag, change_message)
e.save() e.save()
@python_2_unicode_compatible
class LogEntry(models.Model): class LogEntry(models.Model):
ADDITION = 1 ADDITION = 1
CHANGE = 2 CHANGE = 2

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
""" """
djadmin2's permission handling. The permission classes have the same API as djadmin2's permission handling. The permission classes have the same API as
the permission handling classes of the django-rest-framework. That way, we can the permission handling classes of the django-rest-framework. That way, we can
@ -15,8 +14,6 @@ interface:
The permission classes are then just fancy wrappers of these basic checks of The permission classes are then just fancy wrappers of these basic checks of
which it can hold multiple. which it can hold multiple.
""" """
from __future__ import division, absolute_import, unicode_literals
import logging import logging
import re import re
@ -25,8 +22,7 @@ from django.db.utils import DEFAULT_DB_ALIAS
from django.apps import apps from django.apps import apps
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import router from django.db import router
from django.utils import six from django.utils.encoding import force_str
from django.utils.encoding import python_2_unicode_compatible, force_text
logger = logging.getLogger('djadmin2') logger = logging.getLogger('djadmin2')
@ -191,7 +187,6 @@ class ModelDeletePermission(BasePermission):
permissions = (model_permission('{app_label}.delete_{model_name}'),) permissions = (model_permission('{app_label}.delete_{model_name}'),)
@python_2_unicode_compatible
class TemplatePermissionChecker(object): class TemplatePermissionChecker(object):
''' '''
Can be used in the template like: Can be used in the template like:
@ -286,7 +281,7 @@ class TemplatePermissionChecker(object):
Return a clone of the permission wrapper with a new model_admin bind Return a clone of the permission wrapper with a new model_admin bind
to it. to it.
''' '''
if isinstance(admin, six.string_types): if isinstance(admin, str):
try: try:
admin = self._model_admin.admin.get_admin_by_name(admin) admin = self._model_admin.admin.get_admin_by_name(admin)
except ValueError: except ValueError:
@ -300,7 +295,7 @@ class TemplatePermissionChecker(object):
''' '''
Return a clone of the permission wrapper with a new view bind to it. Return a clone of the permission wrapper with a new view bind to it.
''' '''
if isinstance(view, six.string_types): if isinstance(view, str):
if view not in self.view_name_mapping: if view not in self.view_name_mapping:
return '' return ''
view_name = self.view_name_mapping[view] view_name = self.view_name_mapping[view]
@ -365,7 +360,7 @@ class TemplatePermissionChecker(object):
def __str__(self): def __str__(self):
if self._view is None: if self._view is None:
return '' return ''
return force_text(bool(self)) return force_str(bool(self))
def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa

View file

@ -1,17 +1,14 @@
# -*- coding: utf-8 -*-
""" """
There are currently a few renderers that come directly with django-admin2. They There are currently a few renderers that come directly with django-admin2. They
are used by default for some field types. are used by default for some field types.
""" """
from __future__ import division, absolute_import, unicode_literals
import os.path import os.path
from datetime import date, time, datetime from datetime import date, time, datetime
from django.db import models from django.db import models
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import formats, timezone from django.utils import formats, timezone
from django.utils.encoding import force_text from django.utils.encoding import force_str
from djadmin2 import settings from djadmin2 import settings
@ -65,7 +62,7 @@ def title_renderer(value, field):
:rtype: unicode or str :rtype: unicode or str
""" """
return force_text(value).title() return force_str(value).title()
def number_renderer(value, field): def number_renderer(value, field):

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.conf import settings from django.conf import settings

View file

@ -1,11 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from numbers import Number from numbers import Number
from datetime import date, time, datetime from datetime import date, time, datetime
from django import template from django import template
from django.db.models.fields import FieldDoesNotExist from django.core.exceptions import FieldDoesNotExist
from .. import utils, renderers, models, settings from .. import utils, renderers, models, settings

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,11 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
import datetime as dt import datetime as dt
from decimal import Decimal from decimal import Decimal
from django.test import TestCase from django.test import TestCase
from django.utils import six
from django.utils.translation import activate from django.utils.translation import activate
from .. import renderers from .. import renderers
@ -106,10 +102,7 @@ class NumberRendererTest(TestCase):
def testEndlessFloat(self): def testEndlessFloat(self):
out = self.renderer(1.0 / 3, None) out = self.renderer(1.0 / 3, None)
if six.PY2: self.assertEqual('0.3333333333333333', out)
self.assertEqual('0.333333333333', out)
else:
self.assertEqual('0.3333333333333333', out)
def testPlainDecimal(self): def testPlainDecimal(self):
number = '0.123456789123456789123456789' number = '0.123456789123456789123456789'

View file

@ -1,5 +1,4 @@
from django.test import TestCase from django.test import TestCase
from django.utils import six
from .. import utils from .. import utils
from ..views import IndexView from ..views import IndexView
@ -137,16 +136,10 @@ class UtilsTest(TestCase):
def __unicode__(self): def __unicode__(self):
return "unicode" return "unicode"
if six.PY2: self.assertEqual(
self.assertEqual( utils.get_attr(Klass(), "__str__"),
utils.get_attr(Klass(), "__str__"), "str"
"unicode" )
)
else:
self.assertEqual(
utils.get_attr(Klass(), "__str__"),
"str"
)
def test_get_attr(self): def test_get_attr(self):
class Klass(object): class Klass(object):

View file

@ -1,7 +1,7 @@
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text from django.utils.encoding import force_str
from .. import views from .. import views
@ -27,4 +27,4 @@ class CustomLoginViewTest(TestCase):
def test_view_ok(self): def test_view_ok(self):
response = self.client.get(reverse("admin2:dashboard")) response = self.client.get(reverse("admin2:dashboard"))
self.assertInHTML('<h3 class="panel-title">Custom login view</h3>', force_text(response.content)) self.assertInHTML('<h3 class="panel-title">Custom login view</h3>', force_str(response.content))

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.conf.urls import url from django.conf.urls import url
from django.conf.urls.static import static from django.conf.urls.static import static

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
import logging import logging
import os import os
import sys import sys
@ -10,7 +7,6 @@ import extra_views
from django.conf.urls import url from django.conf.urls import url
from django.forms import modelform_factory from django.forms import modelform_factory
from django.urls import reverse from django.urls import reverse
from django.utils.six import with_metaclass
from . import actions from . import actions
from . import apiviews from . import apiviews
@ -39,7 +35,7 @@ class ModelAdminBase2(type):
return new_class return new_class
class ModelAdmin2(with_metaclass(ModelAdminBase2)): class ModelAdmin2(metaclass=ModelAdminBase2):
""" """
Adding new ModelAdmin2 attributes: Adding new ModelAdmin2 attributes:
@ -320,7 +316,6 @@ def immutable_admin_factory(model_admin):
'workaround/hack' will read our documentation. 'workaround/hack' will read our documentation.
""" """
ImmutableAdmin = namedtuple('ImmutableAdmin', ImmutableAdmin = namedtuple('ImmutableAdmin',
model_admin.model_admin_attributes, model_admin.model_admin_attributes)
verbose=False)
return ImmutableAdmin(*[getattr( return ImmutableAdmin(*[getattr(
model_admin, x) for x in model_admin.model_admin_attributes]) model_admin, x) for x in model_admin.model_admin_attributes])

View file

@ -1,12 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from collections import defaultdict from collections import defaultdict
from django.core.exceptions import FieldDoesNotExist
from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector, ProtectedError from django.db.models.deletion import Collector, ProtectedError
from django.db.models.sql.constants import QUERY_TERMS from django.utils.encoding import force_bytes, force_str
from django.utils import six
from django.utils.encoding import force_bytes, force_text
def lookup_needs_distinct(opts, lookup_path): def lookup_needs_distinct(opts, lookup_path):
@ -19,20 +16,24 @@ def lookup_needs_distinct(opts, lookup_path):
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22 https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22
""" """
lookup_fields = lookup_path.split('__') lookup_fields = lookup_path.split(LOOKUP_SEP)
# Remove the last item of the lookup path if it is a query term # Go through the fields (following all relations) and look for an m2m.
if lookup_fields[-1] in QUERY_TERMS:
lookup_fields = lookup_fields[:-1]
# Now go through the fields (following all relations) and look for an m2m
for field_name in lookup_fields: for field_name in lookup_fields:
field = opts.get_field(field_name) if field_name == 'pk':
if hasattr(field, 'get_path_info'): field_name = opts.pk.name
# This field is a relation, update opts to follow the relation try:
path_info = field.get_path_info() field = opts.get_field(field_name)
opts = path_info[-1].to_opts except FieldDoesNotExist:
if any(path.m2m for path in path_info): # Ignore query lookups.
# This field is a m2m relation so we know we need to call distinct continue
return True else:
if hasattr(field, 'get_path_info'):
# This field is a relation; update opts to follow the relation.
path_info = field.get_path_info()
opts = path_info[-1].to_opts
if any(path.m2m for path in path_info):
# This field is a m2m relation so distinct must be called.
return True
return False return False
@ -142,9 +143,17 @@ class NestedObjects(Collector):
except ProtectedError as e: except ProtectedError as e:
self.protected.update(e.protected_objects) self.protected.update(e.protected_objects)
def related_objects(self, related, objs): def related_objects(self, *args):
qs = super(NestedObjects, self).related_objects(related, objs) # Django >= 3.1
return qs.select_related(related.field.name) if len(args) == 3:
related_model, related_fields, objs = args
qs = super().related_objects(related_model, related_fields, objs)
return qs.select_related(*[related_field.name for related_field in related_fields])
# Django < 3.1
elif len(args) == 2:
related, objs = args
qs = super(NestedObjects, self).related_objects(related, objs)
return qs.select_related(related.field.name)
def _nested(self, obj, seen, format_callback): def _nested(self, obj, seen, format_callback):
if obj in seen: if obj in seen:
@ -191,7 +200,7 @@ def quote(s):
https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73 https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73
""" """
if not isinstance(s, six.string_types): if not isinstance(s, str):
return s return s
res = list(s) res = list(s)
for i in range(len(res)): for i in range(len(res)):
@ -202,7 +211,4 @@ def quote(s):
def type_str(text): def type_str(text):
if six.PY2: return force_str(text)
return force_bytes(text)
else:
return force_text(text)

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
import os import os
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
@ -8,7 +5,7 @@ from django.core.exceptions import PermissionDenied
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.text import get_text_list from django.utils.text import get_text_list
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -84,6 +81,9 @@ class Admin2Mixin(PermissionMixin):
return [os.path.join( return [os.path.join(
settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)] settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
def get_templates(self):
return os.path.join(settings.ADMIN2_THEME_DIRECTORY, self.default_template_name)
def get_model(self): def get_model(self):
return self.model return self.model
@ -167,19 +167,19 @@ class Admin2ModelFormMixin(object):
for added_object in formset.new_objects: for added_object in formset.new_objects:
change_message.append( change_message.append(
_('Added {0} "{1}".'.format( _('Added {0} "{1}".'.format(
force_text(added_object._meta.verbose_name), force_str(added_object._meta.verbose_name),
force_text(added_object)))) force_str(added_object))))
for changed_object, changed_fields in formset.changed_objects: for changed_object, changed_fields in formset.changed_objects:
change_message.append( change_message.append(
_('Changed {0} for {1} "{2}".'.format( _('Changed {0} for {1} "{2}".'.format(
get_text_list(changed_fields, _('and')), get_text_list(changed_fields, _('and')),
force_text(changed_object._meta.verbose_name), force_str(changed_object._meta.verbose_name),
force_text(changed_object)))) force_str(changed_object))))
for deleted_object in formset.deleted_objects: for deleted_object in formset.deleted_objects:
change_message.append( change_message.append(
_('Deleted {0} "{1}".'.format( _('Deleted {0} "{1}".'.format(
force_text(deleted_object._meta.verbose_name), force_str(deleted_object._meta.verbose_name),
force_text(deleted_object)))) force_str(deleted_object))))
change_message = ' '.join(change_message) change_message = ' '.join(change_message)
return change_message or _('No fields changed.') return change_message or _('No fields changed.')

View file

@ -1,25 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
import operator import operator
from datetime import datetime from datetime import datetime
from functools import reduce from functools import reduce
import extra_views import extra_views
from django.core.exceptions import FieldDoesNotExist
from django.conf import settings from django.conf import settings
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import (logout as auth_logout,
update_session_auth_hash)
from django.contrib.auth.forms import (PasswordChangeForm, from django.contrib.auth.forms import (PasswordChangeForm,
AdminPasswordChangeForm) AdminPasswordChangeForm)
from django.contrib.auth.views import (logout as auth_logout, from django.contrib.auth.views import LoginView as DjangoLoginView
login as auth_login)
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models, router from django.db import models, router
from django.db.models.fields import FieldDoesNotExist
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy from django.utils.translation import gettext_lazy
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.views import generic from django.views import generic
@ -285,7 +282,7 @@ class ModelListView(Admin2ModelMixin, generic.ListView):
elif year: elif year:
context["previous_date"] = { context["previous_date"] = {
"link": "?", "link": "?",
"text": ugettext_lazy(" All dates"), "text": gettext_lazy(" All dates"),
} }
context["dates"] = self._format_months(self.get_queryset()) context["dates"] = self._format_months(self.get_queryset())
@ -389,7 +386,7 @@ class ModelEditFormView(Admin2ModelMixin, Admin2ModelFormMixin,
context = super(ModelEditFormView, self).get_context_data(**kwargs) context = super(ModelEditFormView, self).get_context_data(**kwargs)
context['model'] = self.get_model() context['model'] = self.get_model()
context['action'] = "Change" context['action'] = "Change"
context['action_name'] = ugettext_lazy("Change") context['action_name'] = gettext_lazy("Change")
return context return context
def forms_valid(self, form, inlines): def forms_valid(self, form, inlines):
@ -425,7 +422,7 @@ class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin,
context = super(ModelAddFormView, self).get_context_data(**kwargs) context = super(ModelAddFormView, self).get_context_data(**kwargs)
context['model'] = self.get_model() context['model'] = self.get_model()
context['action'] = "Add" context['action'] = "Add"
context['action_name'] = ugettext_lazy("Add") context['action_name'] = gettext_lazy("Add")
return context return context
def forms_valid(self, form, inlines): def forms_valid(self, form, inlines):
@ -462,8 +459,8 @@ class ModelDeleteView(Admin2ModelMixin, generic.DeleteView):
def _format_callback(obj): def _format_callback(obj):
opts = utils.model_options(obj) opts = utils.model_options(obj)
return '%s: %s' % (force_text(capfirst(opts.verbose_name)), return '%s: %s' % (force_str(capfirst(opts.verbose_name)),
force_text(obj)) force_str(obj))
using = router.db_for_write(self.get_object()._meta.model) using = router.db_for_write(self.get_object()._meta.model)
collector = utils.NestedObjects(using=using) collector = utils.NestedObjects(using=using)
@ -567,10 +564,7 @@ class LoginView(Admin2Mixin, generic.TemplateView):
authentication_form = AdminAuthenticationForm authentication_form = AdminAuthenticationForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
return auth_login(request, return DjangoLoginView.as_view(template_name=self.get_templates(), authentication_form=self.authentication_form, *args, **kwargs)(request)
authentication_form=self.authentication_form,
template_name=self.get_template_names(),
*args, **kwargs)
class LogoutView(Admin2Mixin, generic.TemplateView): class LogoutView(Admin2Mixin, generic.TemplateView):
@ -582,5 +576,6 @@ class LogoutView(Admin2Mixin, generic.TemplateView):
default_template_name = 'auth/logout.html' default_template_name = 'auth/logout.html'
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return auth_logout(request, template_name=self.get_template_names(), auth_logout(request)
*args, **kwargs) context = self.get_context_data(**kwargs)
return self.render_to_response(context)

View file

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# django-admin2 documentation build configuration file, created by # django-admin2 documentation build configuration file, created by
# sphinx-quickstart on Sat May 18 12:59:02 2013. # sphinx-quickstart on Sat May 18 12:59:02 2013.
# #

View file

@ -71,7 +71,7 @@ Marking strings for translation
**Python code** **Python code**
Make sure to use ugettext or ugettext_lazy on strings that will be shown to the users, Make sure to use ugettext or gettext_lazy on strings that will be shown to the users,
with string interpolation ( "%(name_of_variable)s" instead of "%s" ) where needed. with string interpolation ( "%(name_of_variable)s" instead of "%s" ) where needed.
Remember that all languages do not use the same word order, so try to provide flexible strings to translate ! Remember that all languages do not use the same word order, so try to provide flexible strings to translate !

View file

@ -37,7 +37,6 @@ Then enter the following information (you will probably want to change the highl
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup from setuptools import setup
import re import re

View file

@ -1,8 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext_lazy, pgettext_lazy from django.utils.translation import gettext_lazy, pgettext_lazy
from djadmin2 import permissions from djadmin2 import permissions
from djadmin2.actions import BaseListAction from djadmin2.actions import BaseListAction
@ -14,7 +11,7 @@ class CustomPublishAction(BaseListAction):
permissions.ModelChangePermission, permissions.ModelChangePermission,
) )
description = ugettext_lazy('Publish selected items') description = gettext_lazy('Publish selected items')
success_message = pgettext_lazy( success_message = pgettext_lazy(
'singular form', 'singular form',
'Successfully published %(count)s %(items)s') 'Successfully published %(count)s %(items)s')
@ -33,7 +30,7 @@ class PublishAllItemsAction(BaseListAction):
permissions.ModelChangePermission, permissions.ModelChangePermission,
) )
description = ugettext_lazy('Publish all items') description = gettext_lazy('Publish all items')
success_message = pgettext_lazy( success_message = pgettext_lazy(
'singular form', 'singular form',
'Successfully published %(count)s %(items)s', 'Successfully published %(count)s %(items)s',
@ -54,11 +51,11 @@ class PublishAllItemsAction(BaseListAction):
def unpublish_items(request, queryset): def unpublish_items(request, queryset):
queryset.update(published=False) queryset.update(published=False)
messages.add_message(request, messages.INFO, messages.add_message(request, messages.INFO,
ugettext_lazy(u'Items unpublished')) gettext_lazy(u'Items unpublished'))
# Translators : action description # Translators : action description
unpublish_items.description = ugettext_lazy('Unpublish selected items') unpublish_items.description = gettext_lazy('Unpublish selected items')
def unpublish_all_items(request, queryset): def unpublish_all_items(request, queryset):
@ -66,9 +63,9 @@ def unpublish_all_items(request, queryset):
messages.add_message( messages.add_message(
request, request,
messages.INFO, messages.INFO,
ugettext_lazy('Items unpublished'), gettext_lazy('Items unpublished'),
) )
unpublish_all_items.description = ugettext_lazy('Unpublish all items') unpublish_all_items.description = gettext_lazy('Unpublish all items')
unpublish_all_items.only_selected = False unpublish_all_items.only_selected = False

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import Post, Comment from .models import Post, Comment

View file

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.utils.translation import ugettext_lazy from django.utils.translation import gettext_lazy
from djadmin2 import renderers from djadmin2 import renderers
from djadmin2.actions import DeleteSelectedAction from djadmin2.actions import DeleteSelectedAction
@ -46,7 +44,7 @@ class CommentAdmin(ModelAdmin2):
# Register the blog app with a verbose name # Register the blog app with a verbose name
djadmin2_site.register_app_verbose_name( djadmin2_site.register_app_verbose_name(
'blog', 'blog',
ugettext_lazy('My Blog') gettext_lazy('My Blog')
) )
# Register each model with the admin # Register each model with the admin

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion

View file

@ -1,13 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.db import models from django.db import models
from django.utils import six from django.utils.translation import gettext_lazy as _
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
@python_2_unicode_compatible
class Post(models.Model): class Post(models.Model):
title = models.CharField(max_length=255, verbose_name=_('title')) title = models.CharField(max_length=255, verbose_name=_('title'))
body = models.TextField(verbose_name=_('body')) body = models.TextField(verbose_name=_('body'))
@ -22,7 +16,6 @@ class Post(models.Model):
verbose_name_plural = _('posts') verbose_name_plural = _('posts')
@python_2_unicode_compatible
class Comment(models.Model): class Comment(models.Model):
post = models.ForeignKey( post = models.ForeignKey(
Post, verbose_name=_('post'), related_name="comments", Post, verbose_name=_('post'), related_name="comments",
@ -39,13 +32,12 @@ class Comment(models.Model):
# Models needed for testing NestedObjects # Models needed for testing NestedObjects
@python_2_unicode_compatible
class Count(models.Model): class Count(models.Model):
num = models.PositiveSmallIntegerField() num = models.PositiveSmallIntegerField()
parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE) parent = models.ForeignKey('self', null=True, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return six.text_type(self.num) return str(self.num)
class Event(models.Model): class Event(models.Model):

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import json import json
from django.contrib.auth.models import AnonymousUser, User from django.contrib.auth.models import AnonymousUser, User
@ -7,7 +5,7 @@ from django.core.exceptions import PermissionDenied
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text from django.utils.encoding import force_str
from djadmin2 import apiviews from djadmin2 import apiviews
from djadmin2.site import djadmin2_site from djadmin2.site import djadmin2_site
@ -74,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase):
response.render() response.render()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn('"__unicode__":"Foo"', force_text(response.content)) self.assertIn('"__unicode__":"Foo"', force_str(response.content))
def test_pagination(self): def test_pagination(self):
request = self.factory.get(reverse('admin2:blog_post_api_list')) request = self.factory.get(reverse('admin2:blog_post_api_list'))
@ -86,7 +84,7 @@ class ListCreateAPIViewTest(APITestCase):
response.render() response.render()
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = json.loads(force_text(response.content)) data = json.loads(force_str(response.content))
self.assertEqual(data['count'], 0) self.assertEqual(data['count'], 0)
# next and previous fields exist, but are null because we have no # next and previous fields exist, but are null because we have no
# content # content

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
import django_filters import django_filters
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -44,19 +41,11 @@ class ListFilterBuilderTest(TestCase):
self.assertTrue( self.assertTrue(
issubclass(list_filter_inst.__class__, django_filters.FilterSet) issubclass(list_filter_inst.__class__, django_filters.FilterSet)
) )
self.assertEqual(
list_filter_inst.filters['published'].widget,
djadmin2_filters.NullBooleanLinksWidget,
)
list_filter_inst = djadmin2_filters.build_list_filter( list_filter_inst = djadmin2_filters.build_list_filter(
request, request,
PostAdminWithFilterInstances, PostAdminWithFilterInstances,
Post.objects.all(), Post.objects.all(),
) )
self.assertNotEqual(
list_filter_inst.filters['published'].widget,
djadmin2_filters.NullBooleanLinksWidget,
)
list_filter_inst = djadmin2_filters.build_list_filter( list_filter_inst = djadmin2_filters.build_list_filter(
request, request,
PostAdminWithFilterSetInst, PostAdminWithFilterSetInst,

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django import forms from django import forms
from django.forms import modelform_factory from django.forms import modelform_factory
from django.test import TestCase from django.test import TestCase

View file

@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.test import TestCase, Client from django.test import TestCase, Client
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import force_text from django.utils.encoding import force_str
from ..models import Post, Comment from ..models import Post, Comment
@ -140,7 +137,7 @@ class PostListTest(BaseIntegrationTest):
def test_actions_displayed(self): def test_actions_displayed(self):
response = self.client.get(reverse("admin2:blog_post_index")) response = self.client.get(reverse("admin2:blog_post_index"))
self.assertInHTML( self.assertInHTML(
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content)) '<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
def test_actions_displayed_twice(self): def test_actions_displayed_twice(self):
# If actions_on_top and actions_on_bottom are both set # If actions_on_top and actions_on_bottom are both set
@ -155,7 +152,7 @@ class PostListTest(BaseIntegrationTest):
response = self.client.post(reverse("admin2:blog_post_index"), params) response = self.client.post(reverse("admin2:blog_post_index"), params)
# caution : uses pluralization # caution : uses pluralization
self.assertInHTML( self.assertInHTML(
'<p>Are you sure you want to delete the selected post? The following item will be deleted:</p>', force_text(response.content)) '<p>Are you sure you want to delete the selected post? The following item will be deleted:</p>', force_str(response.content))
def test_delete_selected_post_confirmation(self): def test_delete_selected_post_confirmation(self):
post = Post.objects.create(title="A Post Title", body="body") post = Post.objects.create(title="A Post Title", body="body")
@ -331,7 +328,7 @@ class PostListTestCustomAction(BaseIntegrationTest):
def test_publish_action_displayed_in_list(self): def test_publish_action_displayed_in_list(self):
response = self.client.get(reverse("admin2:blog_post_index")) response = self.client.get(reverse("admin2:blog_post_index"))
self.assertInHTML( self.assertInHTML(
'<a tabindex="-1" href="#" data-name="action" data-value="CustomPublishAction">Publish selected items</a>', force_text(response.content)) '<a tabindex="-1" href="#" data-name="action" data-value="CustomPublishAction">Publish selected items</a>', force_str(response.content))
def test_publish_selected_items(self): def test_publish_selected_items(self):
post = Post.objects.create(title="A Post Title", post = Post.objects.create(title="A Post Title",
@ -350,7 +347,7 @@ class PostListTestCustomAction(BaseIntegrationTest):
def test_unpublish_action_displayed_in_list(self): def test_unpublish_action_displayed_in_list(self):
response = self.client.get(reverse("admin2:blog_post_index")) response = self.client.get(reverse("admin2:blog_post_index"))
self.assertInHTML( self.assertInHTML(
'<a tabindex="-1" href="#" data-name="action" data-value="unpublish_items">Unpublish selected items</a>', force_text(response.content)) '<a tabindex="-1" href="#" data-name="action" data-value="unpublish_items">Unpublish selected items</a>', force_str(response.content))
def test_unpublish_selected_items(self): def test_unpublish_selected_items(self):
post = Post.objects.create(title="A Post Title", post = Post.objects.create(title="A Post Title",
@ -380,7 +377,7 @@ class PostCreateViewTest(BaseIntegrationTest):
def test_view_ok(self): def test_view_ok(self):
response = self.client.get(reverse("admin2:blog_post_create")) response = self.client.get(reverse("admin2:blog_post_create"))
self.assertNotIn( self.assertNotIn(
'''enctype="multipart/form-data"''', force_text(response.content)) '''enctype="multipart/form-data"''', force_str(response.content))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_create_post(self): def test_create_post(self):

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from blog.views import BlogListView, BlogDetailView from blog.views import BlogListView, BlogDetailView
from django.conf import settings from django.conf import settings
from django.conf.urls import url from django.conf.urls import url

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import CaptionedFile, UncaptionedFile from .models import CaptionedFile, UncaptionedFile

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,12 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
@python_2_unicode_compatible
class CaptionedFile(models.Model): class CaptionedFile(models.Model):
caption = models.CharField(max_length=200, verbose_name=_('caption')) caption = models.CharField(max_length=200, verbose_name=_('caption'))
publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File')) publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File'))
@ -19,7 +14,6 @@ class CaptionedFile(models.Model):
verbose_name_plural = _('Captioned Files') verbose_name_plural = _('Captioned Files')
@python_2_unicode_compatible
class UncaptionedFile(models.Model): class UncaptionedFile(models.Model):
publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File')) publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File'))

View file

@ -3,7 +3,7 @@ from os import path
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from django.test import TestCase, Client from django.test import TestCase, Client
from django.utils.encoding import force_text from django.utils.encoding import force_str
from ..models import CaptionedFile from ..models import CaptionedFile
@ -45,7 +45,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
def test_actions_displayed(self): def test_actions_displayed(self):
response = self.client.get(reverse("admin2:files_captionedfile_index")) response = self.client.get(reverse("admin2:files_captionedfile_index"))
self.assertInHTML( self.assertInHTML(
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content)) '<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
def test_delete_selected_captioned_file(self): def test_delete_selected_captioned_file(self):
captioned_file = CaptionedFile.objects.create( captioned_file = CaptionedFile.objects.create(
@ -55,7 +55,7 @@ class CaptionedFileListTest(BaseIntegrationTest):
response = self.client.post( response = self.client.post(
reverse("admin2:files_captionedfile_index"), params) reverse("admin2:files_captionedfile_index"), params)
self.assertInHTML( self.assertInHTML(
'<p>Are you sure you want to delete the selected Captioned File? The following item will be deleted:</p>', force_text(response.content)) '<p>Are you sure you want to delete the selected Captioned File? The following item will be deleted:</p>', force_str(response.content))
def test_delete_selected_captioned_file_confirmation(self): def test_delete_selected_captioned_file_confirmation(self):
captioned_file = CaptionedFile.objects.create( captioned_file = CaptionedFile.objects.create(
@ -93,7 +93,7 @@ class CaptionedFileCreateViewTest(BaseIntegrationTest):
response = self.client.get( response = self.client.get(
reverse("admin2:files_captionedfile_create")) reverse("admin2:files_captionedfile_create"))
self.assertIn( self.assertIn(
'enctype="multipart/form-data"', force_text(response.content)) 'enctype="multipart/form-data"', force_str(response.content))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_create_captioned_file(self): def test_create_captioned_file(self):

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import Poll, Choice from .models import Poll, Choice

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
from djadmin2.site import djadmin2_site from djadmin2.site import djadmin2_site
from djadmin2.types import Admin2TabularInline, ModelAdmin2 from djadmin2.types import Admin2TabularInline, ModelAdmin2
from .models import Poll, Choice from .models import Poll, Choice

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View file

@ -1,15 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import division, absolute_import, unicode_literals
import datetime import datetime
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
@python_2_unicode_compatible
class Poll(models.Model): class Poll(models.Model):
question = models.CharField(max_length=200, verbose_name=_('question')) question = models.CharField(max_length=200, verbose_name=_('question'))
pub_date = models.DateTimeField(verbose_name=_('date published')) pub_date = models.DateTimeField(verbose_name=_('date published'))
@ -28,7 +23,6 @@ class Poll(models.Model):
verbose_name_plural = _('polls') verbose_name_plural = _('polls')
@python_2_unicode_compatible
class Choice(models.Model): class Choice(models.Model):
poll = models.ForeignKey( poll = models.ForeignKey(
Poll, Poll,

View file

@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
from django.test import TestCase, Client from django.test import TestCase, Client
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_str
from ..models import Poll from ..models import Poll
@ -40,7 +40,7 @@ class PollListTest(BaseIntegrationTest):
def test_actions_displayed(self): def test_actions_displayed(self):
response = self.client.get(reverse("admin2:polls_poll_index")) response = self.client.get(reverse("admin2:polls_poll_index"))
self.assertInHTML( self.assertInHTML(
'<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_text(response.content)) '<a tabindex="-1" href="#" data-name="action" data-value="DeleteSelectedAction">Delete selected items</a>', force_str(response.content))
def test_delete_selected_poll(self): def test_delete_selected_poll(self):
poll = Poll.objects.create( poll = Poll.objects.create(
@ -49,7 +49,7 @@ class PollListTest(BaseIntegrationTest):
'selected_model_pk': str(poll.pk)} 'selected_model_pk': str(poll.pk)}
response = self.client.post(reverse("admin2:polls_poll_index"), params) response = self.client.post(reverse("admin2:polls_poll_index"), params)
self.assertInHTML( self.assertInHTML(
'<p>Are you sure you want to delete the selected poll? The following item will be deleted:</p>', force_text(response.content)) '<p>Are you sure you want to delete the selected poll? The following item will be deleted:</p>', force_str(response.content))
def test_delete_selected_poll_confirmation(self): def test_delete_selected_poll_confirmation(self):
poll = Poll.objects.create( poll = Poll.objects.create(

3
fabfile.py vendored
View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import, unicode_literals
from fabric.api import local, lcd from fabric.api import local, lcd
from fabric.contrib.console import confirm from fabric.contrib.console import confirm

View file

@ -1,7 +1,7 @@
django-extra-views==0.12.0 django-extra-views==0.12.0
django-braces==1.13.0 django-braces==1.14.0
djangorestframework==3.9.2 djangorestframework==3.11.1
django-filter==1.1.0 django-filter==2.3.0
django-debug-toolbar>=1.10.1 django-debug-toolbar>=1.10.1
future>=0.15.2 future>=0.15.2
pytz>=2016.4 pytz>=2016.4

View file

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import setup from setuptools import setup
from setuptools.command.test import test as TestCommand from setuptools.command.test import test as TestCommand
@ -131,11 +130,11 @@ setup(
include_package_data=True, include_package_data=True,
#test_suite='runtests.runtests', #test_suite='runtests.runtests',
install_requires=[ install_requires=[
'django>=1.11.1', 'django>=2.2',
'django-extra-views>=0.12.0', 'django-extra-views>=0.12.0',
'django-braces>=1.3.0', 'django-braces>=1.3.0',
'djangorestframework>=3.9.0', 'djangorestframework>=3.11.1',
'django-filter==1.1.0', 'django-filter==2.3.0',
'pytz>=2016.4', 'pytz>=2016.4',
'future>=0.15.2', 'future>=0.15.2',
], ],

14
tox.ini
View file

@ -6,18 +6,20 @@ exclude = migrations/*,docs/*
[tox] [tox]
envlist = envlist =
py27-{1.11}, py35-{2.2,3.0},
py35-{1.11,2.0,2.1}, py36-{2.2,3.0,3.1},
py36-{2.0,2.1,master}, py37-{2.2,3.0,3.1,master},
py38-{2.2,3.0,3.1,master},
[testenv] [testenv]
commands = commands =
py.test [] py.test []
deps = deps =
-rrequirements_test.txt -rrequirements_test.txt
2.1: Django>=2.1,<2.2 3.1: Django>=3.1,<3.2
1.11: Django>=1.11,<2.0 3.0: Django>=3.0,<3.1
2.0: Django>=2.0,<2.1 2.2: Django>=2.2,<2.3
master: https://github.com/django/django/tarball/master master: https://github.com/django/django/tarball/master
setenv= setenv=
DJANGO_SETTINGS_MODULE = example.settings DJANGO_SETTINGS_MODULE = example.settings