From 6bfa53f1dca3da508183458684f254d202d2e243 Mon Sep 17 00:00:00 2001 From: Johnson Chetty Date: Fri, 14 Dec 2012 10:58:20 +0100 Subject: [PATCH] allauth folder added --- itf/allauth/__init__.py | 0 itf/allauth/account/__init__.py | 0 itf/allauth/account/admin.py | 8 + itf/allauth/account/app_settings.py | 57 ++ itf/allauth/account/auth_backends.py | 40 + itf/allauth/account/context_processors.py | 6 + itf/allauth/account/decorators.py | 38 + itf/allauth/account/forms.py | 479 ++++++++++ itf/allauth/account/models.py | 11 + itf/allauth/account/signals.py | 4 + itf/allauth/account/templatetags/__init__.py | 1 + .../account/templatetags/account_tags.py | 47 + itf/allauth/account/tests.py | 98 ++ itf/allauth/account/urls.py | 24 + itf/allauth/account/utils.py | 162 ++++ itf/allauth/account/views.py | 250 ++++++ itf/allauth/app_settings.py | 10 + itf/allauth/locale/nl/LC_MESSAGES/django.mo | Bin 0 -> 13044 bytes itf/allauth/locale/nl/LC_MESSAGES/django.po | 689 ++++++++++++++ itf/allauth/models.py | 0 itf/allauth/socialaccount/__init__.py | 0 itf/allauth/socialaccount/admin.py | 21 + itf/allauth/socialaccount/app_settings.py | 21 + .../socialaccount/context_processors.py | 5 + itf/allauth/socialaccount/fields.py | 60 ++ itf/allauth/socialaccount/forms.py | 55 ++ itf/allauth/socialaccount/helpers.py | 179 ++++ .../socialaccount/migrations/0001_initial.py | 73 ++ .../migrations/0002_genericmodels.py | 150 ++++ ...__add_unique_socialaccount_uid_provider.py | 96 ++ .../socialaccount/migrations/__init__.py | 0 itf/allauth/socialaccount/models.py | 197 ++++ .../socialaccount/providers/__init__.py | 36 + itf/allauth/socialaccount/providers/base.py | 52 ++ .../providers/facebook/__init__.py | 0 .../facebook/data/FacebookLocales.xml | 850 ++++++++++++++++++ .../socialaccount/providers/facebook/forms.py | 5 + .../providers/facebook/locale.py | 70 ++ .../facebook/migrations/0001_initial.py | 110 +++ ...add_unique_facebookaccesstoken_app_acco.py | 108 +++ .../migrations/0003_tosocialaccount.py | 142 +++ ...bookaccesstoken__del_unique_facebookacc.py | 63 ++ .../providers/facebook/migrations/__init__.py | 0 .../providers/facebook/models.py | 3 + .../providers/facebook/provider.py | 92 ++ .../facebook/templates/facebook/channel.html | 1 + .../templates/facebook/fbconnect.html | 46 + .../socialaccount/providers/facebook/urls.py | 14 + .../socialaccount/providers/facebook/views.py | 83 ++ .../providers/github/__init__.py | 0 .../socialaccount/providers/github/models.py | 3 + .../providers/github/provider.py | 24 + .../socialaccount/providers/github/urls.py | 5 + .../socialaccount/providers/github/views.py | 35 + .../providers/google/__init__.py | 0 .../socialaccount/providers/google/models.py | 3 + .../providers/google/provider.py | 35 + .../socialaccount/providers/google/urls.py | 4 + .../socialaccount/providers/google/views.py | 46 + .../providers/linkedin/__init__.py | 0 .../providers/linkedin/models.py | 3 + .../providers/linkedin/provider.py | 15 + .../socialaccount/providers/linkedin/urls.py | 4 + .../socialaccount/providers/linkedin/views.py | 68 ++ .../socialaccount/providers/oauth/__init__.py | 0 .../socialaccount/providers/oauth/client.py | 185 ++++ .../socialaccount/providers/oauth/models.py | 3 + .../socialaccount/providers/oauth/provider.py | 13 + .../socialaccount/providers/oauth/urls.py | 12 + .../socialaccount/providers/oauth/views.py | 84 ++ .../providers/oauth2/__init__.py | 0 .../socialaccount/providers/oauth2/client.py | 58 ++ .../socialaccount/providers/oauth2/models.py | 3 + .../providers/oauth2/provider.py | 23 + .../socialaccount/providers/oauth2/urls.py | 11 + .../socialaccount/providers/oauth2/views.py | 76 ++ .../providers/openid/__init__.py | 0 .../socialaccount/providers/openid/admin.py | 13 + .../socialaccount/providers/openid/forms.py | 7 + .../openid/migrations/0001_initial.py | 123 +++ .../openid/migrations/0002_tosocialaccount.py | 118 +++ .../0003_auto__del_openidaccount.py | 46 + .../providers/openid/migrations/__init__.py | 0 .../socialaccount/providers/openid/models.py | 22 + .../providers/openid/provider.py | 58 ++ .../socialaccount/providers/openid/tests.py | 16 + .../socialaccount/providers/openid/urls.py | 8 + .../socialaccount/providers/openid/utils.py | 76 ++ .../socialaccount/providers/openid/views.py | 109 +++ .../providers/twitter/__init__.py | 0 .../twitter/migrations/0001_initial.py | 115 +++ .../twitter/migrations/0002_snowflake.py | 91 ++ .../migrations/0003_tosocialaccount.py | 128 +++ ...uto__del_twitteraccount__del_twitterapp.py | 47 + .../providers/twitter/migrations/__init__.py | 0 .../socialaccount/providers/twitter/models.py | 3 + .../providers/twitter/provider.py | 46 + .../socialaccount/providers/twitter/urls.py | 4 + .../socialaccount/providers/twitter/views.py | 47 + itf/allauth/socialaccount/requests.py | 57 ++ .../socialaccount/templatetags/__init__.py | 0 .../templatetags/socialaccount_tags.py | 49 + itf/allauth/socialaccount/tests.py | 69 ++ itf/allauth/socialaccount/urls.py | 10 + itf/allauth/socialaccount/views.py | 63 ++ itf/allauth/templates/account/base.html | 3 + itf/allauth/templates/account/email.html | 71 ++ itf/allauth/templates/account/login.html | 53 ++ itf/allauth/templates/account/logout.html | 11 + .../templates/account/password_change.html | 14 + .../templates/account/password_delete.html | 14 + .../account/password_delete_done.html | 10 + .../templates/account/password_reset.html | 30 + .../account/password_reset_done.html | 16 + .../account/password_reset_from_key.html | 24 + .../account/password_reset_key_message.txt | 9 + .../templates/account/password_set.html | 15 + itf/allauth/templates/account/signup.html | 28 + .../account/snippets/already_logged_in.html | 5 + .../templates/account/verification_sent.html | 12 + .../account/verified_email_required.html | 26 + .../emailconfirmation/confirm_email.html | 20 + .../email_confirmation_message.txt | 4 + .../email_confirmation_subject.txt | 5 + itf/allauth/templates/openid/base.html | 1 + itf/allauth/templates/openid/login.html | 19 + .../socialaccount/account_inactive.html | 11 + .../socialaccount/authentication_error.html | 11 + itf/allauth/templates/socialaccount/base.html | 2 + .../templates/socialaccount/connections.html | 56 ++ .../socialaccount/login_cancelled.html | 17 + .../templates/socialaccount/signup.html | 25 + .../socialaccount/snippets/login_extra.html | 4 + .../socialaccount/snippets/provider_list.html | 19 + itf/allauth/tests.py | 15 + itf/allauth/urls.py | 21 + itf/allauth/utils.py | 85 ++ 137 files changed, 7150 insertions(+) create mode 100644 itf/allauth/__init__.py create mode 100644 itf/allauth/account/__init__.py create mode 100644 itf/allauth/account/admin.py create mode 100644 itf/allauth/account/app_settings.py create mode 100644 itf/allauth/account/auth_backends.py create mode 100644 itf/allauth/account/context_processors.py create mode 100644 itf/allauth/account/decorators.py create mode 100644 itf/allauth/account/forms.py create mode 100644 itf/allauth/account/models.py create mode 100644 itf/allauth/account/signals.py create mode 100644 itf/allauth/account/templatetags/__init__.py create mode 100644 itf/allauth/account/templatetags/account_tags.py create mode 100644 itf/allauth/account/tests.py create mode 100644 itf/allauth/account/urls.py create mode 100644 itf/allauth/account/utils.py create mode 100644 itf/allauth/account/views.py create mode 100644 itf/allauth/app_settings.py create mode 100644 itf/allauth/locale/nl/LC_MESSAGES/django.mo create mode 100644 itf/allauth/locale/nl/LC_MESSAGES/django.po create mode 100644 itf/allauth/models.py create mode 100644 itf/allauth/socialaccount/__init__.py create mode 100644 itf/allauth/socialaccount/admin.py create mode 100644 itf/allauth/socialaccount/app_settings.py create mode 100644 itf/allauth/socialaccount/context_processors.py create mode 100644 itf/allauth/socialaccount/fields.py create mode 100644 itf/allauth/socialaccount/forms.py create mode 100644 itf/allauth/socialaccount/helpers.py create mode 100644 itf/allauth/socialaccount/migrations/0001_initial.py create mode 100644 itf/allauth/socialaccount/migrations/0002_genericmodels.py create mode 100644 itf/allauth/socialaccount/migrations/0003_auto__add_unique_socialaccount_uid_provider.py create mode 100644 itf/allauth/socialaccount/migrations/__init__.py create mode 100644 itf/allauth/socialaccount/models.py create mode 100644 itf/allauth/socialaccount/providers/__init__.py create mode 100644 itf/allauth/socialaccount/providers/base.py create mode 100644 itf/allauth/socialaccount/providers/facebook/__init__.py create mode 100644 itf/allauth/socialaccount/providers/facebook/data/FacebookLocales.xml create mode 100644 itf/allauth/socialaccount/providers/facebook/forms.py create mode 100644 itf/allauth/socialaccount/providers/facebook/locale.py create mode 100644 itf/allauth/socialaccount/providers/facebook/migrations/0001_initial.py create mode 100644 itf/allauth/socialaccount/providers/facebook/migrations/0002_auto__add_facebookaccesstoken__add_unique_facebookaccesstoken_app_acco.py create mode 100644 itf/allauth/socialaccount/providers/facebook/migrations/0003_tosocialaccount.py create mode 100644 itf/allauth/socialaccount/providers/facebook/migrations/0004_auto__del_facebookapp__del_facebookaccesstoken__del_unique_facebookacc.py create mode 100644 itf/allauth/socialaccount/providers/facebook/migrations/__init__.py create mode 100644 itf/allauth/socialaccount/providers/facebook/models.py create mode 100644 itf/allauth/socialaccount/providers/facebook/provider.py create mode 100644 itf/allauth/socialaccount/providers/facebook/templates/facebook/channel.html create mode 100644 itf/allauth/socialaccount/providers/facebook/templates/facebook/fbconnect.html create mode 100644 itf/allauth/socialaccount/providers/facebook/urls.py create mode 100644 itf/allauth/socialaccount/providers/facebook/views.py create mode 100644 itf/allauth/socialaccount/providers/github/__init__.py create mode 100644 itf/allauth/socialaccount/providers/github/models.py create mode 100644 itf/allauth/socialaccount/providers/github/provider.py create mode 100644 itf/allauth/socialaccount/providers/github/urls.py create mode 100644 itf/allauth/socialaccount/providers/github/views.py create mode 100644 itf/allauth/socialaccount/providers/google/__init__.py create mode 100644 itf/allauth/socialaccount/providers/google/models.py create mode 100644 itf/allauth/socialaccount/providers/google/provider.py create mode 100644 itf/allauth/socialaccount/providers/google/urls.py create mode 100644 itf/allauth/socialaccount/providers/google/views.py create mode 100644 itf/allauth/socialaccount/providers/linkedin/__init__.py create mode 100644 itf/allauth/socialaccount/providers/linkedin/models.py create mode 100644 itf/allauth/socialaccount/providers/linkedin/provider.py create mode 100644 itf/allauth/socialaccount/providers/linkedin/urls.py create mode 100644 itf/allauth/socialaccount/providers/linkedin/views.py create mode 100644 itf/allauth/socialaccount/providers/oauth/__init__.py create mode 100644 itf/allauth/socialaccount/providers/oauth/client.py create mode 100644 itf/allauth/socialaccount/providers/oauth/models.py create mode 100644 itf/allauth/socialaccount/providers/oauth/provider.py create mode 100644 itf/allauth/socialaccount/providers/oauth/urls.py create mode 100644 itf/allauth/socialaccount/providers/oauth/views.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/__init__.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/client.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/models.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/provider.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/urls.py create mode 100644 itf/allauth/socialaccount/providers/oauth2/views.py create mode 100644 itf/allauth/socialaccount/providers/openid/__init__.py create mode 100644 itf/allauth/socialaccount/providers/openid/admin.py create mode 100644 itf/allauth/socialaccount/providers/openid/forms.py create mode 100644 itf/allauth/socialaccount/providers/openid/migrations/0001_initial.py create mode 100644 itf/allauth/socialaccount/providers/openid/migrations/0002_tosocialaccount.py create mode 100644 itf/allauth/socialaccount/providers/openid/migrations/0003_auto__del_openidaccount.py create mode 100644 itf/allauth/socialaccount/providers/openid/migrations/__init__.py create mode 100644 itf/allauth/socialaccount/providers/openid/models.py create mode 100644 itf/allauth/socialaccount/providers/openid/provider.py create mode 100644 itf/allauth/socialaccount/providers/openid/tests.py create mode 100644 itf/allauth/socialaccount/providers/openid/urls.py create mode 100644 itf/allauth/socialaccount/providers/openid/utils.py create mode 100644 itf/allauth/socialaccount/providers/openid/views.py create mode 100644 itf/allauth/socialaccount/providers/twitter/__init__.py create mode 100644 itf/allauth/socialaccount/providers/twitter/migrations/0001_initial.py create mode 100644 itf/allauth/socialaccount/providers/twitter/migrations/0002_snowflake.py create mode 100644 itf/allauth/socialaccount/providers/twitter/migrations/0003_tosocialaccount.py create mode 100644 itf/allauth/socialaccount/providers/twitter/migrations/0004_auto__del_twitteraccount__del_twitterapp.py create mode 100644 itf/allauth/socialaccount/providers/twitter/migrations/__init__.py create mode 100644 itf/allauth/socialaccount/providers/twitter/models.py create mode 100644 itf/allauth/socialaccount/providers/twitter/provider.py create mode 100644 itf/allauth/socialaccount/providers/twitter/urls.py create mode 100644 itf/allauth/socialaccount/providers/twitter/views.py create mode 100644 itf/allauth/socialaccount/requests.py create mode 100644 itf/allauth/socialaccount/templatetags/__init__.py create mode 100644 itf/allauth/socialaccount/templatetags/socialaccount_tags.py create mode 100644 itf/allauth/socialaccount/tests.py create mode 100644 itf/allauth/socialaccount/urls.py create mode 100644 itf/allauth/socialaccount/views.py create mode 100644 itf/allauth/templates/account/base.html create mode 100644 itf/allauth/templates/account/email.html create mode 100644 itf/allauth/templates/account/login.html create mode 100644 itf/allauth/templates/account/logout.html create mode 100644 itf/allauth/templates/account/password_change.html create mode 100644 itf/allauth/templates/account/password_delete.html create mode 100644 itf/allauth/templates/account/password_delete_done.html create mode 100644 itf/allauth/templates/account/password_reset.html create mode 100644 itf/allauth/templates/account/password_reset_done.html create mode 100644 itf/allauth/templates/account/password_reset_from_key.html create mode 100644 itf/allauth/templates/account/password_reset_key_message.txt create mode 100644 itf/allauth/templates/account/password_set.html create mode 100644 itf/allauth/templates/account/signup.html create mode 100644 itf/allauth/templates/account/snippets/already_logged_in.html create mode 100644 itf/allauth/templates/account/verification_sent.html create mode 100644 itf/allauth/templates/account/verified_email_required.html create mode 100644 itf/allauth/templates/emailconfirmation/confirm_email.html create mode 100644 itf/allauth/templates/emailconfirmation/email_confirmation_message.txt create mode 100644 itf/allauth/templates/emailconfirmation/email_confirmation_subject.txt create mode 100644 itf/allauth/templates/openid/base.html create mode 100644 itf/allauth/templates/openid/login.html create mode 100644 itf/allauth/templates/socialaccount/account_inactive.html create mode 100644 itf/allauth/templates/socialaccount/authentication_error.html create mode 100644 itf/allauth/templates/socialaccount/base.html create mode 100644 itf/allauth/templates/socialaccount/connections.html create mode 100644 itf/allauth/templates/socialaccount/login_cancelled.html create mode 100644 itf/allauth/templates/socialaccount/signup.html create mode 100644 itf/allauth/templates/socialaccount/snippets/login_extra.html create mode 100644 itf/allauth/templates/socialaccount/snippets/provider_list.html create mode 100644 itf/allauth/tests.py create mode 100644 itf/allauth/urls.py create mode 100644 itf/allauth/utils.py diff --git a/itf/allauth/__init__.py b/itf/allauth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/account/__init__.py b/itf/allauth/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/account/admin.py b/itf/allauth/account/admin.py new file mode 100644 index 0000000..d9a3fdb --- /dev/null +++ b/itf/allauth/account/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +# from models import PasswordReset +# +# class PasswordResetAdmin(admin.ModelAdmin): +# list_display = ["user", "temp_key", "timestamp", "reset"] +# +# admin.site.register(PasswordReset, PasswordResetAdmin) diff --git a/itf/allauth/account/app_settings.py b/itf/allauth/account/app_settings.py new file mode 100644 index 0000000..1d87298 --- /dev/null +++ b/itf/allauth/account/app_settings.py @@ -0,0 +1,57 @@ +import warnings +from django.conf import settings + +class AuthenticationMethod: + USERNAME = 'username' + EMAIL = 'email' + USERNAME_EMAIL = 'username_email' + +# The user is required to hand over an e-mail address when signing up +EMAIL_REQUIRED = getattr(settings, "ACCOUNT_EMAIL_REQUIRED", False) + +# After signing up, keep the user account inactive until the email +# address is verified +EMAIL_VERIFICATION = getattr(settings, "ACCOUNT_EMAIL_VERIFICATION", False) + +# Login by email address, not username +if hasattr(settings, "ACCOUNT_EMAIL_AUTHENTICATION"): + warnings.warn("ACCOUNT_EMAIL_AUTHENTICATION is deprecated, use ACCOUNT_AUTHENTICATION_METHOD", + DeprecationWarning) + if getattr(settings, "ACCOUNT_EMAIL_AUTHENTICATION"): + AUTHENTICATION_METHOD = AuthenticationMethod.EMAIL + else: + AUTHENTICATION_METHOD = AuthenticationMethod.USERNAME +else: + AUTHENTICATION_METHOD = getattr(settings, "ACCOUNT_AUTHENTICATION_METHOD", + AuthenticationMethod.USERNAME) + +# Enforce uniqueness of e-mail addresses +UNIQUE_EMAIL = getattr(settings, "ACCOUNT_UNIQUE_EMAIL", True) + +# Signup password verification +SIGNUP_PASSWORD_VERIFICATION = getattr(settings, + "ACCOUNT_SIGNUP_PASSWORD_VERIFICATION", + True) + +# Minimum Password Length +PASSWORD_MIN_LENGTH = getattr(settings, "ACCOUNT_PASSWORD_MIN_LENGTH", 6) + +# Subject-line prefix to use for email messages sent +EMAIL_SUBJECT_PREFIX = getattr(settings, "ACCOUNT_EMAIL_SUBJECT_PREFIX", None) + +# Signup form +SIGNUP_FORM_CLASS = getattr(settings, "ACCOUNT_SIGNUP_FORM_CLASS", None) + +# The user is required to enter a username when signing up +USERNAME_REQUIRED = getattr(settings, "ACCOUNT_USERNAME_REQUIRED", True) + +# render_value parameter as passed to PasswordInput fields +PASSWORD_INPUT_RENDER_VALUE = getattr(settings, + "ACCOUNT_PASSWORD_INPUT_RENDER_VALUE", + False) + +# If login is by email, email must be required +assert (not AUTHENTICATION_METHOD==AuthenticationMethod.EMAIL) or EMAIL_REQUIRED +# If login includes email, login must be unique +assert (AUTHENTICATION_METHOD==AuthenticationMethod.USERNAME) or UNIQUE_EMAIL +assert (not EMAIL_VERIFICATION) or EMAIL_REQUIRED diff --git a/itf/allauth/account/auth_backends.py b/itf/allauth/account/auth_backends.py new file mode 100644 index 0000000..75e7cae --- /dev/null +++ b/itf/allauth/account/auth_backends.py @@ -0,0 +1,40 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth.models import User +from django.db.models import Q + +from app_settings import AuthenticationMethod +import app_settings + +class AuthenticationBackend(ModelBackend): + + def authenticate(self, **credentials): + ret = None + if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL: + ret = self._authenticate_by_email(**credentials) + elif app_settings.AUTHENTICATION_METHOD \ + == AuthenticationMethod.USERNAME_EMAIL: + ret = self._authenticate_by_email(**credentials) + if not ret: + ret = self._authenticate_by_username(**credentials) + else: + ret = self._authenticate_by_username(**credentials) + return ret + + def _authenticate_by_username(self, **credentials): + return super(AuthenticationBackend, self).authenticate(**credentials) + + def _authenticate_by_email(self, **credentials): + # Even though allauth will pass along `email`, other apps may + # not respect this setting. For example, when using + # django-tastypie basic authentication, the login is always + # passed as `username`. So let's place nice with other apps + # and use username as fallback + email = credentials.get('email', credentials.get('username')) + if email: + users = User.objects.filter(Q(email__iexact=email) + | Q(emailaddress__email__iexact + =email)) + for user in users: + if user.check_password(credentials["password"]): + return user + return None diff --git a/itf/allauth/account/context_processors.py b/itf/allauth/account/context_processors.py new file mode 100644 index 0000000..95867b2 --- /dev/null +++ b/itf/allauth/account/context_processors.py @@ -0,0 +1,6 @@ +from django.conf import settings + +def account(request): + return { + "CONTACT_EMAIL": getattr(settings, "CONTACT_EMAIL", "support@example.com") + } diff --git a/itf/allauth/account/decorators.py b/itf/allauth/account/decorators.py new file mode 100644 index 0000000..fc98a97 --- /dev/null +++ b/itf/allauth/account/decorators.py @@ -0,0 +1,38 @@ +from django.contrib.auth.decorators import login_required +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.shortcuts import render + +from emailconfirmation.models import EmailAddress + +from utils import send_email_confirmation + + +def verified_email_required(function=None, + login_url=None, + redirect_field_name=REDIRECT_FIELD_NAME): + """ + Even when email verification is not mandatory during signup, there + may be circumstances during which you really want to prevent + unverified users to proceed. This decorator ensures the user is + authenticated and has a verified email address. If the former is + not the case then the behavior is identical to that of the + standard `login_required` decorator. If the latter does not hold, + email verification mails are automatically resend and the user is + presented with a page informing him he needs to verify his email + address. + """ + def decorator(view_func): + @login_required(redirect_field_name=redirect_field_name, + login_url=login_url) + def _wrapped_view(request, *args, **kwargs): + if not EmailAddress.objects.filter(user=request.user, + verified=True).exists(): + send_email_confirmation(request.user, request) + return render(request, + 'account/verified_email_required.html') + return view_func(request, *args, **kwargs) + return _wrapped_view + + if function: + return decorator(function) + return decorator diff --git a/itf/allauth/account/forms.py b/itf/allauth/account/forms.py new file mode 100644 index 0000000..e5b8b3f --- /dev/null +++ b/itf/allauth/account/forms.py @@ -0,0 +1,479 @@ +import base64 +import re +import uuid + +from django import forms +from django.conf import settings +from django.core.mail import send_mail +from django.core.urlresolvers import reverse +from django.core import exceptions +from django.db.models import Q +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _, ugettext +from django.utils.http import int_to_base36 +from django.utils.importlib import import_module + +from django.contrib import messages +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from django.contrib.sites.models import Site + +from emailconfirmation.models import EmailAddress + +# from models import PasswordReset +from utils import perform_login, send_email_confirmation, format_email_subject +from allauth.utils import email_address_exists +from app_settings import AuthenticationMethod + +import app_settings + +alnum_re = re.compile(r"^\w+$") + + +class PasswordField(forms.CharField): + + def __init__(self, *args, **kwargs): + render_value = kwargs.pop('render_value', + app_settings.PASSWORD_INPUT_RENDER_VALUE) + kwargs['widget'] = forms.PasswordInput(render_value=render_value) + super(PasswordField, self).__init__(*args, **kwargs) + +class SetPasswordField(PasswordField): + + def clean(self, value): + value = super(SetPasswordField, self).clean(value) + min_length = app_settings.PASSWORD_MIN_LENGTH + if len(value) < min_length: + raise forms.ValidationError(_("Password must be a minimum of {0} " + "characters.").format(min_length)) + return value + +class LoginForm(forms.Form): + + password = PasswordField( + label = _("Password")) + remember = forms.BooleanField( + label = _("Remember Me"), + # help_text = _("If checked you will stay logged in for 3 weeks"), + required = False + ) + + user = None + + def __init__(self, *args, **kwargs): + super(LoginForm, self).__init__(*args, **kwargs) + if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL: + login_widget = forms.TextInput(attrs={'placeholder': + _('E-mail address') }) + login_field = forms.EmailField(label=_("E-mail"), + widget=login_widget) + elif app_settings.AUTHENTICATION_METHOD \ + == AuthenticationMethod.USERNAME: + login_widget = forms.TextInput(attrs={'placeholder': + _('Username') }) + login_field = forms.CharField(label=_("Username"), + widget=login_widget, + max_length=30) + else: + assert app_settings.AUTHENTICATION_METHOD \ + == AuthenticationMethod.USERNAME_EMAIL + login_widget = forms.TextInput(attrs={'placeholder': + _('Username or e-mail') }) + login_field = forms.CharField(label=ugettext("Login"), + widget=login_widget) + self.fields["login"] = login_field + self.fields.keyOrder = ["login", "password", "remember"] + + def user_credentials(self): + """ + Provides the credentials required to authenticate the user for + login. + """ + credentials = {} + login = self.cleaned_data["login"] + if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL: + credentials["email"] = login + elif (app_settings.AUTHENTICATION_METHOD + == AuthenticationMethod.USERNAME): + credentials["username"] = login + else: + if "@" in login and "." in login: + credentials["email"] = login + else: + credentials["username"] = login + credentials["password"] = self.cleaned_data["password"] + return credentials + + def clean(self): + if self._errors: + return + user = authenticate(**self.user_credentials()) + if user: + if user.is_active: + self.user = user + else: + raise forms.ValidationError(_("This account is currently" + " inactive.")) + else: + if app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL: + error = _("The e-mail address and/or password you specified" + " are not correct.") + elif app_settings.AUTHENTICATION_METHOD \ + == AuthenticationMethod.USERNAME: + error = _("The username and/or password you specified are" + " not correct.") + else: + error = _("The login and/or password you specified are not" + " correct.") + raise forms.ValidationError(error) + return self.cleaned_data + + def login(self, request, redirect_url=None): + ret = perform_login(request, self.user, redirect_url=redirect_url) + if self.cleaned_data["remember"]: + request.session.set_expiry(60 * 60 * 24 * 7 * 3) + else: + request.session.set_expiry(0) + return ret + + + +class _DummyCustomSignupForm(forms.Form): + def save(self, user): + pass + +def _base_signup_form_class(): + if not app_settings.SIGNUP_FORM_CLASS: + return _DummyCustomSignupForm + try: + fc_module, fc_classname = app_settings.SIGNUP_FORM_CLASS.rsplit('.', 1) + except ValueError: + raise exceptions.ImproperlyConfigured('%s does not point to a form' + ' class' + % app_settings.SIGNUP_FORM_CLASS) + try: + mod = import_module(fc_module) + except ImportError, e: + raise exceptions.ImproperlyConfigured('Error importing form class %s:' + ' "%s"' % (fc_module, e)) + try: + fc_class = getattr(mod, fc_classname) + except AttributeError: + raise exceptions.ImproperlyConfigured('Module "%s" does not define a' + ' "%s" class' % (fc_module, + fc_classname)) + if not hasattr(fc_class, 'save'): + raise exceptions.ImproperlyConfigured('The custom signup form must' + ' implement a "save" method') + return fc_class + + +class BaseSignupForm(_base_signup_form_class()): + username = forms.CharField( + label = _("Username"), + max_length = 30, + widget = forms.TextInput() + ) + email = forms.EmailField(widget=forms.TextInput()) + + def __init__(self, *args, **kwargs): + super(BaseSignupForm, self).__init__(*args, **kwargs) + if (app_settings.EMAIL_REQUIRED or + app_settings.EMAIL_VERIFICATION or + app_settings.AUTHENTICATION_METHOD == AuthenticationMethod.EMAIL): + self.fields["email"].label = ugettext("E-mail") + self.fields["email"].required = True + else: + self.fields["email"].label = ugettext("E-mail (optional)") + self.fields["email"].required = False + if not app_settings.USERNAME_REQUIRED: + del self.fields["username"] + + def random_username(self): + return base64.urlsafe_b64encode(uuid.uuid4().bytes).strip('=') + + def clean_username(self): + value = self.cleaned_data["username"] + if not alnum_re.search(value): + raise forms.ValidationError(_("Usernames can only contain " + "letters, numbers and underscores.")) + try: + User.objects.get(username__iexact=value) + except User.DoesNotExist: + return value + raise forms.ValidationError(_("This username is already taken. Please " + "choose another.")) + + def clean_email(self): + value = self.cleaned_data["email"] + if app_settings.UNIQUE_EMAIL: + if value and email_address_exists(value): + raise forms.ValidationError \ + (_("A user is registered with this e-mail address.")) + return value + + def create_user(self, commit=True): + user = User() + # data collected by providers, if any, is passed as `initial` + # signup form data. This may contain fields such as + # `first_name`, whereas these may not have field counterparts + # in the form itself. So let's pick these up here... + data = self.initial + user.last_name = data.get('last_name', '') + user.first_name = data.get('first_name', '') + if app_settings.USERNAME_REQUIRED: + user.username = self.cleaned_data["username"] + else: + while True: + user.username = self.random_username() + try: + User.objects.get(username=user.username) + except User.DoesNotExist: + break + user.email = self.cleaned_data["email"].strip().lower() + user.set_unusable_password() + if commit: + user.save() + return user + + +class SignupForm(BaseSignupForm): + + password1 = SetPasswordField(label=_("Password")) + password2 = PasswordField(label=_("Password (again)")) + confirmation_key = forms.CharField( + max_length = 40, + required = False, + widget = forms.HiddenInput()) + + def __init__(self, *args, **kwargs): + super(SignupForm, self).__init__(*args, **kwargs) + current_order =self.fields.keyOrder + preferred_order = self.fields.keyOrder = ["username", + "password1", + "password2", + "email"] + if not app_settings.USERNAME_REQUIRED: + preferred_order = self.fields.keyOrder = ["email", + "password1", + "password2"] + # Make sure custom fields are put below main signup fields + self.fields.keyOrder = preferred_order + [ f for f in current_order if not f in preferred_order ] + if not app_settings.SIGNUP_PASSWORD_VERIFICATION: + del self.fields["password2"] + + def clean(self): + super(SignupForm, self).clean() + if app_settings.SIGNUP_PASSWORD_VERIFICATION \ + and "password1" in self.cleaned_data \ + and "password2" in self.cleaned_data: + if self.cleaned_data["password1"] != self.cleaned_data["password2"]: + raise forms.ValidationError(_("You must type the same password each time.")) + return self.cleaned_data + + def create_user(self, commit=True): + user = super(SignupForm, self).create_user(commit=False) + password = self.cleaned_data.get("password1") + if password: + user.set_password(password) + if commit: + user.save() + return user + + def save(self, request=None): + # don't assume a username is available. it is a common removal if + # site developer wants to use e-mail authentication. + email = self.cleaned_data["email"] + + if self.cleaned_data.get("confirmation_key"): + from friends.models import JoinInvitation # @@@ temporary fix for issue 93 + try: + join_invitation = JoinInvitation.objects.get(confirmation_key=self.cleaned_data["confirmation_key"]) + confirmed = True + except JoinInvitation.DoesNotExist: + confirmed = False + else: + confirmed = False + + new_user = self.create_user() + super(SignupForm, self).save(new_user, request) + + # @@@ clean up some of the repetition below -- DRY! + if confirmed: + if email == join_invitation.contact.email: + join_invitation.accept(new_user) # should go before creation of EmailAddress below + if request: + messages.add_message(request, messages.INFO, + ugettext(u"Your e-mail address has already been verified") + ) + # already verified so can just create + EmailAddress(user=new_user, email=email, verified=True, primary=True).save() + else: + join_invitation.accept(new_user) # should go before creation of EmailAddress below + if email: + if request: + messages.add_message(request, messages.INFO, + ugettext(u"Confirmation e-mail sent to %(email)s") % { + "email": email, + } + ) + EmailAddress.objects.add_email(new_user, email) + else: + send_email_confirmation(new_user, request=request) + + self.after_signup(new_user) + + return new_user + + def after_signup(self, user, **kwargs): + """ + An extension point for subclasses. + """ + pass + + +class UserForm(forms.Form): + + def __init__(self, user=None, *args, **kwargs): + self.user = user + super(UserForm, self).__init__(*args, **kwargs) + + +class AddEmailForm(UserForm): + + email = forms.EmailField( + label = _("E-mail"), + required = True, + widget = forms.TextInput(attrs={"size": "30"}) + ) + + def clean_email(self): + value = self.cleaned_data["email"] + errors = { + "this_account": _("This e-mail address already associated with this account."), + "different_account": _("This e-mail address already associated with another account."), + } + emails = EmailAddress.objects.filter(email__iexact=value) + if emails.filter(user=self.user).exists(): + raise forms.ValidationError(errors["this_account"]) + if app_settings.UNIQUE_EMAIL: + if emails.exclude(user=self.user).exists(): + raise forms.ValidationError(errors["different_account"]) + return value + + def save(self): + return EmailAddress.objects.add_email(self.user, self.cleaned_data["email"]) + + +class ChangePasswordForm(UserForm): + + oldpassword = PasswordField(label=_("Current Password")) + password1 = SetPasswordField(label=_("New Password")) + password2 = PasswordField(label=_("New Password (again)")) + + def clean_oldpassword(self): + if not self.user.check_password(self.cleaned_data.get("oldpassword")): + raise forms.ValidationError(_("Please type your current password.")) + return self.cleaned_data["oldpassword"] + + def clean_password2(self): + if "password1" in self.cleaned_data and "password2" in self.cleaned_data: + if self.cleaned_data["password1"] != self.cleaned_data["password2"]: + raise forms.ValidationError(_("You must type the same password each time.")) + return self.cleaned_data["password2"] + + def save(self): + self.user.set_password(self.cleaned_data["password1"]) + self.user.save() + + +class SetPasswordForm(UserForm): + + password1 = SetPasswordField(label=_("Password")) + password2 = PasswordField(label=_("Password (again)")) + + def clean_password2(self): + if "password1" in self.cleaned_data and "password2" in self.cleaned_data: + if self.cleaned_data["password1"] != self.cleaned_data["password2"]: + raise forms.ValidationError(_("You must type the same password each time.")) + return self.cleaned_data["password2"] + + def save(self): + self.user.set_password(self.cleaned_data["password1"]) + self.user.save() + + +class ResetPasswordForm(forms.Form): + + email = forms.EmailField( + label = _("E-mail"), + required = True, + widget = forms.TextInput(attrs={"size":"30"}) + ) + + def clean_email(self): + email = self.cleaned_data["email"] + self.users = User.objects.filter(Q(email__iexact=email) + | Q(emailaddress__email__iexact=email)).distinct() + if not self.users.exists(): + raise forms.ValidationError(_("The e-mail address is not assigned to any user account")) + return self.cleaned_data["email"] + + def save(self, **kwargs): + + email = self.cleaned_data["email"] + token_generator = kwargs.get("token_generator", default_token_generator) + + for user in self.users: + + temp_key = token_generator.make_token(user) + + # save it to the password reset model + # password_reset = PasswordReset(user=user, temp_key=temp_key) + # password_reset.save() + + current_site = Site.objects.get_current() + + # send the password reset email + subject = format_email_subject(_("Password Reset E-mail")) + path = reverse("account_reset_password_from_key", + kwargs=dict(uidb36=int_to_base36(user.id), + key=temp_key)) + url = 'http://%s%s' % (current_site.domain, + path) + message = render_to_string \ + ("account/password_reset_key_message.txt", + { "site": current_site, + "user": user, + "password_reset_url": url }) + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [email]) + return self.cleaned_data["email"] + + +class ResetPasswordKeyForm(forms.Form): + + password1 = SetPasswordField(label=_("New Password")) + password2 = PasswordField(label=_("New Password (again)")) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + self.temp_key = kwargs.pop("temp_key", None) + super(ResetPasswordKeyForm, self).__init__(*args, **kwargs) + + # FIXME: Inspecting other fields -> should be put in def clean(self) ? + def clean_password2(self): + if "password1" in self.cleaned_data and "password2" in self.cleaned_data: + if self.cleaned_data["password1"] != self.cleaned_data["password2"]: + raise forms.ValidationError(_("You must type the same password each time.")) + return self.cleaned_data["password2"] + + def save(self): + # set the new user password + user = self.user + user.set_password(self.cleaned_data["password1"]) + user.save() + # mark password reset object as reset + # PasswordReset.objects.filter(temp_key=self.temp_key).update(reset=True) + + diff --git a/itf/allauth/account/models.py b/itf/allauth/account/models.py new file mode 100644 index 0000000..2c958ba --- /dev/null +++ b/itf/allauth/account/models.py @@ -0,0 +1,11 @@ +from emailconfirmation.models import EmailConfirmation +from emailconfirmation.signals import email_confirmed + + +def mark_user_active(sender, instance=None, **kwargs): + user = kwargs.get("email_address").user + user.is_active = True + user.save() + + +email_confirmed.connect(mark_user_active, sender=EmailConfirmation) diff --git a/itf/allauth/account/signals.py b/itf/allauth/account/signals.py new file mode 100644 index 0000000..1254b3a --- /dev/null +++ b/itf/allauth/account/signals.py @@ -0,0 +1,4 @@ +import django.dispatch + + +user_logged_in = django.dispatch.Signal(providing_args=["request", "user"]) \ No newline at end of file diff --git a/itf/allauth/account/templatetags/__init__.py b/itf/allauth/account/templatetags/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/itf/allauth/account/templatetags/__init__.py @@ -0,0 +1 @@ + diff --git a/itf/allauth/account/templatetags/account_tags.py b/itf/allauth/account/templatetags/account_tags.py new file mode 100644 index 0000000..cd829e9 --- /dev/null +++ b/itf/allauth/account/templatetags/account_tags.py @@ -0,0 +1,47 @@ +from django import template + +from allauth.account.utils import user_display + +register = template.Library() + +class UserDisplayNode(template.Node): + + def __init__(self, user, as_var=None): + self.user_var = template.Variable(user) + self.as_var = as_var + + def render(self, context): + user = self.user_var.resolve(context) + + display = user_display(user) + + if self.as_var: + context[self.as_var] = display + return "" + return display + + +@register.tag(name="user_display") +def do_user_display(parser, token): + """ + Example usage:: + + {% user_display user %} + + or if you need to use in a {% blocktrans %}:: + + {% user_display user as user_display %} + {% blocktrans %}{{ user_display }} has sent you a gift.{% endblocktrans %} + + """ + bits = token.split_contents() + if len(bits) == 2: + user = bits[1] + as_var = None + elif len(bits) == 4: + user = bits[1] + as_var = bits[3] + else: + raise template.TemplateSyntaxError("'%s' takes either two or four arguments" % bits[0]) + + return UserDisplayNode(user, as_var) diff --git a/itf/allauth/account/tests.py b/itf/allauth/account/tests.py new file mode 100644 index 0000000..24a37b7 --- /dev/null +++ b/itf/allauth/account/tests.py @@ -0,0 +1,98 @@ +from datetime import timedelta +try: + from django.utils.timezone import now +except ImportError: + from datetime import datetime + now = datetime.now + +from django.test import TestCase +from django.conf import settings +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.test.client import Client +from django.core import mail +from django.contrib.sites.models import Site + +from emailconfirmation.models import EmailAddress, EmailConfirmation + +from app_settings import AuthenticationMethod +import app_settings + +class AccountTests(TestCase): + def setUp(self): + self.OLD_EMAIL_VERIFICATION = app_settings.EMAIL_VERIFICATION + self.OLD_AUTHENTICATION_METHOD = app_settings.AUTHENTICATION_METHOD + self.OLD_SIGNUP_FORM_CLASS = app_settings.SIGNUP_FORM_CLASS + self.OLD_USERNAME_REQUIRED = app_settings.USERNAME_REQUIRED + app_settings.EMAIL_VERIFICATION = True + app_settings.AUTHENTICATION_METHOD = AuthenticationMethod.USERNAME + app_settings.SIGNUP_FORM_CLASS = None + app_settings.USERNAME_REQUIRED = True + + if 'allauth.socialaccount' in settings.INSTALLED_APPS: + # Otherwise ImproperlyConfigured exceptions may occur + from ..socialaccount.models import SocialApp + SocialApp.objects.create(name='testfb', + provider='facebook', + site=Site.objects.get_current()) + + def test_email_verification_mandatory(self): + c = Client() + # Signup + resp = c.post(reverse('account_signup'), + { 'username': 'johndoe', + 'email': 'john@doe.com', + 'password1': 'johndoe', + 'password2': 'johndoe' }) + self.assertEquals(resp.status_code, 200) + self.assertEquals(mail.outbox[0].to, ['john@doe.com']) + self.assertEquals(len(mail.outbox), 1) + self.assertTemplateUsed(resp, + 'account/verification_sent.html') + # Attempt to login, unverified + for attempt in [1, 2]: + resp = c.post(reverse('account_login'), + { 'login': 'johndoe', + 'password': 'johndoe'}) + # is_active is controlled by the admin to manually disable + # users. I don't want this flag to flip automatically whenever + # users verify their email adresses. + self.assertTrue(User.objects.filter(username='johndoe', + is_active=True).exists()) + self.assertTemplateUsed(resp, + 'account/verification_sent.html') + self.assertEquals(len(mail.outbox), attempt) + # Wait for cooldown + EmailConfirmation.objects.update(sent=now() + - timedelta(days=1)) + # Verify, and re-attempt to login. + EmailAddress.objects.filter(user__username='johndoe') \ + .update(verified=True) + resp = c.post(reverse('account_login'), + { 'login': 'johndoe', + 'password': 'johndoe'}) + self.assertEquals(resp['location'], + 'http://testserver'+settings.LOGIN_REDIRECT_URL) + + + + + + def x_test_email_escaping(self): + """ + Test is only valid if emailconfirmation is listed after + allauth in INSTALLED_APPS + """ + site = Site.objects.get_current() + site.name = '' + site.save() + u = User.objects.create(username='test', + email='foo@bar.com') + EmailAddress.objects.add_email(u, u.email) + + def tearDown(self): + app_settings.EMAIL_VERIFICATION = self.OLD_EMAIL_VERIFICATION + app_settings.AUTHENTICATION_METHOD = self.OLD_AUTHENTICATION_METHOD + app_settings.SIGNUP_FORM_CLASS = self.OLD_SIGNUP_FORM_CLASS + app_settings.USERNAME_REQUIRED = self.OLD_USERNAME_REQUIRED + diff --git a/itf/allauth/account/urls.py b/itf/allauth/account/urls.py new file mode 100644 index 0000000..9c2c5da --- /dev/null +++ b/itf/allauth/account/urls.py @@ -0,0 +1,24 @@ +from django.conf.urls.defaults import patterns, url + +import views + +urlpatterns = patterns("", + url(r"^email/$", views.email, name="account_email"), + url(r"^signup/$", views.signup, name="account_signup"), + url(r"^login/$", views.login, name="account_login"), + url(r"^password/change/$", views.password_change, name="account_change_password"), + url(r"^password/set/$", views.password_set, name="account_set_password"), +# url(r"^password_delete/$", views.password_delete, name="acct_passwd_delete"), +# url(r"^password_delete/done/$", "django.views.generic.simple.direct_to_template", { +# "template": "account/password_delete_done.html", +# }, name="acct_passwd_delete_done"), + url(r"^logout/$", views.logout, name="account_logout"), + + url(r"^confirm_email/(\w+)/$", "emailconfirmation.views.confirm_email", name="account_confirm_email"), + + # password reset + url(r"^password/reset/$", views.password_reset, name="account_reset_password"), + url(r"^password/reset/done/$", views.password_reset_done, name="account_reset_password_done"), + url(r"^password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", views.password_reset_from_key, name="account_reset_password_from_key"), + +) diff --git a/itf/allauth/account/utils.py b/itf/allauth/account/utils.py new file mode 100644 index 0000000..df3aef3 --- /dev/null +++ b/itf/allauth/account/utils.py @@ -0,0 +1,162 @@ +from datetime import timedelta +try: + from django.utils.timezone import now +except ImportError: + from datetime import datetime + now = datetime.now + +from django.contrib import messages +from django.shortcuts import render +from django.contrib.sites.models import Site +from django.conf import settings +from django.core.urlresolvers import reverse +from django.contrib.auth import login +from django.utils.translation import ugettext_lazy as _, ugettext +from django.http import HttpResponseRedirect + +from emailconfirmation.models import EmailAddress, EmailConfirmation + +from allauth.utils import import_callable + +from signals import user_logged_in + +import app_settings + + +LOGIN_REDIRECT_URLNAME = getattr(settings, "LOGIN_REDIRECT_URLNAME", "") + + +def get_default_redirect(request, redirect_field_name="next", + login_redirect_urlname=LOGIN_REDIRECT_URLNAME, session_key_value="redirect_to"): + """ + Returns the URL to be used in login procedures by looking at different + values in the following order: + + - a REQUEST value, GET or POST, named "next" by default. + - LOGIN_REDIRECT_URL - the URL in the setting + - LOGIN_REDIRECT_URLNAME - the name of a URLconf entry in the settings + """ + if login_redirect_urlname: + default_redirect_to = reverse(login_redirect_urlname) + else: + default_redirect_to = settings.LOGIN_REDIRECT_URL + redirect_to = request.REQUEST.get(redirect_field_name) + if not redirect_to: + # try the session if available + if hasattr(request, "session"): + redirect_to = request.session.get(session_key_value) + # light security check -- make sure redirect_to isn't garabage. + if not redirect_to or "://" in redirect_to or " " in redirect_to: + redirect_to = default_redirect_to + return redirect_to + + + +_user_display_callable = None + +def user_display(user): + global _user_display_callable + if not _user_display_callable: + f = getattr(settings, "ACCOUNT_USER_DISPLAY", + lambda user: user.username) + _user_display_callable = import_callable(f) + return _user_display_callable(user) + + +# def has_openid(request): +# """ +# Given a HttpRequest determine whether the OpenID on it is associated thus +# allowing caller to know whether OpenID is good to depend on. +# """ +# from django_openid.models import UserOpenidAssociation +# for association in UserOpenidAssociation.objects.filter(user=request.user): +# if association.openid == unicode(request.openid): +# return True +# return False + + +def perform_login(request, user, redirect_url=None): + # not is_active: social users are redirected to a template + # local users are stopped due to form validation checking is_active + assert user.is_active + if (app_settings.EMAIL_VERIFICATION + and not EmailAddress.objects.filter(user=user, + verified=True).exists()): + send_email_confirmation(user, request=request) + return render(request, + "account/verification_sent.html", + { "email": user.email }) + # HACK: This may not be nice. The proper Django way is to use an + # authentication backend, but I fail to see any added benefit + # whereas I do see the downsides (having to bother the integrator + # to set up authentication backends in settings.py + if not hasattr(user, 'backend'): + user.backend = "django.contrib.auth.backends.ModelBackend" + user_logged_in.send(sender=user.__class__, request=request, user=user) + login(request, user) + messages.add_message(request, messages.SUCCESS, + ugettext("Successfully signed in as %(user)s.") % { "user": user_display(user) } ) + + if not redirect_url: + redirect_url = get_default_redirect(request) + return HttpResponseRedirect(redirect_url) + + +def complete_signup(request, user, success_url): + return perform_login(request, user, redirect_url=success_url) + + +def send_email_confirmation(user, request=None): + """ + E-mail verification mails are sent: + a) Explicitly: when a user signs up + b) Implicitly: when a user attempts to log in using an unverified + e-mail while EMAIL_VERIFICATION is mandatory. + + Especially in case of b), we want to limit the number of mails + sent (consider a user retrying a few times), which is why there is + a cooldown period before sending a new mail. + """ + COOLDOWN_PERIOD = timedelta(minutes=3) + email = user.email + if email: + try: + email_address = EmailAddress.objects.get(user=user, + email__iexact=email) + email_confirmation_sent = EmailConfirmation.objects \ + .filter(sent__gt=now() - COOLDOWN_PERIOD, + email_address=email_address) \ + .exists() + if not email_confirmation_sent: + EmailConfirmation.objects.send_confirmation(email_address) + except EmailAddress.DoesNotExist: + EmailAddress.objects.add_email(user, user.email) + email_confirmation_sent = False + if request and not email_confirmation_sent: + messages.info(request, + _(u"Confirmation e-mail sent to %(email)s") % {"email": email} + ) + +def format_email_subject(subject): + prefix = app_settings.EMAIL_SUBJECT_PREFIX + if prefix is None: + site = Site.objects.get_current() + prefix = "[{name}] ".format(name=site.name) + return prefix + unicode(subject) + + +def sync_user_email_addresses(user): + """ + Keep user.email in sync with user.emailadress_set. + + Under some circumstances the user.email may not have ended up as + an EmailAddress record, e.g. in the case of manually created admin + users. + """ + if user.email and not EmailAddress.objects.filter(user=user, + email=user.email).exists(): + EmailAddress.objects.create(user=user, + email=user.email, + primary=False, + verified=False) + diff --git a/itf/allauth/account/views.py b/itf/allauth/account/views.py new file mode 100644 index 0000000..ff9d9e3 --- /dev/null +++ b/itf/allauth/account/views.py @@ -0,0 +1,250 @@ +from django.core.urlresolvers import reverse +from django.contrib.sites.models import Site +from django.http import HttpResponseRedirect, Http404 +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.utils.http import base36_to_int +from django.utils.translation import ugettext + +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.contrib.auth.tokens import default_token_generator +from emailconfirmation.models import EmailAddress, EmailConfirmation + +from allauth.utils import passthrough_login_redirect_url + +from utils import get_default_redirect, complete_signup +from forms import AddEmailForm, ChangePasswordForm +from forms import LoginForm, ResetPasswordKeyForm +from forms import ResetPasswordForm, SetPasswordForm, SignupForm +from utils import sync_user_email_addresses + +def login(request, **kwargs): + form_class = kwargs.pop("form_class", LoginForm) + template_name = kwargs.pop("template_name", "account/login.html") + success_url = kwargs.pop("success_url", None) + url_required = kwargs.pop("url_required", False) + extra_context = kwargs.pop("extra_context", {}) + redirect_field_name = kwargs.pop("redirect_field_name", "next") + + if extra_context is None: + extra_context = {} + if success_url is None: + success_url = get_default_redirect(request, redirect_field_name) + + if request.method == "POST" and not url_required: + form = form_class(request.POST) + if form.is_valid(): + return form.login(request, redirect_url=success_url) + else: + form = form_class() + + ctx = { + "form": form, + "signup_url": passthrough_login_redirect_url(request, + reverse("account_signup")), + "site": Site.objects.get_current(), + "url_required": url_required, + "redirect_field_name": redirect_field_name, + "redirect_field_value": request.REQUEST.get(redirect_field_name), + } + ctx.update(extra_context) + return render_to_response(template_name, RequestContext(request, ctx)) + + +def signup(request, **kwargs): + form_class = kwargs.pop("form_class", SignupForm) + template_name = kwargs.pop("template_name", "account/signup.html") + redirect_field_name = kwargs.pop("redirect_field_name", "next") + success_url = kwargs.pop("success_url", None) + + if success_url is None: + success_url = get_default_redirect(request, redirect_field_name) + + if request.method == "POST": + form = form_class(request.POST) + if form.is_valid(): + user = form.save(request=request) + return complete_signup(request, user, success_url) + + else: + form = form_class() + ctx = {"form": form, + "login_url": passthrough_login_redirect_url(request, + reverse("account_login")), + "redirect_field_name": redirect_field_name, + "redirect_field_value": request.REQUEST.get(redirect_field_name) } + return render_to_response(template_name, RequestContext(request, ctx)) + + +@login_required +def email(request, **kwargs): + form_class = kwargs.pop("form_class", AddEmailForm) + template_name = kwargs.pop("template_name", "account/email.html") + sync_user_email_addresses(request.user) + if request.method == "POST" and request.user.is_authenticated(): + if request.POST.has_key("action_add"): + add_email_form = form_class(request.user, request.POST) + if add_email_form.is_valid(): + add_email_form.save() + messages.add_message(request, messages.INFO, + ugettext(u"Confirmation e-mail sent to %(email)s") % { + "email": add_email_form.cleaned_data["email"] + } + ) + add_email_form = form_class() # @@@ + else: + add_email_form = form_class() + if request.POST.get("email"): + if request.POST.has_key("action_send"): + email = request.POST["email"] + try: + email_address = EmailAddress.objects.get( + user=request.user, + email=email, + ) + messages.add_message(request, messages.INFO, + ugettext("Confirmation e-mail sent to %(email)s") % { + "email": email, + } + ) + EmailConfirmation.objects.send_confirmation(email_address) + except EmailAddress.DoesNotExist: + pass + elif request.POST.has_key("action_remove"): + email = request.POST["email"] + try: + email_address = EmailAddress.objects.get( + user=request.user, + email=email + ) + email_address.delete() + messages.add_message(request, messages.SUCCESS, + ugettext("Removed e-mail address %(email)s") % { + "email": email, + } + ) + except EmailAddress.DoesNotExist: + pass + elif request.POST.has_key("action_primary"): + email = request.POST["email"] + email_address = EmailAddress.objects.get( + user=request.user, + email=email, + ) + email_address.set_as_primary() + messages.add_message(request, messages.SUCCESS, + ugettext("Primary e-mail address set")) + else: + add_email_form = form_class() + ctx = { "add_email_form": add_email_form } + return render_to_response(template_name, RequestContext(request, ctx)) + + +@login_required +def password_change(request, **kwargs): + + form_class = kwargs.pop("form_class", ChangePasswordForm) + template_name = kwargs.pop("template_name", "account/password_change.html") + + if not request.user.has_usable_password(): + return HttpResponseRedirect(reverse(password_set)) + + if request.method == "POST": + password_change_form = form_class(request.user, request.POST) + if password_change_form.is_valid(): + password_change_form.save() + messages.add_message(request, messages.SUCCESS, + ugettext(u"Password successfully changed.") + ) + password_change_form = form_class(request.user) + else: + password_change_form = form_class(request.user) + ctx = { "password_change_form": password_change_form } + return render_to_response(template_name, RequestContext(request, ctx)) + + +@login_required +def password_set(request, **kwargs): + + form_class = kwargs.pop("form_class", SetPasswordForm) + template_name = kwargs.pop("template_name", "account/password_set.html") + + if request.user.has_usable_password(): + return HttpResponseRedirect(reverse(password_change)) + + if request.method == "POST": + password_set_form = form_class(request.user, request.POST) + if password_set_form.is_valid(): + password_set_form.save() + messages.add_message(request, messages.SUCCESS, + ugettext(u"Password successfully set.") + ) + return HttpResponseRedirect(reverse(password_change)) + else: + password_set_form = form_class(request.user) + ctx = { "password_set_form": password_set_form } + return render_to_response(template_name, RequestContext(request, ctx)) + + +def password_reset(request, **kwargs): + + form_class = kwargs.pop("form_class", ResetPasswordForm) + template_name = kwargs.pop("template_name", "account/password_reset.html") + + if request.method == "POST": + password_reset_form = form_class(request.POST) + if password_reset_form.is_valid(): + password_reset_form.save() + return HttpResponseRedirect(reverse(password_reset_done)) + else: + password_reset_form = form_class() + + return render_to_response(template_name, RequestContext(request, { "password_reset_form": password_reset_form, })) + + +def password_reset_done(request, **kwargs): + + return render_to_response(kwargs.pop("template_name", "account/password_reset_done.html"), RequestContext(request, {})) + + +def password_reset_from_key(request, uidb36, key, **kwargs): + + form_class = kwargs.get("form_class", ResetPasswordKeyForm) + template_name = kwargs.get("template_name", "account/password_reset_from_key.html") + token_generator = kwargs.get("token_generator", default_token_generator) + + # pull out user + try: + uid_int = base36_to_int(uidb36) + except ValueError: + raise Http404 + + user = get_object_or_404(User, id=uid_int) + + if token_generator.check_token(user, key): + if request.method == "POST": + password_reset_key_form = form_class(request.POST, user=user, temp_key=key) + if password_reset_key_form.is_valid(): + password_reset_key_form.save() + messages.add_message(request, messages.SUCCESS, + ugettext(u"Password successfully changed.") + ) + password_reset_key_form = None + else: + password_reset_key_form = form_class() + ctx = { "form": password_reset_key_form, } + else: + ctx = { "token_fail": True, } + + return render_to_response(template_name, RequestContext(request, ctx)) + + +def logout(request, **kwargs): + messages.add_message(request, messages.SUCCESS, + ugettext("You have signed out.") + ) + kwargs['template_name'] = kwargs.pop('template_name', 'account/logout.html') + from django.contrib.auth.views import logout as _logout + return _logout(request, **kwargs) diff --git a/itf/allauth/app_settings.py b/itf/allauth/app_settings.py new file mode 100644 index 0000000..e423a1c --- /dev/null +++ b/itf/allauth/app_settings.py @@ -0,0 +1,10 @@ +from django.conf import settings + +SOCIALACCOUNT_ENABLED = 'allauth.socialaccount' in settings.INSTALLED_APPS + +if SOCIALACCOUNT_ENABLED: + assert 'allauth.socialaccount.context_processors.socialaccount' \ + in settings.TEMPLATE_CONTEXT_PROCESSORS + +LOGIN_REDIRECT_URL = getattr(settings, 'LOGIN_REDIRECT_URL', '/') + diff --git a/itf/allauth/locale/nl/LC_MESSAGES/django.mo b/itf/allauth/locale/nl/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..fceff5d760c599766d0bedf06418b974a746f11a GIT binary patch literal 13044 zcmc(lYm6kdXJ(h(*8_dTs!mn)jAtX0NQ59IJS_-OBq#nr@P|k|0v|96LI`2YmJ(hH7^EPOfJAW&CWucK zh~GK))}woNjUOLqspemGtLol!&*Oj2y*2-E(^bC~@Y~?{QI7b^AdrTyd=G#4-SE9Z z@B#2funi{Q)!^gc_kmvoZvdYHTi|oxP2ktTw}IaRmq5AsAn*W4Q}7w^t>6>j4}!k~ z?ggI#rN0;b{r>^)=KA_;gFr+&0p19n1b+k!L3#dB@OtnU{q^JEi0j`19|7NST@bt- z{497E_+?P~eF1zkxc4nio?Ahhf_prk0KcE>v*0b@2KWx}G0=ct2CoAD8kF&$2d@FY z35r}VdAx#~a{ng&`~Y|}I19cH{Bh8NKMZ~X{B`gP;2yb;l0@GRgL1zEiad{kOf7f} z6uo>NG~gFO(a+PM%;R}b-v1YmFM_Bv_%BdRjF4jYtHGZD-wDoxC%`+wPl1QQKLJH= z()nkByFqFHD{!AQWAuB#IZ*o90!81S0}p|}4~oA31AH9373Y!up9Mu8nbgOCYeCW9 zr$HVKeifAWpY-kj42m9J^w-y*%&&8OBlr^dP4F@Bd7SDK;3iIZ3VaC^x#xcfp9B9M z9DyH2NV*EX11^JC(D}Q;Rq)IeL9hdgoxX~*?FU~54}yD9n)ty(;E#cifT%S13@CE{ zCio`sDX_@29{(1U=U)JC1+Osf{w#<~1xLX5fvcd{|C6AM`wJi{37!CD9#4VdKhJ`q zpT7aOz!$)u2Rk=Azx{XcC%L{JXB9nv7!>>cE+}&RAt?R)1t|0S1}OUaH&EjJI-EuH zeGnA=eh?IW{2VCb{t_tu@f#o}41ODw`91?me}Cuk+u-e7zYLxaNY;KxCk_aA{zg5LrqK7N^pJHYRNxL{!RIlKoHzc~lK2mBTA zkHM$Gqu?h|qR9CyC~@|@zusanvG315fr5p4yLE_0-6V|1qU25XH2KI z#x(l7fxjCZ;{PYK#r+XGLkp_^8^KlAtn*ytJo48iJm61Ix%xuceiJ8{Nq?5VAM)o{ zdldT}=Qzf34~OtT`8!pffGhs|%^=|v^f~VGZMe1m-s^E4JnPR(=yGz#Uvxl1FL;;( zlPTvM1;uyQIK&6<=MdkY=a3(+7(AqB&PP7z&(DEx_2 z+9X(T8a)-}Igj)LRInapLqF4MdcHXn-IQv~ZBodX-bVZLU}2nP(pdF&%*M8`jq6km zjY{Hp$83kLgUk+7v{MYM$$6n$V6;h79SOYV9hiM7GftB*-tTXef{F=6eid};p3Tb! z?=!*uY1U6`agX?h0?Vk2jl5Qln8gHNx|H9Uj*Zjp8BB|#*<0{k)Y8*gV(oGkG0%vF z#LLkb9%Bm=742YYUE92O>=%I1&m)I=6S%MvoXnw&Q+C0k(}^`yT_UuM(pAd2qOZ)nr-#7v{QspB9p*{@p$He z|CseG9hyCR^F6QP#ms(WY;*CMsW+F!=>z?17Q&=!Sk?VWL3ck;*x=;ICQHZ6 z8Hv!PB&a;TJgLO08eA;&sz=Rf@q(#K9m3oU(f)8eH0iqe=z&KKRG8ri zHp|;}ANg2qvp$yaa%9=N+-(NumLnifG>$6M#I_j3k?bR|sB+67g4;&QZJxvOwmF&2 zh!3_(vX*|vqY3&NkHq0rzadnSM}@tRghRU@lQ=Ha*zppyG@Ry2xr&{UXL~45sb%Ct z64AY9c&uX05i(&|1gGt+M&mh~Me9-5k-}*^w8IY3ykdDkn8(93=`@HuE_~ zC80SbOIR@s@5}Urw&_9r#=kwMue+r5I>fD}Mi`^-I7&828ac{Q@|rQD6wy2QddGG{ zR0XG!X_3lj-Hs)|Y45d-@vdIRc36|=-hDby?}83fdT#>bObXzbQ}pQbYU#i-qz(m} zN&(SOge?Q8sn5&3-iz|82i2TdUxGzKZ1omMZTO7f0%q`>dvI4D~&d6 zt#wt10}Ti!KbY_KRlcl6+2otTFhX&3YJ@9b@Q;im3?ms2@7j`3CQlB+q9xf>{BL{U z%8G6rMnie1W7~$@?ux$AIKxfs4C%S-OkWpAZTarf?F1qnL?ap65#L00*)~pHl|4jt z-p2~kRMIZpt2;;;411|s+QHdGhae(@vr?M6h`xZOM{&5bpKgjNRupIIQ*b6ueDP)q z2}$r}t=n$3*3vR_yQjp)F3@L8KNTJ9-KTXcQRy7IrQ?e;RVM}&IZIVdbi-(ABI!da z4QPl8PO{95Nyb8(B2LeYlOCrGY2hYEugp+ScFg;wJYRGQEbHU*VJ5}HTyWmHT3D;c zxM&7y65In_&**Wf*iB83#hSA|l| zI#Ep~9T%1v=@hk?YQ2$S%CshDB@>yL*`W?)n3`}u3zd$`hR!csQWAF+imhii%F->S zr;NHzXvWb+Um-));}Hc;DplAUnDQH|A1BN>G2#C=k#IYgjymaB7bZlj3uXx3sWY1{ zJ#aghoj7StR3~jSkE(q$r4(9?9Lf5gFNs_qp>xv&? zF)g9VvYzDX4)D-jC6@2d6+Z-}_v~uDFh#VICC8(-M4-tB={W8|dAinO$q2A3*rMCknf9nQypz%gb-QSjTE6e6F0UzzyJ=Hd)&xsp zjI^}$3BdWXrUc3HQt;-&t(D%@piQWgr526i#I(})h}%PHK@H4Z~8=a#|~nnX7!O-&$qtj^M+ zMa0E_oZ{SS!*?bt0xgU+M@n^2l_@stvegj{tv-4SeLA!H3clI_Da$3KktC3(Lo2qi zF%cvA#}`S65Z0Z`%vF4yepy}A`KyOwM5y8=2*7m;kuQoqo0r}n2=lU_E>mI z;aS&W)eO|%+GauNzT$da6-X>y!X3$mzJO!@qFKVVT1yFXI1-S|!dic(o`Kd?nAl}x zw#ANo$o7uY{}X_mWSR zolVvL{*_8=RRQX}j5VJQUfpM!3E8Du5xSE~j=($TaHnocvjAf|LiLqS?UhVpx{#3t zS-L^-JG<1Ir4Y#3pP4hK<`*8EKfY+rEuKEJbaJ(I+K$q!m|e;HQE&F>xS!9irE{ir z>g3w&0!$^FI9S?0{a1GgWXJ#gpj!Gq@D9dic`+X zFfV4;vM|Zxut>8xbN|`pWwScJvS<$J*2sPH$Bvy|Jagu7Ynd&@aoD%BYc?FtndSM_ z<7Y+WL(9we#Zeo#sh>gAm8F%%dVaUJ54092l-Tu}U1PC2XA1jBaT~Q=l-#3hbWD8j z*|qy;@2Z~@vDR%iyO?y-o)ksqu1-|6s;$u#%U)eNYV3|~ShLASB{gReZS-meo7x`b zu__@DDO5ch7o)!Qe7L9^qj;k%iu$&bjiXI?tj{K??3HHLl75C{kRAn7v!Bavl-hcq z)GZ(Edb!6|q;@N{3?7^VCi)|gt#3sV^u;2l3K6+?!gdUE@uK?RIntc=1naL@@*L0i z;G8g=n5|58UCM!U)VEBrCuMcY9~J|=u^q*fkWr$Zo#?))=ujnMe78T*4o%R&kBQ>EMFGP|eN*4w9{>|#UMZU)F&no)01t4~}v zr_VTORt9P4q-r$h#wu~sog}R}3*-gcABoIJ?Bt)>4nuOannzlb$d0!gI>N@sAl| z7GA-8oJ$W>2W*@Ud1GKZg&Ma~Bi47_NI`}X-K(r$p-HFD{bJ`i9z>pRrAvE#6qd0Nr`E3 z7#VU9S&d;1~%7AU(YXXNob!7smFhlZx|vY(vro zD<1q+wjmt#O^{a2vo$yNNHv-Ck6|`Q3YB()hbN3z`%Q4#CrgMcm|yS1I0!eLZh+ed=mhtKQw^i3x^8m9;Eplk!}NkTiw@us}OFHkG4gu>!~O zt$;w{vkfEST)p6!C17IWW-3WQ>E@!$xo+)*btfZVp8=G>YXW8oTxqlPVK}4-pigvRC(AsvQ-rsY$H0Fc^ z>bhQvHuk2rnG6FNb4m-%pz+$c+?x=%SD>h?Dy#A&42Q2qZ`jd~P$@+=?kJ?h#IJB` z!eN*K8iS~HE554aBv>W#tNn*dJIsYnuv z*}ky3FRs`bNMKz{Y3suFPrpRaw9Pq~SLtbYEK}qr;`^+H(QB5*Qod-H8@AO8!`?Qf zv2&C%k7%}+WFjI6`S&Ses$xP}gh`7_P1(k&jEcawx6Ol*e9&l*+nu%wsG8#|3YK)K z+f76B;PI+{o?Zy4Y9<+S00X+RTEc$9aFxU?J&)RpsofN{Rw1jOJeW?IMpYML2*2Bl z&&-U07GE{%E2~K1EzOQU=Yt6-Gz~u`-|4H+;7J$a@1=|jc->hoU z3f1nI^@7mst_@-N?*gYd*%0RQ#cq}0!6J=u4C_;~JnS_=t|8cnuSylA%6(pVy{hp4 zit}1kp?l&^4`O{$hx;T@siGk&ll#A=qzj4mS2Ek{mG$IM6X#^uy48Z`q{b3)_jWR3 ziM`>iX7GA0lD!w0QPZie;x}NUcwHR97ud4sRz`HBT1Ed=r>b=po4(0KEkTuVg=}m2rIuC$PcG#f z>jr0PAugRKYsQE#w^X-m^=buL{s)298|8LxSx(BrzN{k{)ot&RZB>+#8hgr(yO(a4 z+1zb7q%Spdx3uu-fPkE4cAdUfa-lE$w8h)BS@f_%kOC_mdiB?f4Oa=4WuU&V3j-7? zPJ#P0YNukGb+DE|QG#I$`qVc{0>j&CR*<0(+Ur*{37(=OSqe^QLyXqwy<-P#$_klF z&4`nD?=Sap%k*EVeq#09HW)ysu0pVjB>U3JPzgC$^0p*9x)rNyaI4o-K?nni>2M0E zO>ltCIFcQ&iRjVP=g6)hbm<0@vRr-(STnzN#fMowE&HjKb(KV3WpI}roz<)T;G@W! zY_gvsC6H*)Kdxy$P0+v8Kx_`9j%JFJYEJ7aN9&pod88cJl>~jUR%3QzEnV)YnJ@9I z6gOphofuAEX!hh*hdEy$73V=Ao$tq&TE)~~wA;b| E0+)1oj{pDw literal 0 HcmV?d00001 diff --git a/itf/allauth/locale/nl/LC_MESSAGES/django.po b/itf/allauth/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 0000000..ace97e7 --- /dev/null +++ b/itf/allauth/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,689 @@ +# 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: 2012-06-22 23:00+0200\n" +"PO-Revision-Date: 2012-06-22 23:04+0200\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: account/forms.py:48 +msgid "Password must be a minimum of {0} characters." +msgstr "Het wachtwoord moet minimaal {0} tekens bevatten." + +#: account/forms.py:55 account/forms.py:243 account/forms.py:393 +msgid "Password" +msgstr "Wachtwoord" + +#: account/forms.py:57 +msgid "Remember Me" +msgstr "Onthouden" + +#: account/forms.py:68 +msgid "E-mail address" +msgstr "E-mail adres" + +#: account/forms.py:69 account/forms.py:185 account/forms.py:346 +#: account/forms.py:410 +msgid "E-mail" +msgstr "E-mail" + +#: account/forms.py:74 account/forms.py:75 account/forms.py:174 +msgid "Username" +msgstr "Gebruikersnaam" + +#: account/forms.py:82 +msgid "Username or e-mail" +msgstr "Gebruikersnaam of e-mail" + +#: account/forms.py:83 +msgid "Login" +msgstr "Inloggen" + +#: account/forms.py:116 +msgid "This account is currently inactive." +msgstr "Dit account is niet actief" + +#: account/forms.py:120 +msgid "The e-mail address and/or password you specified are not correct." +msgstr "Je e-mail adres en wachtwoord komen niet overeen." + +#: account/forms.py:124 +msgid "The username and/or password you specified are not correct." +msgstr "Je gebruikersnaam en wachtwoord komen niet overeen." + +#: account/forms.py:127 +msgid "The login and/or password you specified are not correct." +msgstr "Je login en wachtwoord komen niet overeen." + +#: account/forms.py:188 +msgid "E-mail (optional)" +msgstr "E-mail (optioneel)" + +#: account/forms.py:199 +msgid "Usernames can only contain letters, numbers and underscores." +msgstr "" +"Gebruikersnamen mogen alleen letters, cijfers en liggende streepjes (_) " +"bevatten." + +#: account/forms.py:205 +msgid "This username is already taken. Please choose another." +msgstr "Deze gebruikersnaam is al in gebruik. Kies a.u.b. een andere naam." + +#: account/forms.py:213 +msgid "A user is registered with this e-mail address." +msgstr "Er is al een gebruiker geregistreerd met dit e-mail adres." + +#: account/forms.py:244 account/forms.py:394 +msgid "Password (again)" +msgstr "Wachtwoord (bevestigen)" + +#: account/forms.py:272 account/forms.py:383 account/forms.py:399 +#: account/forms.py:468 +msgid "You must type the same password each time." +msgstr "Je moet hetzelfde wachtwoord twee keer intoetsen." + +#: account/forms.py:308 +msgid "Your e-mail address has already been verified" +msgstr "Je e-mail adres is al geverifieerd" + +#: account/forms.py:317 account/utils.py:135 account/views.py:93 +#: account/views.py:109 +#, python-format +msgid "Confirmation e-mail sent to %(email)s" +msgstr "Bevestigings e-mail verzonden aan %(email)s" + +#: account/forms.py:354 +msgid "This e-mail address already associated with this account." +msgstr "Dit e-mail adres is al geassocieerd met dit account." + +#: account/forms.py:355 +msgid "This e-mail address already associated with another account." +msgstr "Dit e-mail adres is al geassocieerd met een ander account." + +#: account/forms.py:371 +msgid "Current Password" +msgstr "Huidig wachtwoord" + +#: account/forms.py:372 account/forms.py:456 +msgid "New Password" +msgstr "Nieuw wachtwoord" + +#: account/forms.py:373 account/forms.py:457 +msgid "New Password (again)" +msgstr "Nieuw wachtwoord (bevestigen)" + +#: account/forms.py:377 +msgid "Please type your current password." +msgstr "Geef je huidige wachtwoord op." + +#: account/forms.py:420 +msgid "The e-mail address is not assigned to any user account" +msgstr "Dit e-mail adres is niet bij ons bekend" + +#: account/forms.py:439 +msgid "Password Reset E-mail" +msgstr "Nieuw wachtwoord" + +#: account/utils.py:96 +#, python-format +msgid "Successfully signed in as %(user)s." +msgstr "Je bent nu ingelogd als %(user)s." + +#: account/views.py:125 +#, python-format +msgid "Removed e-mail address %(email)s" +msgstr "E-mail adres %(email)s verwijderd" + +#: account/views.py:139 +msgid "Primary e-mail address set" +msgstr "Primair e-mail adres ingesteld" + +#: account/views.py:160 account/views.py:233 +msgid "Password successfully changed." +msgstr "Wachtwoord wijziging geslaagd." + +#: account/views.py:183 +msgid "Password successfully set." +msgstr "Je wachtwoord is gewijzigd." + +#: account/views.py:247 templates/account/logout.html:10 +msgid "You have signed out." +msgstr "Je bent afgemeld." + +#: socialaccount/forms.py:34 +msgid "Your local account has no password setup." +msgstr "Je account heeft geen wachtwoord ingesteld." + +#: socialaccount/forms.py:38 +msgid "Your local account has no verified e-mail address." +msgstr "Je account heeft geen geverifieerd e-mail adres." + +#: socialaccount/helpers.py:108 +msgid "The social account has been connected to your existing account" +msgstr "Het externe account is gekoppeld aan je bestaande account" + +#: socialaccount/views.py:58 +msgid "The social account has been disconnected" +msgstr "Het externe account is ontkoppeld" + +#: socialaccount/providers/oauth/client.py:79 +#, python-format +msgid "Invalid response while obtaining request token from \"%s\"." +msgstr "" +"Ongeldig antwoord ontvangen tijdens het ophalen van een verzoeksleutel van " +"\"%s\"." + +#: socialaccount/providers/oauth/client.py:102 +#, python-format +msgid "Invalid response while obtaining access token from \"%s\"." +msgstr "" +"Ongeldig antwoord ontvangen tijdens het ophalen van een toegangssleutel van " +"\"%s\"." + +#: socialaccount/providers/oauth/client.py:115 +#, python-format +msgid "No request token saved for \"%s\"." +msgstr "Geen verzoeksleutel opgeslagen voor \"%s\"." + +#: socialaccount/providers/oauth/client.py:163 +#, python-format +msgid "No access token saved for \"%s\"." +msgstr "Geen toegangssleutel opgeslagen voor \"%s\"." + +#: socialaccount/providers/oauth/client.py:183 +#, python-format +msgid "No access to private resources at \"%s\"." +msgstr "Geen toegang tot prive data bij \"%s\"." + +#: templates/account/email.html:5 +msgid "Account" +msgstr "Account" + +#: templates/account/email.html:8 +msgid "E-mail Addresses" +msgstr "E-mail adressen" + +#: templates/account/email.html:10 +msgid "The following e-mail addresses are associated to your account:" +msgstr "De volgende e-mail adressen zijn gekoppeld aan jouw account:" + +#: templates/account/email.html:24 +msgid "Verified" +msgstr "Geverifieerd" + +#: templates/account/email.html:26 +msgid "Unverified" +msgstr "Ongeverifieerd" + +#: templates/account/email.html:28 +msgid "Primary" +msgstr "Primair" + +#: templates/account/email.html:34 +msgid "Make Primary" +msgstr "Maak primair" + +#: templates/account/email.html:35 +msgid "Re-send Verification" +msgstr "Stuur verificatie e-mail opnieuw" + +#: templates/account/email.html:36 +msgid "Remove" +msgstr "Verwijder" + +#: templates/account/email.html:43 +msgid "Warning:" +msgstr "Waarschuwing:" + +#: templates/account/email.html:43 +msgid "" +"You currently do not have any e-mail address set up. You should really add " +"an e-mail address so you can receive notifications, reset your password, etc." +msgstr "" +"Het is raadzaam een e-mail adres toe te voegen zodat je notificaties kunt " +"ontvangen, je wachtwoord opnieuw kunt instellen, enz." + +#: templates/account/email.html:48 +msgid "Add E-mail Address" +msgstr "Voeg e-mail adres toe" + +#: templates/account/email.html:53 +msgid "Add E-mail" +msgstr "E-mail toevoegen" + +#: templates/account/email.html:63 +msgid "Do you really want to remove the selected e-mail address?" +msgstr "Wil je het geselecteerde e-mail adres echt verwijderen?" + +#: templates/account/login.html:6 templates/account/login.html.py:11 +#: templates/account/login.html:43 +msgid "Sign In" +msgstr "Aanmelden" + +#: templates/account/login.html:16 +#, python-format +msgid "" +"Please sign in with one\n" +"of your existing third party accounts. Or, sign up for a %(site_name)s account and sign in\n" +"below:" +msgstr "" +"Meld je aan met een van je bestaande externe accounts. Of, registreer voor een %(site_name)s account en " +"meld je hiermee aan:" + +#: templates/account/login.html:27 +msgid "or" +msgstr "of" + +#: templates/account/login.html:42 +msgid "Forgot Password?" +msgstr "Wachtwoord vergeten?" + +#: templates/account/logout.html:5 templates/account/logout.html.py:8 +msgid "Signed Out" +msgstr "Afgemeld" + +#: templates/account/password_change.html:4 +#: templates/account/password_change.html:7 +#: templates/account/password_change.html:12 +#: templates/account/password_reset_from_key.html:4 +#: templates/account/password_reset_from_key.html:7 +msgid "Change Password" +msgstr "Wachtwoord wijzigen" + +#: templates/account/password_delete.html:5 +#: templates/account/password_delete.html:8 +msgid "Delete Password" +msgstr "Wis wachtwoord" + +#: templates/account/password_delete.html:9 +msgid "" +"You may delete your password since you are currently logged in using OpenID." +msgstr "Je kunt je wachtwoord wissen omdat je via OpenID bent ingelogd." + +#: templates/account/password_delete.html:12 +msgid "delete my password" +msgstr "Wis mijn wachtwoord" + +#: templates/account/password_delete_done.html:5 +#: templates/account/password_delete_done.html:8 +msgid "Password Deleted" +msgstr "Wachtwoord gewist" + +#: templates/account/password_delete_done.html:9 +msgid "Your password has been deleted." +msgstr "Je wachtwoord is gewist." + +#: templates/account/password_reset.html:6 +#: templates/account/password_reset.html:10 +#: templates/account/password_reset_done.html:6 +#: templates/account/password_reset_done.html:9 +msgid "Password Reset" +msgstr "Nieuw wachtwoord" + +#: templates/account/password_reset.html:15 +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll send you " +"an e-mail allowing you to reset it." +msgstr "" +"Wachtwoord vergeten? Vul je e-mail adres in en we sturen je een e-mail " +"waarmee je een nieuw wachtwoord kunt instellen." + +#: templates/account/password_reset.html:20 +msgid "Reset My Password" +msgstr "Herstel mijn wachtwoord" + +#: templates/account/password_reset.html:23 +#, python-format +msgid "" +"If you have any trouble resetting your password, contact us at %(CONTACT_EMAIL)s." +msgstr "" +"Als je problemen hebt je wachtwoord opnieuw in te stellen, neem dan contact " +"op met %(CONTACT_EMAIL)s." + +#: templates/account/password_reset_done.html:15 +#, python-format +msgid "" +"We have sent you an e-mail. If you do not receive it within a few minutes, " +"contact us at %(CONTACT_EMAIL)s." +msgstr "" +"We hebben je een e-mail verstuurd. Als je deze niet binnen enkele minuten " +"ontvangen hebt, neem dan contact op met " +"%(CONTACT_EMAIL)s." + +#: templates/account/password_reset_from_key.html:7 +msgid "Bad Token" +msgstr "Ongeldige sleutel" + +#: templates/account/password_reset_from_key.html:11 +#, python-format +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" +"De link om je wachtwoord opnieuw in te stellen is niet geldig. Mogelijk is " +"deze al een keer gebruikt. Herstel je " +"wachtwoord opnieuw." + +#: templates/account/password_reset_from_key.html:17 +msgid "change password" +msgstr "Wachtwoord wijzigen" + +#: templates/account/password_reset_from_key.html:20 +msgid "Your password is now changed." +msgstr "Je wachtwoord is gewijzigd." + +#: templates/account/password_reset_key_message.txt:1 +#, python-format +msgid "" +"You're receiving this e-mail because you or someone else has requested a " +"password for your user account at %(site_domain)s.\n" +"It can be safely ignored if you did not request a password reset. Click the " +"link below to reset your password.\n" +"\n" +"%(password_reset_url)s\n" +"\n" +"In case you forgot, your username is %(username)s.\n" +"\n" +"Thanks for using our site!\n" +msgstr "" +"Je ontvangt deze mail omdat er een verzoek is ingelegd om het wachtwoord " +"behorende bij je %(site_domain)s account opnieuw in te stellen. Je kunt deze " +"gerust mail negeren als je dit niet zelf gedaan hebt. Anders, klik op de " +"volgende link om je wachtwoord opnieuw in te stellen.\n" +"\n" +"%(password_reset_url)s\n" +"\n" +"Deze link hoort bij je account met gebruikersnaam %(username)s.\n" +"\n" +"Bedankt voor het gebruik van onze site!\n" + +#: templates/account/password_set.html:5 templates/account/password_set.html:8 +#: templates/account/password_set.html:13 +msgid "Set Password" +msgstr "Zet wachtwoord" + +#: templates/account/signup.html:5 templates/socialaccount/signup.html:5 +msgid "Signup" +msgstr "Registreren" + +#: templates/account/signup.html:8 templates/account/signup.html.py:21 +#: templates/socialaccount/signup.html:8 +#: templates/socialaccount/signup.html:19 +msgid "Sign Up" +msgstr "Registreren" + +#: templates/account/signup.html:13 +#, python-format +msgid "" +"Already have an account? Then please sign in." +msgstr "Heb je al een account? Meld je dan aan." + +#: templates/account/verification_sent.html:5 +#: templates/account/verification_sent.html:8 +#: templates/account/verified_email_required.html:5 +#: templates/account/verified_email_required.html:8 +msgid "Verify Your E-mail Address" +msgstr "Verifieer je e-mail adres" + +#: templates/account/verification_sent.html:10 +#, python-format +msgid "" +"We have sent you an e-mail to %(email)s for verification. Follow the " +"link provided to finalize the signup process. If you do not receive it " +"within a few minutes, contact us at " +"%(CONTACT_EMAIL)s." +msgstr "" +"We hebben een e-mail verstuurd aan %(email)s ter verificatie. Volg de " +"link in deze mail om je registratie af te sluiten. Als je de e-mail niet " +"binnen enkele minuten ontvangt, neem dan contact op met %(CONTACT_EMAIL)s." + +#: templates/account/verified_email_required.html:12 +msgid "" +"This part of the site requires us to verify that\n" +"you are who you claim to be. For this purpose, we require that you\n" +"verify ownership of your e-mail address. " +msgstr "" +"Voor dit gedeelte van de site is het nodig dat we je identiteit\n" +"verifiëren. Via een verificatie e-mail kunnen we controleren dat je\n" +"daadwerkelijk toegang hebt tot het opgegeven e-mail adres." + +#: templates/account/verified_email_required.html:16 +#, python-format +msgid "" +"We have sent an e-mail to you for\n" +"verification. Please click on the link inside this\n" +"e-mail. If you do not receive it within a few minutes, contact us\n" +"at %(CONTACT_EMAIL)s. \n" +msgstr "" +"Volg de link in de verificatie e-mail om de controle af te ronden. Als je de\n" +"e-mail niet binnen enkele minuten ontvangt, neem dan contact op met %(CONTACT_EMAIL)s. \n" + +#: templates/account/verified_email_required.html:22 +#, python-format +msgid "" +"Note: you can still change your e-" +"mail address." +msgstr "Merk op: je kunt altijd je e-mail adres wijzigen." + +#: templates/account/snippets/already_logged_in.html:5 +msgid "Note" +msgstr "Notitie" + +#: templates/account/snippets/already_logged_in.html:5 +#, python-format +msgid "you are already logged in as %(user_display)s." +msgstr "je bent al ingelogd als %(user_display)s." + +#: templates/emailconfirmation/confirm_email.html:6 +#: templates/emailconfirmation/confirm_email.html:12 +msgid "E-mail Address Confirmation" +msgstr "E-mail adres bevestiging" + +#: templates/emailconfirmation/confirm_email.html:16 +#, python-format +msgid "" +"You have confirmed that %(email)s is an e-" +"mail address for user %(user_display)s." +msgstr "" +"Je hebt bevestigd dat %(email)s een e-mail " +"adres is voor gebruiker %(user_display)s." + +#: templates/emailconfirmation/confirm_email.html:18 +msgid "Invalid confirmation key." +msgstr "Ongeldige bevestigingssleutel." + +#: templates/emailconfirmation/email_confirmation_message.txt:1 +#, python-format +msgid "" +"User %(user_display)s at %(site_name)s has given this as an email address.\n" +"\n" +"To confirm this is correct, go to %(activate_url)s\n" +msgstr "" +"Gebruiker %(user_display)s van %(site_name)s heeft dit als e-mail adres " +"opgegeven.\n" +"\n" +"Bezoek de volgende link en bevestig dat dit correct is: %(activate_url)s\n" + +#: templates/emailconfirmation/email_confirmation_subject.txt:1 +msgid "Confirm E-mail Address" +msgstr "Bevestig e-mail adres" + +#: templates/openid/login.html:9 +msgid "OpenID Sign In" +msgstr "Aanmelden via OpenID" + +#: templates/socialaccount/account_inactive.html:5 +#: templates/socialaccount/account_inactive.html:8 +msgid "Account Inactive" +msgstr "Account inactief" + +#: templates/socialaccount/account_inactive.html:10 +msgid "This account is inactive." +msgstr "Dit account is niet actief" + +#: templates/socialaccount/authentication_error.html:5 +#: templates/socialaccount/authentication_error.html:8 +msgid "Social Network Login Failure" +msgstr "Aanmelden Mislukt" + +#: templates/socialaccount/authentication_error.html:10 +msgid "" +"An error occured while attempting to login via your social network account." +msgstr "" +"Er is een fout opgetreden toen we je wilde inloggen via je externe account." + +#: templates/socialaccount/connections.html:5 +#: templates/socialaccount/connections.html:8 +msgid "Account Connections" +msgstr "Account Connecties" + +#: templates/socialaccount/connections.html:11 +msgid "" +"You can sign in to your account using any of the following third party " +"accounts:" +msgstr "Je kunt jezelf aanmelden met een van de volgende externe accounts:" + +#: templates/socialaccount/connections.html:46 +msgid "Add a 3rd Party Account" +msgstr "Voeg een extern account toe" + +#: templates/socialaccount/login_cancelled.html:5 +#: templates/socialaccount/login_cancelled.html:9 +msgid "Login Cancelled" +msgstr "Aanmelden geannuleerd" + +#: templates/socialaccount/login_cancelled.html:13 +#, python-format +msgid "" +"You decided to cancel logging in to our site using one of your exisiting " +"accounts. If this was a mistake, please proceed to sign in." +msgstr "" +"Je hebt het aanmelden via een extern account geannuleerd. Als dit een " +"vergissing was, meld je dan opnieuw aan." + +#: templates/socialaccount/signup.html:10 +#, python-format +msgid "" +"You are about to use your %(provider_name)s account to login to \n" +"%(site_name)s. As a final step, please complete the following form:" +msgstr "" +"Om bij %(site_name)s in te kunnen loggen via %(provider_name)s hebben we de " +"volgende gegevens nodig:" + +#~ msgid "OpenID" +#~ msgstr "OpenID" + +#~ msgid "Already have an account?" +#~ msgstr "Heb je al een account?" + +#~ msgid "Sign in" +#~ msgstr "Aanmelden" + +#~ msgid "Language" +#~ msgstr "Taal" + +#~ msgid "Pinax can be used in your preferred language." +#~ msgstr "Deze site kan in jouw voorkeurstaal gebruikt worden." + +#~ msgid "Change my language" +#~ msgstr "Verander mijn taal" + +#~ msgid "Timezone" +#~ msgstr "Tijdzone" + +#, fuzzy +#~ msgid "" +#~ "You're receiving this e-mail because you requested a password reset\n" +#~ "for your user account at Pinax.\n" +#~ "\n" +#~ "Your new password is: %(new_password)s\n" +#~ "\n" +#~ "Your username, in case you've forgotten: %(username)s\n" +#~ "\n" +#~ "You should log in as soon as possible and change your password.\n" +#~ "\n" +#~ "Thanks for using our site!\n" +#~ msgstr "" +#~ "Je ontvangt deze mail omdat er een verzoek is ingelegd om het wachtwoord\n" +#~ "behorende bij je %(site_name)s account opnieuw in te stellen.\n" +#~ "\n" +#~ "Je nieuwe wachtwoord is: %(new_password)s\n" +#~ "\n" +#~ "Je gebruikersnaam, voor het geval je die vergeten bent, is: %(username)s\n" +#~ "\n" +#~ "Je moet zo snel mogelijk inloggen en bovenstaand wachtwoord veranderen.\n" +#~ "\n" +#~ "Bedankt voor het gebruik van onze site!\n" + +#~ msgid "If checked you will stay logged in for 3 weeks" +#~ msgstr "Bij 'Onthouden' blijf je ingelogd gedurende 3 weken" + +#~ msgid "Timezone successfully updated." +#~ msgstr "Tijdzone gewijzigd." + +#~ msgid "Language successfully updated." +#~ msgstr "Taal gewijzigd." + +#~ msgid "E-Mail Addresses" +#~ msgstr "E-mail adressen" + +#~ msgid "None" +#~ msgstr "Geen" + +#~ msgid "add" +#~ msgstr "Voeg toe" + +#~ msgid "Log In" +#~ msgstr "Inloggen" + +#~ msgid "Log in" +#~ msgstr "Inloggen" + +#~ msgid "Logout" +#~ msgstr "Afmelden" + +#~ msgid "" +#~ "When you receive the new password, you should log in and change it as soon as possible." +#~ msgstr "" +#~ "Zodra je het nieuwe wachtwoord ontvangen hebt moet je zo snel mogelijk inloggen en het wachtwoord wijzigen." + +#~ msgid "You are already logged in." +#~ msgstr "Je bent al ingelogd." + +#~ msgid "" +#~ "By clicking \"Sign Up\", you are indicating that you have read and agree " +#~ "to the Terms of Use and Privacy Policy." +#~ msgstr "" +#~ "Door te registreren geef je aan dat je de gebruiksvoorwaarden en de privacy " +#~ "policy gelezen hebt en ermee akkoord gaat." + +#~ msgid "" +#~ "If you have any trouble creating your account, contact us at %(contact_email)s." +#~ msgstr "" +#~ "Als je problemen hebt om een account aan te maken, neem dan contact op " +#~ "met %(contact_email)s." + +#~ msgid "Log in »" +#~ msgstr "Inloggen" diff --git a/itf/allauth/models.py b/itf/allauth/models.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/__init__.py b/itf/allauth/socialaccount/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/admin.py b/itf/allauth/socialaccount/admin.py new file mode 100644 index 0000000..ab21fc1 --- /dev/null +++ b/itf/allauth/socialaccount/admin.py @@ -0,0 +1,21 @@ +from django.contrib import admin + +from models import SocialApp, SocialAccount, SocialToken + + +class SocialAppAdmin(admin.ModelAdmin): + list_display = ('name', 'provider', 'site') + + +class SocialAccountAdmin(admin.ModelAdmin): + raw_id_fields = ('user',) + list_display = ('user', 'uid', 'provider') + + +class SocialTokenAdmin(admin.ModelAdmin): + raw_id_fields = ('app', 'account',) + list_display = ('app', 'account', 'token') + +admin.site.register(SocialApp, SocialAppAdmin) +admin.site.register(SocialToken, SocialTokenAdmin) +admin.site.register(SocialAccount, SocialAccountAdmin) diff --git a/itf/allauth/socialaccount/app_settings.py b/itf/allauth/socialaccount/app_settings.py new file mode 100644 index 0000000..f7d4607 --- /dev/null +++ b/itf/allauth/socialaccount/app_settings.py @@ -0,0 +1,21 @@ +from django.conf import settings + +from allauth.account import app_settings as account_settings + +# Request e-mail address from 3rd party account provider? E.g. OpenID AX +QUERY_EMAIL = getattr(settings, "SOCIALACCOUNT_QUERY_EMAIL", + account_settings.EMAIL_REQUIRED) + +# Attempt to bypass the signup form by using fields (e.g. username, +# email) retrieved from the social account provider. If a conflict +# arises due to a duplicate e-mail signup form will still kick in. +AUTO_SIGNUP = getattr(settings, "SOCIALACCOUNT_AUTO_SIGNUP", True) + +# Enable support for django-avatar. When enabled, the profile image of +# the user is copied locally into django-avatar at signup. +AVATAR_SUPPORT = getattr(settings, "SOCIALACCOUNT_AVATAR_SUPPORT", + 'avatar' in settings.INSTALLED_APPS) + + +# Provider specific settings +PROVIDERS = getattr(settings, "SOCIALACCOUNT_PROVIDERS", {}) diff --git a/itf/allauth/socialaccount/context_processors.py b/itf/allauth/socialaccount/context_processors.py new file mode 100644 index 0000000..46c80b1 --- /dev/null +++ b/itf/allauth/socialaccount/context_processors.py @@ -0,0 +1,5 @@ +import providers + +def socialaccount(request): + ctx = { 'providers': providers.registry.get_list() } + return dict(socialaccount=ctx) diff --git a/itf/allauth/socialaccount/fields.py b/itf/allauth/socialaccount/fields.py new file mode 100644 index 0000000..c6cbb5c --- /dev/null +++ b/itf/allauth/socialaccount/fields.py @@ -0,0 +1,60 @@ +# Courtesy of django-social-auth + +from django.core.exceptions import ValidationError +from django.db import models +from django.utils import simplejson +from django.utils.encoding import smart_unicode + + +class JSONField(models.TextField): + """Simple JSON field that stores python structures as JSON strings + on database. + """ + __metaclass__ = models.SubfieldBase + + def to_python(self, value): + """ + Convert the input JSON value into python structures, raises + django.core.exceptions.ValidationError if the data can't be converted. + """ + if self.blank and not value: + return None + if isinstance(value, basestring): + try: + return simplejson.loads(value) + except Exception, e: + raise ValidationError(str(e)) + else: + return value + + def validate(self, value, model_instance): + """Check value is a valid JSON string, raise ValidationError on + error.""" + if isinstance(value, basestring): + super(JSONField, self).validate(value, model_instance) + try: + simplejson.loads(value) + except Exception, e: + raise ValidationError(str(e)) + + def get_prep_value(self, value): + """Convert value to JSON string before save""" + try: + return simplejson.dumps(value) + except Exception, e: + raise ValidationError(str(e)) + + def value_to_string(self, obj): + """Return value from object converted to string properly""" + return smart_unicode(self.get_prep_value(self._get_val_from_obj(obj))) + + def value_from_object(self, obj): + """Return value dumped to string.""" + return self.get_prep_value(self._get_val_from_obj(obj)) + + +try: + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ["^allauth\.socialaccount\.fields\.JSONField"]) +except: + pass diff --git a/itf/allauth/socialaccount/forms.py b/itf/allauth/socialaccount/forms.py new file mode 100644 index 0000000..a7e370d --- /dev/null +++ b/itf/allauth/socialaccount/forms.py @@ -0,0 +1,55 @@ +from django.utils.translation import ugettext_lazy as _ +from django import forms + +from emailconfirmation.models import EmailAddress +from models import SocialAccount +from allauth.account.forms import BaseSignupForm +from allauth.account.utils import send_email_confirmation + + +class SignupForm(BaseSignupForm): + + def __init__(self, *args, **kwargs): + self.sociallogin = kwargs.pop('sociallogin') + user = self.sociallogin.account.user + initial = { 'email': user.email or '', + 'username': user.username or '', + 'first_name': user.first_name or '', + 'last_name': user.last_name or '' } + kwargs['initial'] = initial + super(SignupForm, self).__init__(*args, **kwargs) + + def save(self, request=None): + new_user = self.create_user() + self.sociallogin.account.user = new_user + self.sociallogin.save() + super(SignupForm, self).save(new_user) + # Confirmation last (save may alter first_name etc -- used in mail) + send_email_confirmation(new_user, request=request) + return new_user + + +class DisconnectForm(forms.Form): + account = forms.ModelChoiceField(queryset=SocialAccount.objects.none(), + widget=forms.RadioSelect, + required=True) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user') + self.accounts = SocialAccount.objects.filter(user=self.user) + super(DisconnectForm, self).__init__(*args, **kwargs) + self.fields['account'].queryset = self.accounts + + def clean(self): + if len(self.accounts) == 1: + # No usable password would render the local account unusable + if not self.user.has_usable_password(): + raise forms.ValidationError(_("Your local account has no password setup.")) + # No email address, no password reset + if EmailAddress.objects.filter(user=self.user, + verified=True).count() == 0: + raise forms.ValidationError(_("Your local account has no verified e-mail address.")) + return self.cleaned_data + + def save(self): + self.cleaned_data['account'].delete() diff --git a/itf/allauth/socialaccount/helpers.py b/itf/allauth/socialaccount/helpers.py new file mode 100644 index 0000000..92461ab --- /dev/null +++ b/itf/allauth/socialaccount/helpers.py @@ -0,0 +1,179 @@ +from django.contrib import messages +from django.shortcuts import render_to_response +from django.http import HttpResponseRedirect +from django.template import RequestContext +from django.core.urlresolvers import reverse +from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ +from django.template.defaultfilters import slugify + +from allauth.utils import get_login_redirect_url, \ + generate_unique_username, email_address_exists +from allauth.account.utils import send_email_confirmation, \ + perform_login, complete_signup +from allauth.account import app_settings as account_settings + +import app_settings + +def _process_signup(request, sociallogin): + # If email is specified, check for duplicate and if so, no auto signup. + auto_signup = app_settings.AUTO_SIGNUP + email = sociallogin.account.user.email + if auto_signup: + # Let's check if auto_signup is really possible... + if email: + if account_settings.UNIQUE_EMAIL: + if email_address_exists(email): + # Oops, another user already has this address. We + # cannot simply connect this social account to the + # existing user. Reason is that the email adress may + # not be verified, meaning, the user may be a hacker + # that has added your email address to his account in + # the hope that you fall in his trap. We cannot check + # on 'email_address.verified' either, because + # 'email_address' is not guaranteed to be verified. + auto_signup = False + # FIXME: We redirect to signup form -- user will + # see email address conflict only after posting + # whereas we detected it here already. + elif account_settings.EMAIL_REQUIRED: + # Nope, email is required and we don't have it yet... + auto_signup = False + if not auto_signup: + request.session['socialaccount_sociallogin'] = sociallogin + url = reverse('socialaccount_signup') + ret = HttpResponseRedirect(url) + else: + # FIXME: There is some duplication of logic inhere + # (create user, send email, in active etc..) + u = sociallogin.account.user + u.username = generate_unique_username(u.username + or email + or 'user') + u.last_name = (u.last_name or '') \ + [0:User._meta.get_field('last_name').max_length] + u.first_name = (u.first_name or '') \ + [0:User._meta.get_field('first_name').max_length] + u.email = email or '' + u.set_unusable_password() + sociallogin.save() + send_email_confirmation(u, request=request) + ret = complete_social_signup(request, sociallogin) + return ret + + +def _login_social_account(request, sociallogin): + user = sociallogin.account.user + if not user.is_active: + ret = render_to_response( + 'socialaccount/account_inactive.html', + {}, + context_instance=RequestContext(request)) + else: + ret = perform_login(request, user, + redirect_url=sociallogin.get_redirect_url()) + return ret + + +def render_authentication_error(request, extra_context={}): + return render_to_response( + "socialaccount/authentication_error.html", + extra_context, context_instance=RequestContext(request)) + + +def complete_social_login(request, sociallogin): + assert not sociallogin.is_existing + sociallogin.lookup() + if request.user.is_authenticated(): + if sociallogin.is_existing: + # Existing social account, existing user + if sociallogin.account.user != request.user: + # Social account of other user. Simply logging in may + # not be correct in the case that the user was + # attempting to hook up another social account to his + # existing user account. For now, this scenario is not + # supported. Issue is that one cannot simply remove + # the social account from the other user, as that may + # render the account unusable. + pass + ret = _login_social_account(request, sociallogin) + else: + # New social account + sociallogin.account.user = request.user + sociallogin.save() + default_next = reverse('socialaccount_connections') + next = sociallogin.get_redirect_url(fallback=default_next) + messages.add_message(request, messages.INFO, + _('The social account has been connected' + ' to your existing account')) + return HttpResponseRedirect(next) + else: + if sociallogin.is_existing: + # Login existing user + ret = _login_social_account(request, sociallogin) + else: + # New social user + ret = _process_signup(request, sociallogin) + return ret + + +def _name_from_url(url): + """ + >>> _name_from_url('http://google.com/dir/file.ext') + u'file.ext' + >>> _name_from_url('http://google.com/dir/') + u'dir' + >>> _name_from_url('http://google.com/dir') + u'dir' + >>> _name_from_url('http://google.com/dir/..') + u'dir' + >>> _name_from_url('http://google.com/dir/../') + u'dir' + >>> _name_from_url('http://google.com') + u'google.com' + >>> _name_from_url('http://google.com/dir/subdir/file..ext') + u'file.ext' + """ + from urlparse import urlparse + + p = urlparse(url) + for base in (p.path.split('/')[-1], + p.path, + p.netloc): + name = ".".join(filter(lambda s: s, + map(slugify, base.split(".")))) + if name: + return name + + +def _copy_avatar(request, user, account): + import urllib2 + from django.core.files.base import ContentFile + from avatar.models import Avatar + url = account.get_avatar_url() + if url: + ava = Avatar(user=user) + ava.primary = Avatar.objects.filter(user=user).count() == 0 + try: + content = urllib2.urlopen(url).read() + name = _name_from_url(url) + ava.avatar.save(name, ContentFile(content)) + except IOError: + # Let's nog make a big deal out of this... + pass + + +def complete_social_signup(request, sociallogin): + if app_settings.AVATAR_SUPPORT: + _copy_avatar(request, sociallogin.account.user, sociallogin.account) + return complete_signup(request, + sociallogin.account.user, + sociallogin.get_redirect_url()) + + +# TODO: Factor out callable importing functionality +# See: account.utils.user_display +def import_path(path): + modname, _, attr = path.rpartition('.') + m = __import__(modname, fromlist=[attr]) + return getattr(m, attr) diff --git a/itf/allauth/socialaccount/migrations/0001_initial.py b/itf/allauth/socialaccount/migrations/0001_initial.py new file mode 100644 index 0000000..fb66b0c --- /dev/null +++ b/itf/allauth/socialaccount/migrations/0001_initial.py @@ -0,0 +1,73 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'SocialAccount' + db.create_table('socialaccount_socialaccount', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('last_login', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + ('date_joined', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), + )) + db.send_create_signal('socialaccount', ['SocialAccount']) + + + def backwards(self, orm): + + # Deleting model 'SocialAccount' + db.delete_table('socialaccount_socialaccount') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['socialaccount'] diff --git a/itf/allauth/socialaccount/migrations/0002_genericmodels.py b/itf/allauth/socialaccount/migrations/0002_genericmodels.py new file mode 100644 index 0000000..af1e0a0 --- /dev/null +++ b/itf/allauth/socialaccount/migrations/0002_genericmodels.py @@ -0,0 +1,150 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'SocialToken' + db.create_table('socialaccount_socialtoken', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('app', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['socialaccount.SocialApp'])), + ('account', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['socialaccount.SocialAccount'])), + ('token', self.gf('django.db.models.fields.CharField')(max_length=200)), + ('token_secret', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)), + )) + db.send_create_signal('socialaccount', ['SocialToken']) + + # Adding unique constraint on 'SocialToken', fields ['app', 'account'] + db.create_unique('socialaccount_socialtoken', ['app_id', 'account_id']) + + # Adding model 'SocialApp' + db.create_table('socialaccount_socialapp', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('provider', self.gf('django.db.models.fields.CharField')(max_length=30)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('key', self.gf('django.db.models.fields.CharField')(max_length=100)), + ('secret', self.gf('django.db.models.fields.CharField')(max_length=100)), + )) + db.send_create_signal('socialaccount', ['SocialApp']) + + # Adding field 'SocialAccount.provider' + db.add_column('socialaccount_socialaccount', 'provider', self.gf('django.db.models.fields.CharField')(default='', max_length=30, blank=True), keep_default=False) + + # Adding field 'SocialAccount.uid' + db.add_column('socialaccount_socialaccount', 'uid', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'SocialAccount.extra_data' + db.add_column('socialaccount_socialaccount', 'extra_data', self.gf('allauth.socialaccount.fields.JSONField')(default='{}'), keep_default=False) + + # Changing field 'SocialAccount.last_login' + db.alter_column('socialaccount_socialaccount', 'last_login', self.gf('django.db.models.fields.DateTimeField')(auto_now=True)) + + # Changing field 'SocialAccount.date_joined' + db.alter_column('socialaccount_socialaccount', 'date_joined', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) + + + def backwards(self, orm): + + # Removing unique constraint on 'SocialToken', fields ['app', 'account'] + db.delete_unique('socialaccount_socialtoken', ['app_id', 'account_id']) + + # Deleting model 'SocialToken' + db.delete_table('socialaccount_socialtoken') + + # Deleting model 'SocialApp' + db.delete_table('socialaccount_socialapp') + + # Deleting field 'SocialAccount.provider' + db.delete_column('socialaccount_socialaccount', 'provider') + + # Deleting field 'SocialAccount.uid' + db.delete_column('socialaccount_socialaccount', 'uid') + + # Deleting field 'SocialAccount.extra_data' + db.delete_column('socialaccount_socialaccount', 'extra_data') + + # Changing field 'SocialAccount.last_login' + db.alter_column('socialaccount_socialaccount', 'last_login', self.gf('django.db.models.fields.DateTimeField')()) + + # Changing field 'SocialAccount.date_joined' + db.alter_column('socialaccount_socialaccount', 'date_joined', self.gf('django.db.models.fields.DateTimeField')()) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'extra_data': ('allauth.socialaccount.fields.JSONField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'socialaccount.socialapp': { + 'Meta': {'object_name': 'SocialApp'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'socialaccount.socialtoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'SocialToken'}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + } + } + + complete_apps = ['socialaccount'] diff --git a/itf/allauth/socialaccount/migrations/0003_auto__add_unique_socialaccount_uid_provider.py b/itf/allauth/socialaccount/migrations/0003_auto__add_unique_socialaccount_uid_provider.py new file mode 100644 index 0000000..089c713 --- /dev/null +++ b/itf/allauth/socialaccount/migrations/0003_auto__add_unique_socialaccount_uid_provider.py @@ -0,0 +1,96 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + depends_on = (('facebook', '0003_tosocialaccount'), + ('twitter', '0003_tosocialaccount'), + ('openid', '0002_tosocialaccount')) + + def forwards(self, orm): + + # Adding unique constraint on 'SocialAccount', fields ['uid', 'provider'] + db.create_unique('socialaccount_socialaccount', ['uid', 'provider']) + + + def backwards(self, orm): + + # Removing unique constraint on 'SocialAccount', fields ['uid', 'provider'] + db.delete_unique('socialaccount_socialaccount', ['uid', 'provider']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'unique_together': "(('provider', 'uid'),)", 'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'extra_data': ('allauth.socialaccount.fields.JSONField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'socialaccount.socialapp': { + 'Meta': {'object_name': 'SocialApp'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'socialaccount.socialtoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'SocialToken'}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + } + } + + complete_apps = ['socialaccount'] diff --git a/itf/allauth/socialaccount/migrations/__init__.py b/itf/allauth/socialaccount/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/models.py b/itf/allauth/socialaccount/models.py new file mode 100644 index 0000000..4fab045 --- /dev/null +++ b/itf/allauth/socialaccount/models.py @@ -0,0 +1,197 @@ +from django.db import models +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.utils import simplejson + +import allauth.app_settings +from allauth.utils import get_login_redirect_url + +import providers +from fields import JSONField + + +class SocialAppManager(models.Manager): + def get_current(self, provider): + site = Site.objects.get_current() + return self.get(site=site, + provider=provider) + + +class SocialApp(models.Model): + objects = SocialAppManager() + + site = models.ForeignKey(Site) + provider = models.CharField(max_length=30, + choices=providers.registry.as_choices()) + name = models.CharField(max_length=40) + key = models.CharField(max_length=100, + help_text='App ID, or consumer key') + secret = models.CharField(max_length=100, + help_text='API secret, or consumer secret') + + def __unicode__(self): + return self.name + +class SocialAccount(models.Model): + user = models.ForeignKey(User) + provider = models.CharField(max_length=30, + choices=providers.registry.as_choices()) + # Just in case you're wondering if an OpenID identity URL is going + # to fit in a 'uid': + # + # Ideally, URLField(max_length=1024, unique=True) would be used + # for identity. However, MySQL has a max_length limitation of 255 + # for URLField. How about models.TextField(unique=True) then? + # Well, that won't work either for MySQL due to another bug[1]. So + # the only way out would be to drop the unique constraint, or + # switch to shorter identity URLs. Opted for the latter, as [2] + # suggests that identity URLs are supposed to be short anyway, at + # least for the old spec. + # + # [1] http://code.djangoproject.com/ticket/2495. + # [2] http://openid.net/specs/openid-authentication-1_1.html#limits + + uid = models.CharField(max_length=255) + last_login = models.DateTimeField(auto_now=True) + date_joined = models.DateTimeField(auto_now_add=True) + extra_data = JSONField(default='{}') + + class Meta: + unique_together = ('provider', 'uid') + + def authenticate(self): + return authenticate(account=self) + + def __unicode__(self): + return unicode(self.user) + + def get_profile_url(self): + return self.get_provider_account().get_profile_url() + + def get_avatar_url(self): + return self.get_provider_account().get_avatar_url() + + def get_provider(self): + return providers.registry.by_id(self.provider) + + def get_provider_account(self): + return self.get_provider().wrap_account(self) + + +class SocialToken(models.Model): + app = models.ForeignKey(SocialApp) + account = models.ForeignKey(SocialAccount) + token = models.CharField(max_length=200) + token_secret = models.CharField(max_length=200, blank=True) + + class Meta: + unique_together = ('app', 'account') + + def __unicode__(self): + return self.token + + +class SocialLogin(object): + """ + Represents a social user that is in the process of being logged + in. This consists of the following information: + + `account` (`SocialAccount` instance): The social account being + logged in. Providers are not responsible for checking whether or + not an account already exists or not. Therefore, a provider + typically creates a new (unsaved) `SocialAccount` instance. The + `User` instance pointed to by the account (`account.user`) may be + prefilled by the provider for use as a starting point later on + during the signup process. + + `token` (`SocialToken` instance): An optional access token token + that results from performing a successful authentication + handshake. + + `state` (`dict`): The state to be preserved during the + authentication handshake. Note that this state may end up in the + url (e.g. OAuth2 `state` parameter) -- do not put any secrets in + there. It currently only contains the url to redirect to after + login. + """ + + def __init__(self, account, token=None): + if token: + assert token.account is None or token.account == account + token.account = account + self.token = token + self.account = account + self.state = {} + + def save(self): + user = self.account.user + user.save() + self.account.user = user + self.account.save() + if self.token: + self.token.account = self.account + self.token.save() + + @property + def is_existing(self): + """ + Account is temporary, not yet backed by a database record. + """ + return self.account.pk + + def lookup(self): + """ + Lookup existing account, if any. + """ + assert not self.is_existing + try: + a = SocialAccount.objects.get(provider=self.account.provider, + uid=self.account.uid) + # Update account + a.extra_data = self.account.extra_data + self.account = a + a.save() + # Update token + if self.token: + assert not self.token.pk + try: + t = SocialToken.objects.get(account=self.account, + app=self.token.app) + t.token = self.token.token + t.token_secret = self.token.token_secret + t.save() + self.token = t + except SocialToken.DoesNotExist: + self.token.account = a + self.token.save() + except SocialAccount.DoesNotExist: + pass + + def get_redirect_url(self, + fallback=allauth.app_settings.LOGIN_REDIRECT_URL): + url = self.state.get('next') or fallback + return url + + @classmethod + def state_from_request(cls, request): + state = {} + next = get_login_redirect_url(request, fallback=None) + if next: + state['next'] = next + return state + + @classmethod + def marshall_state(cls, request): + state = cls.state_from_request(request) + return simplejson.dumps(state) + + @classmethod + def unmarshall_state(cls, state_string): + if state_string: + state = simplejson.loads(state_string) + else: + state = {} + return state + + diff --git a/itf/allauth/socialaccount/providers/__init__.py b/itf/allauth/socialaccount/providers/__init__.py new file mode 100644 index 0000000..fde92d1 --- /dev/null +++ b/itf/allauth/socialaccount/providers/__init__.py @@ -0,0 +1,36 @@ +from django.conf import settings +from django.utils import importlib + +class ProviderRegistry(object): + def __init__(self): + self.provider_map = {} + self.loaded = False + + def get_list(self): + self.load() + return self.provider_map.values() + + def register(self, cls): + self.load() + self.provider_map[cls.id] = cls() + + def by_id(self, id): + self.load() + return self.provider_map[id] + + def as_choices(self): + self.load() + for provider in self.get_list(): + yield (provider.id, provider.name) + + def load(self): + if not self.loaded: + for app in settings.INSTALLED_APPS: + provider_module = app + '.provider' + try: + importlib.import_module(provider_module) + except ImportError: + pass + self.loaded = True + +registry = ProviderRegistry() diff --git a/itf/allauth/socialaccount/providers/base.py b/itf/allauth/socialaccount/providers/base.py new file mode 100644 index 0000000..eda6ad4 --- /dev/null +++ b/itf/allauth/socialaccount/providers/base.py @@ -0,0 +1,52 @@ +from allauth.socialaccount import app_settings +from allauth.socialaccount.models import SocialApp + +class Provider(object): + def get_login_url(self, request, next=None, **kwargs): + """ + Builds the URL to redirect to when initiating a login for this + provider. + """ + raise NotImplementedError, "get_login_url() for " + self.name + + def get_app(self, request): + return SocialApp.objects.get_current(self.id) + + def media_js(self, request): + """ + Some providers may require extra scripts (e.g. a Facebook connect) + """ + return '' + + def wrap_account(self, social_account): + return self.account_class(social_account) + + def get_settings(self): + return app_settings.PROVIDERS.get(self.id, {}) + +class ProviderAccount(object): + def __init__(self, social_account): + self.account = social_account + + def get_profile_url(self): + return None + + def get_avatar_url(self): + return None + + def get_brand(self): + """ + Returns a dict containing an id and name identifying the + brand. Useful when displaying logos next to accounts in + templates. + + For most providers, these are identical to the provider. For + OpenID however, the brand can derived from the OpenID identity + url. + """ + provider = self.account.get_provider() + return dict(id=provider.id, + name=provider.name) + + def __unicode__(self): + return self.get_brand()['name'] diff --git a/itf/allauth/socialaccount/providers/facebook/__init__.py b/itf/allauth/socialaccount/providers/facebook/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/facebook/data/FacebookLocales.xml b/itf/allauth/socialaccount/providers/facebook/data/FacebookLocales.xml new file mode 100644 index 0000000..c79ff40 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/data/FacebookLocales.xml @@ -0,0 +1,850 @@ + + + +Afrikaans + + + +FB +af_ZA + + + + + +Arabic + + + +FB +ar_AR + + + + + +Azerbaijani + + + +FB +az_AZ + + + + + +Belarusian + + + +FB +be_BY + + + + + +Bulgarian + + + +FB +bg_BG + + + + + +Bengali + + + +FB +bn_IN + + + + + +Bosnian + + + +FB +bs_BA + + + + + +Catalan + + + +FB +ca_ES + + + + + +Czech + + + +FB +cs_CZ + + + + + +Welsh + + + +FB +cy_GB + + + + + +Danish + + + +FB +da_DK + + + + + +German + + + +FB +de_DE + + + + + +Greek + + + +FB +el_GR + + + + + +English (UK) + + + +FB +en_GB + + + + + +English (Pirate) + + + +FB +en_PI + + + + + +English (Upside Down) + + + +FB +en_UD + + + + + +English (US) + + + +FB +en_US + + + + + +Esperanto + + + +FB +eo_EO + + + + + +Spanish (Spain) + + + +FB +es_ES + + + + + +Spanish + + + +FB +es_LA + + + + + +Estonian + + + +FB +et_EE + + + + + +Basque + + + +FB +eu_ES + + + + + +Persian + + + +FB +fa_IR + + + + + +Leet Speak + + + +FB +fb_LT + + + + + +Finnish + + + +FB +fi_FI + + + + + +Faroese + + + +FB +fo_FO + + + + + +French (Canada) + + + +FB +fr_CA + + + + + +French (France) + + + +FB +fr_FR + + + + + +Frisian + + + +FB +fy_NL + + + + + +Irish + + + +FB +ga_IE + + + + + +Galician + + + +FB +gl_ES + + + + + +Hebrew + + + +FB +he_IL + + + + + +Hindi + + + +FB +hi_IN + + + + + +Croatian + + + +FB +hr_HR + + + + + +Hungarian + + + +FB +hu_HU + + + + + +Armenian + + + +FB +hy_AM + + + + + +Indonesian + + + +FB +id_ID + + + + + +Icelandic + + + +FB +is_IS + + + + + +Italian + + + +FB +it_IT + + + + + +Japanese + + + +FB +ja_JP + + + + + +Georgian + + + +FB +ka_GE + + + + + +Khmer + + + +FB +km_KH + + + + + +Korean + + + +FB +ko_KR + + + + + +Kurdish + + + +FB +ku_TR + + + + + +Latin + + + +FB +la_VA + + + + + +Lithuanian + + + +FB +lt_LT + + + + + +Latvian + + + +FB +lv_LV + + + + + +Macedonian + + + +FB +mk_MK + + + + + +Malayalam + + + +FB +ml_IN + + + + + +Malay + + + +FB +ms_MY + + + + + +Norwegian (bokmal) + + + +FB +nb_NO + + + + + +Nepali + + + +FB +ne_NP + + + + + +Dutch + + + +FB +nl_NL + + + + + +Norwegian (nynorsk) + + + +FB +nn_NO + + + + + +Punjabi + + + +FB +pa_IN + + + + + +Polish + + + +FB +pl_PL + + + + + +Pashto + + + +FB +ps_AF + + + + + +Portuguese (Brazil) + + + +FB +pt_BR + + + + + +Portuguese (Portugal) + + + +FB +pt_PT + + + + + +Romanian + + + +FB +ro_RO + + + + + +Russian + + + +FB +ru_RU + + + + + +Slovak + + + +FB +sk_SK + + + + + +Slovenian + + + +FB +sl_SI + + + + + +Albanian + + + +FB +sq_AL + + + + + +Serbian + + + +FB +sr_RS + + + + + +Swedish + + + +FB +sv_SE + + + + + +Swahili + + + +FB +sw_KE + + + + + +Tamil + + + +FB +ta_IN + + + + + +Telugu + + + +FB +te_IN + + + + + +Thai + + + +FB +th_TH + + + + + +Filipino + + + +FB +tl_PH + + + + + +Turkish + + + +FB +tr_TR + + + + + +Ukrainian + + + +FB +uk_UA + + + + + +Vietnamese + + + +FB +vi_VN + + + + + +Simplified Chinese (China) + + + +FB +zh_CN + + + + + +Traditional Chinese (Hong Kong) + + + +FB +zh_HK + + + + + +Traditional Chinese (Taiwan) + + + +FB +zh_TW + + + + + \ No newline at end of file diff --git a/itf/allauth/socialaccount/providers/facebook/forms.py b/itf/allauth/socialaccount/providers/facebook/forms.py new file mode 100644 index 0000000..9c22978 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/forms.py @@ -0,0 +1,5 @@ +from django import forms + + +class FacebookConnectForm(forms.Form): + access_token = forms.CharField(required=True) diff --git a/itf/allauth/socialaccount/providers/facebook/locale.py b/itf/allauth/socialaccount/providers/facebook/locale.py new file mode 100644 index 0000000..9d442b7 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/locale.py @@ -0,0 +1,70 @@ +# Default locale mapping for the Facebook JS SDK +# The list of supported locales is at +# https://www.facebook.com/translations/FacebookLocales.xml +import os + +from django.utils.translation import get_language, to_locale + + +def _build_locale_table(filename_or_file): + """ + Parses the FacebookLocales.xml file and builds a dict relating every + available language ('en, 'es, 'zh', ...) with a list of available regions + for that language ('en' -> 'US', 'EN') and an (arbitrary) default region. + """ + # Require the XML parser module only if we want the default mapping + from xml.dom.minidom import parse + + dom = parse(filename_or_file) + + reps = dom.getElementsByTagName('representation') + locs = map(lambda r: r.childNodes[0].data, reps) + + locale_map = {} + for loc in locs: + lang, _, reg = loc.partition('_') + lang_map = locale_map.setdefault(lang, {'regs': [], 'default': reg}) + lang_map['regs'].append(reg) + + # Default region overrides (arbitrary) + locale_map['en']['default'] = 'US' + # Special case: Use es_ES for Spain and es_LA for everything else + locale_map['es']['default'] = 'LA' + locale_map['zh']['default'] = 'CN' + locale_map['fr']['default'] = 'FR' + locale_map['pt']['default'] = 'PT' + + return locale_map + + +def get_default_locale_callable(): + """ + Wrapper function so that the default mapping is only built when needed + """ + exec_dir = os.path.dirname(os.path.realpath(__file__)) + xml_path = os.path.join(exec_dir, 'data', 'FacebookLocales.xml') + + fb_locales = _build_locale_table(xml_path) + + def default_locale(request): + """ + Guess an appropiate FB locale based on the active Django locale. + If the active locale is available, it is returned. Otherwise, + it tries to return another locale with the same language. If there + isn't one avaible, 'en_US' is returned. + """ + locale = to_locale(get_language()) + lang, _, reg = locale.partition('_') + + lang_map = fb_locales.get(lang) + if lang_map is not None: + if reg in lang_map['regs']: + chosen = lang + '_' + reg + else: + chosen = lang + '_' + lang_map['default'] + else: + chosen = 'en_US' + + return chosen + + return default_locale diff --git a/itf/allauth/socialaccount/providers/facebook/migrations/0001_initial.py b/itf/allauth/socialaccount/providers/facebook/migrations/0001_initial.py new file mode 100644 index 0000000..15ecddb --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/migrations/0001_initial.py @@ -0,0 +1,110 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + depends_on = (('socialaccount', '0001_initial'),) + + def forwards(self, orm): + + # Adding model 'FacebookApp' + db.create_table('facebook_facebookapp', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('application_id', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('api_key', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('application_secret', self.gf('django.db.models.fields.CharField')(max_length=80)), + )) + db.send_create_signal('facebook', ['FacebookApp']) + + # Adding model 'FacebookAccount' + db.create_table('facebook_facebookaccount', ( + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('social_id', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('link', self.gf('django.db.models.fields.URLField')(max_length=200)), + )) + db.send_create_signal('facebook', ['FacebookAccount']) + + + def backwards(self, orm): + + # Deleting model 'FacebookApp' + db.delete_table('facebook_facebookapp') + + # Deleting model 'FacebookAccount' + db.delete_table('facebook_facebookaccount') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'facebook.facebookaccount': { + 'Meta': {'object_name': 'FacebookAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'link': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'social_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'facebook.facebookapp': { + 'Meta': {'object_name': 'FacebookApp'}, + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_id': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['facebook'] diff --git a/itf/allauth/socialaccount/providers/facebook/migrations/0002_auto__add_facebookaccesstoken__add_unique_facebookaccesstoken_app_acco.py b/itf/allauth/socialaccount/providers/facebook/migrations/0002_auto__add_facebookaccesstoken__add_unique_facebookaccesstoken_app_acco.py new file mode 100644 index 0000000..b1a780d --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/migrations/0002_auto__add_facebookaccesstoken__add_unique_facebookaccesstoken_app_acco.py @@ -0,0 +1,108 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'FacebookAccessToken' + db.create_table('facebook_facebookaccesstoken', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('app', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['facebook.FacebookApp'])), + ('account', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['facebook.FacebookAccount'])), + ('access_token', self.gf('django.db.models.fields.CharField')(max_length=200)), + )) + db.send_create_signal('facebook', ['FacebookAccessToken']) + + # Adding unique constraint on 'FacebookAccessToken', fields ['app', 'account'] + db.create_unique('facebook_facebookaccesstoken', ['app_id', 'account_id']) + + + def backwards(self, orm): + + # Removing unique constraint on 'FacebookAccessToken', fields ['app', 'account'] + db.delete_unique('facebook_facebookaccesstoken', ['app_id', 'account_id']) + + # Deleting model 'FacebookAccessToken' + db.delete_table('facebook_facebookaccesstoken') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'facebook.facebookaccesstoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'FacebookAccessToken'}, + 'access_token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['facebook.FacebookAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['facebook.FacebookApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'facebook.facebookaccount': { + 'Meta': {'object_name': 'FacebookAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'link': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'social_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'facebook.facebookapp': { + 'Meta': {'object_name': 'FacebookApp'}, + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_id': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['facebook'] diff --git a/itf/allauth/socialaccount/providers/facebook/migrations/0003_tosocialaccount.py b/itf/allauth/socialaccount/providers/facebook/migrations/0003_tosocialaccount.py new file mode 100644 index 0000000..a103e4e --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/migrations/0003_tosocialaccount.py @@ -0,0 +1,142 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = (('socialaccount', '0002_genericmodels'),) + + def forwards(self, orm): + # Migrate FB apps + app_id_to_sapp = {} + for app in orm.FacebookApp.objects.all(): + sapp = orm['socialaccount.SocialApp'].objects \ + .create(site=app.site, + provider='facebook', + name=app.name, + key=app.application_id, + secret=app.application_secret) + app_id_to_sapp[app.id] = sapp + # Migrate FB accounts + acc_id_to_sacc = {} + for acc in orm.FacebookAccount.objects.all(): + sacc = acc.socialaccount_ptr + sacc.uid = acc.social_id + sacc.extra_data = { 'link': acc.link, + 'name': acc.name } + sacc.provider = 'facebook' + sacc.save() + acc_id_to_sacc[acc.id] = sacc + # Migrate tokens + for token in orm.FacebookAccessToken.objects.all(): + sapp = app_id_to_sapp[token.app.id] + sacc = acc_id_to_sacc[token.account.id] + orm['socialaccount.SocialToken'].objects \ + .create(app=sapp, + account=sacc, + token=token.access_token, + token_secret='') + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'facebook.facebookaccesstoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'FacebookAccessToken'}, + 'access_token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['facebook.FacebookAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['facebook.FacebookApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'facebook.facebookaccount': { + 'Meta': {'object_name': 'FacebookAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'link': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'social_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'facebook.facebookapp': { + 'Meta': {'object_name': 'FacebookApp'}, + 'api_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_id': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'application_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'extra_data': ('allauth.socialaccount.fields.JSONField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'socialaccount.socialapp': { + 'Meta': {'object_name': 'SocialApp'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'socialaccount.socialtoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'SocialToken'}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + } + } + + complete_apps = ['socialaccount', 'facebook'] diff --git a/itf/allauth/socialaccount/providers/facebook/migrations/0004_auto__del_facebookapp__del_facebookaccesstoken__del_unique_facebookacc.py b/itf/allauth/socialaccount/providers/facebook/migrations/0004_auto__del_facebookapp__del_facebookaccesstoken__del_unique_facebookacc.py new file mode 100644 index 0000000..0032293 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/migrations/0004_auto__del_facebookapp__del_facebookaccesstoken__del_unique_facebookacc.py @@ -0,0 +1,63 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Removing unique constraint on 'FacebookAccessToken', fields ['app', 'account'] + db.delete_unique('facebook_facebookaccesstoken', ['app_id', 'account_id']) + + # Deleting model 'FacebookApp' + db.delete_table('facebook_facebookapp') + + # Deleting model 'FacebookAccessToken' + db.delete_table('facebook_facebookaccesstoken') + + # Deleting model 'FacebookAccount' + db.delete_table('facebook_facebookaccount') + + + def backwards(self, orm): + + # Adding model 'FacebookApp' + db.create_table('facebook_facebookapp', ( + ('application_id', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('api_key', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('application_secret', self.gf('django.db.models.fields.CharField')(max_length=80)), + )) + db.send_create_signal('facebook', ['FacebookApp']) + + # Adding model 'FacebookAccessToken' + db.create_table('facebook_facebookaccesstoken', ( + ('access_token', self.gf('django.db.models.fields.CharField')(max_length=200)), + ('account', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['facebook.FacebookAccount'])), + ('app', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['facebook.FacebookApp'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('facebook', ['FacebookAccessToken']) + + # Adding unique constraint on 'FacebookAccessToken', fields ['app', 'account'] + db.create_unique('facebook_facebookaccesstoken', ['app_id', 'account_id']) + + # Adding model 'FacebookAccount' + db.create_table('facebook_facebookaccount', ( + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('social_id', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)), + ('link', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('facebook', ['FacebookAccount']) + + + models = { + + } + + complete_apps = ['facebook'] diff --git a/itf/allauth/socialaccount/providers/facebook/migrations/__init__.py b/itf/allauth/socialaccount/providers/facebook/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/facebook/models.py b/itf/allauth/socialaccount/providers/facebook/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/facebook/provider.py b/itf/allauth/socialaccount/providers/facebook/provider.py new file mode 100644 index 0000000..bfb3aa6 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/provider.py @@ -0,0 +1,92 @@ +from django.core.urlresolvers import reverse +from django.core.exceptions import ImproperlyConfigured +from django.template.loader import render_to_string +from django.template import RequestContext + +from allauth.utils import import_callable +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import ProviderAccount +from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider +from allauth.socialaccount.app_settings import QUERY_EMAIL +from allauth.socialaccount.models import SocialApp +from allauth.socialaccount.helpers import import_path + +from locale import get_default_locale_callable + + +class FacebookAccount(ProviderAccount): + def get_profile_url(self): + return self.account.extra_data.get('link') + + def get_avatar_url(self): + uid = self.account.uid + return 'http://graph.facebook.com/%s/picture?type=large' % uid + + def __unicode__(self): + dflt = super(FacebookAccount, self).__unicode__() + return self.account.extra_data.get('name', dflt) + + +class FacebookProvider(OAuth2Provider): + id = 'facebook' + name = 'Facebook' + package = 'allauth.socialaccount.providers.facebook' + account_class = FacebookAccount + + def __init__(self): + self._locale_callable_cache = None + super(FacebookProvider, self).__init__() + + def get_method(self): + return self.get_settings().get('METHOD', 'oauth2') + + def get_login_url(self, request, **kwargs): + method = kwargs.get('method', self.get_method()) + if method == 'js_sdk': + next = "'%s'" % (kwargs.get('next') or '') + ret = "javascript:FB_login(%s)" % next + else: + assert method == 'oauth2' + ret = super(FacebookProvider, self).get_login_url(request, + **kwargs) + return ret + + def _get_locale_callable(self): + settings = self.get_settings() + f = settings.get('LOCALE_FUNC') + if f: + f = import_callable(f) + else: + f = get_default_locale_callable() + return f + + def get_locale_for_request(self, request): + if not self._locale_callable_cache: + self._locale_callable_cache = self._get_locale_callable() + return self._locale_callable_cache(request) + + def get_default_scope(self): + scope = [] + if QUERY_EMAIL: + scope.append('email') + return scope + + def media_js(self, request): + perms = ','.join(self.get_scope()) + locale = self.get_locale_for_request(request) + try: + app = self.get_app(request) + except SocialApp.DoesNotExist: + raise ImproperlyConfigured("No Facebook app configured: please" + " add a SocialApp using the Django" + " admin") + ctx = {'facebook_app': app, + 'facebook_channel_url': + request.build_absolute_uri(reverse('facebook_channel')), + 'facebook_perms': perms, + 'facebook_jssdk_locale': locale} + return render_to_string('facebook/fbconnect.html', + ctx, + RequestContext(request)) + +providers.registry.register(FacebookProvider) diff --git a/itf/allauth/socialaccount/providers/facebook/templates/facebook/channel.html b/itf/allauth/socialaccount/providers/facebook/templates/facebook/channel.html new file mode 100644 index 0000000..6063ba9 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/templates/facebook/channel.html @@ -0,0 +1 @@ + diff --git a/itf/allauth/socialaccount/providers/facebook/templates/facebook/fbconnect.html b/itf/allauth/socialaccount/providers/facebook/templates/facebook/fbconnect.html new file mode 100644 index 0000000..7cf4124 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/templates/facebook/fbconnect.html @@ -0,0 +1,46 @@ +{% load url from future %} +
+ + +
+{% csrf_token %} + + + +
diff --git a/itf/allauth/socialaccount/providers/facebook/urls.py b/itf/allauth/socialaccount/providers/facebook/urls.py new file mode 100644 index 0000000..1f7aad7 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/urls.py @@ -0,0 +1,14 @@ +from django.conf.urls.defaults import patterns, url + +from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns + +from provider import FacebookProvider +import views + +urlpatterns = default_urlpatterns(FacebookProvider) + +urlpatterns += patterns('', + url('^facebook/login/token/$', views.login_by_token, + name="facebook_login_by_token"), + url('^facebook/channel/$', views.channel, name='facebook_channel'), + ) diff --git a/itf/allauth/socialaccount/providers/facebook/views.py b/itf/allauth/socialaccount/providers/facebook/views.py new file mode 100644 index 0000000..35abad1 --- /dev/null +++ b/itf/allauth/socialaccount/providers/facebook/views.py @@ -0,0 +1,83 @@ +from django.utils.cache import patch_response_headers +from django.contrib.auth.models import User +from django.shortcuts import render + +from allauth.socialaccount.models import SocialAccount, SocialLogin, SocialToken +from allauth.socialaccount.helpers import complete_social_login +from allauth.socialaccount.helpers import render_authentication_error +from allauth.socialaccount import providers +from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter, + OAuth2LoginView, + OAuth2CallbackView) +from allauth.socialaccount import requests + +from forms import FacebookConnectForm +from provider import FacebookProvider + +from allauth.utils import valid_email_or_none + +def fb_complete_login(app, token): + resp = requests.get('https://graph.facebook.com/me', + params={ 'access_token': token.token }) + extra_data = resp.json + email = valid_email_or_none(extra_data.get('email')) + uid = extra_data['id'] + user = User(email=email) + # some facebook accounts don't have this data + for k in ['username', 'first_name', 'last_name']: + v = extra_data.get(k) + if v: + setattr(user, k, v) + account = SocialAccount(uid=uid, + provider=FacebookProvider.id, + extra_data=extra_data, + user=user) + return SocialLogin(account) + + +class FacebookOAuth2Adapter(OAuth2Adapter): + provider_id = FacebookProvider.id + + authorize_url = 'https://www.facebook.com/dialog/oauth' + access_token_url = 'https://graph.facebook.com/oauth/access_token' + + def complete_login(self, request, app, access_token): + return fb_complete_login(app, access_token) + + +oauth2_login = OAuth2LoginView.adapter_view(FacebookOAuth2Adapter) +oauth2_callback = OAuth2CallbackView.adapter_view(FacebookOAuth2Adapter) + + +def login_by_token(request): + ret = None + if request.method == 'POST': + form = FacebookConnectForm(request.POST) + if form.is_valid(): + try: + app = providers.registry.by_id(FacebookProvider.id) \ + .get_app(request) + access_token = form.cleaned_data['access_token'] + token = SocialToken(app=app, + token=access_token) + login = fb_complete_login(app, token) + login.token = token + login.state = SocialLogin.state_from_request(request) + ret = complete_social_login(request, login) + except: + # FIXME: Catch only what is needed + pass + if not ret: + ret = render_authentication_error(request) + return ret + + +def channel(request): + provider = providers.registry.by_id(FacebookProvider.id) + locale = provider.get_locale_for_request(request) + response = render(request, 'facebook/channel.html', + {'facebook_jssdk_locale': locale}) + cache_expire = 60 * 60 * 24 * 365 + patch_response_headers(response, cache_expire) + response['Pragma'] = 'Public' + return response diff --git a/itf/allauth/socialaccount/providers/github/__init__.py b/itf/allauth/socialaccount/providers/github/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/github/models.py b/itf/allauth/socialaccount/providers/github/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/github/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/github/provider.py b/itf/allauth/socialaccount/providers/github/provider.py new file mode 100644 index 0000000..c8d797e --- /dev/null +++ b/itf/allauth/socialaccount/providers/github/provider.py @@ -0,0 +1,24 @@ +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import ProviderAccount +from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider + + +class GitHubAccount(ProviderAccount): + def get_profile_url(self): + return self.account.extra_data.get('html_url') + + def get_avatar_url(self): + return self.account.extra_data.get('avatar_url') + + def __unicode__(self): + dflt = super(GitHubAccount, self).__unicode__() + return self.account.extra_data.get('name', dflt) + + +class GitHubProvider(OAuth2Provider): + id = 'github' + name = 'GitHub' + package = 'allauth.socialaccount.providers.github' + account_class = GitHubAccount + +providers.registry.register(GitHubProvider) diff --git a/itf/allauth/socialaccount/providers/github/urls.py b/itf/allauth/socialaccount/providers/github/urls.py new file mode 100644 index 0000000..0b92fc6 --- /dev/null +++ b/itf/allauth/socialaccount/providers/github/urls.py @@ -0,0 +1,5 @@ +from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns +from provider import GitHubProvider + +urlpatterns = default_urlpatterns(GitHubProvider) + diff --git a/itf/allauth/socialaccount/providers/github/views.py b/itf/allauth/socialaccount/providers/github/views.py new file mode 100644 index 0000000..eced38f --- /dev/null +++ b/itf/allauth/socialaccount/providers/github/views.py @@ -0,0 +1,35 @@ +from django.contrib.auth.models import User + +from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter, + OAuth2LoginView, + OAuth2CallbackView) +from allauth.socialaccount import requests + +from allauth.socialaccount.models import SocialAccount, SocialLogin + +from provider import GitHubProvider + +class GitHubOAuth2Adapter(OAuth2Adapter): + provider_id = GitHubProvider.id + access_token_url = 'https://github.com/login/oauth/access_token' + authorize_url = 'https://github.com/login/oauth/authorize' + profile_url = 'https://api.github.com/user' + + def complete_login(self, request, app, token): + resp = requests.get(self.profile_url, + params={ 'access_token': token.token }) + extra_data = resp.json + uid = str(extra_data['id']) + user = User(username=extra_data.get('login', ''), + email=extra_data.get('email', ''), + first_name=extra_data.get('name', '')) + account = SocialAccount(user=user, + uid=uid, + extra_data=extra_data, + provider=self.provider_id) + return SocialLogin(account) + + +oauth2_login = OAuth2LoginView.adapter_view(GitHubOAuth2Adapter) +oauth2_callback = OAuth2CallbackView.adapter_view(GitHubOAuth2Adapter) + diff --git a/itf/allauth/socialaccount/providers/google/__init__.py b/itf/allauth/socialaccount/providers/google/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/google/models.py b/itf/allauth/socialaccount/providers/google/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/google/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/google/provider.py b/itf/allauth/socialaccount/providers/google/provider.py new file mode 100644 index 0000000..52ccf42 --- /dev/null +++ b/itf/allauth/socialaccount/providers/google/provider.py @@ -0,0 +1,35 @@ +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import ProviderAccount +from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider +from allauth.socialaccount.app_settings import QUERY_EMAIL + +class Scope: + USERINFO_PROFILE = 'https://www.googleapis.com/auth/userinfo.profile' + USERINFO_EMAIL = 'https://www.googleapis.com/auth/userinfo.email' + + +class GoogleAccount(ProviderAccount): + def get_profile_url(self): + return self.account.extra_data.get('link') + + def get_avatar_url(self): + return self.account.extra_data.get('picture') + + def __unicode__(self): + dflt = super(GoogleAccount, self).__unicode__() + return self.account.extra_data.get('name', dflt) + + +class GoogleProvider(OAuth2Provider): + id = 'google' + name = 'Google' + package = 'allauth.socialaccount.providers.google' + account_class = GoogleAccount + + def get_default_scope(self): + scope = [Scope.USERINFO_PROFILE] + if QUERY_EMAIL: + scope.append(Scope.USERINFO_EMAIL) + return scope + +providers.registry.register(GoogleProvider) diff --git a/itf/allauth/socialaccount/providers/google/urls.py b/itf/allauth/socialaccount/providers/google/urls.py new file mode 100644 index 0000000..2644d93 --- /dev/null +++ b/itf/allauth/socialaccount/providers/google/urls.py @@ -0,0 +1,4 @@ +from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns +from provider import GoogleProvider + +urlpatterns = default_urlpatterns(GoogleProvider) diff --git a/itf/allauth/socialaccount/providers/google/views.py b/itf/allauth/socialaccount/providers/google/views.py new file mode 100644 index 0000000..d5d99ba --- /dev/null +++ b/itf/allauth/socialaccount/providers/google/views.py @@ -0,0 +1,46 @@ +from django.contrib.auth.models import User + +from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter, + OAuth2LoginView, + OAuth2CallbackView) + +from allauth.socialaccount import requests +from allauth.socialaccount.models import SocialLogin, SocialAccount + +from provider import GoogleProvider + +class GoogleOAuth2Adapter(OAuth2Adapter): + provider_id = GoogleProvider.id + access_token_url = 'https://accounts.google.com/o/oauth2/token' + authorize_url = 'https://accounts.google.com/o/oauth2/auth' + profile_url = 'https://www.googleapis.com/oauth2/v1/userinfo' + + def complete_login(self, request, app, token): + resp = requests.get(self.profile_url, + { 'access_token': token.token, + 'alt': 'json' }) + extra_data = resp.json + # extra_data is something of the form: + # + # {u'family_name': u'Penners', u'name': u'Raymond Penners', + # u'picture': u'https://lh5.googleusercontent.com/-GOFYGBVOdBQ/AAAAAAAAAAI/AAAAAAAAAGM/WzRfPkv4xbo/photo.jpg', + # u'locale': u'nl', u'gender': u'male', + # u'email': u'raymond.penners@gmail.com', + # u'link': u'https://plus.google.com/108204268033311374519', + # u'given_name': u'Raymond', u'id': u'108204268033311374519', + # u'verified_email': True} + # + # TODO: We could use verified_email to bypass allauth email verification + uid = str(extra_data['id']) + user = User(email=extra_data.get('email', ''), + last_name=extra_data['family_name'], + first_name=extra_data['given_name']) + account = SocialAccount(extra_data=extra_data, + uid=uid, + provider=self.provider_id, + user=user) + return SocialLogin(account) + +oauth2_login = OAuth2LoginView.adapter_view(GoogleOAuth2Adapter) +oauth2_callback = OAuth2CallbackView.adapter_view(GoogleOAuth2Adapter) + diff --git a/itf/allauth/socialaccount/providers/linkedin/__init__.py b/itf/allauth/socialaccount/providers/linkedin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/linkedin/models.py b/itf/allauth/socialaccount/providers/linkedin/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/linkedin/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/linkedin/provider.py b/itf/allauth/socialaccount/providers/linkedin/provider.py new file mode 100644 index 0000000..4750789 --- /dev/null +++ b/itf/allauth/socialaccount/providers/linkedin/provider.py @@ -0,0 +1,15 @@ +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import ProviderAccount +from allauth.socialaccount.providers.oauth.provider import OAuthProvider + +class LinkedInAccount(ProviderAccount): + pass + + +class LinkedInProvider(OAuthProvider): + id = 'linkedin' + name = 'LinkedIn' + package = 'allauth.socialaccount.providers.linkedin' + account_class = LinkedInAccount + +providers.registry.register(LinkedInProvider) diff --git a/itf/allauth/socialaccount/providers/linkedin/urls.py b/itf/allauth/socialaccount/providers/linkedin/urls.py new file mode 100644 index 0000000..d6ee10c --- /dev/null +++ b/itf/allauth/socialaccount/providers/linkedin/urls.py @@ -0,0 +1,4 @@ +from allauth.socialaccount.providers.oauth.urls import default_urlpatterns +from provider import LinkedInProvider + +urlpatterns = default_urlpatterns(LinkedInProvider) diff --git a/itf/allauth/socialaccount/providers/linkedin/views.py b/itf/allauth/socialaccount/providers/linkedin/views.py new file mode 100644 index 0000000..3d07d6d --- /dev/null +++ b/itf/allauth/socialaccount/providers/linkedin/views.py @@ -0,0 +1,68 @@ +from xml.etree import ElementTree +from xml.parsers.expat import ExpatError + +from django.contrib.auth.models import User + +from allauth.socialaccount.providers.oauth.client import OAuth +from allauth.socialaccount.providers.oauth.views import (OAuthAdapter, + OAuthLoginView, + OAuthCallbackView) +from allauth.socialaccount.models import SocialAccount, SocialLogin + +from provider import LinkedInProvider + + +class LinkedInAPI(OAuth): + url = 'https://api.linkedin.com/v1/people/~' + fields = ['id', 'first-name', 'last-name'] + + def get_user_info(self): + url = self.url + ':(%s)' % ','.join(self.fields) + raw_xml = self.query(url) + try: + return self.to_dict(ElementTree.fromstring(raw_xml)) + except (ExpatError, KeyError, IndexError): + return None + + def to_dict(self, xml): + """ + Convert XML structure to dict recursively, repeated keys + entries are returned as in list containers. + """ + children = xml.getchildren() + if not children: + return xml.text + else: + out = {} + for node in xml.getchildren(): + if node.tag in out: + if not isinstance(out[node.tag], list): + out[node.tag] = [out[node.tag]] + out[node.tag].append(self.to_dict(node)) + else: + out[node.tag] = self.to_dict(node) + return out + + +class LinkedInOAuthAdapter(OAuthAdapter): + provider_id = LinkedInProvider.id + request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken' + access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken' + authorize_url = 'https://www.linkedin.com/uas/oauth/authenticate' + + def complete_login(self, request, app, token): + client = LinkedInAPI(request, app.key, app.secret, + self.request_token_url) + extra_data = client.get_user_info() + uid = extra_data['id'] + user = User(first_name=extra_data.get('first-name', ''), + last_name=extra_data.get('last-name', '')) + account = SocialAccount(user=user, + provider=self.provider_id, + extra_data=extra_data, + uid=uid) + return SocialLogin(account) + +oauth_login = OAuthLoginView.adapter_view(LinkedInOAuthAdapter) +oauth_callback = OAuthCallbackView.adapter_view(LinkedInOAuthAdapter) + diff --git a/itf/allauth/socialaccount/providers/oauth/__init__.py b/itf/allauth/socialaccount/providers/oauth/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/oauth/client.py b/itf/allauth/socialaccount/providers/oauth/client.py new file mode 100644 index 0000000..173cd71 --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth/client.py @@ -0,0 +1,185 @@ +""" +Parts derived from socialregistration and authorized by: alen, pinda +Inspired by: + http://github.com/leah/python-oauth/blob/master/oauth/example/client.py + http://github.com/facebook/tornado/blob/master/tornado/auth.py +""" + +import urllib +import urllib2 + +from django.http import HttpResponseRedirect +from django.utils.translation import gettext as _ + +# parse_qsl was moved from the cgi namespace to urlparse in Python2.6. +# this allows backwards compatibility +try: + from urlparse import parse_qsl +except ImportError: + from cgi import parse_qsl + +import oauth2 as oauth + + +def get_token_prefix(url): + """ + Returns a prefix for the token to store in the session so we can hold + more than one single oauth provider's access key in the session. + + Example: + + The request token url ``http://twitter.com/oauth/request_token`` + returns ``twitter.com`` + + """ + return urllib2.urlparse.urlparse(url).netloc + + +class OAuthError(Exception): + pass + + +class OAuthClient(object): + + def __init__(self, request, consumer_key, consumer_secret, request_token_url, + access_token_url, authorization_url, callback_url, parameters=None): + + self.request = request + + self.request_token_url = request_token_url + self.access_token_url = access_token_url + self.authorization_url = authorization_url + + self.consumer_key = consumer_key + self.consumer_secret = consumer_secret + + self.consumer = oauth.Consumer(consumer_key, consumer_secret) + self.client = oauth.Client(self.consumer) + + self.signature_method = oauth.SignatureMethod_HMAC_SHA1() + + self.parameters = parameters + + self.callback_url = callback_url + + self.errors = [] + self.request_token = None + self.access_token = None + + def _get_request_token(self): + """ + Obtain a temporary request token to authorize an access token and to + sign the request to obtain the access token + """ + if self.request_token is None: + rt_url = self.request_token_url + '?' + urllib.urlencode({'oauth_callback': self.request.build_absolute_uri(self.callback_url)}) + response, content = self.client.request(rt_url, "GET") + if response['status'] != '200': + raise OAuthError( + _('Invalid response while obtaining request token from "%s".') % get_token_prefix(self.request_token_url)) + self.request_token = dict(parse_qsl(content)) + self.request.session['oauth_%s_request_token' % get_token_prefix(self.request_token_url)] = self.request_token + return self.request_token + + def get_access_token(self): + """ + Obtain the access token to access private resources at the API endpoint. + """ + if self.access_token is None: + request_token = self._get_rt_from_session() + token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) + self.client = oauth.Client(self.consumer, token) + at_url = self.access_token_url + + # Passing along oauth_verifier is required according to: + # http://groups.google.com/group/twitter-development-talk/browse_frm/thread/472500cfe9e7cdb9# + # Though, the custom oauth_callback seems to work without it? + if self.request.REQUEST.has_key('oauth_verifier'): + at_url = at_url + '?' + urllib.urlencode({'oauth_verifier': self.request.REQUEST['oauth_verifier']}) + response, content = self.client.request(at_url, "GET") + if response['status'] != '200': + raise OAuthError( + _('Invalid response while obtaining access token from "%s".') % get_token_prefix(self.request_token_url)) + self.access_token = dict(parse_qsl(content)) + + self.request.session['oauth_%s_access_token' % get_token_prefix(self.request_token_url)] = self.access_token + return self.access_token + + def _get_rt_from_session(self): + """ + Returns the request token cached in the session by ``_get_request_token`` + """ + try: + return self.request.session['oauth_%s_request_token' % get_token_prefix(self.request_token_url)] + except KeyError: + raise OAuthError(_('No request token saved for "%s".') % get_token_prefix(self.request_token_url)) + + def _get_authorization_url(self): + request_token = self._get_request_token() + return '%s?oauth_token=%s&oauth_callback=%s' % (self.authorization_url, + request_token['oauth_token'], self.request.build_absolute_uri(self.callback_url)) + + def is_valid(self): + try: + self._get_rt_from_session() + self.get_access_token() + except OAuthError, e: + self.errors.append(e.args[0]) + return False + return True + + def get_redirect(self): + """ + Returns a ``HttpResponseRedirect`` object to redirect the user to the + URL the OAuth provider handles authorization. + """ + return HttpResponseRedirect(self._get_authorization_url()) + + +class OAuth(object): + """ + Base class to perform oauth signed requests from access keys saved in a user's + session. + See the ``OAuthTwitter`` class below for an example. + """ + + def __init__(self, request, consumer_key, secret_key, request_token_url): + self.request = request + + self.consumer_key = consumer_key + self.secret_key = secret_key + self.consumer = oauth.Consumer(consumer_key, secret_key) + + self.request_token_url = request_token_url + + def _get_at_from_session(self): + """ + Get the saved access token for private resources from the session. + """ + try: + return self.request.session['oauth_%s_access_token' % get_token_prefix(self.request_token_url)] + except KeyError: + raise OAuthError( + _('No access token saved for "%s".') % get_token_prefix(self.request_token_url)) + + def query(self, url, method="GET", params=dict(), headers=dict()): + """ + Request a API endpoint at ``url`` with ``params`` being either the + POST or GET data. + """ + access_token = self._get_at_from_session() + + token = oauth.Token(access_token['oauth_token'], access_token['oauth_token_secret']) + + client = oauth.Client(self.consumer, token) + + body = urllib.urlencode(params) + + response, content = client.request(url, method=method, headers=headers, + body=body) + + if response['status'] != '200': + raise OAuthError( + _('No access to private resources at "%s".') % get_token_prefix(self.request_token_url)) + + return content diff --git a/itf/allauth/socialaccount/providers/oauth/models.py b/itf/allauth/socialaccount/providers/oauth/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/oauth/provider.py b/itf/allauth/socialaccount/providers/oauth/provider.py new file mode 100644 index 0000000..df0a151 --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth/provider.py @@ -0,0 +1,13 @@ +from django.core.urlresolvers import reverse +from django.utils.http import urlencode + +from allauth.socialaccount.providers.base import Provider + +class OAuthProvider(Provider): + def get_login_url(self, request, **kwargs): + url = reverse(self.id + "_login") + if kwargs: + url = url + '?' + urlencode(kwargs) + return url + + diff --git a/itf/allauth/socialaccount/providers/oauth/urls.py b/itf/allauth/socialaccount/providers/oauth/urls.py new file mode 100644 index 0000000..d960f4b --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth/urls.py @@ -0,0 +1,12 @@ +from django.conf.urls.defaults import patterns, url, include + + +def default_urlpatterns(provider): + + urlpatterns = patterns(provider.package + '.views', + url('^login/$', 'oauth_login', + name=provider.id + "_login"), + url('^login/callback/$', 'oauth_callback', + name=provider.id + "_callback")) + + return patterns('', url('^' + provider.id + '/', include(urlpatterns))) diff --git a/itf/allauth/socialaccount/providers/oauth/views.py b/itf/allauth/socialaccount/providers/oauth/views.py new file mode 100644 index 0000000..a38795a --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth/views.py @@ -0,0 +1,84 @@ +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect +from django.utils.http import urlencode + +from allauth.socialaccount.helpers import render_authentication_error +from allauth.socialaccount.providers.oauth.client import (OAuthClient, + OAuthError) +from allauth.socialaccount.helpers import complete_social_login +from allauth.socialaccount import providers +from allauth.socialaccount.models import SocialToken, SocialLogin + +class OAuthAdapter(object): + + def complete_login(self, request, app): + """ + Returns a SocialLogin instance + """ + raise NotImplementedError + + def get_provider(self): + return providers.registry.by_id(self.provider_id) + + +class OAuthView(object): + @classmethod + def adapter_view(cls, adapter): + def view(request, *args, **kwargs): + self = cls() + self.request = request + self.adapter = adapter() + return self.dispatch(request, *args, **kwargs) + return view + + def _get_client(self, request, callback_url): + app = self.adapter.get_provider().get_app(request) + client = OAuthClient(request, app.key, app.secret, + self.adapter.request_token_url, + self.adapter.access_token_url, + self.adapter.authorize_url, + callback_url) + return client + + +class OAuthLoginView(OAuthView): + def dispatch(self, request): + callback_url = reverse(self.adapter.provider_id + "_callback") + # TODO: Can't this be moved as query param into callback? + # Tried but failed somehow, needs further study... + request.session['oauth_login_state'] \ + = SocialLogin.marshall_state(request) + client = self._get_client(request, callback_url) + try: + return client.get_redirect() + except OAuthError: + return render_authentication_error(request) + + +class OAuthCallbackView(OAuthView): + def dispatch(self, request): + """ + View to handle final steps of OAuth based authentication where the user + gets redirected back to from the service provider + """ + login_done_url = reverse(self.adapter.provider_id + "_callback") + client = self._get_client(request, login_done_url) + if not client.is_valid(): + if request.GET.has_key('denied'): + return HttpResponseRedirect(reverse('socialaccount_login_cancelled')) + extra_context = dict(oauth_client=client) + return render_authentication_error(request, extra_context) + app = self.adapter.get_provider().get_app(request) + try: + access_token = client.get_access_token() + token = SocialToken(app=app, + token=access_token['oauth_token'], + token_secret=access_token['oauth_token_secret']) + login = self.adapter.complete_login(request, app, token) + token.account = login.account + login.token = token + login.state = SocialLogin.unmarshall_state \ + (request.session.pop('oauth_login_state', None)) + return complete_social_login(request, login) + except OAuthError: + return render_authentication_error(request) diff --git a/itf/allauth/socialaccount/providers/oauth2/__init__.py b/itf/allauth/socialaccount/providers/oauth2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/oauth2/client.py b/itf/allauth/socialaccount/providers/oauth2/client.py new file mode 100644 index 0000000..c41bf2b --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth2/client.py @@ -0,0 +1,58 @@ +import urllib +import urlparse + +from allauth.socialaccount import requests + +class OAuth2Error(Exception): + pass + + +class OAuth2Client(object): + + def __init__(self, request, consumer_key, consumer_secret, + authorization_url, + access_token_url, + callback_url, + scope): + self.request = request + self.authorization_url = authorization_url + self.access_token_url = access_token_url + self.callback_url = callback_url + self.consumer_key = consumer_key + self.consumer_secret = consumer_secret + self.scope = ' '.join(scope) + self.state = None + + def get_redirect_url(self): + params = { + 'client_id': self.consumer_key, + 'redirect_uri': self.callback_url, + 'scope': self.scope, + 'response_type': 'code' + } + if self.state: + params['state'] = self.state + return '%s?%s' % (self.authorization_url, urllib.urlencode(params)) + + def get_access_token(self, code): + params = {'client_id': self.consumer_key, + 'redirect_uri': self.callback_url, + 'grant_type': 'authorization_code', + 'client_secret': self.consumer_secret, + 'scope': self.scope, + 'code': code} + url = self.access_token_url + # TODO: Proper exception handling + resp = requests.post(url, params) + access_token = None + if resp.status_code == 200: + if resp.headers['content-type'] == 'application/json': + data = resp.json + else: + data = dict(urlparse.parse_qsl(resp.content)) + access_token = data.get('access_token') + if not access_token: + raise OAuth2Error('Error retrieving access token: %s' + % resp.content) + + return access_token diff --git a/itf/allauth/socialaccount/providers/oauth2/models.py b/itf/allauth/socialaccount/providers/oauth2/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth2/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/oauth2/provider.py b/itf/allauth/socialaccount/providers/oauth2/provider.py new file mode 100644 index 0000000..46fd7c7 --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth2/provider.py @@ -0,0 +1,23 @@ +from django.core.urlresolvers import reverse +from django.utils.http import urlencode + +from allauth.socialaccount.providers.base import Provider + +class OAuth2Provider(Provider): + def get_login_url(self, request, **kwargs): + url = reverse(self.id + "_login") + if kwargs: + url = url + '?' + urlencode(kwargs) + return url + + def get_scope(self): + settings = self.get_settings() + scope = settings.get('SCOPE') + if scope is None: + scope = self.get_default_scope() + return scope + + def get_default_scope(self): + return [] + + diff --git a/itf/allauth/socialaccount/providers/oauth2/urls.py b/itf/allauth/socialaccount/providers/oauth2/urls.py new file mode 100644 index 0000000..0cb120b --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth2/urls.py @@ -0,0 +1,11 @@ +from django.conf.urls.defaults import patterns, url, include + + +def default_urlpatterns(provider): + urlpatterns = patterns(provider.package + '.views', + url('^login/$', 'oauth2_login', + name=provider.id + "_login"), + url('^login/callback/$', 'oauth2_callback', + name=provider.id + "_callback")) + + return patterns('', url('^' + provider.id + '/', include(urlpatterns))) diff --git a/itf/allauth/socialaccount/providers/oauth2/views.py b/itf/allauth/socialaccount/providers/oauth2/views.py new file mode 100644 index 0000000..5599a7a --- /dev/null +++ b/itf/allauth/socialaccount/providers/oauth2/views.py @@ -0,0 +1,76 @@ +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect + +from allauth.socialaccount.helpers import render_authentication_error +from allauth.socialaccount import providers +from allauth.socialaccount.providers.oauth2.client import (OAuth2Client, + OAuth2Error) +from allauth.socialaccount.helpers import complete_social_login +from allauth.socialaccount.models import SocialToken, SocialLogin + + +class OAuth2Adapter(object): + + def get_provider(self): + return providers.registry.by_id(self.provider_id) + + def complete_login(self, request, app, access_token): + """ + Returns a SocialLogin instance + """ + raise NotImplementedError + +class OAuth2View(object): + @classmethod + def adapter_view(cls, adapter): + def view(request, *args, **kwargs): + self = cls() + self.request = request + self.adapter = adapter() + return self.dispatch(request, *args, **kwargs) + return view + + def get_client(self, request, app): + callback_url = reverse(self.adapter.provider_id + "_callback") + callback_url = request.build_absolute_uri(callback_url) + client = OAuth2Client(self.request, app.key, app.secret, + self.adapter.authorize_url, + self.adapter.access_token_url, + callback_url, + self.adapter.get_provider().get_scope()) + return client + + +class OAuth2LoginView(OAuth2View): + def dispatch(self, request): + app = self.adapter.get_provider().get_app(self.request) + client = self.get_client(request, app) + client.state = SocialLogin.marshall_state(request) + try: + return HttpResponseRedirect(client.get_redirect_url()) + except OAuth2Error: + return render_authentication_error(request) + + +class OAuth2CallbackView(OAuth2View): + def dispatch(self, request): + if 'error' in request.GET or not 'code' in request.GET: + # TODO: Distinguish cancel from error + return render_authentication_error(request) + app = self.adapter.get_provider().get_app(self.request) + client = self.get_client(request, app) + try: + access_token = client.get_access_token(request.GET['code']) + token = SocialToken(app=app, + token=access_token) + login = self.adapter.complete_login(request, + app, + token) + token.account = login.account + login.token = token + login.state = SocialLogin.unmarshall_state(request.REQUEST + .get('state')) + return complete_social_login(request, login) + except OAuth2Error: + return render_authentication_error(request) + diff --git a/itf/allauth/socialaccount/providers/openid/__init__.py b/itf/allauth/socialaccount/providers/openid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/openid/admin.py b/itf/allauth/socialaccount/providers/openid/admin.py new file mode 100644 index 0000000..dc1469f --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin + +from models import OpenIDStore, OpenIDNonce + + +class OpenIDStoreAdmin(admin.ModelAdmin): + pass + +class OpenIDNonceAdmin(admin.ModelAdmin): + pass + +admin.site.register(OpenIDStore, OpenIDStoreAdmin) +admin.site.register(OpenIDNonce, OpenIDNonceAdmin) diff --git a/itf/allauth/socialaccount/providers/openid/forms.py b/itf/allauth/socialaccount/providers/openid/forms.py new file mode 100644 index 0000000..b70a6ce --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/forms.py @@ -0,0 +1,7 @@ + +from django import forms + + +class LoginForm(forms.Form): + openid = forms.URLField(label=('OpenID'), + help_text='Get an OpenID') diff --git a/itf/allauth/socialaccount/providers/openid/migrations/0001_initial.py b/itf/allauth/socialaccount/providers/openid/migrations/0001_initial.py new file mode 100644 index 0000000..d81bf0e --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/migrations/0001_initial.py @@ -0,0 +1,123 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + depends_on = (('socialaccount', '0001_initial'),) + + def forwards(self, orm): + + # Adding model 'OpenIDAccount' + db.create_table('openid_openidaccount', ( + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('identity', self.gf('django.db.models.fields.URLField')(unique=True, max_length=255)), + )) + db.send_create_signal('openid', ['OpenIDAccount']) + + # Adding model 'OpenIDStore' + db.create_table('openid_openidstore', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('server_url', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('handle', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('secret', self.gf('django.db.models.fields.TextField')()), + ('issued', self.gf('django.db.models.fields.IntegerField')()), + ('lifetime', self.gf('django.db.models.fields.IntegerField')()), + ('assoc_type', self.gf('django.db.models.fields.TextField')()), + )) + db.send_create_signal('openid', ['OpenIDStore']) + + # Adding model 'OpenIDNonce' + db.create_table('openid_openidnonce', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('server_url', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('timestamp', self.gf('django.db.models.fields.IntegerField')()), + ('salt', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('openid', ['OpenIDNonce']) + + + def backwards(self, orm): + + # Deleting model 'OpenIDAccount' + db.delete_table('openid_openidaccount') + + # Deleting model 'OpenIDStore' + db.delete_table('openid_openidstore') + + # Deleting model 'OpenIDNonce' + db.delete_table('openid_openidnonce') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'openid.openidaccount': { + 'Meta': {'object_name': 'OpenIDAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'identity': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'openid.openidnonce': { + 'Meta': {'object_name': 'OpenIDNonce'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'salt': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {}) + }, + 'openid.openidstore': { + 'Meta': {'object_name': 'OpenIDStore'}, + 'assoc_type': ('django.db.models.fields.TextField', [], {}), + 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issued': ('django.db.models.fields.IntegerField', [], {}), + 'lifetime': ('django.db.models.fields.IntegerField', [], {}), + 'secret': ('django.db.models.fields.TextField', [], {}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + } + } + + complete_apps = ['openid'] diff --git a/itf/allauth/socialaccount/providers/openid/migrations/0002_tosocialaccount.py b/itf/allauth/socialaccount/providers/openid/migrations/0002_tosocialaccount.py new file mode 100644 index 0000000..73f1fc6 --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/migrations/0002_tosocialaccount.py @@ -0,0 +1,118 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = (('socialaccount', '0002_genericmodels'),) + + def forwards(self, orm): + for acc in orm.OpenIDAccount.objects.all(): + sacc = acc.socialaccount_ptr + sacc.uid = acc.identity + sacc.provider = 'openid' + sacc.save() + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'openid.openidaccount': { + 'Meta': {'object_name': 'OpenIDAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'identity': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'openid.openidnonce': { + 'Meta': {'object_name': 'OpenIDNonce'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'salt': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {}) + }, + 'openid.openidstore': { + 'Meta': {'object_name': 'OpenIDStore'}, + 'assoc_type': ('django.db.models.fields.TextField', [], {}), + 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issued': ('django.db.models.fields.IntegerField', [], {}), + 'lifetime': ('django.db.models.fields.IntegerField', [], {}), + 'secret': ('django.db.models.fields.TextField', [], {}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'extra_data': ('allauth.socialaccount.fields.JSONField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'socialaccount.socialapp': { + 'Meta': {'object_name': 'SocialApp'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'socialaccount.socialtoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'SocialToken'}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + } + } + + complete_apps = ['socialaccount', 'openid'] diff --git a/itf/allauth/socialaccount/providers/openid/migrations/0003_auto__del_openidaccount.py b/itf/allauth/socialaccount/providers/openid/migrations/0003_auto__del_openidaccount.py new file mode 100644 index 0000000..1c9c616 --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/migrations/0003_auto__del_openidaccount.py @@ -0,0 +1,46 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'OpenIDAccount' + db.delete_table('openid_openidaccount') + + + def backwards(self, orm): + + # Adding model 'OpenIDAccount' + db.create_table('openid_openidaccount', ( + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('identity', self.gf('django.db.models.fields.URLField')(max_length=255, unique=True)), + )) + db.send_create_signal('openid', ['OpenIDAccount']) + + + models = { + 'openid.openidnonce': { + 'Meta': {'object_name': 'OpenIDNonce'}, + 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'salt': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'timestamp': ('django.db.models.fields.IntegerField', [], {}) + }, + 'openid.openidstore': { + 'Meta': {'object_name': 'OpenIDStore'}, + 'assoc_type': ('django.db.models.fields.TextField', [], {}), + 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'issued': ('django.db.models.fields.IntegerField', [], {}), + 'lifetime': ('django.db.models.fields.IntegerField', [], {}), + 'secret': ('django.db.models.fields.TextField', [], {}), + 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + } + } + + complete_apps = ['openid'] diff --git a/itf/allauth/socialaccount/providers/openid/migrations/__init__.py b/itf/allauth/socialaccount/providers/openid/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/openid/models.py b/itf/allauth/socialaccount/providers/openid/models.py new file mode 100644 index 0000000..37b8529 --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/models.py @@ -0,0 +1,22 @@ +from django.db import models + +class OpenIDStore(models.Model): + server_url = models.CharField(max_length=255) + handle = models.CharField(max_length=255) + secret = models.TextField() + issued = models.IntegerField() + lifetime = models.IntegerField() + assoc_type = models.TextField() + + def __unicode__(self): + return self.server_url + + +class OpenIDNonce(models.Model): + server_url = models.CharField(max_length=255) + timestamp = models.IntegerField() + salt = models.CharField(max_length=255) + date_created = models.DateTimeField(auto_now_add=True) + + def __unicode__(self): + return self.server_url diff --git a/itf/allauth/socialaccount/providers/openid/provider.py b/itf/allauth/socialaccount/providers/openid/provider.py new file mode 100644 index 0000000..7be4584 --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/provider.py @@ -0,0 +1,58 @@ +from urlparse import urlparse +from django.core.urlresolvers import reverse +from django.utils.http import urlencode + +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import Provider, ProviderAccount + +class OpenIDAccount(ProviderAccount): + def get_brand(self): + ret = super(OpenIDAccount, self).get_brand() + domain = urlparse(self.account.uid).netloc + # FIXME: Instead of hardcoding, derive this from the domains + # listed in the openid endpoints setting. + provider_map = {'yahoo': dict(id='yahoo', + name='Yahoo'), + 'hyves': dict(id='hyves', + name='Hyves'), + 'google': dict(id='google', + name='Google')} + for d, p in provider_map.iteritems(): + if domain.lower().find(d) >= 0: + ret = p + break + return ret + + def __unicode__(self): + return self.account.uid + + +class OpenIDProvider(Provider): + id = 'openid' + name = 'OpenID' + package = 'allauth.socialaccount.providers.openid' + account_class = OpenIDAccount + + def get_login_url(self, request, next=None, openid=None): + url = reverse('openid_login') + query = {} + if openid: + query['openid'] = openid + if next: + query['next'] = next + if query: + url += '?' + urlencode(query) + return url + + def get_brands(self): + # These defaults are a bit too arbitrary... + default_servers = [dict(id='yahoo', + name='Yahoo', + openid_url='http://me.yahoo.com'), + dict(id='hyves', + name='Hyves', + openid_url='http://hyves.nl')] + return self.get_settings().get('SERVERS', default_servers) + + +providers.registry.register(OpenIDProvider) diff --git a/itf/allauth/socialaccount/providers/openid/tests.py b/itf/allauth/socialaccount/providers/openid/tests.py new file mode 100644 index 0000000..9a7ec20 --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/tests.py @@ -0,0 +1,16 @@ +from django.test import TestCase +from django.core.urlresolvers import reverse + +import views + + +class OpenIDTests(TestCase): + + def test_discovery_failure(self): + """ + This used to generate a server 500: + DiscoveryFailure: No usable OpenID services found for http://www.google.com/ + """ + resp = self.client.post(reverse(views.login), + dict(openid='http://www.google.com')) + self.assertTrue(resp.context['form'].errors.has_key('openid')) diff --git a/itf/allauth/socialaccount/providers/openid/urls.py b/itf/allauth/socialaccount/providers/openid/urls.py new file mode 100644 index 0000000..3616e5e --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import patterns, url + +import views + +urlpatterns = patterns('', + url('^openid/login/$', views.login, name="openid_login"), + url('^openid/callback/$', views.callback), + ) diff --git a/itf/allauth/socialaccount/providers/openid/utils.py b/itf/allauth/socialaccount/providers/openid/utils.py new file mode 100644 index 0000000..79552da --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/utils.py @@ -0,0 +1,76 @@ +import base64 + +from django.http import HttpResponseRedirect + +from openid.store.interface import OpenIDStore as OIDStore +from openid.association import Association as OIDAssociation + +from models import OpenIDStore, OpenIDNonce + + +class DBOpenIDStore(OIDStore): + max_nonce_age = 6 * 60 * 60 + + def storeAssociation(self, server_url, assoc=None): + stored_assoc = OpenIDStore.objects.create( + server_url=server_url, + handle=assoc.handle, + secret=base64.encodestring(assoc.secret), + issued=assoc.issued, + lifetime=assoc.lifetime, + assoc_type=assoc.assoc_type + ) + + def getAssociation(self, server_url, handle=None): + stored_assocs = OpenIDStore.objects.filter( + server_url=server_url + ) + if handle: + stored_assocs = stored_assocs.filter(handle=handle) + + stored_assocs.order_by('-issued') + + if stored_assocs.count() == 0: + return None + + return_val = None + + for stored_assoc in stored_assocs: + assoc = OIDAssociation( + stored_assoc.handle, base64.decodestring(stored_assoc.secret), + stored_assoc.issued, stored_assoc.lifetime, stored_assoc.assoc_type + ) + + if assoc.getExpiresIn() == 0: + stored_assoc.delete() + else: + if return_val is None: + return_val = assoc + + return return_val + + def removeAssociation(self, server_url, handle): + stored_assocs = OpenIDStore.objects.filter( + server_url=server_url + ) + if handle: + stored_assocs = stored_assocs.filter(handle=handle) + + stored_assocs.delete() + + def useNonce(self, server_url, timestamp, salt): + try: + nonce = OpenIDNonce.objects.get( + server_url=server_url, + timestamp=timestamp, + salt=salt + ) + except OpenIDNonce.DoesNotExist: + nonce = OpenIDNonce.objects.create( + server_url=server_url, + timestamp=timestamp, + salt=salt + ) + return True + + return False diff --git a/itf/allauth/socialaccount/providers/openid/views.py b/itf/allauth/socialaccount/providers/openid/views.py new file mode 100644 index 0000000..562e2ce --- /dev/null +++ b/itf/allauth/socialaccount/providers/openid/views.py @@ -0,0 +1,109 @@ +from django.contrib.auth.models import User +from django.utils.http import urlencode +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.core.urlresolvers import reverse +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt + +from openid.consumer.discover import DiscoveryFailure +from openid.consumer import consumer +from openid.extensions.sreg import SRegRequest, SRegResponse +from openid.extensions.ax import FetchRequest, FetchResponse, AttrInfo + +from allauth.socialaccount.app_settings import QUERY_EMAIL +from allauth.socialaccount.models import SocialAccount, SocialLogin +from allauth.socialaccount.helpers import render_authentication_error +from allauth.socialaccount.helpers import complete_social_login +from allauth.utils import valid_email_or_none + +from utils import DBOpenIDStore +from forms import LoginForm +from provider import OpenIDProvider + +class AXAttribute: + CONTACT_EMAIL = 'http://axschema.org/contact/email' + + +class SRegField: + EMAIL = 'email' + + +def _openid_consumer(request): + store = DBOpenIDStore() + client = consumer.Consumer(request.session, store) + return client + + +def login(request): + if request.GET.has_key('openid') or request.method == 'POST': + form = LoginForm(request.REQUEST) + if form.is_valid(): + client = _openid_consumer(request) + try: + auth_request = client.begin(form.cleaned_data['openid']) + if QUERY_EMAIL: + sreg = SRegRequest() + sreg.requestField(field_name=SRegField.EMAIL, required=True) + auth_request.addExtension(sreg) + ax = FetchRequest() + ax.add(AttrInfo(AXAttribute.CONTACT_EMAIL, + required=True)) + auth_request.addExtension(ax) + callback_url = reverse(callback) + state = SocialLogin.marshall_state(request) + callback_url = callback_url + '?' + urlencode(dict(state=state)) + redirect_url = auth_request.redirectURL( + request.build_absolute_uri('/'), + request.build_absolute_uri(callback_url)) + return HttpResponseRedirect(redirect_url) + except DiscoveryFailure, e: + if request.method == 'POST': + form._errors["openid"] = form.error_class([e]) + else: + return render_authentication_error(request) + else: + form = LoginForm() + d = dict(form=form) + return render_to_response('openid/login.html', + d, context_instance=RequestContext(request)) + + +def _get_email_from_response(response): + email = None + sreg = SRegResponse.fromSuccessResponse(response) + if sreg: + email = valid_email_or_none(sreg.get(SRegField.EMAIL)) + if not email: + ax = FetchResponse.fromSuccessResponse(response) + if ax: + try: + values = ax.get(AXAttribute.CONTACT_EMAIL) + if values: + email = valid_email_or_none(values[0]) + except KeyError: + pass + return email + + +@csrf_exempt +def callback(request): + client = _openid_consumer(request) + response = client.complete( + dict(request.REQUEST.items()), + request.build_absolute_uri(request.path)) + if response.status == consumer.SUCCESS: + user = User(email=_get_email_from_response(response)) + account = SocialAccount(uid=response.identity_url, + provider=OpenIDProvider.id, + user=user, + extra_data={}) + login = SocialLogin(account) + login.state = SocialLogin.unmarshall_state(request.REQUEST.get('state')) + ret = complete_social_login(request, login) + elif response.status == consumer.CANCEL: + ret = HttpResponseRedirect(reverse('socialaccount_login_cancelled')) + else: + ret = render_authentication_error(request) + return ret + diff --git a/itf/allauth/socialaccount/providers/twitter/__init__.py b/itf/allauth/socialaccount/providers/twitter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/twitter/migrations/0001_initial.py b/itf/allauth/socialaccount/providers/twitter/migrations/0001_initial.py new file mode 100644 index 0000000..cc98d70 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/migrations/0001_initial.py @@ -0,0 +1,115 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + depends_on = (('socialaccount', '0001_initial'),) + + + def forwards(self, orm): + + # Adding model 'TwitterApp' + db.create_table('twitter_twitterapp', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('consumer_key', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('consumer_secret', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('request_token_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('access_token_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('authorize_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + )) + db.send_create_signal('twitter', ['TwitterApp']) + + # Adding model 'TwitterAccount' + db.create_table('twitter_twitteraccount', ( + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('social_id', self.gf('django.db.models.fields.PositiveIntegerField')(unique=True)), + ('username', self.gf('django.db.models.fields.CharField')(max_length=15)), + ('profile_image_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + )) + db.send_create_signal('twitter', ['TwitterAccount']) + + + def backwards(self, orm): + + # Deleting model 'TwitterApp' + db.delete_table('twitter_twitterapp') + + # Deleting model 'TwitterAccount' + db.delete_table('twitter_twitteraccount') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'twitter.twitteraccount': { + 'Meta': {'object_name': 'TwitterAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'profile_image_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'social_id': ('django.db.models.fields.PositiveIntegerField', [], {'unique': 'True'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + 'twitter.twitterapp': { + 'Meta': {'object_name': 'TwitterApp'}, + 'access_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'authorize_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'consumer_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'consumer_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'request_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + } + } + + complete_apps = ['twitter'] diff --git a/itf/allauth/socialaccount/providers/twitter/migrations/0002_snowflake.py b/itf/allauth/socialaccount/providers/twitter/migrations/0002_snowflake.py new file mode 100644 index 0000000..040e153 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/migrations/0002_snowflake.py @@ -0,0 +1,91 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'TwitterAccount.social_id' + db.alter_column('twitter_twitteraccount', 'social_id', self.gf('django.db.models.fields.BigIntegerField')(unique=True)) + + + def backwards(self, orm): + + # Changing field 'TwitterAccount.social_id' + db.alter_column('twitter_twitteraccount', 'social_id', self.gf('django.db.models.fields.PositiveIntegerField')(unique=True)) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'twitter.twitteraccount': { + 'Meta': {'object_name': 'TwitterAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'profile_image_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'social_id': ('django.db.models.fields.BigIntegerField', [], {'unique': 'True'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + 'twitter.twitterapp': { + 'Meta': {'object_name': 'TwitterApp'}, + 'access_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'authorize_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'consumer_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'consumer_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'request_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + } + } + + complete_apps = ['twitter'] diff --git a/itf/allauth/socialaccount/providers/twitter/migrations/0003_tosocialaccount.py b/itf/allauth/socialaccount/providers/twitter/migrations/0003_tosocialaccount.py new file mode 100644 index 0000000..45020b0 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/migrations/0003_tosocialaccount.py @@ -0,0 +1,128 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = (('socialaccount', '0002_genericmodels'),) + + def forwards(self, orm): + # Migrate apps + app_id_to_sapp = {} + for app in orm.TwitterApp.objects.all(): + sapp = orm['socialaccount.SocialApp'].objects \ + .create(site=app.site, + provider='twitter', + name=app.name, + key=app.consumer_key, + secret=app.consumer_secret) + app_id_to_sapp[app.id] = sapp + # Migrate accounts + acc_id_to_sacc = {} + for acc in orm.TwitterAccount.objects.all(): + sacc = acc.socialaccount_ptr + sacc.uid = str(acc.social_id) + sacc.extra_data = { 'screen_name': acc.username, + 'profile_image_url': acc.profile_image_url } + sacc.provider = 'twitter' + sacc.save() + acc_id_to_sacc[acc.id] = sacc + + + def backwards(self, orm): + "Write your backwards methods here." + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'socialaccount.socialaccount': { + 'Meta': {'object_name': 'SocialAccount'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'extra_data': ('allauth.socialaccount.fields.JSONField', [], {'default': "'{}'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'socialaccount.socialapp': { + 'Meta': {'object_name': 'SocialApp'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'provider': ('django.db.models.fields.CharField', [], {'max_length': '30'}), + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + }, + 'socialaccount.socialtoken': { + 'Meta': {'unique_together': "(('app', 'account'),)", 'object_name': 'SocialToken'}, + 'account': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialAccount']"}), + 'app': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['socialaccount.SocialApp']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '200'}), + 'token_secret': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'twitter.twitteraccount': { + 'Meta': {'object_name': 'TwitterAccount', '_ormbases': ['socialaccount.SocialAccount']}, + 'profile_image_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'social_id': ('django.db.models.fields.BigIntegerField', [], {'unique': 'True'}), + 'socialaccount_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['socialaccount.SocialAccount']", 'unique': 'True', 'primary_key': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + 'twitter.twitterapp': { + 'Meta': {'object_name': 'TwitterApp'}, + 'access_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'authorize_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'consumer_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'consumer_secret': ('django.db.models.fields.CharField', [], {'max_length': '80'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'request_token_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}) + } + } + + complete_apps = ['socialaccount', 'twitter'] diff --git a/itf/allauth/socialaccount/providers/twitter/migrations/0004_auto__del_twitteraccount__del_twitterapp.py b/itf/allauth/socialaccount/providers/twitter/migrations/0004_auto__del_twitteraccount__del_twitterapp.py new file mode 100644 index 0000000..ba322dc --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/migrations/0004_auto__del_twitteraccount__del_twitterapp.py @@ -0,0 +1,47 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Deleting model 'TwitterAccount' + db.delete_table('twitter_twitteraccount') + + # Deleting model 'TwitterApp' + db.delete_table('twitter_twitterapp') + + + def backwards(self, orm): + + # Adding model 'TwitterAccount' + db.create_table('twitter_twitteraccount', ( + ('username', self.gf('django.db.models.fields.CharField')(max_length=15)), + ('social_id', self.gf('django.db.models.fields.BigIntegerField')(unique=True)), + ('socialaccount_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['socialaccount.SocialAccount'], unique=True, primary_key=True)), + ('profile_image_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + )) + db.send_create_signal('twitter', ['TwitterAccount']) + + # Adding model 'TwitterApp' + db.create_table('twitter_twitterapp', ( + ('consumer_secret', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('request_token_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=40)), + ('authorize_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('consumer_key', self.gf('django.db.models.fields.CharField')(max_length=80)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('access_token_url', self.gf('django.db.models.fields.URLField')(max_length=200)), + ('site', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sites.Site'])), + )) + db.send_create_signal('twitter', ['TwitterApp']) + + + models = { + + } + + complete_apps = ['twitter'] diff --git a/itf/allauth/socialaccount/providers/twitter/migrations/__init__.py b/itf/allauth/socialaccount/providers/twitter/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/providers/twitter/models.py b/itf/allauth/socialaccount/providers/twitter/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/itf/allauth/socialaccount/providers/twitter/provider.py b/itf/allauth/socialaccount/providers/twitter/provider.py new file mode 100644 index 0000000..a169b70 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/provider.py @@ -0,0 +1,46 @@ +from django.core.urlresolvers import reverse +from django.utils.http import urlencode + +from allauth.socialaccount import providers +from allauth.socialaccount.providers.base import Provider, ProviderAccount + + +class TwitterAccount(ProviderAccount): + def get_screen_name(self): + return self.account.extra_data.get('screen_name') + + def get_profile_url(self): + ret = None + screen_name = self.get_screen_name() + if screen_name: + ret = 'http://twitter.com/' + screen_name + return ret + + def get_avatar_url(self): + ret = None + profile_image_url = self.account.extra_data.get('profile_image_url') + if profile_image_url: + # Hmm, hack to get our hands on the large image. Not + # really documented, but seems to work. + ret = profile_image_url.replace('_normal', '') + return ret + + def __unicode__(self): + screen_name = self.get_screen_name() + return screen_name or super(TwitterAccount, self).__unicode__() + + +class TwitterProvider(Provider): + id = 'twitter' + name = 'Twitter' + package = 'allauth.socialaccount.providers.twitter' + account_class = TwitterAccount + + def get_login_url(self, request, **kwargs): + url = reverse('twitter_login') + if kwargs: + url = url + '?' + urlencode(kwargs) + return url + + +providers.registry.register(TwitterProvider) diff --git a/itf/allauth/socialaccount/providers/twitter/urls.py b/itf/allauth/socialaccount/providers/twitter/urls.py new file mode 100644 index 0000000..291704f --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/urls.py @@ -0,0 +1,4 @@ +from allauth.socialaccount.providers.oauth.urls import default_urlpatterns +from provider import TwitterProvider + +urlpatterns = default_urlpatterns(TwitterProvider) diff --git a/itf/allauth/socialaccount/providers/twitter/views.py b/itf/allauth/socialaccount/providers/twitter/views.py new file mode 100644 index 0000000..6125967 --- /dev/null +++ b/itf/allauth/socialaccount/providers/twitter/views.py @@ -0,0 +1,47 @@ +from django.utils import simplejson +from django.contrib.auth.models import User + +from allauth.socialaccount.providers.oauth.client import OAuth +from allauth.socialaccount.providers.oauth.views import (OAuthAdapter, + OAuthLoginView, + OAuthCallbackView) +from allauth.socialaccount.models import SocialLogin, SocialAccount + +from provider import TwitterProvider + + +class TwitterAPI(OAuth): + """ + Verifying twitter credentials + """ + url = 'https://twitter.com/account/verify_credentials.json' + + def get_user_info(self): + user = simplejson.loads(self.query(self.url)) + return user + + +class TwitterOAuthAdapter(OAuthAdapter): + provider_id = TwitterProvider.id + request_token_url = 'https://api.twitter.com/oauth/request_token' + access_token_url = 'https://api.twitter.com/oauth/access_token' + # Issue #42 -- this one authenticates over and over again... + # authorize_url = 'https://api.twitter.com/oauth/authorize' + authorize_url = 'https://api.twitter.com/oauth/authenticate' + + def complete_login(self, request, app, token): + client = TwitterAPI(request, app.key, app.secret, + self.request_token_url) + extra_data = client.get_user_info() + uid = extra_data['id'] + user = User(username=extra_data['screen_name']) + account = SocialAccount(user=user, + uid=uid, + provider=TwitterProvider.id, + extra_data=extra_data) + return SocialLogin(account) + + +oauth_login = OAuthLoginView.adapter_view(TwitterOAuthAdapter) +oauth_callback = OAuthCallbackView.adapter_view(TwitterOAuthAdapter) + diff --git a/itf/allauth/socialaccount/requests.py b/itf/allauth/socialaccount/requests.py new file mode 100644 index 0000000..bb2d4cc --- /dev/null +++ b/itf/allauth/socialaccount/requests.py @@ -0,0 +1,57 @@ +import urllib +import httplib2 + +class Response(object): + def __init__(self, status_code, content, headers={}): + self.status_code = status_code + self.content = content + self.headers = headers + + @classmethod + def from_httplib(cls, resp, content): + return Response(resp.status, + content, + headers=dict(resp.iteritems())) + + + @property + def json(self): + import json + return json.loads(self.content) + +_mocked_responses = [] + +def mock_next_request(resp): + global _mocked_responses + _mocked_responses.append(resp) + +def _mockable_request(f): + def new_f(*args, **kwargs): + global _mocked_responses + if _mocked_responses: + return _mocked_responses.pop(0) + return f(*args, **kwargs) + return new_f + +@_mockable_request +def get(url, params={}): + global _mocked_responses + if _mocked_responses: + return _mocked_responses.pop(0) + client = httplib2.Http() + query = urllib.urlencode(params) + if query: + url += '?' + query + resp, content = client.request(url, 'GET') + return Response.from_httplib(resp, content) + +@_mockable_request +def post(url, params): + client = httplib2.Http() + headers = { 'content-type': 'application/x-www-form-urlencoded' } + resp, content = client.request(url, 'POST', + body=urllib.urlencode(params), + headers=headers) + return Response.from_httplib(resp, content) + + diff --git a/itf/allauth/socialaccount/templatetags/__init__.py b/itf/allauth/socialaccount/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/itf/allauth/socialaccount/templatetags/socialaccount_tags.py b/itf/allauth/socialaccount/templatetags/socialaccount_tags.py new file mode 100644 index 0000000..3a47b3d --- /dev/null +++ b/itf/allauth/socialaccount/templatetags/socialaccount_tags.py @@ -0,0 +1,49 @@ +from django.template.defaulttags import token_kwargs +from django import template + +from allauth.socialaccount import providers + +register = template.Library() + +class ProviderLoginURLNode(template.Node): + def __init__(self, provider_id, params): + self.provider_id_var = template.Variable(provider_id) + self.params = params + + def render(self, context): + provider_id = self.provider_id_var.resolve(context) + provider = providers.registry.by_id(provider_id) + query = dict([(name, var.resolve(context)) for name, var + in self.params.iteritems()]) + request = context['request'] + if not query.has_key('next'): + next = request.REQUEST.get('next') + if next: + query['next'] = next + else: + if not query['next']: + del query['next'] + return provider.get_login_url(request, **query) + +@register.tag +def provider_login_url(parser, token): + """ + {% provider_login_url "facebook" next=bla %} + {% provider_login_url "openid" openid="http://me.yahoo.com" next=bla %} + """ + bits = token.split_contents() + provider_id = bits[1] + params = token_kwargs(bits[2:], parser, support_legacy=False) + return ProviderLoginURLNode(provider_id, params) + +class ProvidersMediaJSNode(template.Node): + def render(self, context): + request = context['request'] + ret = '\n'.join([p.media_js(request) + for p in providers.registry.get_list()]) + return ret + + +@register.tag +def providers_media_js(parser, token): + return ProvidersMediaJSNode() diff --git a/itf/allauth/socialaccount/tests.py b/itf/allauth/socialaccount/tests.py new file mode 100644 index 0000000..e1aa3bc --- /dev/null +++ b/itf/allauth/socialaccount/tests.py @@ -0,0 +1,69 @@ +import urlparse +import warnings + +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.contrib.sites.models import Site + +import providers +from allauth.socialaccount import requests + +from providers.oauth2.provider import OAuth2Provider + +from models import SocialApp + + +mocked_oauth_responses = { + 'google': requests.Response(200, """ +{"family_name": "Penners", "name": "Raymond Penners", + "picture": "https://lh5.googleusercontent.com/-GOFYGBVOdBQ/AAAAAAAAAAI/AAAAAAAAAGM/WzRfPkv4xbo/photo.jpg", + "locale": "nl", "gender": "male", + "email": "raymond.penners@gmail.com", + "link": "https://plus.google.com/108204268033311374519", + "given_name": "Raymond", "id": "108204268033311374519", + "verified_email": true} +""") +} + +def create_oauth2_tests(provider): + def setUp(self): + self.app = SocialApp.objects.create(site=Site.objects.get_current(), + provider=self.provider.id, + name='oauth2 test', + key='123', + secret='abc') + + def test_login(self): + resp = self.client.get(reverse(self.provider.id + '_login')) + p = urlparse.urlparse(resp['location']) + q = urlparse.parse_qs(p.query) + complete_url = reverse(self.provider.id+'_callback') + self.assertGreater(q['redirect_uri'][0] + .find(complete_url), 0) + resp_mock = mocked_oauth_responses.get(self.provider.id) + if not resp_mock: + warnings.warn("Cannot test provider %s, no oauth mock" + % self.provider.id) + return + requests.mock_next_request \ + (requests.Response(200, + '{"access_token":"testac"}', + {'content-type': + 'application/json'})) + requests.mock_next_request(resp_mock) + resp = self.client.get(complete_url, + { 'code': 'test' }) + self.assertRedirects(resp, reverse('socialaccount_signup')) + + + impl = { 'setUp': setUp, + 'test_login': test_login } + class_name = 'OAuth2Tests_'+provider.id + Class = type(class_name, (TestCase,), impl) + globals()[class_name] = Class + Class.provider = provider + +for provider in providers.registry.get_list(): + if isinstance(provider,OAuth2Provider): + create_oauth2_tests(provider) + diff --git a/itf/allauth/socialaccount/urls.py b/itf/allauth/socialaccount/urls.py new file mode 100644 index 0000000..47f6353 --- /dev/null +++ b/itf/allauth/socialaccount/urls.py @@ -0,0 +1,10 @@ +from django.conf.urls.defaults import patterns, url + +import views + +urlpatterns = patterns('', + url('^login/cancelled/$', views.login_cancelled, + name='socialaccount_login_cancelled'), + url('^login/error/$', views.login_error, name='socialaccount_login_error'), + url('^signup/$', views.signup, name='socialaccount_signup'), + url('^connections/$', views.connections, name='socialaccount_connections')) diff --git a/itf/allauth/socialaccount/views.py b/itf/allauth/socialaccount/views.py new file mode 100644 index 0000000..78c9773 --- /dev/null +++ b/itf/allauth/socialaccount/views.py @@ -0,0 +1,63 @@ +from django.utils.translation import ugettext_lazy as _ +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.core.urlresolvers import reverse +from django.contrib.sites.models import Site +from django.template import RequestContext +from django.shortcuts import render_to_response +from django.contrib.auth.decorators import login_required + +from forms import DisconnectForm, SignupForm + +import helpers + + +def signup(request, **kwargs): + if request.user.is_authenticated(): + return HttpResponseRedirect(reverse(connections)) + sociallogin = request.session.get('socialaccount_sociallogin') + if not sociallogin: + return HttpResponseRedirect(reverse('account_login')) + form_class = kwargs.pop("form_class", SignupForm) + template_name = kwargs.pop("template_name", 'socialaccount/signup.html') + if request.method == "POST": + form = form_class(request.POST, sociallogin=sociallogin) + if form.is_valid(): + form.save(request=request) + return helpers.complete_social_signup(request, sociallogin) + else: + form = form_class(sociallogin=sociallogin) + dictionary = dict(site=Site.objects.get_current(), + account=sociallogin.account, + form=form) + return render_to_response(template_name, dictionary, + RequestContext(request)) + + +def login_cancelled(request): + d = {} + return render_to_response('socialaccount/login_cancelled.html', d, + context_instance=RequestContext(request)) + + +def login_error(request): + return helpers.render_authentication_error(request) + + +@login_required +def connections(request): + form = None + if request.method == 'POST': + form = DisconnectForm(request.POST, user=request.user) + if form.is_valid(): + messages.add_message(request, messages.INFO, + _('The social account has been disconnected')) + form.save() + form = None + if not form: + form = DisconnectForm(user=request.user) + d = dict(form=form) + return render_to_response( + 'socialaccount/connections.html', + d, + context_instance=RequestContext(request)) diff --git a/itf/allauth/templates/account/base.html b/itf/allauth/templates/account/base.html new file mode 100644 index 0000000..3070348 --- /dev/null +++ b/itf/allauth/templates/account/base.html @@ -0,0 +1,3 @@ +{% extends "base.html" %} + + diff --git a/itf/allauth/templates/account/email.html b/itf/allauth/templates/account/email.html new file mode 100644 index 0000000..8b5fec8 --- /dev/null +++ b/itf/allauth/templates/account/email.html @@ -0,0 +1,71 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load url from future %} + +{% block head_title %}{% trans "Account" %}{% endblock %} + +{% block content %} +

