From b8ab2e4408561cf3e4aa9efb0a499b1186e389fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 11:33:09 +0100 Subject: [PATCH 1/6] Make sure that the urls.py is loaded before the tests are run. Django 1.6 doesn't guarantee this anymore. Part of a Fix for #387. --- example/blog/tests/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/blog/tests/__init__.py b/example/blog/tests/__init__.py index dee6a40..8a7bd1e 100644 --- a/example/blog/tests/__init__.py +++ b/example/blog/tests/__init__.py @@ -1,3 +1,7 @@ +# make sure that everything is setup for tests. Django 1.6 doesn't necessarily +# load the urls.py before the tests are run. +import example.urls + from test_apiviews import * from test_builtin_api_resources import * from test_permissions import * From d9530074e412ed672743fa9e32932ba45ac91d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 12:02:43 +0100 Subject: [PATCH 2/6] Django 1.6 changed the widgets its using for IntegerFields, this needed adjustment in the floppifying rule for those fields. Part of a Fix for #387. --- djadmin2/forms.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 75ec62d..ef3e33f 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -6,6 +6,7 @@ from copy import deepcopy from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm +import django import django.forms import django.forms.models import django.forms.extras.widgets @@ -182,6 +183,33 @@ _django_field_to_floppyform_widget = { } +def allow_floppify_widget_for_field(field): + ''' + We only allow to replace a widget with the floppyform counterpart if the + original, by django determined widget is still in place. We don't want to + override custom widgets that a user specified. + ''' + # There is a special case for IntegerFields (and all subclasses) that + # replaces the default TextInput with a NumberInput, if localization is + # turned off. That applies for Django 1.6 upwards. + # See the relevant source code in django: + # https://github.com/django/django/blob/1.6/django/forms/fields.py#L225 + if django.VERSION >= (1, 6): + if isinstance(field, django.forms.IntegerField) and not field.localize: + if field.widget.__class__ is django.forms.NumberInput: + return True + + # We can check if the widget was replaced by comparing the class of the + # specified widget with the default widget that is specified on the field + # class. + if field.widget.__class__ is field.__class__.widget: + return True + + # At that point we are assuming that the user replaced the original widget + # with a custom one. So we don't allow to overwrite it. + return False + + def floppify_widget(widget, field=None): ''' Get an instance of django.forms.widgets.Widget and return a new widget @@ -203,10 +231,7 @@ def floppify_widget(widget, field=None): create_widget = _django_field_to_floppyform_widget.get( field.__class__) if create_widget is not None: - # check if the default widget was replaced by a different one, in - # that case we cannot create the field specific floppyforms - # widget. - if field.widget.__class__ is field.__class__.widget: + if allow_floppify_widget_for_field(field): return create_widget(widget) create_widget = _django_to_floppyforms_widget.get(widget.__class__) if create_widget is not None: From ca86c60499f2ee862cccdbbcb1138cfc57f2dc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 12:15:52 +0100 Subject: [PATCH 3/6] Django 1.6 changed the MultiWidget.needs_multipart_form attribute into a property. We didn't plan for that in floppify code yet. We check now for properties which makes it compatible with Django 1.6 and more futureproof. Part of a Fix for #387. --- djadmin2/forms.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index ef3e33f..554560b 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -28,7 +28,13 @@ def _copy_attributes(original, new_widget, attributes): for attr in attributes: original_value = getattr(original, attr) original_value = deepcopy(original_value) - setattr(new_widget, attr, original_value) + + # Don't set the attribute if it is a property. In that case we can be + # sure that the widget class is taking care of the calculation for that + # property. + old_value_on_new_widget = getattr(new_widget.__class__, attr, None) + if not isinstance(old_value_on_new_widget, property): + setattr(new_widget, attr, original_value) def _create_widget(widget_class, copy_attributes=(), init_arguments=()): From 930375e3b23f3f77fca6a1b17b7fbad130c6c796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 13:12:21 +0100 Subject: [PATCH 4/6] Remove deprecated setup_environ call from the docs configuration script. That made the documentation building fail for Django 1.6. Part of a Fix for #387. --- docs/conf.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6dcfc9e..633df25 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,10 +29,6 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.example.settings") sys.path.insert(0, os.path.abspath('../../')) sys.path.insert(0, os.path.abspath('../')) -from example.example import settings -from django.core.management import setup_environ -setup_environ(settings) - # For intersphinx ext_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext")) sys.path.append(ext_path) From 34b98fb8ac5bcb09dc3a568ae57ab3b78106f77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 16:50:41 +0100 Subject: [PATCH 5/6] Better error messages for admin views that fail to instantiate. --- djadmin2/types.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/djadmin2/types.py b/djadmin2/types.py index 3674d82..fc15e98 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -4,6 +4,7 @@ from __future__ import division, absolute_import, unicode_literals from collections import namedtuple import logging import os +import sys from django.core.urlresolvers import reverse from django.conf.urls import patterns, url @@ -210,16 +211,25 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): def get_urls(self): pattern_list = [] - for view in self.views: - view.model_admin = self - get_kwargs = getattr(self, "get_%s_kwargs" % view.name, None) + for admin_view in self.views: + admin_view.model_admin = self + get_kwargs = getattr(self, "get_%s_kwargs" % admin_view.name, None) if not get_kwargs: - get_kwargs = view.get_view_kwargs + get_kwargs = admin_view.get_view_kwargs + try: + view_instance = admin_view.view.as_view(**get_kwargs()) + except Exception as e: + trace = sys.exc_info()[2] + new_exception = TypeError( + 'Cannot instantiate admin view "{}.{}". ' + 'The error that got raised was: {}'.format( + self.__class__.__name__, admin_view.name, e)) + raise new_exception, None, trace pattern_list.append( url( - regex=view.url, - view=view.view.as_view(**get_kwargs()), - name=self.get_prefixed_view_name(view.name) + regex=admin_view.url, + view=view_instance, + name=self.get_prefixed_view_name(admin_view.name) ) ) return patterns('', *pattern_list) From 4072d4c009c3c892abcf98d757fa47e3d6d763a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gregor=20M=C3=BCllegger?= Date: Thu, 28 Nov 2013 18:00:22 +0100 Subject: [PATCH 6/6] Fixing failing tests, that only occured on Travis. You cannot use a plane, generic View in the Admin since it always needs to be a model based one. At least unless you overwrite the get_*_kwargs method. --- djadmin2/tests/test_types.py | 4 ++-- djadmin2/tests/test_views.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/djadmin2/tests/test_types.py b/djadmin2/tests/test_types.py index 6986868..288fd97 100644 --- a/djadmin2/tests/test_types.py +++ b/djadmin2/tests/test_types.py @@ -2,8 +2,8 @@ from django.db import models from django.test import TestCase from django.views.generic import View +from .. import views from ..types import ModelAdmin2, immutable_admin_factory -from ..views import AdminView from ..core import Admin2 @@ -48,7 +48,7 @@ class ModelAdminTest(TestCase): def setUp(self): class MyModelAdmin(ModelAdmin2): - my_view = AdminView(r'^$', View) + my_view = views.AdminView(r'^$', views.ModelListView) self.model_admin = MyModelAdmin diff --git a/djadmin2/tests/test_views.py b/djadmin2/tests/test_views.py index 9773a9c..8609e71 100644 --- a/djadmin2/tests/test_views.py +++ b/djadmin2/tests/test_views.py @@ -1,19 +1,19 @@ from django.test import TestCase from django.views.generic import View -from ..views import AdminView +from .. import views class AdminViewTest(TestCase): def setUp(self): - self.admin_view = AdminView(r'^$', View, name='admin-view') + self.admin_view = views.AdminView(r'^$', views.ModelListView, name='admin-view') def test_url(self): self.assertEquals(self.admin_view.url, r'^$') def test_view(self): - self.assertEquals(self.admin_view.view, View) + self.assertEquals(self.admin_view.view, views.ModelListView) def test_name(self): self.assertEquals(self.admin_view.name, 'admin-view')