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 %} +

加入的组

+
+ +
+{% 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 %} + +
+ 当前状态:
+ {{ profile_form.status }} + {% if profile_form.status.errors %} + {{ profile_form.status.errors }} + {% endif %}
+
+ {{ profile_form.interests }}
+ {% if profile_form.interests.errors %} + {{ profile_form.interests.errors }} + {% endif %}
+ + + +{% 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 %} +

从已有头像中选择:

+
+
    + {{ primary_avatar_form.as_ul }} +
+
+ +
+ {% 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 %} +
+
    + {{ delete_avatar_form.as_ul }} +
+
+ +
+ {% 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' %}

+
+{{ form.old_password.errors }} +

{{ form.old_password }}

+{{ form.new_password1.errors }} +

{{ form.new_password1 }}

+{{ form.new_password2.errors }} +

{{ form.new_password2 }}

+

+
+
+{% 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." %}

+ +
+{{ form.new_password1.errors }} +

{{ form.new_password1 }}

+{{ form.new_password2.errors }} +

{{ form.new_password2 }}

+

+
+ +{% 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 地址,我们会把新密码设置说明通过邮件发送给您。

+
+ {{ form.email }} + +{{ form.email.errors }} +
+{% 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 %} +

用户注册

+
+ + {{ form.username }} + {% if form.username.errors %} + {{ form.username.errors }} + {% endif %}
+ + {{ form.email }}(我们将给您发送帐号激活邮件.) + {% if form.email.errors %} + {{ form.email.errors }} + {% endif %}
+ + {{ form.password1 }} + {% if form.password1.errors %} + {{ form.password1.errors }} + {% endif %}
+ + {{ form.password2 }}
+ +
+{% 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("""%s""" % + (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 %} +
+
    + {{ primary_avatar_form.as_ul }} +
+ +
+ {% 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 %} +
+
    + {{ delete_avatar_form.as_ul }} +
+ +
+ {% 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 """%s""" % (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 """%s""" % ( + 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()