{% trans "E-mail Addresses" %}

+{% if user.emailaddress_set.all %} +

{% trans 'The following e-mail addresses are associated to your account:' %}

+ + + +{% else %} +

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

+ +{% endif %} + + +

{% trans "Add E-mail Address" %}

+ +
+ {% csrf_token %} + {{ add_email_form.as_p}} + +
+ +{% endblock %} + + +{% block extra_body %} + +{% endblock %} diff --git a/itf/allauth/templates/account/login.html b/itf/allauth/templates/account/login.html new file mode 100644 index 0000000..21a38f7 --- /dev/null +++ b/itf/allauth/templates/account/login.html @@ -0,0 +1,53 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account_tags %} +{% load url from future %} + +{% block head_title %}{% trans "Sign In" %}{% endblock %} + + +{% block content %} + +

{% trans "Sign In" %}

+ +{% if not user.is_authenticated %} + +{% if socialaccount.providers %} +

{% blocktrans with site.name as site_name %}Please sign in with one +of your existing third party accounts. Or, sign up for a {{site_name}} account and sign in +below:{% endblocktrans %}

+ +
+ +
    +{% include "socialaccount/snippets/provider_list.html" %} +
+ + + +
+ +{% include "socialaccount/snippets/login_extra.html" %} + +{% endif %} +{% endif %} + + + +{% if user.is_authenticated %} +{% include "account/snippets/already_logged_in.html" %} +{% endif %} + + +{% endblock %} + diff --git a/itf/allauth/templates/account/logout.html b/itf/allauth/templates/account/logout.html new file mode 100644 index 0000000..b280b5b --- /dev/null +++ b/itf/allauth/templates/account/logout.html @@ -0,0 +1,11 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Signed Out" %}{% endblock %} + +{% block content %} +

