diff --git a/.coveragerc b/.coveragerc
index 75a24c2..671ccc9 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,7 +4,6 @@
src/postorius/tests/*.py
src/postorius/tests/*/*.py
src/postorius/doc/*.py
- src/postorius/south_migrations/*.py
src/postorius/migrations/*.py
[html]
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e2eb17f..2dee5a3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,15 +2,15 @@
django-1.8:
script:
- - tox -e py27-django18
+ - tox -e py27-django18-head-coverage
django-1.9:
script:
- - tox -e py27-django19
+ - tox -e py27-django19-head-coverage
django-1.10:
script:
- - tox -e py27-django110
+ - tox -e py27-django110-head-coverage
pep8:
script:
@@ -18,5 +18,5 @@
django-latest:
script:
- - tox -e py27-django-latest
+ - tox -e py27-django-latest-head
allow_failure: true
diff --git a/README.rst b/README.rst
index 85afdad..e21904a 100644
--- a/README.rst
+++ b/README.rst
@@ -35,8 +35,7 @@
============
Postorius requires Python 2.7 or newer and mailmanclient,
-the official Python bindings for GNU Mailman, it also requires
-django-browserid.
+the official Python bindings for GNU Mailman.
The minimum Django version is 1.8.
Postorius needs a running version of GNU Mailman version 3.
diff --git a/example_project/.gitignore b/example_project/.gitignore
index 214d9d9..1392c1d 100644
--- a/example_project/.gitignore
+++ b/example_project/.gitignore
@@ -1,3 +1,5 @@
postorius.db
static
venv
+settings_local.py
+logs/*.log
diff --git a/example_project/logs/.keep b/example_project/logs/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/example_project/logs/.keep
diff --git a/example_project/settings.py b/example_project/settings.py
index 99aacd1..56f2670 100644
--- a/example_project/settings.py
+++ b/example_project/settings.py
@@ -39,12 +39,14 @@
SECRET_KEY = '$!-7^wl#wiifjbh)5@f7ji%x!vp7s1vzbvwt26hxv$idixq0u0'
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = False
ADMINS = (
#('Admin', 'webmaster@example.com'),
)
+SITE_ID = 1
+
ALLOWED_HOSTS = []
# Mailman API credentials
@@ -60,12 +62,26 @@
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
+ 'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'postorius',
- 'django_browserid',
+ 'django_mailman3',
+ 'django_gravatar',
+ 'allauth',
+ 'allauth.account',
+ 'allauth.socialaccount',
+ 'allauth.socialaccount.providers.openid',
+ 'django_mailman3.lib.auth.fedora',
+ 'allauth.socialaccount.providers.github',
+ 'allauth.socialaccount.providers.gitlab',
+ 'allauth.socialaccount.providers.google',
+ #'allauth.socialaccount.providers.facebook',
+ 'allauth.socialaccount.providers.twitter',
+ 'allauth.socialaccount.providers.stackexchange',
)
+
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -83,6 +99,7 @@
# is the only app you want to serve.
ROOT_URLCONF = 'urls'
+
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
@@ -99,6 +116,7 @@
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
+ 'django_mailman3.context_processors.common',
'postorius.context_processors.postorius',
],
},
@@ -164,18 +182,16 @@
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'
-LOGIN_URL = 'user_login'
+LOGIN_URL = 'account_login'
LOGIN_REDIRECT_URL = 'list_index'
-LOGOUT_URL = 'user_logout'
+LOGOUT_URL = 'account_logout'
-# Use the email username as identifier, but truncate it because
-# the User.username field is only 30 chars long.
-def username(email):
- return email.rsplit('@', 1)[0][:30]
-BROWSERID_USERNAME_ALGO = username
-
+# From Address for emails sent to users
+DEFAULT_FROM_EMAIL = 'postorius@localhost.local'
+# From Address for emails sent to admins
+SERVER_EMAIL = 'root@localhost.local'
# Compatibility with Bootstrap 3
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
@@ -184,37 +200,93 @@
AUTHENTICATION_BACKENDS = (
- 'django_browserid.auth.BrowserIDBackend',
'django.contrib.auth.backends.ModelBackend',
+ 'allauth.account.auth_backends.AuthenticationBackend',
)
+# Django Allauth
+ACCOUNT_AUTHENTICATION_METHOD = "username_email"
+ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_EMAIL_VERIFICATION = "mandatory"
+ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
+ACCOUNT_UNIQUE_EMAIL = True
-# From Address for emails sent to users
-DEFAULT_FROM_EMAIL = 'postorius@localhost.local'
-# From Address for emails sent to admins
-SERVER_EMAIL = 'root@localhost.local'
+SOCIALACCOUNT_PROVIDERS = {
+ 'openid': {
+ 'SERVERS': [
+ dict(id='yahoo',
+ name='Yahoo',
+ openid_url='http://me.yahoo.com'),
+ ],
+ },
+ 'google': {
+ 'SCOPE': ['profile', 'email'],
+ 'AUTH_PARAMS': {'access_type': 'online'},
+ },
+ 'facebook': {
+ 'METHOD': 'oauth2',
+ 'SCOPE': ['email'],
+ 'FIELDS': [
+ 'email',
+ 'name',
+ 'first_name',
+ 'last_name',
+ 'locale',
+ 'timezone',
+ ],
+ 'VERSION': 'v2.4',
+ },
+}
+
+
# These can be set to override the defaults but are not mandatory:
# EMAIL_CONFIRMATION_TEMPLATE = 'postorius/address_confirmation_message.txt'
# EMAIL_CONFIRMATION_SUBJECT = 'Confirmation needed'
-# You can enable logging by uncommenting the following lines
-# LOGGING = {
-# 'version': 1,
-# 'disable_existing_loggers': False,
-# 'handlers': {
-# 'console': {
-# 'class': 'logging.StreamHandler'
-# },
-# },
-# 'loggers': {
-# 'django': {
-# 'handlers': ['console'],
-# 'level': 'INFO',
-# },
-# 'django_browserid': {
-# 'handlers': ['console'],
-# 'level': 'DEBUG',
-# },
-# },
-# }
+
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.StreamHandler',
+ 'formatter': 'simple',
+ },
+ 'file':{
+ 'level': 'INFO',
+ #'class': 'logging.handlers.RotatingFileHandler',
+ 'class': 'logging.handlers.WatchedFileHandler',
+ 'filename': os.path.join(BASE_DIR, 'logs', 'postorius.log'),
+ 'formatter': 'verbose',
+ },
+ },
+ 'loggers': {
+ 'django': {
+ 'handlers': ['console', 'file'],
+ 'level': 'INFO',
+ },
+ 'django.request': {
+ 'handlers': ['console', 'file'],
+ 'level': 'ERROR',
+ },
+ 'postorius': {
+ 'handlers': ['console', 'file'],
+ 'level': 'INFO',
+ },
+ },
+ 'formatters': {
+ 'simple': {
+ 'format': '%(levelname)s: %(message)s'
+ },
+ 'verbose': {
+ 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s'
+ },
+ },
+}
+
+
+try:
+ from settings_local import *
+except ImportError:
+ pass
diff --git a/example_project/test_settings.py b/example_project/test_settings.py
index 8f08d9a..d615896 100644
--- a/example_project/test_settings.py
+++ b/example_project/test_settings.py
@@ -26,3 +26,34 @@
MAILMAN_REST_API_URL = 'http://localhost:9001'
MAILMAN_REST_API_USER = 'restadmin'
MAILMAN_REST_API_PASS = 'restpass'
+
+
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.StreamHandler',
+ 'formatter': 'verbose',
+ },
+ },
+ 'loggers': {
+ 'django': {
+ 'handlers': ['console'],
+ 'level': 'INFO',
+ },
+ 'django.request': {
+ 'handlers': ['console'],
+ 'level': 'ERROR',
+ },
+ 'postorius': {
+ 'handlers': ['console'],
+ 'level': 'INFO',
+ },
+ },
+ 'formatters': {
+ 'verbose': {
+ 'format': '%(levelname)s %(asctime)s %(process)d %(name)s %(message)s'
+ },
+ },
+}
diff --git a/example_project/urls.py b/example_project/urls.py
index 57e1492..80ff17b 100644
--- a/example_project/urls.py
+++ b/example_project/urls.py
@@ -18,15 +18,18 @@
from django.conf.urls import include, url
-
from django.contrib import admin
-admin.autodiscover()
-
-from postorius.views import list as list_views
+from django.core.urlresolvers import reverse_lazy
+from django.views.generic import RedirectView
urlpatterns = [
- url(r'^admin/', include(admin.site.urls)),
- url('', include('django_browserid.urls')),
- url(r'^$', list_views.list_index),
+ url(r'^$', RedirectView.as_view(
+ url=reverse_lazy('postorius.views.list.list_index'),
+ permanent=True)),
url(r'^postorius/', include('postorius.urls')),
+ #url(r'^hyperkitty/', include('hyperkitty.urls')),
+ url(r'', include('django_mailman3.urls')),
+ url(r'^accounts/', include('allauth.urls')),
+ # Django admin
+ url(r'^admin/', include(admin.site.urls)),
]
diff --git a/setup.py b/setup.py
index 2bb5baf..832ad45 100644
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@
install_requires=[
'Django>=1.8',
'Django<1.10',
- 'django-browserid',
+ 'django-mailman3',
'mailmanclient',
],
)
diff --git a/src/postorius/__init__.py b/src/postorius/__init__.py
index 04133cf..2728a65 100644
--- a/src/postorius/__init__.py
+++ b/src/postorius/__init__.py
@@ -16,4 +16,7 @@
# You should have received a copy of the GNU General Public License along with
# Postorius. If not, see
\n'
-
-NEXT_PART = re.compile(r'--------------[ ]next[ ]part[ ]--------------\n')
-
-
-def guess_extension(ctype, ext):
- all_exts = guess_all_extensions(ctype, strict=False)
- if ext in all_exts:
- return ext
- return all_exts and all_exts[0]
-
-
-def get_charset(message, default="ascii", guess=False):
- if message.get_content_charset():
- return message.get_content_charset().decode("ascii")
- if message.get_charset():
- return message.get_charset().decode("ascii")
- charset = default
- if not guess:
- return charset
- text = message.get_payload(decode=True)
- for encoding in ["ascii", "utf-8", "iso-8859-15"]:
- try:
- text.decode(encoding)
- except UnicodeDecodeError:
- continue
- else:
- charset = encoding
- break
- return charset
-
-
-def oneline(s):
- """Inspired by mailman.utilities.string.oneline"""
- try:
- h = make_header(decode_header(s))
- ustr = h.__unicode__()
- return ''.join(ustr.splitlines())
- except (LookupError, UnicodeError, ValueError, HeaderParseError):
- return ''.join(s.splitlines())
-
-
-class Scrubber(object):
- def __init__(self, msg):
- self.msg = msg
-
- def scrub(self):
- attachments = []
- for part_num, part in enumerate(self.msg.walk()):
- ctype = part.get_content_type()
- if not isinstance(ctype, unicode):
- ctype = ctype.decode("ascii")
- if ctype == 'text/plain':
- disposition = part.get('content-disposition')
- if disposition and disposition.decode(
- "ascii", "replace").strip().startswith("attachment"):
- attachments.append(self.parse_attachment(part, part_num))
- part.set_payload('')
- elif ctype == 'text/html':
- attachments.append(self.parse_attachment(part, part_num,
- filter_html=False))
- part.set_payload('')
- elif ctype == 'message/rfc822':
- attachments.append(self.parse_attachment(part, part_num))
- part.set_payload('')
- elif part.get_payload() and not part.is_multipart():
- payload = part.get_payload(decode=True)
- ctype = part.get_content_type()
- if not isinstance(ctype, unicode):
- ctype.decode("ascii")
- if payload is None:
- continue
- attachments.append(self.parse_attachment(part, part_num))
- if self.msg.is_multipart():
- text = []
- for part in self.msg.walk():
- if not part.get_payload() or part.is_multipart():
- continue
- partctype = part.get_content_type()
- if partctype != 'text/plain' and partctype != 'text/html':
- continue
- try:
- t = part.get_payload(decode=True) or ''
- except (binascii.Error, TypeError):
- t = part.get_payload() or ''
- partcharset = get_charset(part, guess=True)
- try:
- t = t.decode(partcharset, 'replace')
- except (UnicodeError, LookupError, ValueError,
- AssertionError):
- t = t.decode('ascii', 'replace')
- if isinstance(t, basestring):
- if not t.endswith('\n'):
- t += '\n'
- text.append(t)
-
- text = u"\n".join(text)
- else:
- text = self.msg.get_payload(decode=True)
- charset = get_charset(self.msg, guess=True)
- try:
- text = text.decode(charset, "replace")
- except (UnicodeError, LookupError, ValueError, AssertionError):
- text = text.decode('ascii', 'replace')
-
- next_part_match = NEXT_PART.search(text)
- if next_part_match:
- text = text[0:next_part_match.start(0)]
-
- return (text, attachments)
-
- def parse_attachment(self, part, counter, filter_html=True):
- decodedpayload = part.get_payload(decode=True)
- ctype = part.get_content_type()
- if not isinstance(ctype, unicode):
- ctype = ctype.decode("ascii")
- charset = get_charset(part, default=None, guess=False)
- try:
- filename = oneline(part.get_filename(''))
- except (TypeError, UnicodeDecodeError):
- filename = u"attachment.bin"
- filename, fnext = os.path.splitext(filename)
- ext = fnext or guess_extension(ctype, fnext)
- if not ext:
- if ctype == 'message/rfc822':
- ext = '.txt'
- else:
- ext = '.bin'
- ext = sre.sub('', ext)
- if not filename:
- filebase = u'attachment'
- else:
- parts = pre.split(filename)
- filename = parts[-1]
- filename = dre.sub('', filename)
- filename = sre.sub('', filename)
- filebase = filename
- if ctype == 'message/rfc822':
- submsg = part.get_payload()
- decodedpayload = str(submsg)
- return (counter, filebase + ext, ctype, charset, decodedpayload)
diff --git a/src/postorius/management/commands/mmclient.py b/src/postorius/management/commands/mmclient.py
index 69b24ac..05bb297 100644
--- a/src/postorius/management/commands/mmclient.py
+++ b/src/postorius/management/commands/mmclient.py
@@ -17,7 +17,7 @@
# Postorius. If not, see