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/}