{% trans "Signed Out" %}

+ +

{% trans "You have signed out." %}

+{% endblock %} diff --git a/itf/allauth/templates/account/password_change.html b/itf/allauth/templates/account/password_change.html new file mode 100644 index 0000000..2762a41 --- /dev/null +++ b/itf/allauth/templates/account/password_change.html @@ -0,0 +1,14 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

{% trans "Change Password" %}

+ +
+ {% csrf_token %} + {{ password_change_form.as_p }} + +
+{% endblock %} diff --git a/itf/allauth/templates/account/password_delete.html b/itf/allauth/templates/account/password_delete.html new file mode 100644 index 0000000..b159f36 --- /dev/null +++ b/itf/allauth/templates/account/password_delete.html @@ -0,0 +1,14 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Delete Password" %}{% endblock %} + +{% block content %} +

{% trans "Delete Password" %}

+

{% blocktrans %}You may delete your password since you are currently logged in using OpenID.{% endblocktrans %}

+
+ {% csrf_token %} + +
+{% endblock %} diff --git a/itf/allauth/templates/account/password_delete_done.html b/itf/allauth/templates/account/password_delete_done.html new file mode 100644 index 0000000..3cb7ef5 --- /dev/null +++ b/itf/allauth/templates/account/password_delete_done.html @@ -0,0 +1,10 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Password Deleted" %}{% endblock %} + +{% block content %} +

