diff --git a/README.rst b/README.rst index d978a8a..413d8ae 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Postorius requires Python 2.6 or newer and mailman.client, the official Python bindings for GNU Mailman, it also requires django-social-auth. -The minimum Django version is 1.6. +The minimum Django version is 1.8. Postorius needs a running version of GNU Mailman version 3. diff --git a/setup.py b/setup.py index 480c249..f6764fd 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,10 @@ packages=find_packages('src'), package_dir={'': 'src'}, include_package_data=True, - install_requires=['django>=1.6', - 'django-browserid', - 'mailmanclient'] + install_requires=[ + 'Django>=1.8', + 'Django<1.10', + 'django-browserid', + 'mailmanclient', + ], ) diff --git a/src/postorius/context_processors.py b/src/postorius/context_processors.py index 8d5e65b..9dfff0f 100644 --- a/src/postorius/context_processors.py +++ b/src/postorius/context_processors.py @@ -48,8 +48,7 @@ 'postorius_base_template': template_to_extend, 'request': request, 'hyperkitty_url': hyperkitty_url, - # Resolve the login and logout URLs from the settings (they can be - # either URLs or view names since Django 1.6) + # Resolve the login and logout URLs from the settings 'login_url': resolve_url(settings.LOGIN_URL), 'logout_url': resolve_url(settings.LOGOUT_URL), } diff --git a/src/postorius/doc/development.rst b/src/postorius/doc/development.rst index 9afbab6..58ce334 100644 --- a/src/postorius/doc/development.rst +++ b/src/postorius/doc/development.rst @@ -43,18 +43,17 @@ # List all currently configured envs: $ tox -l - py27-django1.5 - py27-django1.6 - py27-django1.7 + py27-django18 + py27-django19 - # Test Django 1.7 on Python2.7 only: - $ tox -e py27-django1.7 + # Test Django 1.8 on Python2.7 only: + $ tox -e py27-django18 # Run only tests in ``test_address_activation``: $ tox -- postorius.tests.test_address_activation # You get the idea... - $ tox -e py27-django1.7 -- postorius.tests.test_address_activation + $ tox -e py27-django18 -- postorius.tests.test_address_activation All test modules reside in the ``postorius/src/postorius/tests`` diff --git a/src/postorius/doc/setup.rst b/src/postorius/doc/setup.rst index 0dd4ba7..38b20f8 100644 --- a/src/postorius/doc/setup.rst +++ b/src/postorius/doc/setup.rst @@ -73,12 +73,18 @@ :: $ cd postorius_standalone - $ python manage.py syncdb + $ python manage.py migrate $ cd .. This will create the ``.db file`` (if you ar using SQLite) and will setup all the -necessary db tables. You will also be prompted to create a superuser which -will act as an admin account for Postorius. +necessary db tables. + +To create a superuser which will act as an admin account for Postorius, run the +following commands:: + + $ cd postorius_standalone + $ python manage.py createsuperuser + $ cd .. Running the development server diff --git a/src/postorius/fieldset_forms.py b/src/postorius/fieldset_forms.py index 69e820c..01550b4 100644 --- a/src/postorius/fieldset_forms.py +++ b/src/postorius/fieldset_forms.py @@ -19,10 +19,7 @@ from django.forms import Form from django.utils import safestring from django.forms.forms import BoundField -try: - from django.forms.utils import ErrorList -except ImportError: - from django.forms.util import ErrorList # Django 1.6 +from django.forms.utils import ErrorList class FieldsetError(Exception): diff --git a/src/postorius/south_migrations/0001_initial.py b/src/postorius/south_migrations/0001_initial.py deleted file mode 100644 index 06e6f0f..0000000 --- a/src/postorius/south_migrations/0001_initial.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -from south.utils import datetime_utils as datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Adding model 'AddressConfirmationProfile' - db.create_table(u'postorius_addressconfirmationprofile', ( - (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('email', self.gf('django.db.models.fields.EmailField')(max_length=75)), - ('activation_key', self.gf('django.db.models.fields.CharField')(max_length=40)), - ('created', self.gf('django.db.models.fields.DateTimeField')()), - ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), - )) - db.send_create_signal(u'postorius', ['AddressConfirmationProfile']) - - - def backwards(self, orm): - # Deleting model 'AddressConfirmationProfile' - db.delete_table(u'postorius_addressconfirmationprofile') - - - models = { - u'auth.group': { - 'Meta': {'object_name': 'Group'}, - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), - 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) - }, - u'auth.permission': { - 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, - 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - }, - u'auth.user': { - 'Meta': {'object_name': 'User'}, - 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), - 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), - 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), - 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), - 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), - 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), - 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) - }, - u'contenttypes.contenttype': { - 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, - 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) - }, - u'postorius.addressconfirmationprofile': { - 'Meta': {'object_name': 'AddressConfirmationProfile'}, - 'activation_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), - 'created': ('django.db.models.fields.DateTimeField', [], {}), - 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), - u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) - } - } - - complete_apps = ['postorius'] \ No newline at end of file diff --git a/src/postorius/south_migrations/__init__.py b/src/postorius/south_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/src/postorius/south_migrations/__init__.py +++ /dev/null diff --git a/src/postorius/tests/mailman_api_tests/test_address_activation.py b/src/postorius/tests/mailman_api_tests/test_address_activation.py index 9dfd084..f4341bb 100644 --- a/src/postorius/tests/mailman_api_tests/test_address_activation.py +++ b/src/postorius/tests/mailman_api_tests/test_address_activation.py @@ -187,11 +187,5 @@ self.assertEqual( set([a.email for a in self.mm_user.addresses]), set(['ler@example.org', 'les@example.org'])) - try: - logged_in_user = response.wsgi_request.user - except AttributeError: - # Django 1.6: the wsgi request is not available, ignore this last - # assertion. - pass - else: - self.assertEqual(logged_in_user.other_emails, ['les@example.org']) + logged_in_user = response.wsgi_request.user + self.assertEqual(logged_in_user.other_emails, ['les@example.org']) diff --git a/src/postorius/tests/utils.py b/src/postorius/tests/utils.py index 08a991e..dc70f10 100644 --- a/src/postorius/tests/utils.py +++ b/src/postorius/tests/utils.py @@ -131,9 +131,6 @@ def assertRedirectsToLogin(self, url): response = self.client.get(url) - if DJANGO_VERSION >= (1, 8): - # Django < 1.8 did not quote "@" signs. - url = quote(url) self.assertRedirects(response, - '{}?next={}'.format(reverse(settings.LOGIN_URL), url)) + '{}?next={}'.format(reverse(settings.LOGIN_URL), quote(url))) diff --git a/src/postorius/views/user.py b/src/postorius/views/user.py index eef78f4..48e454f 100644 --- a/src/postorius/views/user.py +++ b/src/postorius/views/user.py @@ -241,17 +241,9 @@ if request.method == 'POST': form = AddressActivationForm(request.POST) if form.is_valid(): - # XXX Use the following when django 1.6 is dropped as a dependency - # It is more efficient because it can be done in one database operation - # - # profile, created = AddressConfirmationProfile.objects.update_or_create( - # email=form.cleaned_data['email'], user=request.user, defaults={ - # 'activation_key': uuid.uuid4().hex}) - try: - profile = AddressConfirmationProfile.objects.get(email=form.cleaned_data['email'], user=request.user) - profile.save() - except AddressConfirmationProfile.DoesNotExist: - profile = AddressConfirmationProfile.objects.create(email=form.cleaned_data['email'], user=request.user) + profile, created = AddressConfirmationProfile.objects.update_or_create( + email=form.cleaned_data['email'], user=request.user, defaults={ + 'activation_key': uuid.uuid4().hex}) try: profile.send_confirmation_link(request) messages.success(request, diff --git a/testing/test_settings.py b/testing/test_settings.py index e16132c..bc083ae 100755 --- a/testing/test_settings.py +++ b/testing/test_settings.py @@ -16,154 +16,144 @@ # You should have received a copy of the GNU General Public License along with # Postorius. If not, see . +""" +Django settings for postorius project. + +For more information on this file, see +https://docs.djangoproject.com/en/1.8/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.8/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os -import sys -"""Django settings for postorius project.""" +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) -import os.path +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '$!-7^wl#wiifjbh)5@f7ji%x!vp7s1vzbvwt26hxv$idixq0u0' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['localhost'] + + # Mailman API credentials for testing MAILMAN_REST_API_URL = 'http://localhost:9001' MAILMAN_REST_API_USER = 'restadmin' MAILMAN_REST_API_PASS = 'restpass' -PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) -DEBUG = True -TEMPLATE_DEBUG = DEBUG -ALLOWED_HOSTS = ('localhost', ) +# Application definition -ADMINS = ( - #('Admin', 'webmaster@example.com'), +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'postorius', + 'django_browserid', ) -MANAGERS = ADMINS +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.security.SecurityMiddleware', + #'postorius.middleware.PostoriusMiddleware', +) + +# Set `postorius.urls` as main url config if Postorius +# is the only app you want to serve. +ROOT_URLCONF = 'testing.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.template.context_processors.csrf', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'postorius.context_processors.postorius', + ], + }, + }, +] + +#WSGI_APPLICATION = 'postorius_standalone.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(PROJECT_PATH,'postorius.db') + 'NAME': os.path.join(BASE_DIR, 'postorius.db'), } } -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'America/Chicago' -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html +# Internationalization +# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/ + LANGUAGE_CODE = 'en-us' -SITE_ID = 1 +TIME_ZONE = 'UTC' -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. USE_I18N = True -# Absolute path to the directory that holds static files. -STATIC_ROOT = os.path.join(PROJECT_PATH, 'static/') -# Absolute path to the directory that holds media files. -MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media/') -# URL that handles the media served from STATIC_ROOT. Make sure to use a +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/{{ docs_version }}/howto/static-files/ + STATIC_URL = '/static/' -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -MEDIA_URL = '/media/' -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/static/admin/' -# Make this unique, and don't share it with anybody. -SECRET_KEY = '$!-7^wl#wiifjbh)5@f7ji%x!vp7s1vzbvwt26hxv$idixq0u0' +LOGIN_URL = 'user_login' +LOGIN_REDIRECT_URL = 'list_index' +LOGOUT_URL = 'user_logout' -# List of callables that know how to import templates from various sources. -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'postorius.context_processors.postorius', - ], - }, - }, -] -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) +def username(email): + return email.rsplit('@', 1)[0] +BROWSERID_USERNAME_ALGO = username + + +# Compatibility with Bootstrap 3 +from django.contrib.messages import constants as messages +MESSAGE_TAGS = { + messages.ERROR: 'danger' +} + AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'django_browserid.auth.BrowserIDBackend', ) -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.csrf", - "django.contrib.messages.context_processors.messages", - "postorius.context_processors.postorius", -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - #'debug_toolbar.middleware.DebugToolbarMiddleware', -) - -# Set `postorius.urls` as main url config if postorius -# is the only app you want to serve. -ROOT_URLCONF = 'testing.urls' - -TEMPLATE_DIRS = ( - # uncomment if you like to overwrite the default templates: - # os.path.join(PROJECT_PATH, "/templates/postorius"), -) - -STATICFILES_FINDERS = ( - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.messages', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - 'django.contrib.staticfiles', - 'postorius', - 'django_browserid', - # These are only used for development - # 'debug_toolbar', -) -LOGIN_URL = 'user_login' -LOGIN_REDIRECT_URL = 'list_index' - -def username(email): - return email.rsplit('@', 1)[0] -BROWSERID_USERNAME_ALGO = username - # Set VCR_RECORD_MODE to 'all' to re-record all API responses. # (Remember to use an empty mailman database!) diff --git a/testing/urls.py b/testing/urls.py index b8aefdd..9558d4c 100644 --- a/testing/urls.py +++ b/testing/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (C) 1998-2012 by the Free Software Foundation, Inc. +# Copyright (C) 1998-2016 by the Free Software Foundation, Inc. # # This file is part of Postorius. # @@ -19,7 +19,6 @@ import postorius from django.conf.urls import include, url -from django.conf import settings urlpatterns = [ url('', include('django_browserid.urls')), diff --git a/tox.ini b/tox.ini index c0cffe9..a673092 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27-django{16,17,18,19} +envlist = py27-django{18,19} [base] deps = @@ -12,8 +12,6 @@ usedevelop = True deps = {[base]deps} - django16: Django>=1.6,<1.7 - django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10a commands =