diff --git a/basic/__init__.py b/base/__init__.py
similarity index 100%
rename from basic/__init__.py
rename to base/__init__.py
diff --git a/base/accounts.py b/base/accounts.py
new file mode 100644
index 0000000000..e6d96fcb76
--- /dev/null
+++ b/base/accounts.py
@@ -0,0 +1,21 @@
+from django.conf import settings
+from django.contrib.auth.models import User
+
+class EmailOrUsernameModelBackend(object):
+ def authenticate(self, username=None, password=None):
+ if '@' in username:
+ kwargs = {'email': username}
+ else:
+ kwargs = {'username': username}
+ try:
+ user = User.objects.get(**kwargs)
+ if user.check_password(password):
+ return user
+ except User.DoesNotExist:
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
diff --git a/basic/models.py b/base/models.py
similarity index 100%
rename from basic/models.py
rename to base/models.py
diff --git a/basic/tests.py b/base/tests.py
similarity index 100%
rename from basic/tests.py
rename to base/tests.py
diff --git a/basic/views.py b/base/views.py
similarity index 100%
rename from basic/views.py
rename to base/views.py
diff --git a/media/avatars/default.png b/media/avatars/default.png
new file mode 100644
index 0000000000..cce2cb3deb
Binary files /dev/null and b/media/avatars/default.png differ
diff --git a/media/css/seahub.css b/media/css/seahub.css
index eb2da09670..a4647b446d 100644
--- a/media/css/seahub.css
+++ b/media/css/seahub.css
@@ -29,6 +29,9 @@ table.default tr.first { background-color: #00FF00; }
#right-panel { float:right; width:800px; }
#footer { color:#999; padding-top:2px; margin:25px 0; border-top:1px solid #DDD; }
/* #header */
+#header .top_operation { float:right; margin:2px 10px 0 0; color:#ddd; font-size:13px; }
+#header .top_operation a { font-weight:normal; font-size:13px; margin-left:2px; text-decoration: none; }
+#header .top_operation a:hover { background:#0A0; color:#FFF; }
#logo { margin-top: 4px; }
#header #ident { margin-top: 8px; float: right; font-size: 12px; }
#header #ident p { color: #808; }
@@ -42,7 +45,7 @@ div.nav-separator { clear: both; border-top: #FF8C00 1px solid; border-bottom: n
/* footer */
#footer a { color:#333; text-decoration:none; }
/* main */
-h2 { font-size:16px; color:#292; padding:4px 0 2px 0px; border-bottom: 2px solid #2B2; background-position:left 22px; margin:10px 0; }
+h2 { font-size:16px; color:#292; padding:4px 0 2px 0px; margin:10px 0; }
h3 { font-size: 14px; color: #808; margin: 10px 0px 4px 0; }
p { line-height: 22px; }
ol { margin:0; padding:0px 0 0 2em; list-style-position:inside; }
diff --git a/profile/__init__.py b/profile/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/profile/admin.py b/profile/admin.py
new file mode 100644
index 0000000000..7a7ad257fa
--- /dev/null
+++ b/profile/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from profile.models import UserProfile
+
+admin.site.register(UserProfile)
\ No newline at end of file
diff --git a/profile/forms.py b/profile/forms.py
new file mode 100644
index 0000000000..905af9403e
--- /dev/null
+++ b/profile/forms.py
@@ -0,0 +1,8 @@
+from django import forms
+
+class SetUserProfileForm(forms.Form):
+
+ status = forms.CharField(max_length=140, required=False)
+ interests = forms.CharField(max_length=256, required=False)
+
+ #signature = forms.CharField()
diff --git a/profile/models.py b/profile/models.py
new file mode 100644
index 0000000000..b220667fca
--- /dev/null
+++ b/profile/models.py
@@ -0,0 +1,8 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class UserProfile(models.Model):
+ user = models.ForeignKey(User, unique=True)
+ status = models.CharField(max_length=40, blank=True)
+
diff --git a/profile/templates/profile/profile.html b/profile/templates/profile/profile.html
new file mode 100644
index 0000000000..0e705bcc97
--- /dev/null
+++ b/profile/templates/profile/profile.html
@@ -0,0 +1,41 @@
+{% extends "profile/profile_base.html" %}
+
+
+{% block left_panel %}
+
+{% endblock %}
+
+
+{% block right_panel %}
+
+当前头像
+{% load avatar_tags %}
+{% avatar user 90 %}
+
+基本信息
+当前状态:{{ user.get_profile.status }}
+我的兴趣:{{ user.get_profile.interests }}
+
+{% if peer %}
+Ccnet 当前设置
+Ccnet ID: {{ peer.peer_id }}
+Ccnet Name: {{ peer.name }}
+{% endif %}
+
+{% if groups %}
+加入的组
+
+
+ {% for group in groups %}
+ - {{ group.name }}
+ {% endfor %}
+
+
+{% endif %}
+
+{% endblock %}
diff --git a/profile/templates/profile/profile_base.html b/profile/templates/profile/profile_base.html
new file mode 100644
index 0000000000..a6bdda9cf7
--- /dev/null
+++ b/profile/templates/profile/profile_base.html
@@ -0,0 +1,7 @@
+{% extends "myhome_base.html" %}
+
+{% block title %}帐号设置{% endblock %}
+
+{% block subnav %}
+
+{% endblock %}
diff --git a/profile/templates/profile/set_profile.html b/profile/templates/profile/set_profile.html
new file mode 100644
index 0000000000..aec8765041
--- /dev/null
+++ b/profile/templates/profile/set_profile.html
@@ -0,0 +1,20 @@
+{% extends "profile/profile_base.html" %}
+
+
+{% block content %}
+
+
+
+{% endblock %}
diff --git a/profile/tests.py b/profile/tests.py
new file mode 100644
index 0000000000..2247054b35
--- /dev/null
+++ b/profile/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/profile/urls.py b/profile/urls.py
new file mode 100644
index 0000000000..9ac40c3832
--- /dev/null
+++ b/profile/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('profile.views',
+ url(r'^$', 'show_profile'),
+ url(r'^edit/$', 'set_profile', name="profile_setting"),
+)
diff --git a/profile/views.py b/profile/views.py
new file mode 100644
index 0000000000..1daa5f00e8
--- /dev/null
+++ b/profile/views.py
@@ -0,0 +1,47 @@
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response, get_object_or_404
+from django.template.loader import get_template
+from django.template import Context, RequestContext
+from django.contrib.auth.decorators import login_required
+from django.core.urlresolvers import reverse
+
+import datetime
+
+from forms import SetUserProfileForm
+from models import UserProfile
+
+
+@login_required
+def show_profile(request):
+ groups = []
+ return render_to_response('profile/profile.html',
+ { 'groups': groups, },
+ context_instance=RequestContext(request))
+
+
+@login_required
+def set_profile(request):
+ if request.method == 'POST':
+ form = SetUserProfileForm(request.POST)
+ if form.is_valid():
+ try:
+ profile = request.user.get_profile()
+ except UserProfile.DoesNotExist:
+ profile = UserProfile(user=request.user)
+ profile.save() # save the profile first, otherwise
+ # status.save() would fail.
+
+ profile.save()
+ return HttpResponseRedirect(reverse(show_profile))
+ else:
+ try:
+ profile = request.user.get_profile()
+ except UserProfile.DoesNotExist:
+ profile = UserProfile(user=request.user)
+
+ profile_form = SetUserProfileForm(profile.__dict__)
+
+ return render_to_response('profile/set_profile.html',
+ { 'profile_form': profile_form, },
+ context_instance=RequestContext(request))
+
diff --git a/setenv.sh.template b/setenv.sh.template
new file mode 100644
index 0000000000..802b80b08d
--- /dev/null
+++ b/setenv.sh.template
@@ -0,0 +1,2 @@
+export CCNET_CONF_DIR=/home/plt/dev/ccnet/seafile/tests/basic/conf1
+export PYTHONPATH=/opt/lib/python2.6/site-packages:thirdpart
diff --git a/settings.py b/settings.py
index c5e55c5096..ba869146d4 100644
--- a/settings.py
+++ b/settings.py
@@ -62,6 +62,8 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.middleware.csrf.CsrfResponseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
@@ -94,9 +96,32 @@ INSTALLED_APPS = (
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
- 'seahub.basic',
+ 'django.contrib.admin',
+ 'registration',
+ 'avatar',
+ 'seahub.base',
+ 'seahub.profile',
)
+AUTHENTICATION_BACKENDS = (
+ 'seahub.base.accounts.EmailOrUsernameModelBackend',
+ 'django.contrib.auth.backends.ModelBackend'
+)
+
+ACCOUNT_ACTIVATION_DAYS = 7
+
+#avatar
+AVATAR_STORAGE_DIR = 'avatars'
+
+AVATAR_GRAVATAR_BACKUP = False
+AVATAR_DEFAULT_URL = MEDIA_URL + '/avatars/default.png'
+
+LOGIN_URL = SITE_ROOT + 'accounts/login'
+
+# profile
+AUTH_PROFILE_MODULE = "profile.UserProfile"
+
+
try:
import local_settings
except ImportError:
diff --git a/templates/accounts.html b/templates/accounts.html
new file mode 100644
index 0000000000..d05d592b44
--- /dev/null
+++ b/templates/accounts.html
@@ -0,0 +1,3 @@
+{% extends "myhome_base.html" %}
+
+{% block title %}帐号设置{% endblock %}
diff --git a/templates/avatar/base.html b/templates/avatar/base.html
new file mode 100644
index 0000000000..f15b3aac49
--- /dev/null
+++ b/templates/avatar/base.html
@@ -0,0 +1,3 @@
+{% extends "accounts.html" %}
+{% block title %}个人头像{% endblock %}
+
diff --git a/templates/avatar/change.html b/templates/avatar/change.html
new file mode 100644
index 0000000000..80a8bf3d87
--- /dev/null
+++ b/templates/avatar/change.html
@@ -0,0 +1,31 @@
+{% extends "avatar/base.html" %}
+{% load avatar_tags %}
+
+{% block content %}
+
+
添加或修改头像
+
+
当前头像:
+ {% avatar user %}
+
+
+ {% if not avatars %}
+
您还没有自己的头像。
+ {% else %}
+
从已有头像中选择:
+
+ {% endif %}
+
上传新头像:
+
+
+
+{% endblock %}
diff --git a/templates/avatar/confirm_delete.html b/templates/avatar/confirm_delete.html
new file mode 100644
index 0000000000..024b8dd02c
--- /dev/null
+++ b/templates/avatar/confirm_delete.html
@@ -0,0 +1,18 @@
+{% extends "avatar/base.html" %}
+
+{% block content %}
+
+
删除头像
+ {% if not avatars %}
+
您还没有上传自己的头像。现在 上传一个.
+ {% else %}
+
+ {% endif %}
+
+{% endblock %}
diff --git a/templates/base.html b/templates/base.html
index b84711d47c..4343f103f3 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -15,8 +15,24 @@
@@ -39,6 +56,7 @@
{% block right_panel %}{% endblock %}
+ {% block content %}{% endblock %}
diff --git a/templates/myhome.html b/templates/myhome.html
new file mode 100644
index 0000000000..b1cdd71c3f
--- /dev/null
+++ b/templates/myhome.html
@@ -0,0 +1 @@
+{% extends "myhome_base.html" %}
diff --git a/templates/myhome_base.html b/templates/myhome_base.html
new file mode 100644
index 0000000000..794e1591dc
--- /dev/null
+++ b/templates/myhome_base.html
@@ -0,0 +1,17 @@
+{% extends "base.html" %}
+{% block nav %}
+
+{% endblock %}
diff --git a/templates/registration/activation_complete.html b/templates/registration/activation_complete.html
new file mode 100644
index 0000000000..153023af82
--- /dev/null
+++ b/templates/registration/activation_complete.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block title %}Create an account{% endblock %}
+
+{% block content %}
+你的帐号已经激活。 登录
+{% endblock %}
+
diff --git a/templates/registration/activation_email.txt b/templates/registration/activation_email.txt
new file mode 100644
index 0000000000..b85e5eead2
--- /dev/null
+++ b/templates/registration/activation_email.txt
@@ -0,0 +1,4 @@
+您好, 感谢注册 {{ site.name }}。
+
+请点击下面的链接激活您的帐号
+http://{{ site.name }}{{ SITE_ROOT }}accounts/activate/{{ activation_key }}/
diff --git a/templates/registration/activation_email_subject.txt b/templates/registration/activation_email_subject.txt
new file mode 100644
index 0000000000..111f8d7101
--- /dev/null
+++ b/templates/registration/activation_email_subject.txt
@@ -0,0 +1 @@
+请激活你的帐号,完成注册
diff --git a/templates/registration/login.html b/templates/registration/login.html
new file mode 100644
index 0000000000..9131e2ee2a
--- /dev/null
+++ b/templates/registration/login.html
@@ -0,0 +1,30 @@
+{% extends "base.html" %}
+{% block title %}用户登录{% endblock %}
+{% block content %}
+用户登录
+
+
+{% endblock %}
+
diff --git a/templates/registration/logout.html b/templates/registration/logout.html
new file mode 100644
index 0000000000..24097da4cc
--- /dev/null
+++ b/templates/registration/logout.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+{% block title %}退出{% endblock %}
+
+{% block content %}
+
+感谢参与.
+
+重新登录
+
+{% endblock %}
diff --git a/templates/registration/password_change_done.html b/templates/registration/password_change_done.html
new file mode 100644
index 0000000000..83b2ea63d8
--- /dev/null
+++ b/templates/registration/password_change_done.html
@@ -0,0 +1,14 @@
+{% extends "accounts.html" %}
+{% load i18n %}
+
+{% block title %}{% trans 'Password change successful' %}{% endblock %}
+
+{% block content %}
+
+{% trans 'Password change successful' %}
+
+{% trans 'Your password was changed.' %}
+
+{% endblock %}
diff --git a/templates/registration/password_change_form.html b/templates/registration/password_change_form.html
new file mode 100644
index 0000000000..8182552893
--- /dev/null
+++ b/templates/registration/password_change_form.html
@@ -0,0 +1,17 @@
+{% extends "accounts.html" %}
+{% load i18n %}
+{% block title %}{% trans 'Password change' %}{% endblock %}
+{% block content %}
+
+
{% trans 'Password change' %}
+
+
+{% endblock %}
diff --git a/templates/registration/password_reset_complete.html b/templates/registration/password_reset_complete.html
new file mode 100644
index 0000000000..5a2d7f8382
--- /dev/null
+++ b/templates/registration/password_reset_complete.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block title %}{% trans 'Password reset complete' %}{% endblock %}
+
+{% block content %}
+
+{% trans 'Password reset complete' %}
+
+{% trans "Your password has been set. You may go ahead and log in now." %}
+
+{% trans 'Log in' %}
+
+{% endblock %}
diff --git a/templates/registration/password_reset_confirm.html b/templates/registration/password_reset_confirm.html
new file mode 100644
index 0000000000..b17ec504db
--- /dev/null
+++ b/templates/registration/password_reset_confirm.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block title %}{% trans 'Password reset' %}{% endblock %}
+
+{% block content %}
+
+{% if validlink %}
+
+{% trans 'Enter new password' %}
+
+{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}
+
+
+
+{% else %}
+
+{% trans 'Password reset unsuccessful' %}
+
+{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/templates/registration/password_reset_done.html b/templates/registration/password_reset_done.html
new file mode 100644
index 0000000000..9c0f440f05
--- /dev/null
+++ b/templates/registration/password_reset_done.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}
{% endblock %}
+
+{% block title %}{% trans 'Password reset successful' %}{% endblock %}
+
+{% block content %}
+
+{% trans 'Password reset successful' %}
+
+{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}
+
+{% endblock %}
diff --git a/templates/registration/password_reset_email.html b/templates/registration/password_reset_email.html
new file mode 100644
index 0000000000..4e4bd6d1b2
--- /dev/null
+++ b/templates/registration/password_reset_email.html
@@ -0,0 +1,15 @@
+{% load i18n %}{% autoescape off %}
+{% trans "You're receiving this e-mail because you requested a password reset" %}
+{% blocktrans %}for your user account at {{ site_name }}{% endblocktrans %}.
+
+{% trans "Please go to the following page and choose a new password:" %}
+{% block reset_link %}
+{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %}
+{% endblock %}
+{% trans "Your username, in case you've forgotten:" %} {{ user.username }}
+
+{% trans "Thanks for using our site!" %}
+
+{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
+
+{% endautoescape %}
diff --git a/templates/registration/password_reset_form.html b/templates/registration/password_reset_form.html
new file mode 100644
index 0000000000..d43e87c47f
--- /dev/null
+++ b/templates/registration/password_reset_form.html
@@ -0,0 +1,14 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block breadcrumbs %}{% endblock %}
+{% block title %}{% trans "Password reset" %}{% endblock %}
+{% block content %}
+{% trans "Password reset" %}
+请在下面输入您的 e-mail 地址,我们会把新密码设置说明通过邮件发送给您。
+
+{% endblock %}
diff --git a/templates/registration/registration_closed.html b/templates/registration/registration_closed.html
new file mode 100644
index 0000000000..12580ea1ca
--- /dev/null
+++ b/templates/registration/registration_closed.html
@@ -0,0 +1,8 @@
+{% extends "base.html" %}
+
+{% block title %}Create an account{% endblock %}
+
+{% block content %}
+Closed.
+{% endblock %}
+
diff --git a/templates/registration/registration_complete.html b/templates/registration/registration_complete.html
new file mode 100644
index 0000000000..0972d2edb2
--- /dev/null
+++ b/templates/registration/registration_complete.html
@@ -0,0 +1,6 @@
+{% extends "base.html" %}
+{% block title %}感谢注册{% endblock %}
+{% block content %}
+感谢注册,激活邮件已发往您的邮箱,请查收。如果您在收件箱里没找到,请检查下是否被当成垃圾邮件了。
+{% endblock %}
+
diff --git a/templates/registration/registration_form.html b/templates/registration/registration_form.html
new file mode 100644
index 0000000000..027c365de8
--- /dev/null
+++ b/templates/registration/registration_form.html
@@ -0,0 +1,26 @@
+{% extends "base.html" %}
+{% block title %}用户注册{% endblock %}
+{% block content %}
+用户注册
+
+{% endblock %}
+
diff --git a/thirdpart/avatar/__init__.py b/thirdpart/avatar/__init__.py
new file mode 100644
index 0000000000..e2337eaaba
--- /dev/null
+++ b/thirdpart/avatar/__init__.py
@@ -0,0 +1,27 @@
+import os.path
+
+from django.conf import settings
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+AUTO_GENERATE_AVATAR_SIZES = getattr(settings, 'AUTO_GENERATE_AVATAR_SIZES', (80,))
+AVATAR_RESIZE_METHOD = getattr(settings, 'AVATAR_RESIZE_METHOD', Image.ANTIALIAS)
+AVATAR_STORAGE_DIR = getattr(settings, 'AVATAR_STORAGE_DIR', 'avatars')
+AVATAR_GRAVATAR_BACKUP = getattr(settings, 'AVATAR_GRAVATAR_BACKUP', True)
+AVATAR_GRAVATAR_DEFAULT = getattr(settings, 'AVATAR_GRAVATAR_DEFAULT', None)
+AVATAR_DEFAULT_URL = getattr(settings, 'AVATAR_DEFAULT_URL',
+ settings.MEDIA_URL + os.path.join(os.path.dirname(__file__), 'default.jpg'))
+
+from django.db.models import signals
+from django.contrib.auth.models import User
+from avatar.models import Avatar
+
+
+def create_default_thumbnails(instance=None, created=False, **kwargs):
+ if created:
+ for size in AUTO_GENERATE_AVATAR_SIZES:
+ instance.create_thumbnail(size)
+signals.post_save.connect(create_default_thumbnails, sender=Avatar)
diff --git a/thirdpart/avatar/admin.py b/thirdpart/avatar/admin.py
new file mode 100644
index 0000000000..4af39fa913
--- /dev/null
+++ b/thirdpart/avatar/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from avatar.models import Avatar
+
+admin.site.register(Avatar)
\ No newline at end of file
diff --git a/thirdpart/avatar/default.jpg b/thirdpart/avatar/default.jpg
new file mode 100644
index 0000000000..37a6276a65
Binary files /dev/null and b/thirdpart/avatar/default.jpg differ
diff --git a/thirdpart/avatar/forms.py b/thirdpart/avatar/forms.py
new file mode 100644
index 0000000000..7c3c0c1155
--- /dev/null
+++ b/thirdpart/avatar/forms.py
@@ -0,0 +1,31 @@
+from django import forms
+from django.forms import widgets
+from django.utils.safestring import mark_safe
+
+def avatar_img(avatar, size):
+ if not avatar.thumbnail_exists(size):
+ avatar.create_thumbnail(size)
+ return mark_safe("""
""" %
+ (avatar.avatar_url(size), unicode(avatar), size, size))
+
+class PrimaryAvatarForm(forms.Form):
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user')
+ size = kwargs.pop('size', 80)
+ super(PrimaryAvatarForm, self).__init__(*args, **kwargs)
+ avatars = user.avatar_set.all()
+ self.fields['choice'] = forms.ChoiceField(
+ choices=[(c.id, avatar_img(c, size)) for c in user.avatar_set.all()],
+ widget=widgets.RadioSelect)
+
+class DeleteAvatarForm(forms.Form):
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop('user')
+ size = kwargs.pop('size', 80)
+ super(DeleteAvatarForm, self).__init__(*args, **kwargs)
+ avatars = user.avatar_set.all()
+ self.fields['choices'] = forms.MultipleChoiceField(
+ choices=[(c.id, avatar_img(c, size)) for c in user.avatar_set.all()],
+ widget=widgets.CheckboxSelectMultiple)
diff --git a/thirdpart/avatar/management/__init__.py b/thirdpart/avatar/management/__init__.py
new file mode 100644
index 0000000000..ca2bd94c32
--- /dev/null
+++ b/thirdpart/avatar/management/__init__.py
@@ -0,0 +1,14 @@
+from django.conf import settings
+from django.db.models import signals
+from django.utils.translation import ugettext_noop as _
+
+if "notification" in settings.INSTALLED_APPS:
+ from notification import models as notification
+
+ def create_notice_types(app, created_models, verbosity, **kwargs):
+ notification.create_notice_type("avatar_updated", _("Avatar Updated"), _("avatar have been updated"))
+ notification.create_notice_type("avatar_friend_updated", _("Friend Updated Avatar"), _("a friend has updated his avatar"))
+
+ signals.post_syncdb.connect(create_notice_types, sender=notification)
+else:
+ print "Skipping creation of NoticeTypes as notification app not found"
diff --git a/thirdpart/avatar/management/commands/__init__.py b/thirdpart/avatar/management/commands/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/thirdpart/avatar/management/commands/__init__.py
@@ -0,0 +1 @@
+
diff --git a/thirdpart/avatar/management/commands/rebuild_avatars.py b/thirdpart/avatar/management/commands/rebuild_avatars.py
new file mode 100644
index 0000000000..28b64ad6d9
--- /dev/null
+++ b/thirdpart/avatar/management/commands/rebuild_avatars.py
@@ -0,0 +1,15 @@
+from django.core.management.base import NoArgsCommand
+from django.conf import settings
+
+from avatar.models import Avatar
+from avatar import AUTO_GENERATE_AVATAR_SIZES
+
+class Command(NoArgsCommand):
+ help = "Regenerates avatar thumbnails for the sizes specified in " + \
+ "settings.AUTO_GENERATE_AVATAR_SIZES."
+
+ def handle_noargs(self, **options):
+ for avatar in Avatar.objects.all():
+ for size in AUTO_GENERATE_AVATAR_SIZES:
+ print "Rebuilding Avatar id=%s at size %s." % (avatar.id, size)
+ avatar.create_thumbnail(size)
\ No newline at end of file
diff --git a/thirdpart/avatar/models.py b/thirdpart/avatar/models.py
new file mode 100644
index 0000000000..ada6c94a4b
--- /dev/null
+++ b/thirdpart/avatar/models.py
@@ -0,0 +1,79 @@
+import datetime
+import os.path
+
+from django.db import models
+from django.contrib.auth.models import User
+from django.core.files.base import ContentFile
+from django.utils.translation import ugettext as _
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+try:
+ from PIL import Image
+except ImportError:
+ import Image
+
+from avatar import AVATAR_STORAGE_DIR, AVATAR_RESIZE_METHOD
+
+def avatar_file_path(instance=None, filename=None, user=None):
+ user = user or instance.user
+ return os.path.join(AVATAR_STORAGE_DIR, user.username, filename)
+
+class Avatar(models.Model):
+ user = models.ForeignKey(User)
+ primary = models.BooleanField(default=False)
+ avatar = models.ImageField(max_length=1024, upload_to=avatar_file_path, blank=True)
+ date_uploaded = models.DateTimeField(default=datetime.datetime.now)
+
+ def __unicode__(self):
+ return _(u'Avatar for %s') % self.user
+
+ def save(self, force_insert=False, force_update=False):
+ if self.primary:
+ avatars = Avatar.objects.filter(user=self.user, primary=True)\
+ .exclude(id=self.id)
+ avatars.update(primary=False)
+ super(Avatar, self).save(force_insert, force_update)
+
+ def thumbnail_exists(self, size):
+ return self.avatar.storage.exists(self.avatar_name(size))
+
+ def create_thumbnail(self, size):
+ try:
+ orig = self.avatar.storage.open(self.avatar.name, 'rb').read()
+ image = Image.open(StringIO(orig))
+ except IOError:
+ return # What should we do here? Render a "sorry, didn't work" img?
+ (w, h) = image.size
+ if w != size or h != size:
+ if w > h:
+ diff = (w - h) / 2
+ image = image.crop((diff, 0, w - diff, h))
+ else:
+ diff = (h - w) / 2
+ image = image.crop((0, diff, w, h - diff))
+ image = image.resize((size, size), AVATAR_RESIZE_METHOD)
+ if image.mode != "RGB":
+ image = image.convert("RGB")
+ thumb = StringIO()
+ image.save(thumb, "JPEG", quality=95)
+ thumb_file = ContentFile(thumb.getvalue())
+ else:
+ thumb_file = ContentFile(orig)
+ thumb = self.avatar.storage.save(self.avatar_name(size), thumb_file)
+
+ def avatar_url(self, size):
+ return self.avatar.storage.url(self.avatar_name(size))
+
+ def avatar_name(self, size):
+ filename = os.path.basename(self.avatar.name)
+ idx = filename.rfind('.')
+ if idx != -1:
+ filename = filename[:idx] + '.jpg'
+ else:
+ filename += '.jpg'
+ return os.path.join(AVATAR_STORAGE_DIR, self.user.username,
+ 'resized', str(size), filename)
diff --git a/thirdpart/avatar/templates/avatar/base.html b/thirdpart/avatar/templates/avatar/base.html
new file mode 100644
index 0000000000..057fa22aec
--- /dev/null
+++ b/thirdpart/avatar/templates/avatar/base.html
@@ -0,0 +1,10 @@
+{% extends "accounts.html" %}
+
+
+
+ {% block title %}django-avatar{% endblock %}
+
+
+ {% block content %}{% endblock %}
+
+
diff --git a/thirdpart/avatar/templates/avatar/change.html b/thirdpart/avatar/templates/avatar/change.html
new file mode 100644
index 0000000000..555be3da0f
--- /dev/null
+++ b/thirdpart/avatar/templates/avatar/change.html
@@ -0,0 +1,21 @@
+{% extends "avatar/base.html" %}
+{% load avatar_tags %}
+
+{% block content %}
+ Your current avatar:
+ {% avatar user %}
+ {% if not avatars %}
+ You do not yet have an avatar. Please upload one now.
+ {% else %}
+
+ {% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/avatar/templates/avatar/confirm_delete.html b/thirdpart/avatar/templates/avatar/confirm_delete.html
new file mode 100644
index 0000000000..8bf9407a31
--- /dev/null
+++ b/thirdpart/avatar/templates/avatar/confirm_delete.html
@@ -0,0 +1,15 @@
+{% extends "avatar/base.html" %}
+
+{% block content %}
+ Please select the avatars that you would like to delete.
+ {% if not avatars %}
+ You have no avatars to delete. Please upload one now.
+ {% else %}
+
+ {% endif %}
+{% endblock %}
\ No newline at end of file
diff --git a/thirdpart/avatar/templates/notifications/avatar_friend_updated/full.txt b/thirdpart/avatar/templates/notifications/avatar_friend_updated/full.txt
new file mode 100644
index 0000000000..a3baba1a39
--- /dev/null
+++ b/thirdpart/avatar/templates/notifications/avatar_friend_updated/full.txt
@@ -0,0 +1,4 @@
+{% load i18n %}{% blocktrans with user as avatar_creator and avatar.get_absolute_url as avatar_url %}{{ avatar_creator }} has updated his avatar {{ avatar }}.
+
+http://{{ current_site }}{{ avatar_url }}
+{% endblocktrans %}
diff --git a/thirdpart/avatar/templates/notifications/avatar_friend_updated/notice.html b/thirdpart/avatar/templates/notifications/avatar_friend_updated/notice.html
new file mode 100644
index 0000000000..bbe9a1ac19
--- /dev/null
+++ b/thirdpart/avatar/templates/notifications/avatar_friend_updated/notice.html
@@ -0,0 +1,2 @@
+{% load i18n %}{% url profile_detail username=user.username as user_url %}
+{% blocktrans with user as avatar_creator and avatar.get_absolute_url as avatar_url %}{{ avatar_creator }} has updated his avatar {{ avatar }}.{% endblocktrans %}
diff --git a/thirdpart/avatar/templates/notifications/avatar_updated/full.txt b/thirdpart/avatar/templates/notifications/avatar_updated/full.txt
new file mode 100644
index 0000000000..c11977ca1c
--- /dev/null
+++ b/thirdpart/avatar/templates/notifications/avatar_updated/full.txt
@@ -0,0 +1,4 @@
+{% load i18n %}{% blocktrans with avatar.get_absolute_url as avatar_url %}Avatar {{ avatar }} has been created.
+
+http://{{ current_site }}{{ avatar_url }}
+{% endblocktrans %}
diff --git a/thirdpart/avatar/templates/notifications/avatar_updated/notice.html b/thirdpart/avatar/templates/notifications/avatar_updated/notice.html
new file mode 100644
index 0000000000..22fd1f1084
--- /dev/null
+++ b/thirdpart/avatar/templates/notifications/avatar_updated/notice.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% blocktrans with avatar.get_absolute_url as avatar_url %}A new tribe {{ avatar }} has been created.{% endblocktrans %}
diff --git a/thirdpart/avatar/templatetags/__init__.py b/thirdpart/avatar/templatetags/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/thirdpart/avatar/templatetags/avatar_tags.py b/thirdpart/avatar/templatetags/avatar_tags.py
new file mode 100644
index 0000000000..217c6bffa6
--- /dev/null
+++ b/thirdpart/avatar/templatetags/avatar_tags.py
@@ -0,0 +1,63 @@
+import urllib
+
+from django import template
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext as _
+from django.utils.hashcompat import md5_constructor
+
+from avatar import AVATAR_DEFAULT_URL, AVATAR_GRAVATAR_BACKUP, AVATAR_GRAVATAR_DEFAULT
+
+register = template.Library()
+
+def avatar_url(user, size=80):
+ if not isinstance(user, User):
+ try:
+ user = User.objects.get(username=user)
+ except User.DoesNotExist:
+ return AVATAR_DEFAULT_URL
+ avatars = user.avatar_set.order_by('-date_uploaded')
+ primary = avatars.filter(primary=True)
+ if primary.count() > 0:
+ avatar = primary[0]
+ elif avatars.count() > 0:
+ avatar = avatars[0]
+ else:
+ avatar = None
+ if avatar is not None:
+ if not avatar.thumbnail_exists(size):
+ avatar.create_thumbnail(size)
+ return avatar.avatar_url(size)
+ else:
+ if AVATAR_GRAVATAR_BACKUP:
+ params = {'s': str(size)}
+ if AVATAR_GRAVATAR_DEFAULT:
+ params['d'] = AVATAR_GRAVATAR_DEFAULT
+ return "http://www.gravatar.com/avatar/%s/?%s" % (
+ md5_constructor(user.email).hexdigest(),
+ urllib.urlencode(params))
+ else:
+ return AVATAR_DEFAULT_URL
+register.simple_tag(avatar_url)
+
+def avatar(user, size=80):
+ if not isinstance(user, User):
+ try:
+ user = User.objects.get(username=user)
+ alt = unicode(user)
+ url = avatar_url(user, size)
+ except User.DoesNotExist:
+ url = AVATAR_DEFAULT_URL
+ alt = _("Default Avatar")
+ else:
+ alt = unicode(user)
+ url = avatar_url(user, size)
+ return """
""" % (url, alt,
+ size, size)
+register.simple_tag(avatar)
+
+def render_avatar(avatar, size=80):
+ if not avatar.thumbnail_exists(size):
+ avatar.create_thumbnail(size)
+ return """
""" % (
+ avatar.avatar_url(size), str(avatar), size, size)
+register.simple_tag(render_avatar)
diff --git a/thirdpart/avatar/urls.py b/thirdpart/avatar/urls.py
new file mode 100644
index 0000000000..0ccbfe8075
--- /dev/null
+++ b/thirdpart/avatar/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('avatar.views',
+ url('^change/$', 'change', name='avatar_change'),
+ url('^delete/$', 'delete', name='avatar_delete'),
+)
diff --git a/thirdpart/avatar/views.py b/thirdpart/avatar/views.py
new file mode 100644
index 0000000000..220dca1c26
--- /dev/null
+++ b/thirdpart/avatar/views.py
@@ -0,0 +1,129 @@
+import os.path
+
+from avatar.models import Avatar, avatar_file_path
+from avatar.forms import PrimaryAvatarForm, DeleteAvatarForm
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth.decorators import login_required
+from django.utils.translation import ugettext as _
+
+from django.db.models import get_app
+from django.core.exceptions import ImproperlyConfigured
+from django.conf import settings
+
+try:
+ notification = get_app('notification')
+except ImproperlyConfigured:
+ notification = None
+
+friends = False
+if 'friends' in settings.INSTALLED_APPS:
+ friends = True
+ from friends.models import Friendship
+
+def _get_next(request):
+ """
+ The part that's the least straightforward about views in this module is how they
+ determine their redirects after they have finished computation.
+
+ In short, they will try and determine the next place to go in the following order:
+
+ 1. If there is a variable named ``next`` in the *POST* parameters, the view will
+ redirect to that variable's value.
+ 2. If there is a variable named ``next`` in the *GET* parameters, the view will
+ redirect to that variable's value.
+ 3. If Django can determine the previous page from the HTTP headers, the view will
+ redirect to that previous page.
+ """
+ next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', None)))
+ if not next:
+ next = request.path
+ return next
+
+def change(request, extra_context={}, next_override=None):
+ avatars = Avatar.objects.filter(user=request.user).order_by('-primary')
+ if avatars.count() > 0:
+ avatar = avatars[0]
+ kwargs = {'initial': {'choice': avatar.id}}
+ else:
+ avatar = None
+ kwargs = {}
+ primary_avatar_form = PrimaryAvatarForm(request.POST or None, user=request.user, **kwargs)
+ if request.method == "POST":
+ updated = False
+ if 'avatar' in request.FILES:
+ path = avatar_file_path(user=request.user,
+ filename=request.FILES['avatar'].name)
+ avatar = Avatar(
+ user = request.user,
+ primary = True,
+ avatar = path,
+ )
+ new_file = avatar.avatar.storage.save(path, request.FILES['avatar'])
+ avatar.save()
+ updated = True
+ request.user.message_set.create(
+ message=_("Successfully uploaded a new avatar."))
+ if 'choice' in request.POST and primary_avatar_form.is_valid():
+ avatar = Avatar.objects.get(id=
+ primary_avatar_form.cleaned_data['choice'])
+ avatar.primary = True
+ avatar.save()
+ updated = True
+ request.user.message_set.create(
+ message=_("Successfully updated your avatar."))
+ if updated and notification:
+ notification.send([request.user], "avatar_updated", {"user": request.user, "avatar": avatar})
+ if friends:
+ notification.send((x['friend'] for x in Friendship.objects.friends_for_user(request.user)), "avatar_friend_updated", {"user": request.user, "avatar": avatar})
+ return HttpResponseRedirect(next_override or _get_next(request))
+ return render_to_response(
+ 'avatar/change.html',
+ extra_context,
+ context_instance = RequestContext(
+ request,
+ { 'avatar': avatar,
+ 'avatars': avatars,
+ 'primary_avatar_form': primary_avatar_form,
+ 'next': next_override or _get_next(request), }
+ )
+ )
+change = login_required(change)
+
+def delete(request, extra_context={}, next_override=None):
+ avatars = Avatar.objects.filter(user=request.user).order_by('-primary')
+ if avatars.count() > 0:
+ avatar = avatars[0]
+ else:
+ avatar = None
+ delete_avatar_form = DeleteAvatarForm(request.POST or None, user=request.user)
+ if request.method == 'POST':
+ if delete_avatar_form.is_valid():
+ ids = delete_avatar_form.cleaned_data['choices']
+ if unicode(avatar.id) in ids and avatars.count() > len(ids):
+ for a in avatars:
+ if unicode(a.id) not in ids:
+ a.primary = True
+ a.save()
+ if notification:
+ notification.send([request.user], "avatar_updated", {"user": request.user, "avatar": a})
+ if friends:
+ notification.send((x['friend'] for x in Friendship.objects.friends_for_user(request.user)), "avatar_friend_updated", {"user": request.user, "avatar": a})
+ break
+ Avatar.objects.filter(id__in=ids).delete()
+ request.user.message_set.create(
+ message=_("Successfully deleted the requested avatars."))
+ return HttpResponseRedirect(next_override or _get_next(request))
+ return render_to_response(
+ 'avatar/confirm_delete.html',
+ extra_context,
+ context_instance = RequestContext(
+ request,
+ { 'avatar': avatar,
+ 'avatars': avatars,
+ 'delete_avatar_form': delete_avatar_form,
+ 'next': next_override or _get_next(request), }
+ )
+ )
+delete = login_required(delete)
diff --git a/thirdpart/registration/__init__.py b/thirdpart/registration/__init__.py
new file mode 100644
index 0000000000..bd4ba7f41f
--- /dev/null
+++ b/thirdpart/registration/__init__.py
@@ -0,0 +1,12 @@
+VERSION = (0, 8, 0, 'alpha', 1)
+
+def get_version():
+ version = '%s.%s' % (VERSION[0], VERSION[1])
+ if VERSION[2]:
+ version = '%s.%s' % (version, VERSION[2])
+ if VERSION[3:] == ('alpha', 0):
+ version = '%s pre-alpha' % version
+ else:
+ if VERSION[3] != 'final':
+ version = '%s %s %s' % (version, VERSION[3], VERSION[4])
+ return version
diff --git a/thirdpart/registration/admin.py b/thirdpart/registration/admin.py
new file mode 100644
index 0000000000..d7626d750d
--- /dev/null
+++ b/thirdpart/registration/admin.py
@@ -0,0 +1,46 @@
+from django.contrib import admin
+from django.contrib.sites.models import RequestSite
+from django.contrib.sites.models import Site
+from django.utils.translation import ugettext_lazy as _
+
+from registration.models import RegistrationProfile
+
+
+class RegistrationAdmin(admin.ModelAdmin):
+ actions = ['activate_users', 'resend_activation_email']
+ list_display = ('user', 'activation_key_expired')
+ raw_id_fields = ['user']
+ search_fields = ('user__username', 'user__first_name')
+
+ def activate_users(self, request, queryset):
+ """
+ Activates the selected users, if they are not alrady
+ activated.
+
+ """
+ for profile in queryset:
+ RegistrationProfile.objects.activate_user(profile.activation_key)
+ activate_users.short_description = _("Activate users")
+
+ def resend_activation_email(self, request, queryset):
+ """
+ Re-sends activation emails for the selected users.
+
+ Note that this will *only* send activation emails for users
+ who are eligible to activate; emails will not be sent to users
+ whose activation keys have expired or who have already
+ activated.
+
+ """
+ if Site._meta.installed:
+ site = Site.objects.get_current()
+ else:
+ site = RequestSite(request)
+
+ for profile in queryset:
+ if not profile.activation_key_expired():
+ profile.send_activation_email(site)
+ resend_activation_email.short_description = _("Re-send activation emails")
+
+
+admin.site.register(RegistrationProfile, RegistrationAdmin)
diff --git a/thirdpart/registration/auth_urls.py b/thirdpart/registration/auth_urls.py
new file mode 100644
index 0000000000..9bb1bc3cbd
--- /dev/null
+++ b/thirdpart/registration/auth_urls.py
@@ -0,0 +1,58 @@
+"""
+URL patterns for the views included in ``django.contrib.auth``.
+
+Including these URLs (via the ``include()`` directive) will set up the
+following patterns based at whatever URL prefix they are included
+under:
+
+* User login at ``login/``.
+
+* User logout at ``logout/``.
+
+* The two-step password change at ``password/change/`` and
+ ``password/change/done/``.
+
+* The four-step password reset at ``password/reset/``,
+ ``password/reset/confirm/``, ``password/reset/complete/`` and
+ ``password/reset/done/``.
+
+The default registration backend already has an ``include()`` for
+these URLs, so under the default setup it is not necessary to manually
+include these views. Other backends may or may not include them;
+consult a specific backend's documentation for details.
+
+"""
+
+from django.conf.urls.defaults import *
+
+from django.contrib.auth import views as auth_views
+
+
+urlpatterns = patterns('',
+ url(r'^login/$',
+ auth_views.login,
+ {'template_name': 'registration/login.html'},
+ name='auth_login'),
+ url(r'^logout/$',
+ auth_views.logout,
+ {'template_name': 'registration/logout.html'},
+ name='auth_logout'),
+ url(r'^password/change/$',
+ auth_views.password_change,
+ name='auth_password_change'),
+ url(r'^password/change/done/$',
+ auth_views.password_change_done,
+ name='auth_password_change_done'),
+ url(r'^password/reset/$',
+ auth_views.password_reset,
+ name='auth_password_reset'),
+ url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$',
+ auth_views.password_reset_confirm,
+ name='auth_password_reset_confirm'),
+ url(r'^password/reset/complete/$',
+ auth_views.password_reset_complete,
+ name='auth_password_reset_complete'),
+ url(r'^password/reset/done/$',
+ auth_views.password_reset_done,
+ name='auth_password_reset_done'),
+)
diff --git a/thirdpart/registration/backends/__init__.py b/thirdpart/registration/backends/__init__.py
new file mode 100644
index 0000000000..0f2ec4bab4
--- /dev/null
+++ b/thirdpart/registration/backends/__init__.py
@@ -0,0 +1,32 @@
+from django.core.exceptions import ImproperlyConfigured
+
+
+# Python 2.7 has an importlib with import_module; for older Pythons,
+# Django's bundled copy provides it.
+try:
+ from importlib import import_module
+except ImportError:
+ from django.utils.importlib import import_module
+
+def get_backend(path):
+ """
+ Return an instance of a registration backend, given the dotted
+ Python import path (as a string) to the backend class.
+
+ If the backend cannot be located (e.g., because no such module
+ exists, or because the module does not contain a class of the
+ appropriate name), ``django.core.exceptions.ImproperlyConfigured``
+ is raised.
+
+ """
+ i = path.rfind('.')
+ module, attr = path[:i], path[i+1:]
+ try:
+ mod = import_module(module)
+ except ImportError, e:
+ raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
+ try:
+ backend_class = getattr(mod, attr)
+ except AttributeError:
+ raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
+ return backend_class()
diff --git a/thirdpart/registration/backends/default/__init__.py b/thirdpart/registration/backends/default/__init__.py
new file mode 100644
index 0000000000..59b3da776d
--- /dev/null
+++ b/thirdpart/registration/backends/default/__init__.py
@@ -0,0 +1,139 @@
+from django.conf import settings
+from django.contrib.sites.models import RequestSite
+from django.contrib.sites.models import Site
+
+from registration import signals
+from registration.forms import RegistrationForm
+from registration.models import RegistrationProfile
+
+
+class DefaultBackend(object):
+ """
+ A registration backend which follows a simple workflow:
+
+ 1. User signs up, inactive account is created.
+
+ 2. Email is sent to user with activation link.
+
+ 3. User clicks activation link, account is now active.
+
+ Using this backend requires that
+
+ * ``registration`` be listed in the ``INSTALLED_APPS`` setting
+ (since this backend makes use of models defined in this
+ application).
+
+ * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying
+ (as an integer) the number of days from registration during
+ which a user may activate their account (after that period
+ expires, activation will be disallowed).
+
+ * The creation of the templates
+ ``registration/activation_email_subject.txt`` and
+ ``registration/activation_email.txt``, which will be used for
+ the activation email. See the notes for this backends
+ ``register`` method for details regarding these templates.
+
+ Additionally, registration can be temporarily closed by adding the
+ setting ``REGISTRATION_OPEN`` and setting it to
+ ``False``. Omitting this setting, or setting it to ``True``, will
+ be interpreted as meaning that registration is currently open and
+ permitted.
+
+ Internally, this is accomplished via storing an activation key in
+ an instance of ``registration.models.RegistrationProfile``. See
+ that model and its custom manager for full documentation of its
+ fields and supported operations.
+
+ """
+ def register(self, request, **kwargs):
+ """
+ Given a username, email address and password, register a new
+ user account, which will initially be inactive.
+
+ Along with the new ``User`` object, a new
+ ``registration.models.RegistrationProfile`` will be created,
+ tied to that ``User``, containing the activation key which
+ will be used for this account.
+
+ An email will be sent to the supplied email address; this
+ email should contain an activation link. The email will be
+ rendered using two templates. See the documentation for
+ ``RegistrationProfile.send_activation_email()`` for
+ information about these templates and the contexts provided to
+ them.
+
+ After the ``User`` and ``RegistrationProfile`` are created and
+ the activation email is sent, the signal
+ ``registration.signals.user_registered`` will be sent, with
+ the new ``User`` as the keyword argument ``user`` and the
+ class of this backend as the sender.
+
+ """
+ username, email, password = kwargs['username'], kwargs['email'], kwargs['password1']
+ if Site._meta.installed:
+ site = Site.objects.get_current()
+ else:
+ site = RequestSite(request)
+ new_user = RegistrationProfile.objects.create_inactive_user(username, email,
+ password, site)
+ signals.user_registered.send(sender=self.__class__,
+ user=new_user,
+ request=request)
+ return new_user
+
+ def activate(self, request, activation_key):
+ """
+ Given an an activation key, look up and activate the user
+ account corresponding to that key (if possible).
+
+ After successful activation, the signal
+ ``registration.signals.user_activated`` will be sent, with the
+ newly activated ``User`` as the keyword argument ``user`` and
+ the class of this backend as the sender.
+
+ """
+ activated = RegistrationProfile.objects.activate_user(activation_key)
+ if activated:
+ signals.user_activated.send(sender=self.__class__,
+ user=activated,
+ request=request)
+ return activated
+
+ def registration_allowed(self, request):
+ """
+ Indicate whether account registration is currently permitted,
+ based on the value of the setting ``REGISTRATION_OPEN``. This
+ is determined as follows:
+
+ * If ``REGISTRATION_OPEN`` is not specified in settings, or is
+ set to ``True``, registration is permitted.
+
+ * If ``REGISTRATION_OPEN`` is both specified and set to
+ ``False``, registration is not permitted.
+
+ """
+ return getattr(settings, 'REGISTRATION_OPEN', True)
+
+ def get_form_class(self, request):
+ """
+ Return the default form class used for user registration.
+
+ """
+ return RegistrationForm
+
+ def post_registration_redirect(self, request, user):
+ """
+ Return the name of the URL to redirect to after successful
+ user registration.
+
+ """
+ return ('registration_complete', (), {})
+
+ def post_activation_redirect(self, request, user):
+ """
+ Return the name of the URL to redirect to after successful
+ account activation.
+
+ """
+ return ('registration_activation_complete', (), {})
diff --git a/thirdpart/registration/backends/default/urls.py b/thirdpart/registration/backends/default/urls.py
new file mode 100644
index 0000000000..9e992c7668
--- /dev/null
+++ b/thirdpart/registration/backends/default/urls.py
@@ -0,0 +1,54 @@
+"""
+URLconf for registration and activation, using django-registration's
+default backend.
+
+If the default behavior of these views is acceptable to you, simply
+use a line like this in your root URLconf to set up the default URLs
+for registration::
+
+ (r'^accounts/', include('registration.backends.default.urls')),
+
+This will also automatically set up the views in
+``django.contrib.auth`` at sensible default locations.
+
+If you'd like to customize the behavior (e.g., by passing extra
+arguments to the various views) or split up the URLs, feel free to set
+up your own URL patterns for these views instead.
+
+"""
+
+
+from django.conf.urls.defaults import *
+from django.views.generic.simple import direct_to_template
+
+from registration.views import activate
+from registration.views import register
+
+
+urlpatterns = patterns('',
+ url(r'^activate/complete/$',
+ direct_to_template,
+ { 'template': 'registration/activation_complete.html' },
+ name='registration_activation_complete'),
+ # Activation keys get matched by \w+ instead of the more specific
+ # [a-fA-F0-9]{40} because a bad activation key should still get to the view;
+ # that way it can return a sensible "invalid key" message instead of a
+ # confusing 404.
+ url(r'^activate/(?P\w+)/$',
+ activate,
+ { 'backend': 'registration.backends.default.DefaultBackend' },
+ name='registration_activate'),
+ url(r'^register/$',
+ register,
+ { 'backend': 'registration.backends.default.DefaultBackend' },
+ name='registration_register'),
+ url(r'^register/complete/$',
+ direct_to_template,
+ { 'template': 'registration/registration_complete.html' },
+ name='registration_complete'),
+ url(r'^register/closed/$',
+ direct_to_template,
+ { 'template': 'registration/registration_closed.html' },
+ name='registration_disallowed'),
+ (r'', include('registration.auth_urls')),
+ )
diff --git a/thirdpart/registration/forms.py b/thirdpart/registration/forms.py
new file mode 100644
index 0000000000..64dbb803ea
--- /dev/null
+++ b/thirdpart/registration/forms.py
@@ -0,0 +1,131 @@
+"""
+Forms and validation code for user registration.
+
+"""
+
+
+from django.contrib.auth.models import User
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+
+# I put this on all required fields, because it's easier to pick up
+# on them with CSS or JavaScript if they have a class of "required"
+# in the HTML. Your mileage may vary. If/when Django ticket #3515
+# lands in trunk, this will no longer be necessary.
+attrs_dict = { 'class': 'required' }
+
+
+class RegistrationForm(forms.Form):
+ """
+ Form for registering a new user account.
+
+ Validates that the requested username is not already in use, and
+ requires the password to be entered twice to catch typos.
+
+ Subclasses should feel free to add any additional validation they
+ need, but should avoid defining a ``save()`` method -- the actual
+ saving of collected user data is delegated to the active
+ registration backend.
+
+ """
+ username = forms.RegexField(regex=r'^\w+$',
+ max_length=30,
+ widget=forms.TextInput(attrs=attrs_dict),
+ label=_("Username"),
+ error_messages={ 'invalid': _("This value must contain only letters, numbers and underscores.") })
+ email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
+ maxlength=75)),
+ label=_("Email address"))
+ password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+ label=_("Password"))
+ password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
+ label=_("Password (again)"))
+
+ def clean_username(self):
+ """
+ Validate that the username is alphanumeric and is not already
+ in use.
+
+ """
+ try:
+ user = User.objects.get(username__iexact=self.cleaned_data['username'])
+ except User.DoesNotExist:
+ return self.cleaned_data['username']
+ raise forms.ValidationError(_("A user with that username already exists."))
+
+ def clean_email(self):
+ try:
+ user = User.objects.get(email__iexact=self.cleaned_data['email'])
+ except User.DoesNotExist:
+ return self.cleaned_data['email']
+
+ raise forms.ValidationError(_("A user with this email alread"))
+
+ def clean(self):
+ """
+ Verifiy that the values entered into the two password fields
+ match. Note that an error here will end up in
+ ``non_field_errors()`` because it doesn't apply to a single
+ field.
+
+ """
+ if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
+ if self.cleaned_data['password1'] != self.cleaned_data['password2']:
+ raise forms.ValidationError(_("The two password fields didn't match."))
+ return self.cleaned_data
+
+
+class RegistrationFormTermsOfService(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which adds a required checkbox
+ for agreeing to a site's Terms of Service.
+
+ """
+ tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
+ label=_(u'I have read and agree to the Terms of Service'),
+ error_messages={ 'required': _("You must agree to the terms to register") })
+
+
+class RegistrationFormUniqueEmail(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which enforces uniqueness of
+ email addresses.
+
+ """
+ def clean_email(self):
+ """
+ Validate that the supplied email address is unique for the
+ site.
+
+ """
+ if User.objects.filter(email__iexact=self.cleaned_data['email']):
+ raise forms.ValidationError(_("This email address is already in use. Please supply a different email address."))
+ return self.cleaned_data['email']
+
+
+class RegistrationFormNoFreeEmail(RegistrationForm):
+ """
+ Subclass of ``RegistrationForm`` which disallows registration with
+ email addresses from popular free webmail services; moderately
+ useful for preventing automated spam registrations.
+
+ To change the list of banned domains, subclass this form and
+ override the attribute ``bad_domains``.
+
+ """
+ bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
+ 'googlemail.com', 'hotmail.com', 'hushmail.com',
+ 'msn.com', 'mail.ru', 'mailinator.com', 'live.com',
+ 'yahoo.com']
+
+ def clean_email(self):
+ """
+ Check the supplied email address against a list of known free
+ webmail domains.
+
+ """
+ email_domain = self.cleaned_data['email'].split('@')[1]
+ if email_domain in self.bad_domains:
+ raise forms.ValidationError(_("Registration using free email addresses is prohibited. Please supply a different email address."))
+ return self.cleaned_data['email']
diff --git a/thirdpart/registration/locale/ar/LC_MESSAGES/django.mo b/thirdpart/registration/locale/ar/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..07bc79f124
Binary files /dev/null and b/thirdpart/registration/locale/ar/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/ar/LC_MESSAGES/django.po b/thirdpart/registration/locale/ar/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..dd61869d36
--- /dev/null
+++ b/thirdpart/registration/locale/ar/LC_MESSAGES/django.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "اسم المستخدم"
+
+#: forms.py:41
+msgid "email address"
+msgstr "عنوان البريد الالكتروني"
+
+#: forms.py:43
+msgid "password"
+msgstr "كلمة المرور"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "تأكيد كلمة المرور"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "يمكن أن يحتوي اسم المستخدم على احرف، ارقام وشرطات سطرية فقط"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "اسم المستخدم مسجل مسبقا. يرجى اختيار اسم اخر."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "يجب ادخال كلمة المرور مطابقة كل مرة"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "أقر بقراءة والموافقة على شروط الخدمة"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "يجب الموافقة على الشروط للتسجيل"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "عنوان البريد الالكتروني مسجل مسبقا. يرجى تزويد عنوان بريد الكتروني مختلف."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "يمنع التسجيل باستخدام عناوين بريد الكترونية مجانية. يرجى تزويد عنوان بريد الكتروني مختلف."
+
+#: models.py:188
+msgid "user"
+msgstr "مستخدم"
+
+#: models.py:189
+msgid "activation key"
+msgstr "رمز التفعيل"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "ملف التسجيل الشخصي"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "ملفات التسجيل الشخصية"
diff --git a/thirdpart/registration/locale/bg/LC_MESSAGES/django.mo b/thirdpart/registration/locale/bg/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..be9adf4d3d
Binary files /dev/null and b/thirdpart/registration/locale/bg/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/bg/LC_MESSAGES/django.po b/thirdpart/registration/locale/bg/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..5089ec17d3
--- /dev/null
+++ b/thirdpart/registration/locale/bg/LC_MESSAGES/django.po
@@ -0,0 +1,78 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2008-03-05 12:37+0200\n"
+"Last-Translator: Vladislav \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Bookmarks: -1,-1,-1,-1,10,-1,-1,-1,-1,-1\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "Потребителско име "
+
+#: forms.py:41
+msgid "email address"
+msgstr "Електронна поща"
+
+#: forms.py:43
+msgid "password"
+msgstr "Парола"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "Парола (проверка)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Потребителските имена могат да съдържат букви, цифри и подчертавки"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Потребителското име е заето. Моля изберето друго."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Грешка при проверка на паролата."
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "Прочел съм и съм съгласен с условията за експлоатация"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Трябва да сте съгласни с условията за да се регистрирате."
+
+#: forms.py:124
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "Адреса на електронната поща е използван. Моля въведете друг адрес."
+
+#: forms.py:149
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "Регистрациите с безплатни адреси е забранен. Моля въведете различен адрес за електронна поща"
+
+#: models.py:188
+msgid "user"
+msgstr "Потребител"
+
+#: models.py:189
+msgid "activation key"
+msgstr "Ключ за активация"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "регистрационен профил"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "регистрационни профили"
+
diff --git a/thirdpart/registration/locale/da/LC_MESSAGES/django.mo b/thirdpart/registration/locale/da/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..9478680094
Binary files /dev/null and b/thirdpart/registration/locale/da/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/da/LC_MESSAGES/django.po b/thirdpart/registration/locale/da/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..7bb0ac8f71
--- /dev/null
+++ b/thirdpart/registration/locale/da/LC_MESSAGES/django.po
@@ -0,0 +1,92 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Rune Bromer , 2007-2009.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration 0.8 \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Rune Bromer \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr "Aktiver brugere"
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr "Gensend aktiveringsemails"
+
+#: forms.py:35
+msgid "Username"
+msgstr "Brugernavn"
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "V¾rdien m kun indeholde bogstaver, tal og underscore."
+
+#: forms.py:39
+msgid "Email address"
+msgstr "E-mailadresse"
+
+#: forms.py:41
+msgid "Password"
+msgstr "Password"
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr "Password (gentag)"
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr "Der findes allerede en bruger med dette brugernavn."
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr "De 2 passwordfelter er ikke ens."
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr "I har l¾st og accepterer betingelserne."
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr "Du skal acceptere betingelserne for at registere"
+
+#: forms.py:95
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"Denne emailadresse er allerede i brug. Benyt venligst en anden. "
+
+#: forms.py:122
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"Registrering med gratis emailadresser er ikke muligt. V¾lg venligst en "
+"anden emailadresse"
+
+#: models.py:165
+msgid "user"
+msgstr "bruger"
+
+#: models.py:166
+msgid "activation key"
+msgstr "Aktiveringsn¿gle"
+
+#: models.py:171
+msgid "registration profile"
+msgstr "Registreringsprofil"
+
+#: models.py:172
+msgid "registration profiles"
+msgstr "Registreringprofiler"
diff --git a/thirdpart/registration/locale/de/LC_MESSAGES/django.mo b/thirdpart/registration/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..b272d4463c
Binary files /dev/null and b/thirdpart/registration/locale/de/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/de/LC_MESSAGES/django.po b/thirdpart/registration/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..b1e44f4299
--- /dev/null
+++ b/thirdpart/registration/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,93 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Jannis Leidel , 2007-2009.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration 0.8 \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-18 21:32+0200\n"
+"PO-Revision-Date: 2007-09-29 16:50+0200\n"
+"Last-Translator: Jannis Leidel \n"
+"Language-Team: Deutsch \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr "Benutzer aktivieren"
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr "Aktivierungs-E-Mail erneut senden"
+
+#: forms.py:35
+msgid "Username"
+msgstr "Benutzername"
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Dieser Wert darf nur Buchstaben, Ziffern und Unterstriche enthalten."
+
+#: forms.py:39
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
+#: forms.py:41
+msgid "Password"
+msgstr "Passwort"
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr "Passwort (wiederholen)"
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr "Dieser Benutzername ist bereits vergeben."
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr "Die beiden Passwörter sind nicht identisch."
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr "Ich habe die Nutzungsvereinbarung gelesen und stimme ihr zu"
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr "Sie müssen der Nutzungsvereinbarung zustimmen, um sich zu registrieren"
+
+#: forms.py:95
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"Diese E-Mail-Adresse wird schon genutzt. Bitte geben Sie eine andere E-Mail-"
+"Adresse an."
+
+#: forms.py:122
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"Die Registrierung mit einer kostenlosen E-Mail-Adresse ist untersagt. Bitte "
+"geben Sie eine andere E-Mail-Adresse an."
+
+#: models.py:165
+msgid "user"
+msgstr "Benutzer"
+
+#: models.py:166
+msgid "activation key"
+msgstr "Aktivierungsschlüssel"
+
+#: models.py:171
+msgid "registration profile"
+msgstr "Registrierungsprofil"
+
+#: models.py:172
+msgid "registration profiles"
+msgstr "Registrierungsprofile"
diff --git a/thirdpart/registration/locale/el/LC_MESSAGES/django.mo b/thirdpart/registration/locale/el/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..acc972683e
Binary files /dev/null and b/thirdpart/registration/locale/el/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/el/LC_MESSAGES/django.po b/thirdpart/registration/locale/el/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..cd38eb1b46
--- /dev/null
+++ b/thirdpart/registration/locale/el/LC_MESSAGES/django.po
@@ -0,0 +1,84 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Panos Laganakos , 2007.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2007-11-14 21:50+0200\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "όνομα χρήστη"
+
+#: forms.py:41
+msgid "email address"
+msgstr "διεύθυνση ηλεκτρονικού ταχυδρομείου"
+
+#: forms.py:43
+msgid "password"
+msgstr "συνθηματικό"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "συνθηματικό (ξανά)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Τα ονόματα χρηστών μπορούν να περιλαμβάνουν μόνο γράμματα, αριθμούς και υπογραμμίσεις"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Αυτό το όνομα χρήστη χρησιμοποίειται ήδη. Παρακαλώ διαλέξτε ένα άλλο."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Πρέπει να εισάγετε το ίδιο συνθηματικό κάθε φορά"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "Διάβασα και συμφωνώ με τους Όρους της Υπηρεσίας"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Πρέπει να συμφωνείται με τους όρους για να εγγραφείτε"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"Η συγκεκριμένη διεύθυνση ηλεκτρονικού ταχυδρομείου χρησιμοποιείται ήδη. "
+"Παρακαλώ δώστε κάποια άλλη."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"Η εγγραφή μέσω δωρεάν διευθύνσεων ηλεκτρονικού ταχυδρομείου απαγορεύεται. ""Παρακαλώ δώστε κάποια άλλη."
+
+#: models.py:188
+msgid "user"
+msgstr "χρήστης"
+
+#: models.py:189
+msgid "activation key"
+msgstr "κλειδί ενεργοποίησης"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "προφίλ εγγραφής"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "προφίλ εγγραφών"
diff --git a/thirdpart/registration/locale/en/LC_MESSAGES/django.mo b/thirdpart/registration/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..87b6226b04
Binary files /dev/null and b/thirdpart/registration/locale/en/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/en/LC_MESSAGES/django.po b/thirdpart/registration/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..e357a4cef3
--- /dev/null
+++ b/thirdpart/registration/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,89 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-12 14:09-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr ""
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr ""
+
+#: forms.py:35
+msgid "username"
+msgstr ""
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr ""
+
+#: forms.py:39
+msgid "Email address"
+msgstr ""
+
+#: forms.py:41
+msgid "Password"
+msgstr ""
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr ""
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr ""
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr ""
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr ""
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr ""
+
+#: forms.py:95
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+
+#: forms.py:122
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+
+#: models.py:165
+msgid "user"
+msgstr ""
+
+#: models.py:166
+msgid "activation key"
+msgstr ""
+
+#: models.py:171
+msgid "registration profile"
+msgstr ""
+
+#: models.py:172
+msgid "registration profiles"
+msgstr ""
diff --git a/thirdpart/registration/locale/es/LC_MESSAGES/django.mo b/thirdpart/registration/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..3872adf0a0
Binary files /dev/null and b/thirdpart/registration/locale/es/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/es/LC_MESSAGES/django.po b/thirdpart/registration/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..ba0384fe54
--- /dev/null
+++ b/thirdpart/registration/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,85 @@
+# Spanish translation for django-registration.
+# Copyright (C) 2007, James Bennet
+# This file is distributed under the same license as the registration package.
+# Ernesto Rico Schmidt , 2008.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration 0.3 \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-11 00:19-0400\n"
+"PO-Revision-Date: 2008-03-11 00:19-0400\n"
+"Last-Translator: Ernesto Rico Schmidt \n"
+"Language-Team: Español \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "nombre de usuario"
+
+#: forms.py:41
+msgid "email address"
+msgstr "dirección de coreo electrónico"
+
+#: forms.py:43
+msgid "password"
+msgstr "contraseña"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "contraseña (otra vez)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Los nombres de usuarios sólo pueden contener letras, números y guiones bajos"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Este nombre de usuario ya está ocupado. Por favor escoge otro"
+
+#: forms.py:71
+msgid "You must type the same password each time"
+msgstr "Tienes que introducir la misma contraseña cada vez"
+
+#: forms.py:100
+msgid "I have read and agree to the Terms of Service"
+msgstr "He leído y acepto los términos de servicio"
+
+#: forms.py:109
+msgid "You must agree to the terms to register"
+msgstr "Tienes que aceptar los términos para registrarte"
+
+#: forms.py:128
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"La dirección de correo electrónico ya está siendo usada. Por favor"
+"proporciona otra dirección."
+
+#: forms.py:153
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"El registro usando una dirección de correo electrónico gratis está prohibido."
+"Por favor proporciona otra dirección."
+
+#: models.py:188
+msgid "user"
+msgstr "usuario"
+
+#: models.py:189
+msgid "activation key"
+msgstr "clave de activación"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "perfil de registro"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "perfiles de registro"
diff --git a/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.mo b/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..ce8b4e56c8
Binary files /dev/null and b/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.po b/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..fb746b5b96
--- /dev/null
+++ b/thirdpart/registration/locale/es_AR/LC_MESSAGES/django.po
@@ -0,0 +1,83 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2008 Leonardo Manuel Rocha
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "nombre de usuario"
+
+#: forms.py:41
+msgid "email address"
+msgstr "dirección de e-mail"
+
+#: forms.py:43
+msgid "password"
+msgstr "contraseña"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "contraseña (nuevamente)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "El nombre de usuario solo puede contener letras, números y guiones bajos"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Ese nombre de usuario ya está asignado. Por favor elija otro."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Debe tipear la misma contraseña cada vez"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "He leído y estoy de acuerdo con las Condiciones de Servicio"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Debe estar de acuerdo con las Condiciones para poder registrarse"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Esa dirección de e-mail ya está en uso. Por favor provea otra "
+"dirección."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "La registración con un e-mail gratuito está prohibida. Por favor "
+"de una dirección de e-mail diferente."
+
+#: models.py:188
+msgid "user"
+msgstr "usuario"
+
+#: models.py:189
+msgid "activation key"
+msgstr "clave de activación"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "perfil de registro"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "perfiles de registro"
diff --git a/thirdpart/registration/locale/fr/LC_MESSAGES/django.mo b/thirdpart/registration/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..f8911c7ec6
Binary files /dev/null and b/thirdpart/registration/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/fr/LC_MESSAGES/django.po b/thirdpart/registration/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..34b520b8be
--- /dev/null
+++ b/thirdpart/registration/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Samuel Adam , 2007.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration 0.3 \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2007-09-20 10:30+0100\n"
+"Last-Translator: Samuel Adam \n"
+"Language-Team: Français \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "pseudo"
+
+#: forms.py:41
+msgid "email address"
+msgstr "adresse email"
+
+#: forms.py:43
+msgid "password"
+msgstr "mot de passe"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "mot de passe (vérification)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Le pseudo ne peut contenir que des lettres, chiffres et le caractère souligné."
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Ce pseudo est déjà utilisé. Veuillez en choisir un autre."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Veuillez indiquer le même mot de passe dans les deux champs"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "J'ai lu et accepté les Conditions Générales d'Utilisation"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Vous devez accepter les conditions d'utilisation pour vous inscrire"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Cette adresse email est déjà utilisée. Veuillez en indiquer une autre."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "L'inscription avec une adresse email d'un compte gratuit est interdite. Veuillez en indiquer une autre."
+
+#: models.py:188
+msgid "user"
+msgstr "utilisateur"
+
+#: models.py:189
+msgid "activation key"
+msgstr "clé d'activation"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "profil d'inscription"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "profils d'inscription"
diff --git a/thirdpart/registration/locale/he/LC_MESSAGES/django.mo b/thirdpart/registration/locale/he/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..be93650323
Binary files /dev/null and b/thirdpart/registration/locale/he/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/he/LC_MESSAGES/django.po b/thirdpart/registration/locale/he/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..5567e08635
--- /dev/null
+++ b/thirdpart/registration/locale/he/LC_MESSAGES/django.po
@@ -0,0 +1,86 @@
+# translation of registration.
+# Copyright (C) 2008 THE registration'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the registration package.
+# <>, 2008.
+# , fuzzy
+# <>, 2008.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: registration\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-02-10 02:01+0200\n"
+"PO-Revision-Date: 2008-02-10 02:05+0200\n"
+"Last-Translator: Meir Kriheli \n"
+"Language-Team: Hebrew\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit"
+
+#: forms.py:38
+msgid "username"
+msgstr "שם משתמש"
+
+#: forms.py:41
+msgid "email address"
+msgstr "דואר אלקטרוני"
+
+#: forms.py:43
+msgid "password"
+msgstr "סיסמה"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "סיסמה (שוב)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "שמות משתמש יכולים להכיל רק אותיות, ספרות וקווים תחתונים"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "שם המשתמש תפוס כבר. נא לבחור אחר."
+
+#: forms.py:64
+msgid "You must type the same password each time"
+msgstr "יש להקליד את אותה הסיסמה פעמיים"
+
+#: forms.py:93
+msgid "I have read and agree to the Terms of Service"
+msgstr "קראתי והסכמתי לתנאי השימוש"
+
+#: forms.py:102
+msgid "You must agree to the terms to register"
+msgstr "עליך להסכים לתנאי השימוש"
+
+#: forms.py:121
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"כתובת הדואר האלקטרוני תפוסה כבר. נא לספק כתובת דואר אחרת."
+
+#: forms.py:146
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"הרישום בעזרת תיבת דואר אלקטרוני חינמית אסור. נא לספק כתובת אחרת."
+
+#: models.py:188
+msgid "user"
+msgstr "משתמש"
+
+#: models.py:189
+msgid "activation key"
+msgstr "מפתח הפעלה"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "פרופיל רישום"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "פרופילי רישום"
+
diff --git a/thirdpart/registration/locale/is/LC_MESSAGES/django.mo b/thirdpart/registration/locale/is/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..bccb71f63a
Binary files /dev/null and b/thirdpart/registration/locale/is/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/is/LC_MESSAGES/django.po b/thirdpart/registration/locale/is/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..479e792a92
--- /dev/null
+++ b/thirdpart/registration/locale/is/LC_MESSAGES/django.po
@@ -0,0 +1,74 @@
+# Icelandic translation of django-registration
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the django-registration
+# package.
+# Björn Kristinsson , 2009.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-22 12:49+0100\n"
+"PO-Revision-Date: 2009-01-22 12:49+0100\n"
+"Last-Translator: Björn Kristinsson \n"
+"Language-Team: Icelandic\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:36
+msgid "username"
+msgstr "notandanafn"
+
+#: forms.py:39
+msgid "email address"
+msgstr "netfang"
+
+#: forms.py:41
+msgid "password"
+msgstr "lykilorð"
+
+#: forms.py:43
+msgid "password (again)"
+msgstr "lykilorð (aftur)"
+
+#: forms.py:55
+msgid "This username is already taken. Please choose another."
+msgstr "Þetta notendanafn er þegar á skrá. Vinsamlega reyndu annað."
+
+#: forms.py:67
+msgid "You must type the same password each time"
+msgstr "Lykilorðin verða að vera eins "
+
+#: forms.py:90
+msgid "I have read and agree to the Terms of Service"
+msgstr "Ég hef lesið og samþykki skilmálana"
+
+#: forms.py:107
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Þetta netfang er þegar á skrá. Vinsamlegast notaðu annað netfang."
+
+#: forms.py:133
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "Óheimilt er að nota ókeypis netföng. Vinsamlegast notaðu annað netfang."
+
+#: models.py:218
+msgid "user"
+msgstr "notandi"
+
+#: models.py:219
+msgid "activation key"
+msgstr "einkennislykill"
+
+#: models.py:224
+msgid "registration profile"
+msgstr "skráningarprófíll"
+
+#: models.py:225
+msgid "registration profiles"
+msgstr "skráningarprófílar"
diff --git a/thirdpart/registration/locale/it/LC_MESSAGES/django.mo b/thirdpart/registration/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..83ec9ddb16
Binary files /dev/null and b/thirdpart/registration/locale/it/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/it/LC_MESSAGES/django.po b/thirdpart/registration/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..00129b044e
--- /dev/null
+++ b/thirdpart/registration/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,82 @@
+# translation of django.po to Italiano
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Nicola Larosa , 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2008-05-27 15:05+0200\n"
+"Last-Translator: Nicola Larosa \n"
+"Language-Team: Italiano\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "nome utente"
+
+#: forms.py:41
+msgid "email address"
+msgstr "indirizzo email"
+
+#: forms.py:43
+msgid "password"
+msgstr "password"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "password (di nuovo)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "I nomi utente possono contenere solo lettere, numeri e sottolineature"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Questo nome utente è già usato. Scegline un altro."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Bisogna inserire la stessa password ogni volta"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "Dichiaro di aver letto e di approvare le Condizioni di Servizio"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Per registrarsi bisogna approvare le condizioni"
+
+#: forms.py:124
+msgid "This email address is already in use. Please supply a different email "
+"address."
+msgstr "Questo indirizzo email è già in uso. Inserisci un altro indirizzo email."
+
+#: forms.py:149
+msgid "Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "La registrazione con indirizzi email gratis non è permessa. "
+"Inserisci un altro indirizzo email."
+
+#: models.py:188
+msgid "user"
+msgstr "utente"
+
+#: models.py:189
+msgid "activation key"
+msgstr "chiave di attivazione"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "profilo di registrazione"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "profili di registrazione"
+
diff --git a/thirdpart/registration/locale/ja/LC_MESSAGES/django.mo b/thirdpart/registration/locale/ja/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..e0332b0e7c
Binary files /dev/null and b/thirdpart/registration/locale/ja/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/ja/LC_MESSAGES/django.po b/thirdpart/registration/locale/ja/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..afaaf948b9
--- /dev/null
+++ b/thirdpart/registration/locale/ja/LC_MESSAGES/django.po
@@ -0,0 +1,78 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Shinya Okano , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration 0.4 \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2008-01-31 10:20+0900\n"
+"Last-Translator: Shinya Okano \n"
+"Language-Team: Japanese \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "ユーザ名"
+
+#: forms.py:41
+msgid "email address"
+msgstr "メールアドレス"
+
+#: forms.py:43
+msgid "password"
+msgstr "パスワード"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "パスワード (確認)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "ユーザ名には半角英数とアンダースコアのみが使用できます。"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "このユーザ名は既に使用されています。他のユーザ名を指定してください。"
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "同じパスワードを入力する必要があります。"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "サービス利用規約を読み、同意します。"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "登録するためには規約に同意する必要があります。"
+
+#: forms.py:124
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "このメールアドレスは既に使用されています。他のメールアドレスを指定して下さい。"
+
+#: forms.py:149
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "自由なメールアドレスを使用した登録は禁止されています。他のメールアドレスを指定してください。"
+
+#: models.py:188
+msgid "user"
+msgstr "ユーザ"
+
+#: models.py:189
+msgid "activation key"
+msgstr "アクティベーションキー"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "登録プロファイル"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "登録プロファイル"
+
diff --git a/thirdpart/registration/locale/ko/LC_MESSAGES/django.mo b/thirdpart/registration/locale/ko/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..a29c7c3fb6
Binary files /dev/null and b/thirdpart/registration/locale/ko/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/ko/LC_MESSAGES/django.po b/thirdpart/registration/locale/ko/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..d466420d7e
--- /dev/null
+++ b/thirdpart/registration/locale/ko/LC_MESSAGES/django.po
@@ -0,0 +1,89 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Young Gyu Park , 2009.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-12 14:09-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Young Gyu Park \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr "활동 사용자"
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr "이 메일 제 전송"
+
+#: forms.py:35
+msgid "username"
+msgstr "사용자 아이디"
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "이 곳에는 숫자, _, 영문 글자만 가능합니다."
+
+#: forms.py:39
+msgid "Email address"
+msgstr "이메일 주소"
+
+#: forms.py:41
+msgid "Password"
+msgstr "사용자 패스워드"
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr "패스워드 (재입력)"
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr "이미 같은 아이디로 사용자가 등록되어 있습니다."
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr "패스워드가 서로 일치하지 않습니다."
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr "약관을 읽었고 그 내용에 동의합니다."
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr "약관에 동의 하셔야만 합니다."
+
+#: forms.py:95
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "이메일이 이미 사용중입니다. 다른 이메일을 등록해 주세요."
+
+#: forms.py:122
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "무료 이메일 계정으로 등록하실 수 없습니다. 다른 이메일을 등록해 주세요"
+
+#: models.py:165
+msgid "user"
+msgstr "사용자"
+
+#: models.py:166
+msgid "activation key"
+msgstr "활성화 키"
+
+#: models.py:171
+msgid "registration profile"
+msgstr "등록 프로파일"
+
+#: models.py:172
+msgid "registration profiles"
+msgstr "등록 프로파일"
diff --git a/thirdpart/registration/locale/nl/LC_MESSAGES/django.mo b/thirdpart/registration/locale/nl/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..9e84eb3ed6
Binary files /dev/null and b/thirdpart/registration/locale/nl/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/nl/LC_MESSAGES/django.po b/thirdpart/registration/locale/nl/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..03cb2e5db8
--- /dev/null
+++ b/thirdpart/registration/locale/nl/LC_MESSAGES/django.po
@@ -0,0 +1,77 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: registration\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-08-14 13:25+0200\n"
+"PO-Revision-Date: 2008-08-14 13:25+0200\n"
+"Last-Translator: Joost Cassee \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "gebruikersnaam"
+
+#: forms.py:41
+msgid "email address"
+msgstr "e-mail adres"
+
+#: forms.py:43
+msgid "password"
+msgstr "wachtwoord"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "wachtwoord (opnieuw)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Gebruikersnamen kunnen alleen letters, nummer en liggende streepjes bevatten."
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Deze gebruikersnaam is reeds in gebruik. Kiest u alstublieft een andere gebruikersnaam."
+
+#: forms.py:71
+msgid "You must type the same password each time"
+msgstr "U moet twee maal hetzelfde wachtwoord typen."
+
+#: forms.py:100
+msgid "I have read and agree to the Terms of Service"
+msgstr "Ik heb de servicevoorwaarden gelezen en ga akkoord."
+
+#: forms.py:109
+msgid "You must agree to the terms to register"
+msgstr "U moet akkoord gaan met de servicevoorwaarden om u te registreren."
+
+#: forms.py:125
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "Dit e-mail adres is reeds in gebruik. Kiest u alstublieft een ander e-mail adres."
+
+#: forms.py:151
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "U kunt u niet registreren met een gratis e-mail adres. Kiest u alstublieft een ander e-mail adres."
+
+#: models.py:191
+msgid "user"
+msgstr "gebruiker"
+
+#: models.py:192
+msgid "activation key"
+msgstr "activatiecode"
+
+#: models.py:197
+msgid "registration profile"
+msgstr "registratieprofiel"
+
+#: models.py:198
+msgid "registration profiles"
+msgstr "registratieprofielen"
diff --git a/thirdpart/registration/locale/pl/LC_MESSAGES/django.mo b/thirdpart/registration/locale/pl/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..1f2a228861
Binary files /dev/null and b/thirdpart/registration/locale/pl/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/pl/LC_MESSAGES/django.po b/thirdpart/registration/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..498fd5b6b3
--- /dev/null
+++ b/thirdpart/registration/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,84 @@
+# Polish translation for django-registration.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the django-registration package.
+# Jarek Zgoda , 2007.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.4\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2007-12-15 12:45+0100\n"
+"Last-Translator: Jarek Zgoda \n"
+"Language-Team: Polish \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "nazwa użytkownika"
+
+#: forms.py:41
+msgid "email address"
+msgstr "adres email"
+
+#: forms.py:43
+msgid "password"
+msgstr "hasło"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "hasło (ponownie)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr ""
+"Nazwa użytkownika może zawierać tylko litery, cyfry i znaki podkreślenia"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Ta nazwa użytkownika jest już zajęta. Wybierz inną."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Musisz wpisać to samo hasło w obu polach"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "Przeczytałem regulamin i akceptuję go"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Musisz zaakceptować regulamin, aby się zarejestrować"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Ten adres email jest już używany. Użyj innego adresu email."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"Nie ma możliwości rejestracji przy użyciu darmowego adresu email. Użyj "
+"innego adresu email."
+
+#: models.py:188
+msgid "user"
+msgstr "użytkownik"
+
+#: models.py:189
+msgid "activation key"
+msgstr "klucz aktywacyjny"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "profil rejestracji"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "profile rejestracji"
diff --git a/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.mo b/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..e81b6207be
Binary files /dev/null and b/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.po b/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..9e8addb6f8
--- /dev/null
+++ b/thirdpart/registration/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "usuário"
+
+#: forms.py:41
+msgid "email address"
+msgstr "endereço de email"
+
+#: forms.py:43
+msgid "password"
+msgstr ""
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "senha (novamente)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Nomes de usuário apenas podem conter letras, números, e underscore"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Este nome de usuário já existe. Por favor, escolha outro."
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "Você deve escrever a mesma senha nos dois campos"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "Eu lí e concordo com os Termos de Uso do serviço"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "Você deve concordar com os termos para registrar-se"
+
+#: forms.py:124
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Este endereço de email já está em uso. Por favor, informe um endereço de email diferente."
+
+#: forms.py:149
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "Registrar-se com contas de email gratuitos está proibido. Por favor, informe um endereço de email diferente."
+
+#: models.py:188
+msgid "user"
+msgstr "usuário"
+
+#: models.py:189
+msgid "activation key"
+msgstr "chave de ativação"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "profile de registro"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "profiles de registro"
diff --git a/thirdpart/registration/locale/ru/LC_MESSAGES/django.mo b/thirdpart/registration/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..1635b4ed68
Binary files /dev/null and b/thirdpart/registration/locale/ru/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/ru/LC_MESSAGES/django.po b/thirdpart/registration/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..072e146a0d
--- /dev/null
+++ b/thirdpart/registration/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,92 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-21 20:12+0600\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr "Активировать учетные записи"
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr "Выслать ключи активации заново"
+
+#: forms.py:35
+msgid "Username"
+msgstr "Имя пользователя"
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Это поле может содержать только буквы, цифры и подчеркивания"
+
+#: forms.py:39
+msgid "Email address"
+msgstr "Адрес электронной почты"
+
+#: forms.py:41
+msgid "Password"
+msgstr "Пароль"
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr "Пароль (снова)"
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr "Пользователь с таким именем уже существует."
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr "Введенные пароли не совпадают."
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr "Я прочитал Правила Использования и согласен с ними"
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr "Для регистрации Вы должны согласиться с Правилами"
+
+#: forms.py:95
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr ""
+"Этот адрес электронной почты уже используется. Пожалуйста, введите другой "
+"адрес."
+
+#: forms.py:122
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr ""
+"Регистрация с использованием свободных почтовых серверов запрещена. "
+"Пожалуйста, введите другой адрес электронной почты."
+
+#: models.py:165
+msgid "user"
+msgstr "пользователь"
+
+#: models.py:166
+msgid "activation key"
+msgstr "ключ активации"
+
+#: models.py:171
+msgid "registration profile"
+msgstr "карточка регистрации"
+
+#: models.py:172
+msgid "registration profiles"
+msgstr "карточки регистрации"
diff --git a/thirdpart/registration/locale/sl/LC_MESSAGES/django.mo b/thirdpart/registration/locale/sl/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..036163f3fe
Binary files /dev/null and b/thirdpart/registration/locale/sl/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/sl/LC_MESSAGES/django.po b/thirdpart/registration/locale/sl/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..c587c70e77
--- /dev/null
+++ b/thirdpart/registration/locale/sl/LC_MESSAGES/django.po
@@ -0,0 +1,87 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.8.1beta\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-10-12 14:09-0500\n"
+"PO-Revision-Date: 2009-10-23 15:49+0100\n"
+"Last-Translator: Domen Kožar \n"
+"Language-Team: Slovenian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Slovenian\n"
+"X-Poedit-Country: SLOVENIA\n"
+
+#: admin.py:23
+msgid "Activate users"
+msgstr "Aktiviraj uporabnike"
+
+#: admin.py:43
+msgid "Re-send activation emails"
+msgstr "Ponovno pošlju aktivacijske emaile"
+
+#: forms.py:35
+msgid "username"
+msgstr "uporabniško ime"
+
+#: forms.py:36
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Vrednost lahko vsebuje samo črke, cifre in podčrtaje."
+
+#: forms.py:39
+msgid "Email address"
+msgstr "Elektronska pošta"
+
+#: forms.py:41
+msgid "Password"
+msgstr "Geslo"
+
+#: forms.py:43
+msgid "Password (again)"
+msgstr "Geslo (ponovno)"
+
+#: forms.py:55
+msgid "A user with that username already exists."
+msgstr "Uporabnik z tem uporabniškim imenom že obstaja."
+
+#: forms.py:67
+msgid "The two password fields didn't match."
+msgstr "Polji z gesli se ne ujemata."
+
+#: forms.py:78
+msgid "I have read and agree to the Terms of Service"
+msgstr "Strinjam se z pogoji uporable"
+
+#: forms.py:79
+msgid "You must agree to the terms to register"
+msgstr "Za registracijo se morate strinjati z pogoji uporabe"
+
+#: forms.py:95
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "Email je že v uporabi, prosimo vnesite drugega."
+
+#: forms.py:122
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "Registracija ni mogoča z brezplačnimi email naslovi. Prosimo vnesite drug email naslov."
+
+#: models.py:165
+msgid "user"
+msgstr "Uporabnik"
+
+#: models.py:166
+msgid "activation key"
+msgstr "Aktivacijski ključ"
+
+#: models.py:171
+msgid "registration profile"
+msgstr "Registracijski profil"
+
+#: models.py:172
+msgid "registration profiles"
+msgstr "Registracijski profili"
+
diff --git a/thirdpart/registration/locale/sr/LC_MESSAGES/django.mo b/thirdpart/registration/locale/sr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..1699326553
Binary files /dev/null and b/thirdpart/registration/locale/sr/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/sr/LC_MESSAGES/django.po b/thirdpart/registration/locale/sr/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..4fa699c551
--- /dev/null
+++ b/thirdpart/registration/locale/sr/LC_MESSAGES/django.po
@@ -0,0 +1,80 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django-registration trunk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-04-05 13:51+0200\n"
+"PO-Revision-Date: 2008-04-05 14:00+0100\n"
+"Last-Translator: Nebojsa Djordjevic \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"X-Poedit-Language: Serbian\n"
+"X-Poedit-Country: YUGOSLAVIA\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "korisničko ime"
+
+#: forms.py:41
+msgid "email address"
+msgstr "email adresa"
+
+#: forms.py:43
+msgid "password"
+msgstr "šifra"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "šifra (ponovo)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Korisničko ime može da se sastoji samo od slova, brojeva i donje crte (\"_\")"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Korisničko ime je već zauzeto. Izaberite drugo."
+
+#: forms.py:71
+msgid "You must type the same password each time"
+msgstr "Unete šifre se ne slažu"
+
+#: forms.py:100
+msgid "I have read and agree to the Terms of Service"
+msgstr "Pročitao sam i slažem se sa uslovima korišćenja"
+
+#: forms.py:109
+msgid "You must agree to the terms to register"
+msgstr "Morate se složiti sa uslovima korišćenja da bi ste se registrovali"
+
+#: forms.py:128
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "Ova e-mail adresa je već u upotrebi. Morate koristiti drugu e-mail adresu."
+
+#: forms.py:153
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "Registracija korišćenjem besplatnig e-mail adresa je zabranjena. Morate uneti drugu e-mail adresu."
+
+#: models.py:188
+msgid "user"
+msgstr "korisnik"
+
+#: models.py:189
+msgid "activation key"
+msgstr "aktivacioni ključ"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "registracioni profil"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "registracioni profili"
+
diff --git a/thirdpart/registration/locale/sv/LC_MESSAGES/django.mo b/thirdpart/registration/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..50eca67e21
Binary files /dev/null and b/thirdpart/registration/locale/sv/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/sv/LC_MESSAGES/django.po b/thirdpart/registration/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..dec76e273b
--- /dev/null
+++ b/thirdpart/registration/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-23 18:59+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Emil Stenström \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: .\forms.py:38
+msgid "username"
+msgstr "Användarnamn"
+
+#: .\forms.py:41
+msgid "email address"
+msgstr "E-postadress"
+
+#: .\forms.py:43
+msgid "password"
+msgstr "Lösenord"
+
+#: .\forms.py:45
+msgid "password (again)"
+msgstr "Lösenord (igen)"
+
+#: .\forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "Användarnamn får bara innehålla bokstäver, siffror och understreck"
+
+#: .\forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "Det användarnamnet är upptaget. Prova ett annat."
+
+#: .\forms.py:71
+msgid "You must type the same password each time"
+msgstr "Båda lösenord måste vara lika"
+
+#: .\forms.py:100
+msgid "I have read and agree to the Terms of Service"
+msgstr "Jag har läst och accepterar avtalet"
+
+#: .\forms.py:109
+msgid "You must agree to the terms to register"
+msgstr "Du måste acceptera avtalet för att registrera dig"
+
+#: .\forms.py:128
+msgid ""
+"This email address is already in use. Please supply a different email "
+"address."
+msgstr "Den e-postadressen är upptagen, använd an annan adress."
+
+#: .\forms.py:153
+msgid ""
+"Registration using free email addresses is prohibited. Please supply a "
+"different email address."
+msgstr "Gratis e-postadresser är inte tillåtna, använd en annan adress."
+
+#: .\models.py:188
+msgid "user"
+msgstr "Användare"
+
+#: .\models.py:189
+msgid "activation key"
+msgstr "Aktiveringsnyckel"
+
+#: .\models.py:194
+msgid "registration profile"
+msgstr "Profil"
+
+#: .\models.py:195
+msgid "registration profiles"
+msgstr "Profiler"
diff --git a/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.mo b/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..ece5cc97b1
Binary files /dev/null and b/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.po b/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..7c609c3161
--- /dev/null
+++ b/thirdpart/registration/locale/zh_CN/LC_MESSAGES/django.po
@@ -0,0 +1,77 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2008-03-20 23:22+0800\n"
+"Last-Translator: hutuworm \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "用户名"
+
+#: forms.py:41
+msgid "email address"
+msgstr "Email 地址"
+
+#: forms.py:43
+msgid "password"
+msgstr "密码"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "密码(重复)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "用户名只能包含字母、数字和下划线"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "该用户名已被占用,请另选一个。"
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "您必须输入两遍同样的密码"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "我已阅读并同意该服务条款"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "您必须同意注册条款"
+
+#: forms.py:124
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "该 Email 地址已有人使用,请提供一个另外的 Email 地址。"
+
+#: forms.py:149
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "禁止使用免费 Email 地址注册,请提供一个另外的 Email 地址。"
+
+#: models.py:188
+msgid "user"
+msgstr "用户"
+
+#: models.py:189
+msgid "activation key"
+msgstr "激活密钥"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "注册信息"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "注册信息"
+
diff --git a/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.mo b/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..24a3534958
Binary files /dev/null and b/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.mo differ
diff --git a/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.po b/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..7cc090d08c
--- /dev/null
+++ b/thirdpart/registration/locale/zh_TW/LC_MESSAGES/django.po
@@ -0,0 +1,77 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-09-19 19:30-0500\n"
+"PO-Revision-Date: 2008-03-20 23:22+0800\n"
+"Last-Translator: hutuworm \n"
+"Language-Team: LANGUAGE \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: forms.py:38
+msgid "username"
+msgstr "用戶名"
+
+#: forms.py:41
+msgid "email address"
+msgstr "Email 地址"
+
+#: forms.py:43
+msgid "password"
+msgstr "密碼"
+
+#: forms.py:45
+msgid "password (again)"
+msgstr "密碼(重復)"
+
+#: forms.py:54
+msgid "Usernames can only contain letters, numbers and underscores"
+msgstr "用戶名只能包含字母、數字和下劃線"
+
+#: forms.py:59
+msgid "This username is already taken. Please choose another."
+msgstr "該用戶名已被佔用,請另選一個。"
+
+#: forms.py:68
+msgid "You must type the same password each time"
+msgstr "您必須輸入兩遍同樣的密碼"
+
+#: forms.py:96
+msgid "I have read and agree to the Terms of Service"
+msgstr "我已閱讀並同意該服務條款"
+
+#: forms.py:105
+msgid "You must agree to the terms to register"
+msgstr "您必須同意注冊條款"
+
+#: forms.py:124
+msgid "This email address is already in use. Please supply a different email address."
+msgstr "該 Email 地址已有人使用,請提供一個另外的 Email 地址。"
+
+#: forms.py:149
+msgid "Registration using free email addresses is prohibited. Please supply a different email address."
+msgstr "禁止使用免費 Email 地址注冊,請提供一個另外的 Email 地址。"
+
+#: models.py:188
+msgid "user"
+msgstr "用戶"
+
+#: models.py:189
+msgid "activation key"
+msgstr "激活密鑰"
+
+#: models.py:194
+msgid "registration profile"
+msgstr "注冊信息"
+
+#: models.py:195
+msgid "registration profiles"
+msgstr "注冊信息"
+
diff --git a/thirdpart/registration/management/__init__.py b/thirdpart/registration/management/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/thirdpart/registration/management/commands/__init__.py b/thirdpart/registration/management/commands/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/thirdpart/registration/management/commands/cleanupregistration.py b/thirdpart/registration/management/commands/cleanupregistration.py
new file mode 100644
index 0000000000..abec5aed3c
--- /dev/null
+++ b/thirdpart/registration/management/commands/cleanupregistration.py
@@ -0,0 +1,19 @@
+"""
+A management command which deletes expired accounts (e.g.,
+accounts which signed up but never activated) from the database.
+
+Calls ``RegistrationProfile.objects.delete_expired_users()``, which
+contains the actual logic for determining which accounts are deleted.
+
+"""
+
+from django.core.management.base import NoArgsCommand
+
+from registration.models import RegistrationProfile
+
+
+class Command(NoArgsCommand):
+ help = "Delete expired user registrations from the database"
+
+ def handle_noargs(self, **options):
+ RegistrationProfile.objects.delete_expired_users()
diff --git a/thirdpart/registration/models.py b/thirdpart/registration/models.py
new file mode 100644
index 0000000000..a2a04ae125
--- /dev/null
+++ b/thirdpart/registration/models.py
@@ -0,0 +1,258 @@
+import datetime
+import random
+import re
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.db import models
+from django.db import transaction
+from django.template.loader import render_to_string
+from django.utils.hashcompat import sha_constructor
+from django.utils.translation import ugettext_lazy as _
+
+
+SHA1_RE = re.compile('^[a-f0-9]{40}$')
+
+
+class RegistrationManager(models.Manager):
+ """
+ Custom manager for the ``RegistrationProfile`` model.
+
+ The methods defined here provide shortcuts for account creation
+ and activation (including generation and emailing of activation
+ keys), and for cleaning out expired inactive accounts.
+
+ """
+ def activate_user(self, activation_key):
+ """
+ Validate an activation key and activate the corresponding
+ ``User`` if valid.
+
+ If the key is valid and has not expired, return the ``User``
+ after activating.
+
+ If the key is not valid or has expired, return ``False``.
+
+ If the key is valid but the ``User`` is already active,
+ return ``False``.
+
+ To prevent reactivation of an account which has been
+ deactivated by site administrators, the activation key is
+ reset to the string constant ``RegistrationProfile.ACTIVATED``
+ after successful activation.
+
+ """
+ # Make sure the key we're trying conforms to the pattern of a
+ # SHA1 hash; if it doesn't, no point trying to look it up in
+ # the database.
+ if SHA1_RE.search(activation_key):
+ try:
+ profile = self.get(activation_key=activation_key)
+ except self.model.DoesNotExist:
+ return False
+ if not profile.activation_key_expired():
+ user = profile.user
+ user.is_active = True
+ user.save()
+ profile.activation_key = self.model.ACTIVATED
+ profile.save()
+ return user
+ return False
+
+ def create_inactive_user(self, username, email, password,
+ site, send_email=True):
+ """
+ Create a new, inactive ``User``, generate a
+ ``RegistrationProfile`` and email its activation key to the
+ ``User``, returning the new ``User``.
+
+ By default, an activation email will be sent to the new
+ user. To disable this, pass ``send_email=False``.
+
+ """
+ new_user = User.objects.create_user(username, email, password)
+ new_user.is_active = False
+ new_user.save()
+
+ registration_profile = self.create_profile(new_user)
+
+ if send_email:
+ registration_profile.send_activation_email(site)
+
+ return new_user
+ create_inactive_user = transaction.commit_on_success(create_inactive_user)
+
+ def create_profile(self, user):
+ """
+ Create a ``RegistrationProfile`` for a given
+ ``User``, and return the ``RegistrationProfile``.
+
+ The activation key for the ``RegistrationProfile`` will be a
+ SHA1 hash, generated from a combination of the ``User``'s
+ username and a random salt.
+
+ """
+ salt = sha_constructor(str(random.random())).hexdigest()[:5]
+ username = user.username
+ if isinstance(username, unicode):
+ username = username.encode('utf-8')
+ activation_key = sha_constructor(salt+username).hexdigest()
+ return self.create(user=user,
+ activation_key=activation_key)
+
+ def delete_expired_users(self):
+ """
+ Remove expired instances of ``RegistrationProfile`` and their
+ associated ``User``s.
+
+ Accounts to be deleted are identified by searching for
+ instances of ``RegistrationProfile`` with expired activation
+ keys, and then checking to see if their associated ``User``
+ instances have the field ``is_active`` set to ``False``; any
+ ``User`` who is both inactive and has an expired activation
+ key will be deleted.
+
+ It is recommended that this method be executed regularly as
+ part of your routine site maintenance; this application
+ provides a custom management command which will call this
+ method, accessible as ``manage.py cleanupregistration``.
+
+ Regularly clearing out accounts which have never been
+ activated serves two useful purposes:
+
+ 1. It alleviates the ocasional need to reset a
+ ``RegistrationProfile`` and/or re-send an activation email
+ when a user does not receive or does not act upon the
+ initial activation email; since the account will be
+ deleted, the user will be able to simply re-register and
+ receive a new activation key.
+
+ 2. It prevents the possibility of a malicious user registering
+ one or more accounts and never activating them (thus
+ denying the use of those usernames to anyone else); since
+ those accounts will be deleted, the usernames will become
+ available for use again.
+
+ If you have a troublesome ``User`` and wish to disable their
+ account while keeping it in the database, simply delete the
+ associated ``RegistrationProfile``; an inactive ``User`` which
+ does not have an associated ``RegistrationProfile`` will not
+ be deleted.
+
+ """
+ for profile in self.all():
+ if profile.activation_key_expired():
+ user = profile.user
+ if not user.is_active:
+ user.delete()
+
+
+class RegistrationProfile(models.Model):
+ """
+ A simple profile which stores an activation key for use during
+ user account registration.
+
+ Generally, you will not want to interact directly with instances
+ of this model; the provided manager includes methods
+ for creating and activating new accounts, as well as for cleaning
+ out accounts which have never been activated.
+
+ While it is possible to use this model as the value of the
+ ``AUTH_PROFILE_MODULE`` setting, it's not recommended that you do
+ so. This model's sole purpose is to store data temporarily during
+ account registration and activation.
+
+ """
+ ACTIVATED = u"ALREADY_ACTIVATED"
+
+ user = models.ForeignKey(User, unique=True, verbose_name=_('user'))
+ activation_key = models.CharField(_('activation key'), max_length=40)
+
+ objects = RegistrationManager()
+
+ class Meta:
+ verbose_name = _('registration profile')
+ verbose_name_plural = _('registration profiles')
+
+ def __unicode__(self):
+ return u"Registration information for %s" % self.user
+
+ def activation_key_expired(self):
+ """
+ Determine whether this ``RegistrationProfile``'s activation
+ key has expired, returning a boolean -- ``True`` if the key
+ has expired.
+
+ Key expiration is determined by a two-step process:
+
+ 1. If the user has already activated, the key will have been
+ reset to the string constant ``ACTIVATED``. Re-activating
+ is not permitted, and so this method returns ``True`` in
+ this case.
+
+ 2. Otherwise, the date the user signed up is incremented by
+ the number of days specified in the setting
+ ``ACCOUNT_ACTIVATION_DAYS`` (which should be the number of
+ days after signup during which a user is allowed to
+ activate their account); if the result is less than or
+ equal to the current date, the key has expired and this
+ method returns ``True``.
+
+ """
+ expiration_date = datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
+ return self.activation_key == self.ACTIVATED or \
+ (self.user.date_joined + expiration_date <= datetime.datetime.now())
+ activation_key_expired.boolean = True
+
+ def send_activation_email(self, site):
+ """
+ Send an activation email to the user associated with this
+ ``RegistrationProfile``.
+
+ The activation email will make use of two templates:
+
+ ``registration/activation_email_subject.txt``
+ This template will be used for the subject line of the
+ email. Because it is used as the subject line of an email,
+ this template's output **must** be only a single line of
+ text; output longer than one line will be forcibly joined
+ into only a single line.
+
+ ``registration/activation_email.txt``
+ This template will be used for the body of the email.
+
+ These templates will each receive the following context
+ variables:
+
+ ``activation_key``
+ The activation key for the new account.
+
+ ``expiration_days``
+ The number of days remaining during which the account may
+ be activated.
+
+ ``site``
+ An object representing the site on which the user
+ registered; depending on whether ``django.contrib.sites``
+ is installed, this may be an instance of either
+ ``django.contrib.sites.models.Site`` (if the sites
+ application is installed) or
+ ``django.contrib.sites.models.RequestSite`` (if
+ not). Consult the documentation for the Django sites
+ framework for details regarding these objects' interfaces.
+
+ """
+ ctx_dict = { 'activation_key': self.activation_key,
+ 'expiration_days': settings.ACCOUNT_ACTIVATION_DAYS,
+ 'site': site,
+ 'SITE_ROOT': settings.SITE_ROOT }
+ subject = render_to_string('registration/activation_email_subject.txt',
+ ctx_dict)
+ # Email subject *must not* contain newlines
+ subject = ''.join(subject.splitlines())
+
+ message = render_to_string('registration/activation_email.txt',
+ ctx_dict)
+
+ self.user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
+
diff --git a/thirdpart/registration/signals.py b/thirdpart/registration/signals.py
new file mode 100644
index 0000000000..343e3a5551
--- /dev/null
+++ b/thirdpart/registration/signals.py
@@ -0,0 +1,8 @@
+from django.dispatch import Signal
+
+
+# A new user has registered.
+user_registered = Signal(providing_args=["user", "request"])
+
+# A user has activated his or her account.
+user_activated = Signal(providing_args=["user", "request"])
diff --git a/thirdpart/registration/tests/__init__.py b/thirdpart/registration/tests/__init__.py
new file mode 100644
index 0000000000..e23e2d165d
--- /dev/null
+++ b/thirdpart/registration/tests/__init__.py
@@ -0,0 +1,4 @@
+from registration.tests.backends import *
+from registration.tests.forms import *
+from registration.tests.models import *
+from registration.tests.views import *
diff --git a/thirdpart/registration/tests/backends.py b/thirdpart/registration/tests/backends.py
new file mode 100644
index 0000000000..ee26823d3f
--- /dev/null
+++ b/thirdpart/registration/tests/backends.py
@@ -0,0 +1,361 @@
+import datetime
+
+from django.conf import settings
+from django.contrib import admin
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.core import mail
+from django.core.exceptions import ImproperlyConfigured
+from django.core.handlers.wsgi import WSGIRequest
+from django.test import Client
+from django.test import TestCase
+
+from registration import forms
+from registration import signals
+from registration.admin import RegistrationAdmin
+from registration.backends import get_backend
+from registration.backends.default import DefaultBackend
+from registration.models import RegistrationProfile
+
+
+class _MockRequestClient(Client):
+ """
+ A ``django.test.Client`` subclass which can return mock
+ ``HttpRequest`` objects.
+
+ """
+ def request(self, **request):
+ """
+ Rather than issuing a request and returning the response, this
+ simply constructs an ``HttpRequest`` object and returns it.
+
+ """
+ environ = {
+ 'HTTP_COOKIE': self.cookies,
+ 'PATH_INFO': '/',
+ 'QUERY_STRING': '',
+ 'REMOTE_ADDR': '127.0.0.1',
+ 'REQUEST_METHOD': 'GET',
+ 'SCRIPT_NAME': '',
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': '80',
+ 'SERVER_PROTOCOL': 'HTTP/1.1',
+ 'wsgi.version': (1,0),
+ 'wsgi.url_scheme': 'http',
+ 'wsgi.errors': self.errors,
+ 'wsgi.multiprocess': True,
+ 'wsgi.multithread': False,
+ 'wsgi.run_once': False,
+ }
+ environ.update(self.defaults)
+ environ.update(request)
+ return WSGIRequest(environ)
+
+
+def _mock_request():
+ """
+ Construct and return a mock ``HttpRequest`` object; this is used
+ in testing backend methods which expect an ``HttpRequest`` but
+ which are not being called from views.
+
+ """
+ return _MockRequestClient().request()
+
+
+class BackendRetrievalTests(TestCase):
+ """
+ Test that utilities for retrieving the active backend work
+ properly.
+
+ """
+ def test_get_backend(self):
+ """
+ Verify that ``get_backend()`` returns the correct value when
+ passed a valid backend.
+
+ """
+ self.failUnless(isinstance(get_backend('registration.backends.default.DefaultBackend'),
+ DefaultBackend))
+
+ def test_backend_error_invalid(self):
+ """
+ Test that a nonexistent/unimportable backend raises the
+ correct exception.
+
+ """
+ self.assertRaises(ImproperlyConfigured, get_backend,
+ 'registration.backends.doesnotexist.NonExistentBackend')
+
+ def test_backend_attribute_error(self):
+ """
+ Test that a backend module which exists but does not have a
+ class of the specified name raises the correct exception.
+
+ """
+ self.assertRaises(ImproperlyConfigured, get_backend,
+ 'registration.backends.default.NonexistentBackend')
+
+
+class DefaultRegistrationBackendTests(TestCase):
+ """
+ Test the default registration backend.
+
+ Running these tests successfull will require two templates to be
+ created for the sending of activation emails; details on these
+ templates and their contexts may be found in the documentation for
+ the default backend.
+
+ """
+ def setUp(self):
+ """
+ Create an instance of the default backend for use in testing,
+ and set ``ACCOUNT_ACTIVATION_DAYS`` if it's not set already.
+
+ """
+ from registration.backends.default import DefaultBackend
+ self.backend = DefaultBackend()
+ self.old_activation = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', None)
+ if self.old_activation is None:
+ settings.ACCOUNT_ACTIVATION_DAYS = 7
+
+ def tearDown(self):
+ """
+ Yank out ``ACCOUNT_ACTIVATION_DAYS`` back out if it wasn't
+ originally set.
+
+ """
+ if self.old_activation is None:
+ settings.ACCOUNT_ACTIVATION_DAYS = self.old_activation
+
+ def test_registration(self):
+ """
+ Test the registration process: registration creates a new
+ inactive account and a new profile with activation key,
+ populates the correct account data and sends an activation
+ email.
+
+ """
+ new_user = self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+
+ # Details of the returned user must match what went in.
+ self.assertEqual(new_user.username, 'bob')
+ self.failUnless(new_user.check_password('secret'))
+ self.assertEqual(new_user.email, 'bob@example.com')
+
+ # New user must not be active.
+ self.failIf(new_user.is_active)
+
+ # A registration profile was created, and an activation email
+ # was sent.
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_registration_no_sites(self):
+ """
+ Test that registration still functions properly when
+ ``django.contrib.sites`` is not installed; the fallback will
+ be a ``RequestSite`` instance.
+
+ """
+ Site._meta.installed = False
+
+ new_user = self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+
+ self.assertEqual(new_user.username, 'bob')
+ self.failUnless(new_user.check_password('secret'))
+ self.assertEqual(new_user.email, 'bob@example.com')
+
+ self.failIf(new_user.is_active)
+
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertEqual(len(mail.outbox), 1)
+
+ Site._meta.installed = True
+
+ def test_valid_activation(self):
+ """
+ Test the activation process: activating within the permitted
+ window sets the account's ``is_active`` field to ``True`` and
+ resets the activation key.
+
+ """
+ valid_user = self.backend.register(_mock_request(),
+ username='alice',
+ email='alice@example.com',
+ password1='swordfish')
+
+ valid_profile = RegistrationProfile.objects.get(user=valid_user)
+ activated = self.backend.activate(_mock_request(),
+ valid_profile.activation_key)
+ self.assertEqual(activated.username, valid_user.username)
+ self.failUnless(activated.is_active)
+
+ # Fetch the profile again to verify its activation key has
+ # been reset.
+ valid_profile = RegistrationProfile.objects.get(user=valid_user)
+ self.assertEqual(valid_profile.activation_key,
+ RegistrationProfile.ACTIVATED)
+
+ def test_invalid_activation(self):
+ """
+ Test the activation process: trying to activate outside the
+ permitted window fails, and leaves the account inactive.
+
+ """
+ expired_user = self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+
+ expired_user.date_joined = expired_user.date_joined - datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
+ expired_user.save()
+ expired_profile = RegistrationProfile.objects.get(user=expired_user)
+ self.failIf(self.backend.activate(_mock_request(),
+ expired_profile.activation_key))
+ self.failUnless(expired_profile.activation_key_expired())
+
+ def test_allow(self):
+ """
+ Test that the setting ``REGISTRATION_OPEN`` appropriately
+ controls whether registration is permitted.
+
+ """
+ old_allowed = getattr(settings, 'REGISTRATION_OPEN', True)
+ settings.REGISTRATION_OPEN = True
+ self.failUnless(self.backend.registration_allowed(_mock_request()))
+
+ settings.REGISTRATION_OPEN = False
+ self.failIf(self.backend.registration_allowed(_mock_request()))
+ settings.REGISTRATION_OPEN = old_allowed
+
+ def test_form_class(self):
+ """
+ Test that the default form class returned is
+ ``registration.forms.RegistrationForm``.
+
+ """
+ self.failUnless(self.backend.get_form_class(_mock_request()) is forms.RegistrationForm)
+
+ def test_post_registration_redirect(self):
+ """
+ Test that the default post-registration redirect is the named
+ pattern ``registration_complete``.
+
+ """
+ self.assertEqual(self.backend.post_registration_redirect(_mock_request(), User()),
+ ('registration_complete', (), {}))
+
+ def test_registration_signal(self):
+ """
+ Test that registering a user sends the ``user_registered``
+ signal.
+
+ """
+ def receiver(sender, **kwargs):
+ self.failUnless('user' in kwargs)
+ self.assertEqual(kwargs['user'].username, 'bob')
+ self.failUnless('request' in kwargs)
+ self.failUnless(isinstance(kwargs['request'], WSGIRequest))
+ received_signals.append(kwargs.get('signal'))
+
+ received_signals = []
+ signals.user_registered.connect(receiver, sender=self.backend.__class__)
+
+ self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+
+ self.assertEqual(len(received_signals), 1)
+ self.assertEqual(received_signals, [signals.user_registered])
+
+ def test_activation_signal_success(self):
+ """
+ Test that successfully activating a user sends the
+ ``user_activated`` signal.
+
+ """
+ def receiver(sender, **kwargs):
+ self.failUnless('user' in kwargs)
+ self.assertEqual(kwargs['user'].username, 'bob')
+ self.failUnless('request' in kwargs)
+ self.failUnless(isinstance(kwargs['request'], WSGIRequest))
+ received_signals.append(kwargs.get('signal'))
+
+ received_signals = []
+ signals.user_activated.connect(receiver, sender=self.backend.__class__)
+
+ new_user = self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.backend.activate(_mock_request(), profile.activation_key)
+
+ self.assertEqual(len(received_signals), 1)
+ self.assertEqual(received_signals, [signals.user_activated])
+
+ def test_activation_signal_failure(self):
+ """
+ Test that an unsuccessful activation attempt does not send the
+ ``user_activated`` signal.
+
+ """
+ receiver = lambda sender, **kwargs: received_signals.append(kwargs.get('signal'))
+
+ received_signals = []
+ signals.user_activated.connect(receiver, sender=self.backend.__class__)
+
+ new_user = self.backend.register(_mock_request(),
+ username='bob',
+ email='bob@example.com',
+ password1='secret')
+ new_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ new_user.save()
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.backend.activate(_mock_request(), profile.activation_key)
+
+ self.assertEqual(len(received_signals), 0)
+
+ def test_email_send_action(self):
+ """
+ Test re-sending of activation emails via admin action.
+
+ """
+ admin_class = RegistrationAdmin(RegistrationProfile, admin.site)
+
+ alice = self.backend.register(_mock_request(),
+ username='alice',
+ email='alice@example.com',
+ password1='swordfish')
+
+ admin_class.resend_activation_email(_mock_request(),
+ RegistrationProfile.objects.all())
+ self.assertEqual(len(mail.outbox), 2) # One on registering, one more on the resend.
+
+ RegistrationProfile.objects.filter(user=alice).update(activation_key=RegistrationProfile.ACTIVATED)
+ admin_class.resend_activation_email(_mock_request(),
+ RegistrationProfile.objects.all())
+ self.assertEqual(len(mail.outbox), 2) # No additional email because the account has activated.
+
+ def test_activation_action(self):
+ """
+ Test manual activation of users view admin action.
+
+ """
+ admin_class = RegistrationAdmin(RegistrationProfile, admin.site)
+
+ alice = self.backend.register(_mock_request(),
+ username='alice',
+ email='alice@example.com',
+ password1='swordfish')
+
+ admin_class.activate_users(_mock_request(),
+ RegistrationProfile.objects.all())
+ self.failUnless(User.objects.get(username='alice').is_active)
diff --git a/thirdpart/registration/tests/forms.py b/thirdpart/registration/tests/forms.py
new file mode 100644
index 0000000000..505374fde9
--- /dev/null
+++ b/thirdpart/registration/tests/forms.py
@@ -0,0 +1,119 @@
+from django.contrib.auth.models import User
+from django.test import TestCase
+
+from registration import forms
+
+
+class RegistrationFormTests(TestCase):
+ """
+ Test the default registration forms.
+
+ """
+ def test_registration_form(self):
+ """
+ Test that ``RegistrationForm`` enforces username constraints
+ and matching passwords.
+
+ """
+ # Create a user so we can verify that duplicate usernames aren't
+ # permitted.
+ User.objects.create_user('alice', 'alice@example.com', 'secret')
+
+ invalid_data_dicts = [
+ # Non-alphanumeric username.
+ {'data': {'username': 'foo/bar',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo'},
+ 'error': ('username', [u"This value must contain only letters, numbers and underscores."])},
+ # Already-existing username.
+ {'data': {'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'secret',
+ 'password2': 'secret'},
+ 'error': ('username', [u"A user with that username already exists."])},
+ # Mismatched passwords.
+ {'data': {'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'bar'},
+ 'error': ('__all__', [u"The two password fields didn't match."])},
+ ]
+
+ for invalid_dict in invalid_data_dicts:
+ form = forms.RegistrationForm(data=invalid_dict['data'])
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors[invalid_dict['error'][0]],
+ invalid_dict['error'][1])
+
+ form = forms.RegistrationForm(data={'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo'})
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_tos(self):
+ """
+ Test that ``RegistrationFormTermsOfService`` requires
+ agreement to the terms of service.
+
+ """
+ form = forms.RegistrationFormTermsOfService(data={'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo'})
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['tos'],
+ [u"You must agree to the terms to register"])
+
+ form = forms.RegistrationFormTermsOfService(data={'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo',
+ 'tos': 'on'})
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_unique_email(self):
+ """
+ Test that ``RegistrationFormUniqueEmail`` validates uniqueness
+ of email addresses.
+
+ """
+ # Create a user so we can verify that duplicate addresses
+ # aren't permitted.
+ User.objects.create_user('alice', 'alice@example.com', 'secret')
+
+ form = forms.RegistrationFormUniqueEmail(data={'username': 'foo',
+ 'email': 'alice@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo'})
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['email'],
+ [u"This email address is already in use. Please supply a different email address."])
+
+ form = forms.RegistrationFormUniqueEmail(data={'username': 'foo',
+ 'email': 'foo@example.com',
+ 'password1': 'foo',
+ 'password2': 'foo'})
+ self.failUnless(form.is_valid())
+
+ def test_registration_form_no_free_email(self):
+ """
+ Test that ``RegistrationFormNoFreeEmail`` disallows
+ registration with free email addresses.
+
+ """
+ base_data = {'username': 'foo',
+ 'password1': 'foo',
+ 'password2': 'foo'}
+ for domain in forms.RegistrationFormNoFreeEmail.bad_domains:
+ invalid_data = base_data.copy()
+ invalid_data['email'] = u"foo@%s" % domain
+ form = forms.RegistrationFormNoFreeEmail(data=invalid_data)
+ self.failIf(form.is_valid())
+ self.assertEqual(form.errors['email'],
+ [u"Registration using free email addresses is prohibited. Please supply a different email address."])
+
+ base_data['email'] = 'foo@example.com'
+ form = forms.RegistrationFormNoFreeEmail(data=base_data)
+ self.failUnless(form.is_valid())
diff --git a/thirdpart/registration/tests/models.py b/thirdpart/registration/tests/models.py
new file mode 100644
index 0000000000..f763cb2c7d
--- /dev/null
+++ b/thirdpart/registration/tests/models.py
@@ -0,0 +1,225 @@
+import datetime
+import re
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.core import mail
+from django.core import management
+from django.test import TestCase
+from django.utils.hashcompat import sha_constructor
+
+from registration.models import RegistrationProfile
+
+
+class RegistrationModelTests(TestCase):
+ """
+ Test the model and manager used in the default backend.
+
+ """
+ user_info = {'username': 'alice',
+ 'password': 'swordfish',
+ 'email': 'alice@example.com'}
+
+ def setUp(self):
+ self.old_activation = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', None)
+ settings.ACCOUNT_ACTIVATION_DAYS = 7
+
+ def tearDown(self):
+ settings.ACCOUNT_ACTIVATION_DAYS = self.old_activation
+
+ def test_profile_creation(self):
+ """
+ Creating a registration profile for a user populates the
+ profile with the correct user and a SHA1 hash to use as
+ activation key.
+
+ """
+ new_user = User.objects.create_user(**self.user_info)
+ profile = RegistrationProfile.objects.create_profile(new_user)
+
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertEqual(profile.user.id, new_user.id)
+ self.failUnless(re.match('^[a-f0-9]{40}$', profile.activation_key))
+ self.assertEqual(unicode(profile),
+ "Registration information for alice")
+
+ def test_activation_email(self):
+ """
+ ``RegistrationProfile.send_activation_email`` sends an
+ email.
+
+ """
+ new_user = User.objects.create_user(**self.user_info)
+ profile = RegistrationProfile.objects.create_profile(new_user)
+ profile.send_activation_email(Site.objects.get_current())
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].to, [self.user_info['email']])
+
+ def test_user_creation(self):
+ """
+ Creating a new user populates the correct data, and sets the
+ user's account inactive.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ self.assertEqual(new_user.username, 'alice')
+ self.assertEqual(new_user.email, 'alice@example.com')
+ self.failUnless(new_user.check_password('swordfish'))
+ self.failIf(new_user.is_active)
+
+ def test_user_creation_email(self):
+ """
+ By default, creating a new user sends an activation email.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_user_creation_no_email(self):
+ """
+ Passing ``send_email=False`` when creating a new user will not
+ send an activation email.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ send_email=False,
+ **self.user_info)
+ self.assertEqual(len(mail.outbox), 0)
+
+ def test_unexpired_account(self):
+ """
+ ``RegistrationProfile.activation_key_expired()`` is ``False``
+ within the activation window.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.failIf(profile.activation_key_expired())
+
+ def test_expired_account(self):
+ """
+ ``RegistrationProfile.activation_key_expired()`` is ``True``
+ outside the activation window.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ new_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ new_user.save()
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.failUnless(profile.activation_key_expired())
+
+ def test_valid_activation(self):
+ """
+ Activating a user within the permitted window makes the
+ account active, and resets the activation key.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ profile = RegistrationProfile.objects.get(user=new_user)
+ activated = RegistrationProfile.objects.activate_user(profile.activation_key)
+
+ self.failUnless(isinstance(activated, User))
+ self.assertEqual(activated.id, new_user.id)
+ self.failUnless(activated.is_active)
+
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.assertEqual(profile.activation_key, RegistrationProfile.ACTIVATED)
+
+ def test_expired_activation(self):
+ """
+ Attempting to activate outside the permitted window does not
+ activate the account.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ new_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ new_user.save()
+
+ profile = RegistrationProfile.objects.get(user=new_user)
+ activated = RegistrationProfile.objects.activate_user(profile.activation_key)
+
+ self.failIf(isinstance(activated, User))
+ self.failIf(activated)
+
+ new_user = User.objects.get(username='alice')
+ self.failIf(new_user.is_active)
+
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.assertNotEqual(profile.activation_key, RegistrationProfile.ACTIVATED)
+
+ def test_activation_invalid_key(self):
+ """
+ Attempting to activate with a key which is not a SHA1 hash
+ fails.
+
+ """
+ self.failIf(RegistrationProfile.objects.activate_user('foo'))
+
+ def test_activation_already_activated(self):
+ """
+ Attempting to re-activate an already-activated account fails.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ profile = RegistrationProfile.objects.get(user=new_user)
+ RegistrationProfile.objects.activate_user(profile.activation_key)
+
+ profile = RegistrationProfile.objects.get(user=new_user)
+ self.failIf(RegistrationProfile.objects.activate_user(profile.activation_key))
+
+ def test_activation_nonexistent_key(self):
+ """
+ Attempting to activate with a non-existent key (i.e., one not
+ associated with any account) fails.
+
+ """
+ # Due to the way activation keys are constructed during
+ # registration, this will never be a valid key.
+ invalid_key = sha_constructor('foo').hexdigest()
+ self.failIf(RegistrationProfile.objects.activate_user(invalid_key))
+
+ def test_expired_user_deletion(self):
+ """
+ ``RegistrationProfile.objects.delete_expired_users()`` only
+ deletes inactive users whose activation window has expired.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ expired_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ username='bob',
+ password='secret',
+ email='bob@example.com')
+ expired_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ expired_user.save()
+
+ RegistrationProfile.objects.delete_expired_users()
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertRaises(User.DoesNotExist, User.objects.get, username='bob')
+
+ def test_management_command(self):
+ """
+ The ``cleanupregistration`` management command properly
+ deletes expired accounts.
+
+ """
+ new_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ **self.user_info)
+ expired_user = RegistrationProfile.objects.create_inactive_user(site=Site.objects.get_current(),
+ username='bob',
+ password='secret',
+ email='bob@example.com')
+ expired_user.date_joined -= datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS + 1)
+ expired_user.save()
+
+ management.call_command('cleanupregistration')
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertRaises(User.DoesNotExist, User.objects.get, username='bob')
diff --git a/thirdpart/registration/tests/urls.py b/thirdpart/registration/tests/urls.py
new file mode 100644
index 0000000000..02ef609000
--- /dev/null
+++ b/thirdpart/registration/tests/urls.py
@@ -0,0 +1,82 @@
+"""
+URLs used in the unit tests for django-registration.
+
+You should not attempt to use these URLs in any sort of real or
+development environment; instead, use
+``registration/backends/default/urls.py``. This URLconf includes those
+URLs, and also adds several additional URLs which serve no purpose
+other than to test that optional keyword arguments are properly
+handled.
+
+"""
+
+from django.conf.urls.defaults import *
+from django.views.generic.simple import direct_to_template
+
+from registration.views import activate
+from registration.views import register
+
+
+urlpatterns = patterns('',
+ # Test the 'activate' view with custom template
+ # name.
+ url(r'^activate-with-template-name/(?P\w+)/$',
+ activate,
+ {'template_name': 'registration/test_template_name.html',
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_activate_template_name'),
+ # Test the 'activate' view with
+ # extra_context_argument.
+ url(r'^activate-extra-context/(?P\w+)/$',
+ activate,
+ {'extra_context': {'foo': 'bar', 'callable': lambda: 'called'},
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_activate_extra_context'),
+ # Test the 'activate' view with success_url argument.
+ url(r'^activate-with-success-url/(?P\w+)/$',
+ activate,
+ {'success_url': 'registration_test_custom_success_url',
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_activate_success_url'),
+ # Test the 'register' view with custom template
+ # name.
+ url(r'^register-with-template-name/$',
+ register,
+ {'template_name': 'registration/test_template_name.html',
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_register_template_name'),
+ # Test the'register' view with extra_context
+ # argument.
+ url(r'^register-extra-context/$',
+ register,
+ {'extra_context': {'foo': 'bar', 'callable': lambda: 'called'},
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_register_extra_context'),
+ # Test the 'register' view with custom URL for
+ # closed registration.
+ url(r'^register-with-disallowed-url/$',
+ register,
+ {'disallowed_url': 'registration_test_custom_disallowed',
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_register_disallowed_url'),
+ # Set up a pattern which will correspond to the
+ # custom 'disallowed_url' above.
+ url(r'^custom-disallowed/$',
+ direct_to_template,
+ {'template': 'registration/registration_closed.html'},
+ name='registration_test_custom_disallowed'),
+ # Test the 'register' view with custom redirect
+ # on successful registration.
+ url(r'^register-with-success_url/$',
+ register,
+ {'success_url': 'registration_test_custom_success_url',
+ 'backend': 'registration.backends.default.DefaultBackend'},
+ name='registration_test_register_success_url'
+ ),
+ # Pattern for custom redirect set above.
+ url(r'^custom-success/$',
+ direct_to_template,
+ {'template': 'registration/test_template_name.html'},
+ name='registration_test_custom_success_url'),
+ (r'', include('registration.backends.default.urls')),
+ )
diff --git a/thirdpart/registration/tests/views.py b/thirdpart/registration/tests/views.py
new file mode 100644
index 0000000000..17d3ad5303
--- /dev/null
+++ b/thirdpart/registration/tests/views.py
@@ -0,0 +1,246 @@
+import datetime
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core import mail
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from registration import forms
+from registration.models import RegistrationProfile
+
+
+class RegistrationViewTests(TestCase):
+ """
+ Test the registration views.
+
+ """
+ urls = 'registration.tests.urls'
+
+ def setUp(self):
+ """
+ These tests use the default backend, since we know it's
+ available; that needs to have ``ACCOUNT_ACTIVATION_DAYS`` set.
+
+ """
+ self.old_activation = getattr(settings, 'ACCOUNT_ACTIVATION_DAYS', None)
+ if self.old_activation is None:
+ settings.ACCOUNT_ACTIVATION_DAYS = 7
+
+ def tearDown(self):
+ """
+ Yank ``ACCOUNT_ACTIVATION_DAYS`` back out if it wasn't
+ originally set.
+
+ """
+ if self.old_activation is None:
+ settings.ACCOUNT_ACTIVATION_DAYS = self.old_activation
+
+ def test_registration_view_initial(self):
+ """
+ A ``GET`` to the ``register`` view uses the appropriate
+ template and populates the registration form into the context.
+
+ """
+ response = self.client.get(reverse('registration_register'))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response,
+ 'registration/registration_form.html')
+ self.failUnless(isinstance(response.context['form'],
+ forms.RegistrationForm))
+
+ def test_registration_view_success(self):
+ """
+ A ``POST`` to the ``register`` view with valid data properly
+ creates a new user and issues a redirect.
+
+ """
+ response = self.client.post(reverse('registration_register'),
+ data={'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'swordfish',
+ 'password2': 'swordfish'})
+ self.assertRedirects(response,
+ 'http://testserver%s' % reverse('registration_complete'))
+ self.assertEqual(RegistrationProfile.objects.count(), 1)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_registration_view_failure(self):
+ """
+ A ``POST`` to the ``register`` view with invalid data does not
+ create a user, and displays appropriate error messages.
+
+ """
+ response = self.client.post(reverse('registration_register'),
+ data={'username': 'bob',
+ 'email': 'bobe@example.com',
+ 'password1': 'foo',
+ 'password2': 'bar'})
+ self.assertEqual(response.status_code, 200)
+ self.failIf(response.context['form'].is_valid())
+ self.assertFormError(response, 'form', field=None,
+ errors=u"The two password fields didn't match.")
+ self.assertEqual(len(mail.outbox), 0)
+
+ def test_registration_view_closed(self):
+ """
+ Any attempt to access the ``register`` view when registration
+ is closed fails and redirects.
+
+ """
+ old_allowed = getattr(settings, 'REGISTRATION_OPEN', True)
+ settings.REGISTRATION_OPEN = False
+
+ closed_redirect = 'http://testserver%s' % reverse('registration_disallowed')
+
+ response = self.client.get(reverse('registration_register'))
+ self.assertRedirects(response, closed_redirect)
+
+ # Even if valid data is posted, it still shouldn't work.
+ response = self.client.post(reverse('registration_register'),
+ data={'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'swordfish',
+ 'password2': 'swordfish'})
+ self.assertRedirects(response, closed_redirect)
+ self.assertEqual(RegistrationProfile.objects.count(), 0)
+
+ settings.REGISTRATION_OPEN = old_allowed
+
+ def test_registration_template_name(self):
+ """
+ Passing ``template_name`` to the ``register`` view will result
+ in that template being used.
+
+ """
+ response = self.client.get(reverse('registration_test_register_template_name'))
+ self.assertTemplateUsed(response,
+ 'registration/test_template_name.html')
+
+ def test_registration_extra_context(self):
+ """
+ Passing ``extra_context`` to the ``register`` view will
+ correctly populate the context.
+
+ """
+ response = self.client.get(reverse('registration_test_register_extra_context'))
+ self.assertEqual(response.context['foo'], 'bar')
+ # Callables in extra_context are called to obtain the value.
+ self.assertEqual(response.context['callable'], 'called')
+
+ def test_registration_disallowed_url(self):
+ """
+ Passing ``disallowed_url`` to the ``register`` view will
+ result in a redirect to that URL when registration is closed.
+
+ """
+ old_allowed = getattr(settings, 'REGISTRATION_OPEN', True)
+ settings.REGISTRATION_OPEN = False
+
+ closed_redirect = 'http://testserver%s' % reverse('registration_test_custom_disallowed')
+
+ response = self.client.get(reverse('registration_test_register_disallowed_url'))
+ self.assertRedirects(response, closed_redirect)
+
+ settings.REGISTRATION_OPEN = old_allowed
+
+ def test_registration_success_url(self):
+ """
+ Passing ``success_url`` to the ``register`` view will result
+ in a redirect to that URL when registration is successful.
+
+ """
+ success_redirect = 'http://testserver%s' % reverse('registration_test_custom_success_url')
+ response = self.client.post(reverse('registration_test_register_success_url'),
+ data={'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'swordfish',
+ 'password2': 'swordfish'})
+ self.assertRedirects(response, success_redirect)
+
+ def test_valid_activation(self):
+ """
+ Test that the ``activate`` view properly handles a valid
+ activation (in this case, based on the default backend's
+ activation window).
+
+ """
+ success_redirect = 'http://testserver%s' % reverse('registration_activation_complete')
+
+ # First, register an account.
+ self.client.post(reverse('registration_register'),
+ data={'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'swordfish',
+ 'password2': 'swordfish'})
+ profile = RegistrationProfile.objects.get(user__username='alice')
+ response = self.client.get(reverse('registration_activate',
+ kwargs={'activation_key': profile.activation_key}))
+ self.assertRedirects(response, success_redirect)
+ self.failUnless(User.objects.get(username='alice').is_active)
+
+ def test_invalid_activation(self):
+ """
+ Test that the ``activate`` view properly handles an invalid
+ activation (in this case, based on the default backend's
+ activation window).
+
+ """
+ # Register an account and reset its date_joined to be outside
+ # the activation window.
+ self.client.post(reverse('registration_register'),
+ data={'username': 'bob',
+ 'email': 'bob@example.com',
+ 'password1': 'secret',
+ 'password2': 'secret'})
+ expired_user = User.objects.get(username='bob')
+ expired_user.date_joined = expired_user.date_joined - datetime.timedelta(days=settings.ACCOUNT_ACTIVATION_DAYS)
+ expired_user.save()
+
+ expired_profile = RegistrationProfile.objects.get(user=expired_user)
+ response = self.client.get(reverse('registration_activate',
+ kwargs={'activation_key': expired_profile.activation_key}))
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['activation_key'],
+ expired_profile.activation_key)
+ self.failIf(User.objects.get(username='bob').is_active)
+
+ def test_activation_success_url(self):
+ """
+ Passing ``success_url`` to the ``activate`` view and
+ successfully activating will result in that URL being used for
+ the redirect.
+
+ """
+ success_redirect = 'http://testserver%s' % reverse('registration_test_custom_success_url')
+ self.client.post(reverse('registration_register'),
+ data={'username': 'alice',
+ 'email': 'alice@example.com',
+ 'password1': 'swordfish',
+ 'password2': 'swordfish'})
+ profile = RegistrationProfile.objects.get(user__username='alice')
+ response = self.client.get(reverse('registration_test_activate_success_url',
+ kwargs={'activation_key': profile.activation_key}))
+ self.assertRedirects(response, success_redirect)
+
+ def test_activation_template_name(self):
+ """
+ Passing ``template_name`` to the ``activate`` view will result
+ in that template being used.
+
+ """
+ response = self.client.get(reverse('registration_test_activate_template_name',
+ kwargs={'activation_key': 'foo'}))
+ self.assertTemplateUsed(response, 'registration/test_template_name.html')
+
+ def test_activation_extra_context(self):
+ """
+ Passing ``extra_context`` to the ``activate`` view will
+ correctly populate the context.
+
+ """
+ response = self.client.get(reverse('registration_test_activate_extra_context',
+ kwargs={'activation_key': 'foo'}))
+ self.assertEqual(response.context['foo'], 'bar')
+ # Callables in extra_context are called to obtain the value.
+ self.assertEqual(response.context['callable'], 'called')
diff --git a/thirdpart/registration/urls.py b/thirdpart/registration/urls.py
new file mode 100644
index 0000000000..af4102653b
--- /dev/null
+++ b/thirdpart/registration/urls.py
@@ -0,0 +1,15 @@
+"""
+Backwards-compatible URLconf for existing django-registration
+installs; this allows the standard ``include('registration.urls')`` to
+continue working, but that usage is deprecated and will be removed for
+django-registration 1.0. For new installs, use
+``include('registration.backends.default.urls')``.
+
+"""
+
+import warnings
+
+warnings.warn("include('registration.urls') is deprecated; use include('registration.backends.default.urls') instead.",
+ PendingDeprecationWarning)
+
+from registration.backends.default.urls import *
diff --git a/thirdpart/registration/views.py b/thirdpart/registration/views.py
new file mode 100644
index 0000000000..5a26910ad1
--- /dev/null
+++ b/thirdpart/registration/views.py
@@ -0,0 +1,204 @@
+"""
+Views which allow users to create and activate accounts.
+
+"""
+
+
+from django.shortcuts import redirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from registration.backends import get_backend
+
+
+def activate(request, backend,
+ template_name='registration/activate.html',
+ success_url=None, extra_context=None, **kwargs):
+ """
+ Activate a user's account.
+
+ The actual activation of the account will be delegated to the
+ backend specified by the ``backend`` keyword argument (see below);
+ the backend's ``activate()`` method will be called, passing any
+ keyword arguments captured from the URL, and will be assumed to
+ return a ``User`` if activation was successful, or a value which
+ evaluates to ``False`` in boolean context if not.
+
+ Upon successful activation, the backend's
+ ``post_activation_redirect()`` method will be called, passing the
+ ``HttpRequest`` and the activated ``User`` to determine the URL to
+ redirect the user to. To override this, pass the argument
+ ``success_url`` (see below).
+
+ On unsuccessful activation, will render the template
+ ``registration/activate.html`` to display an error message; to
+ override thise, pass the argument ``template_name`` (see below).
+
+ **Arguments**
+
+ ``backend``
+ The dotted Python import path to the backend class to
+ use. Required.
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context. Optional.
+
+ ``success_url``
+ The name of a URL pattern to redirect to on successful
+ acivation. This is optional; if not specified, this will be
+ obtained by calling the backend's
+ ``post_activation_redirect()`` method.
+
+ ``template_name``
+ A custom template to use. This is optional; if not specified,
+ this will default to ``registration/activate.html``.
+
+ ``\*\*kwargs``
+ Any keyword arguments captured from the URL, such as an
+ activation key, which will be passed to the backend's
+ ``activate()`` method.
+
+ **Context:**
+
+ The context will be populated from the keyword arguments captured
+ in the URL, and any extra variables supplied in the
+ ``extra_context`` argument (see above).
+
+ **Template:**
+
+ registration/activate.html or ``template_name`` keyword argument.
+
+ """
+ backend = get_backend(backend)
+ account = backend.activate(request, **kwargs)
+
+ if account:
+ if success_url is None:
+ to, args, kwargs = backend.post_activation_redirect(request, account)
+ return redirect(to, *args, **kwargs)
+ else:
+ return redirect(success_url)
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+
+ return render_to_response(template_name,
+ kwargs,
+ context_instance=context)
+
+
+def register(request, backend, success_url=None, form_class=None,
+ disallowed_url='registration_disallowed',
+ template_name='registration/registration_form.html',
+ extra_context=None):
+ """
+ Allow a new user to register an account.
+
+ The actual registration of the account will be delegated to the
+ backend specified by the ``backend`` keyword argument (see below);
+ it will be used as follows:
+
+ 1. The backend's ``registration_allowed()`` method will be called,
+ passing the ``HttpRequest``, to determine whether registration
+ of an account is to be allowed; if not, a redirect is issued to
+ the view corresponding to the named URL pattern
+ ``registration_disallowed``. To override this, see the list of
+ optional arguments for this view (below).
+
+ 2. The form to use for account registration will be obtained by
+ calling the backend's ``get_form_class()`` method, passing the
+ ``HttpRequest``. To override this, see the list of optional
+ arguments for this view (below).
+
+ 3. If valid, the form's ``cleaned_data`` will be passed (as
+ keyword arguments, and along with the ``HttpRequest``) to the
+ backend's ``register()`` method, which should return the new
+ ``User`` object.
+
+ 4. Upon successful registration, the backend's
+ ``post_registration_redirect()`` method will be called, passing
+ the ``HttpRequest`` and the new ``User``, to determine the URL
+ to redirect the user to. To override this, see the list of
+ optional arguments for this view (below).
+
+ **Required arguments**
+
+ None.
+
+ **Optional arguments**
+
+ ``backend``
+ The dotted Python import path to the backend class to use.
+
+ ``disallowed_url``
+ URL to redirect to if registration is not permitted for the
+ current ``HttpRequest``. Must be a value which can legally be
+ passed to ``django.shortcuts.redirect``. If not supplied, this
+ will be whatever URL corresponds to the named URL pattern
+ ``registration_disallowed``.
+
+ ``form_class``
+ The form class to use for registration. If not supplied, this
+ will be retrieved from the registration backend.
+
+ ``extra_context``
+ A dictionary of variables to add to the template context. Any
+ callable object in this dictionary will be called to produce
+ the end result which appears in the context.
+
+ ``success_url``
+ URL to redirect to after successful registration. Must be a
+ value which can legally be passed to
+ ``django.shortcuts.redirect``. If not supplied, this will be
+ retrieved from the registration backend.
+
+ ``template_name``
+ A custom template to use. If not supplied, this will default
+ to ``registration/registration_form.html``.
+
+ **Context:**
+
+ ``form``
+ The registration form.
+
+ Any extra variables supplied in the ``extra_context`` argument
+ (see above).
+
+ **Template:**
+
+ registration/registration_form.html or ``template_name`` keyword
+ argument.
+
+ """
+ backend = get_backend(backend)
+ if not backend.registration_allowed(request):
+ return redirect(disallowed_url)
+ if form_class is None:
+ form_class = backend.get_form_class(request)
+
+ if request.method == 'POST':
+ form = form_class(data=request.POST, files=request.FILES)
+ if form.is_valid():
+ new_user = backend.register(request, **form.cleaned_data)
+ if success_url is None:
+ to, args, kwargs = backend.post_registration_redirect(request, new_user)
+ return redirect(to, *args, **kwargs)
+ else:
+ return redirect(success_url)
+ else:
+ form = form_class()
+
+ if extra_context is None:
+ extra_context = {}
+ context = RequestContext(request)
+ for key, value in extra_context.items():
+ context[key] = callable(value) and value() or value
+
+ return render_to_response(template_name,
+ { 'form': form },
+ context_instance=context)
diff --git a/urls.py b/urls.py
index 2240c826e1..0273807367 100644
--- a/urls.py
+++ b/urls.py
@@ -1,10 +1,10 @@
from django.conf.urls.defaults import *
from django.conf import settings
-from seahub.views import root, home, peers, groups
+from seahub.views import root, home, peers, groups, myhome
# Uncomment the next two lines to enable the admin:
-# from django.contrib import admin
-# admin.autodiscover()
+from django.contrib import admin
+admin.autodiscover()
urlpatterns = patterns('',
# Example:
@@ -15,12 +15,18 @@ urlpatterns = patterns('',
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
- # (r'^admin/', include(admin.site.urls)),
+ (r'^admin/', include(admin.site.urls)),
+
+ (r'^accounts/', include('registration.backends.default.urls')),
(r'^$', root),
(r'^home/$', home),
+ (r'^home/my/$', myhome),
(r'^peers/$', peers),
(r'^groups/$', groups),
+
+ (r'^avatar/', include('avatar.urls')),
+ (r'^profile/', include('seahub.profile.urls')),
)
if settings.DEBUG:
diff --git a/views.py b/views.py
index 3a9bcdb07b..7673ea9d18 100644
--- a/views.py
+++ b/views.py
@@ -2,6 +2,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.template import RequestContext
+from django.contrib.auth.decorators import login_required
from seaserv import cclient, ccnet_rpc, get_groups, get_users
@@ -14,6 +15,13 @@ def home(request):
}, context_instance=RequestContext(request))
+@login_required
+def myhome(request):
+ return render_to_response('myhome.html', {
+ }, context_instance=RequestContext(request))
+
+
+
def peers(request):
peer_type = request.REQUEST.get('type', 'all')
peer_ids = ccnet_rpc.list_peers()