{% trans "Password Deleted" %}

+

{% blocktrans %}Your password has been deleted.{% endblocktrans %}

+{% endblock %} diff --git a/itf/allauth/templates/account/password_reset.html b/itf/allauth/templates/account/password_reset.html new file mode 100644 index 0000000..dfa3ee0 --- /dev/null +++ b/itf/allauth/templates/account/password_reset.html @@ -0,0 +1,30 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account_tags %} + +{% block head_title %}{% trans "Password Reset" %}{% endblock %} + +{% block content %} + +

{% trans "Password Reset" %}

+ {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + +

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

+ +
+ {% csrf_token %} + {{ password_reset_form.as_p }} + +
+ +

{% blocktrans %}If you have any trouble resetting your password, contact us at {{ CONTACT_EMAIL }}.{% endblocktrans %}

+{% endblock %} + +{% block extra_body %} + +{% endblock %} diff --git a/itf/allauth/templates/account/password_reset_done.html b/itf/allauth/templates/account/password_reset_done.html new file mode 100644 index 0000000..e6294c0 --- /dev/null +++ b/itf/allauth/templates/account/password_reset_done.html @@ -0,0 +1,16 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account_tags %} + +{% block head_title %}{% trans "Password Reset" %}{% endblock %} + +{% block content %} +

