diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index 081553e20..334d6b1c8 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -396,6 +396,8 @@ class GroupEditTests(TestCase):
bof_state = GroupStateName.objects.get(slug="bof")
+ area = Group.objects.filter(type="area").first()
+
# normal get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@@ -412,21 +414,30 @@ class GroupEditTests(TestCase):
# acronym contains non-alphanumeric
r = self.client.post(url, dict(acronym="test...", name="Testing WG", state=bof_state.pk))
self.assertEqual(r.status_code, 200)
+ self.assertTrue(len(q('form .has-error')) > 0)
# acronym contains hyphen
r = self.client.post(url, dict(acronym="test-wg", name="Testing WG", state=bof_state.pk))
self.assertEqual(r.status_code, 200)
+ self.assertTrue(len(q('form .has-error')) > 0)
# acronym too short
r = self.client.post(url, dict(acronym="t", name="Testing WG", state=bof_state.pk))
self.assertEqual(r.status_code, 200)
+ self.assertTrue(len(q('form .has-error')) > 0)
# acronym doesn't start with an alpha character
r = self.client.post(url, dict(acronym="1startwithalpha", name="Testing WG", state=bof_state.pk))
self.assertEqual(r.status_code, 200)
+ self.assertTrue(len(q('form .has-error')) > 0)
- # creation
+ # no parent group given
r = self.client.post(url, dict(acronym="testwg", name="Testing WG", state=bof_state.pk))
+ self.assertEqual(r.status_code, 200)
+ self.assertTrue(len(q('form .has-error')) > 0)
+
+ # Ok creation
+ r = self.client.post(url, dict(acronym="testwg", name="Testing WG", state=bof_state.pk, parent=area.pk))
self.assertEqual(r.status_code, 302)
self.assertEqual(len(Group.objects.filter(type="wg")), num_wgs + 1)
group = Group.objects.get(acronym="testwg")
diff --git a/ietf/ietfauth/forms.py b/ietf/ietfauth/forms.py
index 3f0959aa5..179c0fe65 100644
--- a/ietf/ietfauth/forms.py
+++ b/ietf/ietfauth/forms.py
@@ -12,6 +12,7 @@ from django.core.urlresolvers import reverse as urlreverse
import debug # pyflakes:ignore
from ietf.person.models import Person, Email
+from ietf.mailinglists.models import Whitelisted
class RegistrationForm(forms.Form):
@@ -118,3 +119,9 @@ class ResetPasswordForm(forms.Form):
class TestEmailForm(forms.Form):
email = forms.EmailField(required=False)
+class WhitelistForm(ModelForm):
+ class Meta:
+ model = Whitelisted
+ exclude = ['by', 'time' ]
+
+
diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py
index 3d7722b0c..d38972e16 100644
--- a/ietf/ietfauth/tests.py
+++ b/ietf/ietfauth/tests.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
-import os, shutil
+from __future__ import unicode_literals
+
+import os, shutil, time
from urlparse import urlsplit
from pyquery import PyQuery
from unittest import skipIf
@@ -15,6 +17,8 @@ from ietf.utils.mail import outbox, empty_outbox
from ietf.person.models import Person, Email
from ietf.group.models import Group, Role, RoleName
from ietf.ietfauth.htpasswd import update_htpasswd_file
+from ietf.mailinglists.models import Subscribed
+
import ietf.ietfauth.views
if os.path.exists(settings.HTPASSWD_COMMAND):
@@ -94,7 +98,7 @@ class IetfAuthTests(TestCase):
return False
- def test_create_account(self):
+ def test_create_account_failure(self):
make_test_data()
url = urlreverse(ietf.ietfauth.views.create_account)
@@ -103,12 +107,21 @@ class IetfAuthTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
- # register email
+ # register email and verify failure
email = 'new-account@example.com'
empty_outbox()
r = self.client.post(url, { 'email': email })
self.assertEqual(r.status_code, 200)
- self.assertTrue("Account created" in unicontent(r))
+ self.assertIn("Account creation failed", unicontent(r))
+
+ def register_and_verify(self, email):
+ url = urlreverse(ietf.ietfauth.views.create_account)
+
+ # register email
+ empty_outbox()
+ r = self.client.post(url, { 'email': email })
+ self.assertEqual(r.status_code, 200)
+ self.assertIn("Account created", unicontent(r))
self.assertEqual(len(outbox), 1)
# go to confirm page
@@ -130,6 +143,41 @@ class IetfAuthTests(TestCase):
self.assertTrue(self.username_in_htpasswd_file(email))
+ def test_create_whitelisted_account(self):
+ email = "new-account@example.com"
+
+ # add whitelist entry
+ r = self.client.post(urlreverse(django.contrib.auth.views.login), {"username":"secretary", "password":"secretary+password"})
+ self.assertEqual(r.status_code, 302)
+ self.assertEqual(urlsplit(r["Location"])[2], urlreverse(ietf.ietfauth.views.profile))
+
+ r = self.client.get(urlreverse(ietf.ietfauth.views.add_account_whitelist))
+ self.assertEqual(r.status_code, 200)
+ self.assertIn("Add a whitelist entry", unicontent(r))
+
+ r = self.client.post(urlreverse(ietf.ietfauth.views.add_account_whitelist), {"email": email})
+ self.assertEqual(r.status_code, 200)
+ self.assertIn("Whitelist entry creation successful", unicontent(r))
+
+ # log out
+ r = self.client.get(urlreverse(django.contrib.auth.views.logout))
+ self.assertEqual(r.status_code, 200)
+
+ # register and verify whitelisted email
+ self.register_and_verify(email)
+
+
+ def test_create_subscribed_account(self):
+ # verify creation with email in subscribed list
+ saved_delay = settings.LIST_ACCOUNT_DELAY
+ settings.LIST_ACCOUNT_DELAY = 1
+ email = "subscribed@example.com"
+ s = Subscribed(email=email)
+ s.save()
+ time.sleep(1.1)
+ self.register_and_verify(email)
+ settings.LIST_ACCOUNT_DELAY = saved_delay
+
def test_profile(self):
make_test_data()
diff --git a/ietf/ietfauth/urls.py b/ietf/ietfauth/urls.py
index 4d68db944..e7c4f0ed0 100644
--- a/ietf/ietfauth/urls.py
+++ b/ietf/ietfauth/urls.py
@@ -3,6 +3,8 @@
from django.conf.urls import patterns, url
from django.contrib.auth.views import login, logout
+from ietf.ietfauth.views import add_account_whitelist
+
urlpatterns = patterns('ietf.ietfauth.views',
url(r'^$', 'index'),
# url(r'^login/$', 'ietf_login'),
@@ -18,4 +20,5 @@ urlpatterns = patterns('ietf.ietfauth.views',
url(r'^reset/$', 'password_reset'),
url(r'^reset/confirm/(?P[^/]+)/$', 'confirm_password_reset'),
url(r'^confirmnewemail/(?P[^/]+)/$', 'confirm_new_email'),
+ (r'whitelist/add/?$', add_account_whitelist),
)
diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py
index 7c7649e66..f3707b9bb 100644
--- a/ietf/ietfauth/views.py
+++ b/ietf/ietfauth/views.py
@@ -32,6 +32,8 @@
# Copyright The IETF Trust 2007, All Rights Reserved
+from datetime import datetime as DateTime, timedelta as TimeDelta
+
from django.conf import settings
from django.http import Http404 #, HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect, get_object_or_404
@@ -42,10 +44,14 @@ import django.core.signing
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
+import debug # pyflakes:ignore
+
from ietf.group.models import Role
-from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm
+from ietf.ietfauth.forms import RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, WhitelistForm
from ietf.ietfauth.forms import get_person_form, RoleEmailForm, NewEmailForm
from ietf.ietfauth.htpasswd import update_htpasswd_file
+from ietf.ietfauth.utils import role_required
+from ietf.mailinglists.models import Subscribed, Whitelisted
from ietf.person.models import Person, Email, Alias
from ietf.utils.mail import send_mail
@@ -85,20 +91,25 @@ def create_account(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
- to_email = form.cleaned_data['email']
+ to_email = form.cleaned_data['email'] # This will be lowercase if form.is_valid()
+ existing = Subscribed.objects.filter(email=to_email).first()
+ ok_to_create = ( Whitelisted.objects.filter(email=to_email).exists()
+ or existing and (existing.time + TimeDelta(seconds=settings.LIST_ACCOUNT_DELAY)) < DateTime.now() )
+ if ok_to_create:
+ auth = django.core.signing.dumps(to_email, salt="create_account")
- auth = django.core.signing.dumps(to_email, salt="create_account")
+ domain = Site.objects.get_current().domain
+ subject = 'Confirm registration at %s' % domain
+ from_email = settings.DEFAULT_FROM_EMAIL
- domain = Site.objects.get_current().domain
- subject = 'Confirm registration at %s' % domain
- from_email = settings.DEFAULT_FROM_EMAIL
-
- send_mail(request, to_email, from_email, subject, 'registration/creation_email.txt', {
- 'domain': domain,
- 'auth': auth,
- 'username': to_email,
- 'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
- })
+ send_mail(request, to_email, from_email, subject, 'registration/creation_email.txt', {
+ 'domain': domain,
+ 'auth': auth,
+ 'username': to_email,
+ 'expire': settings.DAYS_TO_EXPIRE_REGISTRATION_LINK,
+ })
+ else:
+ return render(request, 'registration/manual.html', { 'account_request_email': settings.ACCOUNT_REQUEST_EMAIL })
else:
form = RegistrationForm()
@@ -359,3 +370,22 @@ def test_email(request):
r.set_cookie("testmailcc", cookie)
return r
+
+@role_required('Secretariat')
+def add_account_whitelist(request):
+ success = False
+ if request.method == 'POST':
+ form = WhitelistForm(request.POST)
+ if form.is_valid():
+ email = form.cleaned_data['email']
+ entry = Whitelisted(email=email, by=request.user.person)
+ entry.save()
+ success = True
+ else:
+ form = WhitelistForm()
+
+ return render(request, 'ietfauth/whitelist_form.html', {
+ 'form': form,
+ 'success': success,
+ })
+
diff --git a/ietf/mailinglists/admin.py b/ietf/mailinglists/admin.py
new file mode 100644
index 000000000..aaa086823
--- /dev/null
+++ b/ietf/mailinglists/admin.py
@@ -0,0 +1,23 @@
+# Copyright The IETF Trust 2016, All Rights Reserved
+
+from django.contrib import admin
+
+from ietf.mailinglists.models import List, Subscribed, Whitelisted
+
+
+class ListAdmin(admin.ModelAdmin):
+ list_display = ('id', 'name', 'description', 'advertised')
+ search_fields = ('name',)
+admin.site.register(List, ListAdmin)
+
+
+class SubscribedAdmin(admin.ModelAdmin):
+ list_display = ('id', 'time', 'email')
+ raw_id_fields = ('lists',)
+ search_fields = ('email',)
+admin.site.register(Subscribed, SubscribedAdmin)
+
+
+class WhitelistedAdmin(admin.ModelAdmin):
+ list_display = ('id', 'time', 'email', 'by')
+admin.site.register(Whitelisted, WhitelistedAdmin)
diff --git a/ietf/mailinglists/management/__init__.py b/ietf/mailinglists/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/mailinglists/management/commands/__init__.py b/ietf/mailinglists/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/mailinglists/management/commands/import_mailman_listinfo.py b/ietf/mailinglists/management/commands/import_mailman_listinfo.py
new file mode 100644
index 000000000..4f9d15770
--- /dev/null
+++ b/ietf/mailinglists/management/commands/import_mailman_listinfo.py
@@ -0,0 +1,61 @@
+# Copyright The IETF Trust 2016, All Rights Reserved
+
+import sys
+from textwrap import dedent
+
+import debug # pyflakes:ignore
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+
+from ietf.mailinglists.models import List, Subscribed
+
+class Command(BaseCommand):
+ """
+ Import list information from Mailman.
+
+ Import announced list names, descriptions, and subscribers, by calling the
+ appropriate Mailman functions and adding entries to the database.
+
+ Run this from cron regularly, with sufficient permissions to access the
+ mailman database files.
+
+ """
+
+ help = dedent(__doc__).strip()
+
+ #option_list = BaseCommand.option_list + ( )
+
+ def note(self, msg):
+ if self.verbosity > 1:
+ self.stdout.write(msg)
+
+ def handle(self, *filenames, **options):
+ """
+
+ * Import announced lists, with appropriate meta-information.
+
+ * For each list, import the members.
+
+ """
+
+ self.verbosity = int(options.get('verbosity'))
+
+ sys.path.append(settings.MAILMAN_LIB_DIR)
+
+ from Mailman import Utils
+ from Mailman import MailList
+
+ for name in Utils.list_names():
+ mlist = MailList.MailList(name, lock=False)
+ self.note("List: %s" % mlist.internal_name())
+ if mlist.advertised:
+ list, created = List.objects.get_or_create(name=mlist.real_name, description=mlist.description, advertised=mlist.advertised)
+ # The following calls return lowercased addresses
+ members = mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()
+ known = [ s.email for s in Subscribed.objects.filter(lists__name=name) ]
+ for addr in members:
+ if not addr in known:
+ self.note(" Adding subscribed: %s" % (addr))
+ new, created = Subscribed.objects.get_or_create(email=addr)
+ new.lists.add(list)
diff --git a/ietf/mailinglists/migrations/0001_initial.py b/ietf/mailinglists/migrations/0001_initial.py
new file mode 100644
index 000000000..f5dcb34ec
--- /dev/null
+++ b/ietf/mailinglists/migrations/0001_initial.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+
+from django.db import migrations
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ]
+
+ operations = [
+ ]
diff --git a/ietf/mailinglists/migrations/0002_list_subscribed_whitelisted.py b/ietf/mailinglists/migrations/0002_list_subscribed_whitelisted.py
new file mode 100644
index 000000000..63bb3d939
--- /dev/null
+++ b/ietf/mailinglists/migrations/0002_list_subscribed_whitelisted.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+import django.core.validators
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('person', '0013_add_plain_name_aliases'),
+ ('mailinglists', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='List',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('name', models.CharField(max_length=32)),
+ ('description', models.CharField(max_length=256)),
+ ('advertised', models.BooleanField(default=True)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Subscribed',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('time', models.DateTimeField(auto_now_add=True)),
+ ('email', models.CharField(max_length=64, validators=[django.core.validators.EmailValidator()])),
+ ('lists', models.ManyToManyField(to='mailinglists.List')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ migrations.CreateModel(
+ name='Whitelisted',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('time', models.DateTimeField(auto_now_add=True)),
+ ('email', models.CharField(max_length=64, verbose_name=b'Email address', validators=[django.core.validators.EmailValidator()])),
+ ('by', models.ForeignKey(to='person.Person')),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/ietf/mailinglists/migrations/__init__.py b/ietf/mailinglists/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/mailinglists/models.py b/ietf/mailinglists/models.py
new file mode 100644
index 000000000..e62d15c3a
--- /dev/null
+++ b/ietf/mailinglists/models.py
@@ -0,0 +1,28 @@
+# Copyright The IETF Trust 2016, All Rights Reserved
+
+from django.db import models
+from django.core.validators import validate_email
+
+from ietf.person.models import Person
+
+class List(models.Model):
+ name = models.CharField(max_length=32)
+ description = models.CharField(max_length=256)
+ advertised = models.BooleanField(default=True)
+ def __unicode__(self):
+ return "" % self.name
+
+class Subscribed(models.Model):
+ time = models.DateTimeField(auto_now_add=True)
+ email = models.CharField(max_length=64, validators=[validate_email])
+ lists = models.ManyToManyField(List)
+ def __unicode__(self):
+ return "" % (self.email, self.time)
+
+class Whitelisted(models.Model):
+ time = models.DateTimeField(auto_now_add=True)
+ email = models.CharField("Email address", max_length=64, validators=[validate_email])
+ by = models.ForeignKey(Person)
+ def __unicode__(self):
+ return "" % (self.email, self.time)
+
diff --git a/ietf/mailinglists/resources.py b/ietf/mailinglists/resources.py
new file mode 100644
index 000000000..49f1786b5
--- /dev/null
+++ b/ietf/mailinglists/resources.py
@@ -0,0 +1,58 @@
+# Copyright The IETF Trust 2016, All Rights Reserved
+# Autogenerated by the makeresources management command 2016-06-12 12:29 PDT
+from tastypie.resources import ModelResource
+from tastypie.fields import ToManyField # pyflakes:ignore
+from tastypie.constants import ALL, ALL_WITH_RELATIONS # pyflakes:ignore
+from tastypie.cache import SimpleCache
+
+from ietf import api
+from ietf.api import ToOneField # pyflakes:ignore
+
+from ietf.mailinglists.models import Whitelisted, List, Subscribed
+
+
+from ietf.person.resources import PersonResource
+class WhitelistedResource(ModelResource):
+ by = ToOneField(PersonResource, 'by')
+ class Meta:
+ queryset = Whitelisted.objects.all()
+ serializer = api.Serializer()
+ cache = SimpleCache()
+ #resource_name = 'whitelisted'
+ filtering = {
+ "id": ALL,
+ "time": ALL,
+ "email": ALL,
+ "by": ALL_WITH_RELATIONS,
+ }
+api.mailinglists.register(WhitelistedResource())
+
+class ListResource(ModelResource):
+ class Meta:
+ queryset = List.objects.all()
+ serializer = api.Serializer()
+ cache = SimpleCache()
+ #resource_name = 'list'
+ filtering = {
+ "id": ALL,
+ "name": ALL,
+ "description": ALL,
+ "advertised": ALL,
+ }
+api.mailinglists.register(ListResource())
+
+class SubscribedResource(ModelResource):
+ lists = ToManyField(ListResource, 'lists', null=True)
+ class Meta:
+ queryset = Subscribed.objects.all()
+ serializer = api.Serializer()
+ cache = SimpleCache()
+ #resource_name = 'subscribed'
+ filtering = {
+ "id": ALL,
+ "time": ALL,
+ "email": ALL,
+ "lists": ALL_WITH_RELATIONS,
+ }
+api.mailinglists.register(SubscribedResource())
+
diff --git a/ietf/mailinglists/tests.py b/ietf/mailinglists/tests.py
index 485494b7a..8e32972a5 100644
--- a/ietf/mailinglists/tests.py
+++ b/ietf/mailinglists/tests.py
@@ -1,3 +1,5 @@
+# Copyright The IETF Trust 2016, All Rights Reserved
+
from django.core.urlresolvers import reverse as urlreverse
from pyquery import PyQuery
diff --git a/ietf/settings.py b/ietf/settings.py
index 42e5e7c6a..e8efd5152 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -658,6 +658,10 @@ MARKUP_SETTINGS = {
MAILMAN_LIB_DIR = '/usr/lib/mailman'
+# This is the number of seconds required between subscribing to an ietf
+# mailing list and datatracker account creation being accepted
+LIST_ACCOUNT_DELAY = 60*60*25 # 25 hours
+ACCOUNT_REQUEST_EMAIL = 'account-request@ietf.org'
# Put the production SECRET_KEY in settings_local.py, and also any other
diff --git a/ietf/templates/base/menu_user.html b/ietf/templates/base/menu_user.html
index 7f6064a06..106f74ec7 100644
--- a/ietf/templates/base/menu_user.html
+++ b/ietf/templates/base/menu_user.html
@@ -41,6 +41,7 @@
Management items
Milestones
Sync discrepancies
+ Account whitelist
{% endif %}
{% if user|has_role:"IANA" %}
diff --git a/ietf/templates/ietfauth/whitelist_form.html b/ietf/templates/ietfauth/whitelist_form.html
new file mode 100644
index 000000000..a3f62d45c
--- /dev/null
+++ b/ietf/templates/ietfauth/whitelist_form.html
@@ -0,0 +1,93 @@
+{% extends "base.html" %}
+{# Copyright The IETF Trust 2016, All Rights Reserved #}
+{% load origin %}
+
+{% load bootstrap3 %}
+
+{% block title %}Set up test email address{% endblock %}
+
+{% block content %}
+ {% origin %}
+ {% if success %}
+ Whitelist entry creation successful
+
+
+
+ Please ask the requestor to try the
+ account creation form
+ again, with the whitelisted email address.
+
+
+
+ {% else %}
+ Add a whitelist entry for account creation.
+
+
+ When an email request comes in for assistance with account creation
+ because the automated account creation has failed, you can add the
+ address to an account creation whitelist here.
+
+
+ Before you do so, please complete the following 3 verification steps:
+
+ -
+
+ Has the person provided relevant information in his request, or has he simply
+ copied the text from the account creation failure message? All genuine (non-spam)
+ account creation requests seen between 2009 and 2016 for tools.ietf.org have
+ contained a reasonable request message, rather than just copy-pasting the account
+ creation failure message. If there's no proper request message, step 2 below can
+ be performed to make sure the request is bogus, but if that also fails, no further
+ effort should be needed.
+
+
+ -
+
+ Google for the person's name within the ietf.org site: "Jane Doe site:ietf.org". If
+ found, and the email address matches an address used in drafts or discussions,
+ things are fine, and it's OK to add the address to the whitelist using this form,
+ and ask the person to please try the
+ account creation form again.
+
+
+ -
+
+
+
+ If google finds no trace of the person being an ietf participant, he or she could
+ still be somebody who is just getting involved in IETF work. A datatracker account
+ is probably not necessary, but in case this is a legitimate request, please email
+ the person and ask:
+
+
+
+ "Which wgs do you require a password for?"
+
+
+
+
+ This is a bit of a trick question, because it is very unlikely that somebody who
+ isn't involved in IETF work will give a reasonable response, while almost any answer
+ from somebody who is doing IETF work will show that they have some clue.
+
+
+
+
+ If the answer to this question shows clue, then add the address to the whitelist
+ using this form, and ask the person to please try the
+ account creation form again.
+
+
+
+
+
+
+ {% endif %}
+{% endblock %}
diff --git a/ietf/templates/registration/manual.html b/ietf/templates/registration/manual.html
new file mode 100644
index 000000000..3161bd3cc
--- /dev/null
+++ b/ietf/templates/registration/manual.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{# Copyright The IETF Trust 2015, All Rights Reserved #}
+{% load origin %}
+
+{% load bootstrap3 %}
+
+{% block title %}Complete account creation{% endblock %}
+
+{% block content %}
+ {% origin %}
+
+ Account creation failed
+
+
+ Manual intervention is needed to enable account creation for you.
+ Please send an email to {{ account_request_email }}
+ and explain 1) the situation and 2) your need for an account,
+ in order to receive further assistance.
+
+
+{% endblock %}