From e01c978bec576e519e9fa041c7a3d49755344473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sun, 12 Apr 2020 11:05:05 +0200 Subject: [PATCH] general: update for compatibility with django 2.2 (#41626) --- corbo/migrations/0001_initial.py | 11 ++++--- corbo/migrations/0002_auto_20150127_2221.py | 3 +- corbo/migrations/0004_auto_20160504_1744.py | 2 +- corbo/models.py | 7 +++-- corbo/monkeypatch.py | 6 ++-- corbo/settings.py | 3 +- corbo/urls.py | 8 ++--- corbo/urls_utils.py | 31 +++++++++++------- corbo/utils.py | 2 +- corbo/views.py | 35 +++++++++++---------- corbo/widgets.py | 2 +- setup.py | 4 +-- tests/settings.py | 2 ++ tests/test_announces.py | 2 +- tests/test_api.py | 12 +++---- tests/test_broadcasting.py | 2 +- tests/test_data_migrations.py | 3 ++ tests/test_manager.py | 2 +- tests/test_subscribers.py | 2 +- tox.ini | 19 +++++------ 20 files changed, 90 insertions(+), 68 deletions(-) diff --git a/corbo/migrations/0001_initial.py b/corbo/migrations/0001_initial.py index e784e4b..f2b9956 100644 --- a/corbo/migrations/0001_initial.py +++ b/corbo/migrations/0001_initial.py @@ -45,8 +45,10 @@ class Migration(migrations.Migration): name='Subscription', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('category', models.ForeignKey(verbose_name='category', to='corbo.Category')), - ('user', models.ForeignKey(verbose_name='user', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ('category', models.ForeignKey(verbose_name='category', + to='corbo.Category', on_delete=models.CASCADE)), + ('user', models.ForeignKey(verbose_name='user', blank=True, + to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), ], options={ }, @@ -57,7 +59,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('identifier', models.CharField(help_text='ex.: email, mobile phone number, jabber id', max_length=128, verbose_name='identifier', blank=True)), - ('subscription', models.ForeignKey(to='corbo.Subscription')), + ('subscription', models.ForeignKey(to='corbo.Subscription', on_delete=models.CASCADE)), ], options={ }, @@ -70,7 +72,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='announce', name='category', - field=models.ForeignKey(verbose_name='category', to='corbo.Category'), + field=models.ForeignKey(verbose_name='category', + to='corbo.Category', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/corbo/migrations/0002_auto_20150127_2221.py b/corbo/migrations/0002_auto_20150127_2221.py index cf1edda..dc83676 100644 --- a/corbo/migrations/0002_auto_20150127_2221.py +++ b/corbo/migrations/0002_auto_20150127_2221.py @@ -18,7 +18,8 @@ class Migration(migrations.Migration): ('channel', models.CharField(max_length=32, verbose_name='channel', choices=[(b'sms', 'SMS'), (b'email', 'Email')])), ('time', models.DateTimeField(auto_now_add=True, verbose_name='sent time')), ('result', models.TextField(verbose_name='result', blank=True)), - ('announce', models.ForeignKey(verbose_name='announce', to='corbo.Announce')), + ('announce', models.ForeignKey(verbose_name='announce', + to='corbo.Announce', on_delete=models.CASCADE)), ], options={ 'ordering': ('-time',), diff --git a/corbo/migrations/0004_auto_20160504_1744.py b/corbo/migrations/0004_auto_20160504_1744.py index bf4ee79..6e4144d 100644 --- a/corbo/migrations/0004_auto_20160504_1744.py +++ b/corbo/migrations/0004_auto_20160504_1744.py @@ -33,7 +33,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='subscription', name='category', - field=models.ForeignKey(verbose_name='Category', to='corbo.Category'), + field=models.ForeignKey(verbose_name='Category', to='corbo.Category', on_delete=models.CASCADE), preserve_default=True, ), migrations.AlterUniqueTogether( diff --git a/corbo/models.py b/corbo/models.py index ee850ec..efd7980 100644 --- a/corbo/models.py +++ b/corbo/models.py @@ -65,7 +65,8 @@ class Category(models.Model): class Announce(models.Model): - category = models.ForeignKey('Category', verbose_name=_('category')) + category = models.ForeignKey('Category', verbose_name=_('category'), + on_delete=models.CASCADE) title = models.CharField(_('title'), max_length=256, help_text=_('maximum 256 characters')) identifier = models.CharField(max_length=256, null=True, blank=True) @@ -150,7 +151,7 @@ class Announce(models.Model): class Broadcast(models.Model): - announce = models.ForeignKey(Announce, verbose_name=_('announce')) + announce = models.ForeignKey(Announce, verbose_name=_('announce'), on_delete=models.CASCADE) deliver_time = models.DateTimeField(_('Deliver time'), null=True) delivery_count = models.IntegerField(_('Delivery count'), default=0) @@ -187,7 +188,7 @@ class Broadcast(models.Model): class Subscription(models.Model): - category = models.ForeignKey('Category', verbose_name=_('Category')) + category = models.ForeignKey('Category', verbose_name=_('Category'), on_delete=models.CASCADE) uuid = models.CharField(_('User identifier'), max_length=128, blank=True) identifier = models.CharField(_('identifier'), max_length=128, blank=True, help_text=_('ex.: mailto, ...')) diff --git a/corbo/monkeypatch.py b/corbo/monkeypatch.py index 9162bf5..cfb04d8 100644 --- a/corbo/monkeypatch.py +++ b/corbo/monkeypatch.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from django.core.urlresolvers import reverse +from django.urls import reverse from django.forms.utils import flatatt from django.template.loader import render_to_string from django.utils.encoding import force_text @@ -24,10 +24,12 @@ from django.utils.translation import get_language import ckeditor.widgets -def ckeditor_render(self, name, value, attrs=None): +def ckeditor_render(self, name, value, attrs=None, renderer=None): if value is None: value = '' final_attrs = {'name': name} + if getattr(self, 'attrs', None): + final_attrs.update(self.attrs) if attrs: final_attrs.update(attrs) if 'filebrowserUploadUrl' not in self.config: diff --git a/corbo/settings.py b/corbo/settings.py index 0571cd4..1306575 100644 --- a/corbo/settings.py +++ b/corbo/settings.py @@ -42,12 +42,11 @@ INSTALLED_APPS = ( 'rest_framework', ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( '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.clickjacking.XFrameOptionsMiddleware', ) diff --git a/corbo/urls.py b/corbo/urls.py index f241002..16d0e3c 100644 --- a/corbo/urls.py +++ b/corbo/urls.py @@ -8,7 +8,7 @@ from django.views.decorators.cache import never_cache from django.contrib.admin.views.decorators import staff_member_required from .urls_utils import decorated_includes, manager_required -from .views import homepage, atom, unsubscribe, unsubscription_done, login, logout +from .views import homepage, atom, unsubscribe, unsubscription_done, LoginView, LogoutView from .manage_urls import urlpatterns as manage_urls from .api_urls import urlpatterns as api_urls @@ -21,14 +21,14 @@ urlpatterns = [ url(r'^atom$', atom, name='atom'), url(r'^manage/', decorated_includes(manager_required, include(manage_urls))), - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^api/', include(api_urls)), url(r'^unsubscribe/done/$', unsubscription_done, name='unsubscription_done'), url(r'^unsubscribe/(?P[\w:-]+)$', unsubscribe, name='unsubscribe'), - url(r'^logout/$', logout, name='auth_logout'), - url(r'^login/$', login, name='auth_login'), + url(r'^logout/$',LogoutView.as_view(), name='auth_logout'), + url(r'^login/$', LoginView.as_view(), name='auth_login'), url(r'^ckeditor/upload/', staff_member_required(ckeditor_views.upload), name='ckeditor_upload'), url(r'^ckeditor/browse/', never_cache(staff_member_required(ckeditor_views.browse)), diff --git a/corbo/urls_utils.py b/corbo/urls_utils.py index 32cd762..471b408 100644 --- a/corbo/urls_utils.py +++ b/corbo/urls_utils.py @@ -16,43 +16,52 @@ # Decorating URL includes, +import django + from django.contrib.auth.decorators import user_passes_test from django.core.exceptions import PermissionDenied -from django.core.urlresolvers import RegexURLPattern, RegexURLResolver -class DecoratedURLPattern(RegexURLPattern): +if django.VERSION < (2, 0, 0): + from django.urls.resolvers import RegexURLPattern as URLPattern + from django.urls.resolvers import RegexURLResolver as URLResolver +else: + from django.urls.resolvers import URLPattern, URLResolver + + +class DecoratedURLPattern(URLPattern): def resolve(self, *args, **kwargs): result = super(DecoratedURLPattern, self).resolve(*args, **kwargs) if result: result.func = self._decorate_with(result.func) return result -class DecoratedRegexURLResolver(RegexURLResolver): + +class DecoratedURLResolver(URLResolver): def resolve(self, *args, **kwargs): - result = super(DecoratedRegexURLResolver, self).resolve(*args, **kwargs) + result = super(DecoratedURLResolver, self).resolve(*args, **kwargs) if result: result.func = self._decorate_with(result.func) return result + def decorated_includes(func, includes, *args, **kwargs): urlconf_module, app_name, namespace = includes for item in urlconf_module: - if isinstance(item, RegexURLPattern): + if isinstance(item, URLResolver): + item.__class__ = DecoratedURLResolver + else: item.__class__ = DecoratedURLPattern - item._decorate_with = func - - elif isinstance(item, RegexURLResolver): - item.__class__ = DecoratedRegexURLResolver - item._decorate_with = func + item._decorate_with = func return urlconf_module, app_name, namespace + def manager_required(function=None, login_url=None): def check_manager(user): if user and user.is_staff: return True - if user and not user.is_anonymous(): + if user and not user.is_anonymous: raise PermissionDenied() # As the last resort, show the login form return False diff --git a/corbo/utils.py b/corbo/utils.py index 530ab16..51693c6 100644 --- a/corbo/utils.py +++ b/corbo/utils.py @@ -27,7 +27,7 @@ from django.conf import settings from django.template import loader from django.utils.translation import activate from django.core.files.storage import DefaultStorage -from django.core.urlresolvers import reverse +from django.urls import reverse from django.core import signing from django.utils.six.moves.urllib import parse as urlparse diff --git a/corbo/views.py b/corbo/views.py index c1e748d..120ed32 100644 --- a/corbo/views.py +++ b/corbo/views.py @@ -4,11 +4,12 @@ from django.conf import settings from django.contrib import messages from django.core import signing from django.utils import timezone -from django.core.urlresolvers import reverse +from django.urls import reverse from django.views.generic import CreateView, UpdateView, DeleteView, \ ListView, TemplateView, RedirectView, DetailView, FormView from django.contrib.syndication.views import Feed from django.shortcuts import resolve_url +from django.utils.decorators import method_decorator from django.utils.encoding import force_text from django.utils.feedgenerator import Atom1Feed as DjangoAtom1Feed from django.utils.http import quote @@ -18,6 +19,7 @@ from django.contrib.auth import views as auth_views from django.contrib import messages from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ngettext +from django.views.decorators.cache import never_cache from . import models from .forms import AnnounceForm, CategoryForm, SubscriptionsImportForm, \ @@ -30,24 +32,23 @@ except ImportError: get_idps = lambda: [] -def login(request, *args, **kwargs): - if any(get_idps()): - if 'next' not in request.GET: - return HttpResponseRedirect(resolve_url('mellon_login')) - return HttpResponseRedirect(resolve_url('mellon_login') + '?next=' + - quote(request.GET.get('next'))) - return auth_views.login(request, *args, **kwargs) +class LoginView(auth_views.LoginView): + def get(self, request, *args, **kwargs): + if any(get_idps()): + if not 'next' in request.GET: + return HttpResponseRedirect(resolve_url('mellon_login')) + return HttpResponseRedirect( + resolve_url('mellon_login') + '?next=' + quote(request.GET.get('next')) + ) + return super(LoginView, self).get(request, *args, **kwargs) -def logout(request, next_page=None): - if any(get_idps()): - return HttpResponseRedirect(resolve_url('mellon_logout')) - auth_logout(request) - if next_page is not None: - next_page = resolve_url(next_page) - else: - next_page = '/' - return HttpResponseRedirect(next_page) +class LogoutView(auth_views.LogoutView): + @method_decorator(never_cache) + def dispatch(self, request, *args, **kwargs): + if any(get_idps()): + return HttpResponseRedirect(resolve_url('mellon_logout')) + return super(LogoutView, self).dispatch(request, *args, **kwargs) class HomepageView(RedirectView): diff --git a/corbo/widgets.py b/corbo/widgets.py index dc541dd..d93d303 100644 --- a/corbo/widgets.py +++ b/corbo/widgets.py @@ -83,7 +83,7 @@ class PickerWidgetMixin(object): super(PickerWidgetMixin, self).__init__(attrs, format=self.format) - def render(self, name, value, attrs=None): + def render(self, name, value, attrs=None, renderer=None): final_attrs = self.build_attrs(attrs) rendered_widget = super(PickerWidgetMixin, self).render(name, value, final_attrs) diff --git a/setup.py b/setup.py index 70cb7f1..3b15455 100644 --- a/setup.py +++ b/setup.py @@ -109,9 +109,9 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 2', ], - install_requires=['django>1.7, <1.12', + install_requires=['django>1.7, <2.3', 'django-ckeditor<4.5.4', - 'djangorestframework>=3.3,<3.7', + 'djangorestframework>=3.3,<3.8', 'html2text', 'gadjo', 'emails', diff --git a/tests/settings.py b/tests/settings.py index 79aa7ee..d595f0b 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,2 +1,4 @@ # Add corbo hobo agent INSTALLED_APPS = ('corbo.hobo_agent', 'hobo.agent.common') + INSTALLED_APPS + +REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = ['rest_framework.authentication.BasicAuthentication'] diff --git a/tests/test_announces.py b/tests/test_announces.py index c1a83d6..839513a 100644 --- a/tests/test_announces.py +++ b/tests/test_announces.py @@ -6,7 +6,7 @@ import tempfile from django.core.files.storage import DefaultStorage from django.utils import timezone -from django.core.urlresolvers import reverse +from django.urls import reverse from django.conf import settings from django.test import override_settings diff --git a/tests/test_api.py b/tests/test_api.py index e1c080c..55aded4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,7 +4,7 @@ import urllib from uuid import uuid4 -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.http import urlencode from django.contrib.auth import get_user_model from django.utils.text import slugify @@ -50,7 +50,7 @@ def user(): def test_get_newsletters(app, categories, announces, user): - resp = app.get(reverse('newsletters'), status=403) + resp = app.get(reverse('newsletters'), status=(401, 403)) app.authorization = ('Basic', ('john.doe', 'password')) resp = app.get(reverse('newsletters')) data = resp.json @@ -65,9 +65,9 @@ def test_get_newsletters(app, categories, announces, user): def test_get_subscriptions(app, categories, announces, user): - resp = app.get(reverse('subscriptions'), status=403) + resp = app.get(reverse('subscriptions'), status=(401, 403)) uuid = str(uuid4()) - resp = app.get(reverse('subscriptions'), params={'uuid': uuid}, status=403) + resp = app.get(reverse('subscriptions'), params={'uuid': uuid}, status=(401, 403)) app.authorization = ('Basic', ('john.doe', 'password')) for identifier, name in channel_choices: @@ -105,7 +105,7 @@ def test_update_subscriptions(app, categories, announces, user): def test_delete_subscriptions(app, categories, announces, user): params = urlencode({'uuid': str(uuid4())}) subscriptions_url = reverse('subscriptions') + '?' + params - resp = app.delete(subscriptions_url, status=403) + resp = app.delete(subscriptions_url, status=(401, 403)) app.authorization = ('Basic', ('john.doe', 'password')) resp = app.delete(subscriptions_url) if resp.json['data']: @@ -119,7 +119,7 @@ def test_simple_email_subscription(app, categories, user): url = '/api/subscribe/?uuid=%s&email=john@example.net' % uuid # anonymous - resp = app.post_json(url, params=payload, status=403) + resp = app.post_json(url, params=payload, status=(401, 403)) assert resp.json['detail'] == 'Authentication credentials were not provided.' # authenticated diff --git a/tests/test_broadcasting.py b/tests/test_broadcasting.py index d7db728..8986fbf 100644 --- a/tests/test_broadcasting.py +++ b/tests/test_broadcasting.py @@ -7,7 +7,7 @@ import mock import random import requests -from django.core.urlresolvers import reverse +from django.urls import reverse from django.core import mail, signing from django.utils import timezone from django.core.files.storage import DefaultStorage diff --git a/tests/test_data_migrations.py b/tests/test_data_migrations.py index f9f5801..7e24b0f 100644 --- a/tests/test_data_migrations.py +++ b/tests/test_data_migrations.py @@ -2,6 +2,7 @@ import uuid import pytest +import django from django.db import connection from django.db.migrations.executor import MigrationExecutor @@ -9,6 +10,8 @@ pytestmark = pytest.mark.django_db def test_subscription_sms_identifier_format_migration(): + if django.VERSION >= (2, 0, 0): + pytest.skip('NotSupportedError') executor = MigrationExecutor(connection) app = 'corbo' migrate_from = [(app, '0009_auto_20170120_1533')] diff --git a/tests/test_manager.py b/tests/test_manager.py index 456049e..51e9b98 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -3,7 +3,7 @@ import mock import os import pytest -from django.core.urlresolvers import reverse +from django.urls import reverse from django.contrib.auth.models import User from django.test import override_settings diff --git a/tests/test_subscribers.py b/tests/test_subscribers.py index ee4f22b..88fa106 100644 --- a/tests/test_subscribers.py +++ b/tests/test_subscribers.py @@ -4,7 +4,7 @@ import pytest from webtest import Upload from django.utils.text import slugify -from django.core.urlresolvers import reverse +from django.urls import reverse from django.contrib.auth import get_user_model from corbo.models import Category, Subscription diff --git a/tox.ini b/tox.ini index dd66cae..940d48f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/corbo/{env:BRANCH_NAME:} -envlist = py2-coverage-django111,py3-django111 +envlist = py2-coverage-django111,py3-django111,py3-django22 [testenv] usedevelop = @@ -10,16 +10,17 @@ setenv = CORBO_SETTINGS_FILE=tests/settings.py coverage: COVERAGE=--junitxml=test_results.xml --cov-report xml --cov-report html --cov=corbo/ --cov-config .coveragerc deps = - django>=1.11,<1.12 + django111: django>=1.11,<1.12 + django22: django>=2.2,<2.3 http://git.entrouvert.org/hobo.git/snapshot/hobo-master.tar.gz pytest-cov - pytest-django>=3.1.1,<3.4.6 - pytest>=3.0.4 - django-webtest<1.9.3 - django-ckeditor<4.5.3 - djangorestframework>=3.3,<3.7 - pylint==1.4.0 - astroid==1.3.2 + pytest-django + pytest + django-webtest + git+http://git.entrouvert.org/debian/django-ckeditor.git + djangorestframework>=3.3,<3.8 + pylint + astroid mock commands = py.test {env:COVERAGE:} {posargs:tests/}