{% trans "Password Reset" %}

+ + {% if user.is_authenticated %} + {% include "account/snippets/already_logged_in.html" %} + {% endif %} + +

{% blocktrans %}We have sent you an e-mail. If you do not receive it within a few minutes, contact us at {{ CONTACT_EMAIL }}.{% endblocktrans %}

+{% endblock %} diff --git a/itf/allauth/templates/account/password_reset_from_key.html b/itf/allauth/templates/account/password_reset_from_key.html new file mode 100644 index 0000000..dd6316f --- /dev/null +++ b/itf/allauth/templates/account/password_reset_from_key.html @@ -0,0 +1,24 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} +{% block head_title %}{% trans "Change Password" %}{% endblock %} + +{% block content %} +

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

+ + {% if token_fail %} + {% url 'account_reset_password' as passwd_reset_url %} +

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

+ {% else %} + {% if form %} +
+ {% csrf_token %} + {{ form.as_p }} + +
+ {% else %} +

{% trans 'Your password is now changed.' %}

+ {% endif %} + {% endif %} +{% endblock %} diff --git a/itf/allauth/templates/account/password_reset_key_message.txt b/itf/allauth/templates/account/password_reset_key_message.txt new file mode 100644 index 0000000..585e8b3 --- /dev/null +++ b/itf/allauth/templates/account/password_reset_key_message.txt @@ -0,0 +1,9 @@ +{% load i18n %}{% blocktrans with site.domain as site_domain and user.username as username %}You're receiving this e-mail because you or someone else has requested a password for your user account at {{site_domain}}. +It can be safely ignored if you did not request a password reset. Click the link below to reset your password. + +{{password_reset_url}} + +In case you forgot, your username is {{username}}. + +Thanks for using our site! +{% endblocktrans %} diff --git a/itf/allauth/templates/account/password_set.html b/itf/allauth/templates/account/password_set.html new file mode 100644 index 0000000..42981ea --- /dev/null +++ b/itf/allauth/templates/account/password_set.html @@ -0,0 +1,15 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Set Password" %}{% endblock %} + +{% block content %} +

{% trans "Set Password" %}

+ +
+ {% csrf_token %} + {{ password_set_form.as_p }} + +
+{% endblock %} diff --git a/itf/allauth/templates/account/signup.html b/itf/allauth/templates/account/signup.html new file mode 100644 index 0000000..d419123 --- /dev/null +++ b/itf/allauth/templates/account/signup.html @@ -0,0 +1,28 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Signup" %}{% endblock %} + +{% block content %} +

{% trans "Sign Up" %}

+ + {% if user.is_authenticated %} +{% include "account/snippets/already_logged_in.html" %} + {% else %} +

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

+ + + + {% endif %} +{% endblock %} + + diff --git a/itf/allauth/templates/account/snippets/already_logged_in.html b/itf/allauth/templates/account/snippets/already_logged_in.html new file mode 100644 index 0000000..13ee8b6 --- /dev/null +++ b/itf/allauth/templates/account/snippets/already_logged_in.html @@ -0,0 +1,5 @@ +{% load i18n %} +{% load account_tags %} + +{% user_display user as user_display %} +

{% trans "Note" %}: {% blocktrans %}you are already logged in as {{ user_display }}.{% endblocktrans %}

diff --git a/itf/allauth/templates/account/verification_sent.html b/itf/allauth/templates/account/verification_sent.html new file mode 100644 index 0000000..dbed4fc --- /dev/null +++ b/itf/allauth/templates/account/verification_sent.html @@ -0,0 +1,12 @@ +{% extends "account/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} + +{% block content %} +

{% trans "Verify Your E-mail Address" %}

+ +

{% blocktrans %}We have sent you an e-mail to {{ email }} for verification. Follow the link provided to finalize the signup process. If you do not receive it within a few minutes, contact us at {{ CONTACT_EMAIL }}.{% endblocktrans %}

+ +{% endblock %} diff --git a/itf/allauth/templates/account/verified_email_required.html b/itf/allauth/templates/account/verified_email_required.html new file mode 100644 index 0000000..456c1e0 --- /dev/null +++ b/itf/allauth/templates/account/verified_email_required.html @@ -0,0 +1,26 @@ +{% extends "account/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} + +{% block content %} +

{% trans "Verify Your E-mail Address" %}

+ +{% url 'account_email' as email_url %} + +

{% blocktrans %}This part of the site requires us to verify that +you are who you claim to be. For this purpose, we require that you +verify ownership of your e-mail address. {% endblocktrans %}

+ +

{% blocktrans %}We have sent an e-mail to you for +verification. Please click on the link inside this +e-mail. If you do not receive it within a few minutes, contact us +at {{ CONTACT_EMAIL }}. +{% endblocktrans %}

+ +

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

+ + +{% endblock %} diff --git a/itf/allauth/templates/emailconfirmation/confirm_email.html b/itf/allauth/templates/emailconfirmation/confirm_email.html new file mode 100644 index 0000000..515e3ab --- /dev/null +++ b/itf/allauth/templates/emailconfirmation/confirm_email.html @@ -0,0 +1,20 @@ +{% extends "account/base.html" %} + +{% load i18n %} +{% load account_tags %} + +{% block head_title %}{% trans "E-mail Address Confirmation" %}{% endblock %} + + +{% block content %} + {% user_display email_address.user as user_display %} + +

{% trans "E-mail Address Confirmation" %}

+ + {% if email_address %} + +

{% blocktrans with email_address.email as email %}You have confirmed that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

+ {% else %} +

{% trans "Invalid confirmation key." %}

+ {% endif %} +{% endblock %} diff --git a/itf/allauth/templates/emailconfirmation/email_confirmation_message.txt b/itf/allauth/templates/emailconfirmation/email_confirmation_message.txt new file mode 100644 index 0000000..4858e15 --- /dev/null +++ b/itf/allauth/templates/emailconfirmation/email_confirmation_message.txt @@ -0,0 +1,4 @@ +{% load account_tags %}{% user_display user as user_display %}{% load i18n %}{% autoescape off %}{% blocktrans with current_site.name as site_name %}User {{ user_display }} at {{ site_name }} has given this as an email address. + +To confirm this is correct, go to {{ activate_url }} +{% endblocktrans %}{% endautoescape %} diff --git a/itf/allauth/templates/emailconfirmation/email_confirmation_subject.txt b/itf/allauth/templates/emailconfirmation/email_confirmation_subject.txt new file mode 100644 index 0000000..0333a85 --- /dev/null +++ b/itf/allauth/templates/emailconfirmation/email_confirmation_subject.txt @@ -0,0 +1,5 @@ +{% load i18n %}{% autoescape off %}[{{current_site.name}}] {% blocktrans %}Confirm E-mail Address{% endblocktrans %}{% endautoescape %}{% comment %} +Local Variables: +require-final-newline: nil; +End: +{% endcomment %} \ No newline at end of file diff --git a/itf/allauth/templates/openid/base.html b/itf/allauth/templates/openid/base.html new file mode 100644 index 0000000..671d403 --- /dev/null +++ b/itf/allauth/templates/openid/base.html @@ -0,0 +1 @@ +{% extends "socialaccount/base.html" %} diff --git a/itf/allauth/templates/openid/login.html b/itf/allauth/templates/openid/login.html new file mode 100644 index 0000000..2bcea25 --- /dev/null +++ b/itf/allauth/templates/openid/login.html @@ -0,0 +1,19 @@ +{% extends "openid/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}OpenID Sign In{% endblock %} + +{% block content %} + +

{% trans 'OpenID Sign In' %}

+ + + + +{% endblock %} diff --git a/itf/allauth/templates/socialaccount/account_inactive.html b/itf/allauth/templates/socialaccount/account_inactive.html new file mode 100644 index 0000000..8c56ceb --- /dev/null +++ b/itf/allauth/templates/socialaccount/account_inactive.html @@ -0,0 +1,11 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Account Inactive" %}{% endblock %} + +{% block content %} +

{% trans "Account Inactive" %}

+ +

{% trans "This account is inactive." %}

+{% endblock %} diff --git a/itf/allauth/templates/socialaccount/authentication_error.html b/itf/allauth/templates/socialaccount/authentication_error.html new file mode 100644 index 0000000..e6274cb --- /dev/null +++ b/itf/allauth/templates/socialaccount/authentication_error.html @@ -0,0 +1,11 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %} + +{% block content %} +

{% trans "Social Network Login Failure" %}

+ +

{% trans "An error occured while attempting to login via your social network account." %}

+{% endblock %} diff --git a/itf/allauth/templates/socialaccount/base.html b/itf/allauth/templates/socialaccount/base.html new file mode 100644 index 0000000..18530d1 --- /dev/null +++ b/itf/allauth/templates/socialaccount/base.html @@ -0,0 +1,2 @@ +{% extends "account/base.html" %} + diff --git a/itf/allauth/templates/socialaccount/connections.html b/itf/allauth/templates/socialaccount/connections.html new file mode 100644 index 0000000..c48c2f6 --- /dev/null +++ b/itf/allauth/templates/socialaccount/connections.html @@ -0,0 +1,56 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Account Connections" %}{% endblock %} + +{% block content %} +

{% trans "Account Connections" %}

+ +{% if form.accounts %} +

{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}

+ + +
+{% csrf_token %} + +
+{% if form.non_field_errors %} +
{{form.non_field_errors}}
+{% endif %} + +{% for base_account in form.accounts %} +{% with base_account.get_provider_account as account %} +
+ +
+{% endwith %} +{% endfor %} + +
+ +
+ +
+ +
+ +{% else %} +

You currently have no social network accounts connected to this account.

+{% endif %} + +

{% trans 'Add a 3rd Party Account' %}

+ +
    +{% include "socialaccount/snippets/provider_list.html" %} +
+ +{% include "socialaccount/snippets/login_extra.html" %} + +{% endblock %} + + diff --git a/itf/allauth/templates/socialaccount/login_cancelled.html b/itf/allauth/templates/socialaccount/login_cancelled.html new file mode 100644 index 0000000..97a13b2 --- /dev/null +++ b/itf/allauth/templates/socialaccount/login_cancelled.html @@ -0,0 +1,17 @@ +{% extends "socialaccount/base.html" %} + +{% load url from future %} +{% load i18n %} + +{% block head_title %}{% trans "Login Cancelled" %}{% endblock %} + +{% block content %} + +

{% trans "Login Cancelled" %}

+ +{% url 'socialaccount_login' as login_url %} + +

{% blocktrans %}You decided to cancel logging in to our site using one of your exisiting accounts. If this was a mistake, please proceed to sign in.{% endblocktrans %}

+ +{% endblock %} + diff --git a/itf/allauth/templates/socialaccount/signup.html b/itf/allauth/templates/socialaccount/signup.html new file mode 100644 index 0000000..a385064 --- /dev/null +++ b/itf/allauth/templates/socialaccount/signup.html @@ -0,0 +1,25 @@ +{% extends "socialaccount/base.html" %} + +{% load i18n %} + +{% block head_title %}{% trans "Signup" %}{% endblock %} + +{% block content %} +

{% trans "Sign Up" %}

+ +

{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to +{{site_name}}. As a final step, please complete the following form:{% endblocktrans %}

+ + + + +{% endblock %} + + diff --git a/itf/allauth/templates/socialaccount/snippets/login_extra.html b/itf/allauth/templates/socialaccount/snippets/login_extra.html new file mode 100644 index 0000000..67e1452 --- /dev/null +++ b/itf/allauth/templates/socialaccount/snippets/login_extra.html @@ -0,0 +1,4 @@ +{% load socialaccount_tags %} + +{% providers_media_js %} + diff --git a/itf/allauth/templates/socialaccount/snippets/provider_list.html b/itf/allauth/templates/socialaccount/snippets/provider_list.html new file mode 100644 index 0000000..8661e8f --- /dev/null +++ b/itf/allauth/templates/socialaccount/snippets/provider_list.html @@ -0,0 +1,19 @@ +{% load socialaccount_tags %} + +{% for provider in socialaccount.providers %} +{% if provider.id == "openid" %} +{% for brand in provider.get_brands %} +
  • + {{brand.name}} +
  • +{% endfor %} +{% endif %} +
  • + {{provider.name}} +
  • +{% endfor %} + diff --git a/itf/allauth/tests.py b/itf/allauth/tests.py new file mode 100644 index 0000000..1b7cae4 --- /dev/null +++ b/itf/allauth/tests.py @@ -0,0 +1,15 @@ +from django.test import TestCase + +import utils + + +class BasicTests(TestCase): + + def test_email_validation(self): + s = 'unfortunately.django.user.email.max_length.is.set.to.75.which.is.too.short@bummer.com' + self.assertEquals(None, utils.valid_email_or_none(s)) + s = 'this.email.address.is.a.bit.too.long.but.should.still.validate.ok@short.com' + self.assertEquals(s, utils.valid_email_or_none(s)) + s = 'x' + s + self.assertEquals(None, utils.valid_email_or_none(s)) + self.assertEquals(None, utils.valid_email_or_none("Bad ?")) diff --git a/itf/allauth/urls.py b/itf/allauth/urls.py new file mode 100644 index 0000000..6182d02 --- /dev/null +++ b/itf/allauth/urls.py @@ -0,0 +1,21 @@ +from django.conf.urls.defaults import url, patterns, include +from django.utils import importlib + +from allauth.socialaccount import providers + +import app_settings + +urlpatterns = patterns('', url('^', include('allauth.account.urls'))) + +if app_settings.SOCIALACCOUNT_ENABLED: + urlpatterns += patterns('', url('^social/', + include('allauth.socialaccount.urls'))) + +for provider in providers.registry.get_list(): + try: + prov_mod = importlib.import_module(provider.package + '.urls') + except ImportError: + continue + prov_urlpatterns = getattr(prov_mod, 'urlpatterns', None) + if prov_urlpatterns: + urlpatterns += prov_urlpatterns diff --git a/itf/allauth/utils.py b/itf/allauth/utils.py new file mode 100644 index 0000000..4df5197 --- /dev/null +++ b/itf/allauth/utils.py @@ -0,0 +1,85 @@ +from django.template.defaultfilters import slugify +from django.contrib.auth.models import User +from django.core.validators import validate_email, ValidationError +from django.db.models import EmailField +from django.utils.http import urlencode +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.utils import importlib + +from emailconfirmation.models import EmailAddress + +import app_settings + +def get_login_redirect_url(request, + fallback=app_settings.LOGIN_REDIRECT_URL): + """ + Returns a url to redirect to after the login + """ + url = request.REQUEST.get(REDIRECT_FIELD_NAME) or fallback + return url + + +def passthrough_login_redirect_url(request, url): + assert url.find("?") < 0 # TODO: Handle this case properly + next = get_login_redirect_url(request, fallback=None) + if next: + url = url + '?' + urlencode({ REDIRECT_FIELD_NAME: next }) + return url + + +def generate_unique_username(txt): + username = slugify(txt.split('@')[0]) + max_length = User._meta.get_field('username').max_length + i = 0 + while True: + try: + if i: + pfx = str(i + 1) + else: + pfx = '' + ret = username[0:max_length - len(pfx)] + pfx + User.objects.get(username=ret) + i += 1 + except User.DoesNotExist: + return ret + + +def valid_email_or_none(email): + ret = None + try: + if email: + validate_email(email) + if len(email) <= EmailField().max_length: + ret = email + except ValidationError: + pass + return ret + + +def email_address_exists(email, exclude_user=None): + emailaddresses = EmailAddress.objects + if exclude_user: + emailaddresses = emailaddresses.exclude(user=exclude_user) + ret = emailaddresses.filter(email__iexact=email).exists() + if not ret: + users = User.objects + if exclude_user: + users = users.exclude(user=exclude_user) + ret = users.filter(email__iexact=email).exists() + return ret + + + +def import_attribute(path): + assert isinstance(path, str) + pkg, attr = path.rsplit('.',1) + ret = getattr(importlib.import_module(pkg), attr) + return ret + +def import_callable(path_or_callable): + if not hasattr(path_or_callable, '__call__'): + ret = import_attribute(path_or_callable) + else: + ret = path_or_callable + return ret +