├── .gitignore
├── AUTHORS.txt
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── emailauth
├── __init__.py
├── admin.py
├── backends.py
├── forms.py
├── locale
│ └── ru
│ │ └── LC_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── cleanupemailauth.py
├── models.py
├── templates
│ └── emailauth
│ │ ├── account.html
│ │ ├── account_single_email.html
│ │ ├── add_email.html
│ │ ├── add_email_continue.html
│ │ ├── base.html
│ │ ├── change_email.html
│ │ ├── change_email_continue.html
│ │ ├── delete_email.html
│ │ ├── logged_out.html
│ │ ├── login.html
│ │ ├── loginform.html
│ │ ├── register.html
│ │ ├── register_continue.html
│ │ ├── request_password.html
│ │ ├── request_password_email.txt
│ │ ├── request_password_email_subject.txt
│ │ ├── reset_password.html
│ │ ├── reset_password_continue.html
│ │ ├── set_default_email.html
│ │ ├── verification_email.txt
│ │ ├── verification_email_subject.txt
│ │ └── verify.html
├── templatetags
│ ├── __init__.py
│ └── emailauth_tags.py
├── tests.py
├── urls.py
├── utils.py
└── views.py
├── example
├── __init__.py
├── emailauth.db
├── manage.py
├── media
│ ├── css
│ │ ├── clearfix.css
│ │ ├── fancy.css
│ │ ├── forms.css
│ │ ├── ie6.css
│ │ ├── reset.css
│ │ ├── site.css
│ │ └── typography.css
│ └── img
│ │ ├── error.png
│ │ ├── exclamation_white.png
│ │ ├── info.png
│ │ ├── question.png
│ │ └── success.png
├── middleware.py
├── settings.py
├── settings_singleemail.py
├── templates
│ ├── 404.html
│ ├── index.html
│ └── master.html
├── urls.py
└── views.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.swp
3 | *~
4 |
5 | # Vim stuff
6 | .vimproject
7 | Session.vim
8 | .ropeproject/
9 |
10 | # virtualenv stuff
11 | bin/
12 | include/
13 | lib/
14 | build/
15 |
--------------------------------------------------------------------------------
/AUTHORS.txt:
--------------------------------------------------------------------------------
1 | Author of django-emailauth is Vasily Sulatskov
2 |
3 |
4 | The CONTRIBUTORS -- people who have submitted patches, reported bugs and etc.:
5 |
6 | * Ivan Gromov
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, Vasily Sulatskov
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are
6 | met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above
11 | copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided
13 | with the distribution.
14 | * Neither the name of the author nor the names of other
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.txt
2 | include MANIFEST.in
3 | include README.rst
4 | include AUTHORS.txt
5 | recursive-include example *.py *.html *.txt *.css
6 | recursive-include emailauth *.html *.txt
7 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ================================
2 | Django user email authentication
3 | ================================
4 |
5 |
6 | This is a Django application providing all essentials for authenticating users
7 | based on email addresses instead of usernames.
8 |
9 | This application can operate in a traditional one user - one email mode as
10 | well as one user - many emails mode.
11 |
12 | This application consists of:
13 |
14 | * UserEmail model
15 |
16 | * Views and forms:
17 |
18 | - Login;
19 | - Password reset;
20 | - Account management:
21 |
22 | - User registration and email confirmation;
23 | - Adding and removing emails to/from existing user accounts;
24 | - Changing default emails
25 | - Changing email (for single email mode)
26 |
27 | * Authentication backends:
28 |
29 | - Email backend for authenticating users who has UserEmail object (regular
30 | site users);
31 | - Fallback backend for users without such objects (most likely that will be
32 | site administration)
33 |
34 |
35 | Motivation for this application
36 | -------------------------------
37 |
38 | For some reason I was lucky to work on projects which required email-based
39 | authentication, and one of these projects could benefit if users could have
40 | several emails.
41 |
42 | To solve basic authentication problem I quickly came up with this:
43 | http://www.djangosnippets.org/snippets/74/
44 |
45 | That trick works but it has several drawbacks:
46 |
47 | * It breaks standard Django tests, so when you run python manage.py test on
48 | your project you'll have to filter out error messages from broken Django
49 | tests. Not good.
50 |
51 | * Standard Django login view can't handle long (longer than 30 characters)
52 | email addresses.
53 |
54 | * If you put email verification status and all such into UserProfile class you
55 | tie code working with emails to your project impairing code reuse.
56 |
57 |
58 | To solve above problems I decided to create this application. It stores all
59 | email-specific data into UserEmail model (verification code, code creation
60 | date, verification status etc.) So this application manages all email-related
61 | data, not messing with UserProfile and saves application user from reinventing
62 | the wheel.
63 |
64 |
65 | Example project
66 | ---------------
67 |
68 | To see this application in action::
69 |
70 | cd emailauth/example
71 | python manage.py syncdb
72 | python manage.py runserver
73 |
74 | Please bear in mind that all emails sent by example project are not actually
75 | sent but printed to stdout instead.
76 |
77 | To see how traditional one user - one email mode works::
78 |
79 | cd emailauth/example
80 | python manage.py syncdb
81 | python manage.py runserver --settings=settings_singleemail
82 |
83 |
84 | Installation and configuration
85 | ------------------------------
86 |
87 | Installation
88 | ~~~~~~~~~~~~
89 |
90 | First you need to somehow obtain 'emailauth' package.
91 |
92 | Place 'emailauth' directory somewhere on your PYHTONPATH, for example in your
93 | project directory, next to your settings.py -- that's the same place where
94 | ``python manage.py startapp`` creates applications.
95 |
96 | If you are using some kind of isolated environment, like virtualenv, you can
97 | just perform a regular installation::
98 |
99 | python setup.py install
100 |
101 | Or::
102 |
103 | pip install django-emailauth
104 |
105 | Or::
106 |
107 | easy_install django-emailauth
108 |
109 |
110 | Configuration
111 | ~~~~~~~~~~~~~
112 |
113 | Now you need to make several changes to your settings.py
114 |
115 | * Add ``'emailauth'`` to your ``INSTALLED_APPS``
116 |
117 | * Plug emailauth's authentication backends::
118 |
119 | AUTHENTICATION_BACKENDS = (
120 | 'emailauth.backends.EmailBackend',
121 | 'emailauth.backends.FallbackBackend',
122 | )
123 |
124 | * Configure ``LOGIN_REDIRECT_URL`` and ``LOGIN_URL``. Emailauth's default
125 | urls.py expects them to be like this::
126 |
127 | LOGIN_REDIRECT_URL = '/account/'
128 | LOGIN_URL = '/login/'
129 |
130 | * Optionally change a life time of email verification codes by changing
131 | ``EMAILAUTH_VERIFICATION_DAYS`` (default value is 3).
132 |
133 | * Optionally set ``EMAILAUTH_USE_SINGLE_EMAIL = False`` if you want to use
134 | emailauth in "multiple-emails mode".
135 |
136 | Now include emailauth's urls.py from your site's urls.py. Of course you may opt
137 | for not including whole emailauth.urls, and write your own configuration, but
138 | if you decide to use the provided urls.py, it will look like this::
139 |
140 | urlpatterns = patterns('',
141 | (r'', include('emailauth.urls')),
142 | )
143 |
144 |
145 | Maintenance
146 | ~~~~~~~~~~~
147 |
148 | By default emailauth uses automatic maintenance - it deletes expired UserEmail
149 | objects when a new unverified email is created.
150 |
151 | If you for some reason want to deactivate it and perform such maintenance
152 | manually you can do it:
153 |
154 | * Set ``EMAILAUTH_USE_AUTOMAINTENANCE = False`` in settings.py
155 |
156 | * Run ``cleanupemailauth`` management command when you want to perform the
157 | cleanup::
158 |
159 | python manage.py clenupemailauth
160 |
161 |
162 | Template customization
163 | ~~~~~~~~~~~~~~~~~~~~~~
164 |
165 | Emailauth comes with a set of templates that should get you started, however
166 | they won't be integrated with your site's templates - they don't extend the
167 | right template and use wrong block for main content.
168 |
169 | Don't worry, it's very easy to fix. All emailauth's templates extend
170 | ``emailauth/base.html`` and use ``emailauth_content`` block for content, so all
171 | you need, is to modify ``emailauth/base.html`` and make it extend right
172 | template and place ``emailauth_content`` block into right block specifc to your
173 | site.
174 |
175 | For example if your site's main template is ``mybase.html`` and you place all
176 | content into ``mycontent`` block, you need to make following
177 | ``emailauth/base.html``::
178 |
179 | {% extends "mybase.html" %}
180 |
181 | {% block mycontent %}
182 | {% block emailauth_content %}
183 | {% endblock %}
184 | {% endblock %}
185 |
186 |
187 | That's all
188 | ~~~~~~~~~~
189 |
190 | By this point, if you started a new project and followed all the above
191 | instructions above you should have a working instance of emailauth application.
192 |
193 | To test it, start a server::
194 |
195 | python manage.py runserver
196 |
197 | And open a registration page in your browser:
198 | ``http://127.0.0.1:8000/register/`` - it should display a registration page.
199 |
--------------------------------------------------------------------------------
/emailauth/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/emailauth/__init__.py
--------------------------------------------------------------------------------
/emailauth/admin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.contrib import admin
3 | from emailauth.models import UserEmail
4 |
5 |
6 | class UserEmailAdmin(admin.ModelAdmin):
7 | model = UserEmail
8 | list_display = ['user', 'email', 'verified',]
9 |
10 | try:
11 | admin.site.register(UserEmail, UserEmailAdmin)
12 | except admin.sites.AlreadyRegistered:
13 | pass
14 |
--------------------------------------------------------------------------------
/emailauth/backends.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.contrib.auth.backends import ModelBackend
3 |
4 | from emailauth.models import UserEmail
5 |
6 |
7 | class EmailBackend(ModelBackend):
8 | def authenticate(self, username=None, password=None):
9 | try:
10 | email = UserEmail.objects.get(email=username, verified=True)
11 | if email.user.check_password(password):
12 | return email.user
13 | except UserEmail.DoesNotExist:
14 | return None
15 |
16 |
17 | class FallbackBackend(ModelBackend):
18 | def authenticate(self, username=None, password=None):
19 | try:
20 | user = User.objects.get(username=username)
21 | if (user.check_password(password) and
22 | not UserEmail.objects.filter(user=user).count()):
23 |
24 | return user
25 |
26 | except User.DoesNotExist:
27 | return None
28 |
--------------------------------------------------------------------------------
/emailauth/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib.auth import authenticate
3 | from django.utils.translation import ugettext_lazy as _
4 |
5 | from django.contrib.auth.models import User
6 |
7 | from emailauth.models import UserEmail
8 |
9 | attrs_dict = {}
10 |
11 | class LoginForm(forms.Form):
12 | email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict)))
13 | password = forms.CharField(widget=forms.PasswordInput(attrs=dict(attrs_dict),
14 | render_value=False))
15 |
16 | def clean(self):
17 | email = self.cleaned_data.get('email')
18 | password = self.cleaned_data.get('password')
19 |
20 | if email and password:
21 | self.user_cache = authenticate(username=email, password=password)
22 | if self.user_cache is None:
23 | raise forms.ValidationError(_("Please enter a correct email and "
24 | "password. Note that both fields are case-sensitive."))
25 | elif not self.user_cache.is_active:
26 | raise forms.ValidationError(_("This account is inactive."))
27 |
28 | return self.cleaned_data
29 |
30 | def get_user_id(self):
31 | if self.user_cache:
32 | return self.user_cache.id
33 | return None
34 |
35 | def get_user(self):
36 | return self.user_cache
37 |
38 |
39 | def get_max_length(model, field_name):
40 | field = model._meta.get_field_by_name(field_name)[0]
41 | return field.max_length
42 |
43 |
44 | def clean_password2(self):
45 | data = self.cleaned_data
46 | if 'password1' in data and 'password2' in data:
47 | if data['password1'] != data['password2']:
48 | raise forms.ValidationError(_(
49 | u'You must type the same password each time.'))
50 | if 'password2' in data:
51 | return data['password2']
52 |
53 |
54 | class RegistrationForm(forms.Form):
55 | email = forms.EmailField(label=_(u'email address'))
56 | first_name = forms.CharField(label=_(u'first name'),
57 | max_length=get_max_length(User, 'first_name'),
58 | help_text=_(u"That's how we'll call you in emails"))
59 | password1 = forms.CharField(widget=forms.PasswordInput(render_value=False),
60 | label=_(u'password'))
61 | password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
62 | label=_(u'password (again)'))
63 |
64 | clean_password2 = clean_password2
65 |
66 | def clean_email(self):
67 | email = self.cleaned_data['email']
68 |
69 | try:
70 | user = UserEmail.objects.get(email=email)
71 | raise forms.ValidationError(_(u'This email is already taken.'))
72 | except UserEmail.DoesNotExist:
73 | pass
74 | return email
75 |
76 |
77 | def save(self):
78 | data = self.cleaned_data
79 | user = User()
80 | user.email = data['email']
81 | user.first_name = data['name']
82 | user.set_password(data['password1'])
83 | user.save()
84 |
85 | desired_username = 'id_%d_%s' % (user.id, user.email)
86 | user.username = desired_username[:get_max_length(User, 'username')]
87 | user.is_active = False
88 | user.save()
89 |
90 | registration_profile = (
91 | RegistrationProfile.objects.create_inactive_profile(user))
92 | registration_profile.save()
93 |
94 | profile = Account()
95 | profile.user = user
96 | profile.save()
97 |
98 | return user, registration_profile
99 |
100 |
101 | class PasswordResetRequestForm(forms.Form):
102 | email = forms.EmailField(label=_(u'your email address'))
103 |
104 | def clean_email(self):
105 | data = self.cleaned_data
106 | try:
107 | user_email = UserEmail.objects.get(email=data['email'])
108 | return data['email']
109 | except UserEmail.DoesNotExist:
110 | raise forms.ValidationError(_(u'Unknown email'))
111 |
112 |
113 | class PasswordResetForm(forms.Form):
114 | password1 = forms.CharField(widget=forms.PasswordInput(render_value=False),
115 | label=_(u'password'))
116 | password2 = forms.CharField(widget=forms.PasswordInput(render_value=False),
117 | label=_(u'password (again)'))
118 |
119 | clean_password2 = clean_password2
120 |
121 |
122 | class AddEmailForm(forms.Form):
123 | email = forms.EmailField(label=_(u'new email address'))
124 |
125 | def clean_email(self):
126 | email = self.cleaned_data['email']
127 |
128 | try:
129 | user = UserEmail.objects.get(email=email)
130 | raise forms.ValidationError(_(u'This email is already taken.'))
131 | except UserEmail.DoesNotExist:
132 | pass
133 | return email
134 |
135 |
136 | class DeleteEmailForm(forms.Form):
137 | yes = forms.BooleanField(required=True)
138 |
139 | def __init__(self, user, *args, **kwds):
140 | self.user = user
141 | super(DeleteEmailForm, self).__init__(*args, **kwds)
142 |
143 | def clean(self):
144 | count = UserEmail.objects.filter(user=self.user).count()
145 | if UserEmail.objects.filter(user=self.user, verified=True).count() < 2:
146 | raise forms.ValidationError(_('You can not delete your last verified '
147 | 'email.'))
148 | return self.cleaned_data
149 |
150 |
151 | class ConfirmationForm(forms.Form):
152 | yes = forms.BooleanField(required=True)
153 |
--------------------------------------------------------------------------------
/emailauth/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/emailauth/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/emailauth/locale/ru/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # Russian translation
2 | # This file is distributed under the same license as the django-emailauth package.
3 | # Ivan Gromov 2009
4 | #
5 | #, fuzzy
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: 0.1\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2009-07-09 18:41+0600\n"
11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 | "Last-Translator: Ivan Gromov \n"
13 | "MIME-Version: 1.0\n"
14 | "Content-Type: text/plain; charset=UTF-8\n"
15 | "Content-Transfer-Encoding: 8bit\n"
16 |
17 | #: forms.py:23
18 | msgid ""
19 | "Please enter a correct email and password. Note that both fields are case-"
20 | "sensitive."
21 | msgstr ""
22 | "Введите правильные электронный адрес и пароль. Оба поля чувствительны к "
23 | "регистру."
24 |
25 | #: forms.py:26
26 | msgid "This account is inactive."
27 | msgstr "Аккаунт заблокирован"
28 |
29 | #: forms.py:49
30 | msgid "You must type the same password each time."
31 | msgstr "Пароль и подтверждение должны совпадать"
32 |
33 | #: forms.py:55
34 | msgid "email address"
35 | msgstr "Электронный адрес"
36 |
37 | #: forms.py:56
38 | msgid "first name"
39 | msgstr "Имя"
40 |
41 | #: forms.py:58
42 | msgid "That's how we'll call you in emails"
43 | msgstr "Как к Вам обращаться в письмах"
44 |
45 | #: forms.py:60 forms.py:115
46 | msgid "password"
47 | msgstr "Пароль"
48 |
49 | #: forms.py:62 forms.py:117
50 | msgid "password (again)"
51 | msgstr "Подтверждение пароля"
52 |
53 | #: forms.py:71 forms.py:130
54 | msgid "This email is already taken."
55 | msgstr "Этот адрес уже используется"
56 |
57 | #: forms.py:102
58 | msgid "your email address"
59 | msgstr "Ваш электронный адрес"
60 |
61 | #: forms.py:110
62 | msgid "Unknown email"
63 | msgstr "Электронный адрес не зарегистрирован"
64 |
65 | #: forms.py:123
66 | msgid "new email address"
67 | msgstr "Новый электронный адрес"
68 |
69 | #: forms.py:146
70 | msgid "You can not delete your last verified email."
71 | msgstr "Нельзя удалять последний подтвержденный электронный адррес"
72 |
73 | #: models.py:59
74 | msgid "user email"
75 | msgstr "Электронный адрес пользователя"
76 |
77 | #: models.py:60
78 | msgid "user emails"
79 | msgstr "Электронные адреса пользователя"
80 |
81 | #: models.py:66
82 | msgid "user"
83 | msgstr "Пользователь"
84 |
85 | #: models.py:71
86 | msgid "verification key"
87 | msgstr "Код подтверждения"
88 |
89 | #: views.py:61
90 | msgid ""
91 | "Your Web browser doesn't appear to have cookies enabled. Cookies are "
92 | "required for logging in."
93 | msgstr ""
94 | "Ваш браузер не поддерживает cookies. Для входа в системуcookies должны быть "
95 | "включены"
96 |
97 | #: views.py:177
98 | #, python-format
99 | msgid "%s email confirmed."
100 | msgstr "Электронный адрес %s подтвержден."
101 |
102 | #: templates/emailauth/account.html:4
103 | #: templates/emailauth/account_single_email.html:4
104 | msgid "Account properties"
105 | msgstr "Свойства профиля"
106 |
107 | #: templates/emailauth/account.html:7
108 | #: templates/emailauth/account_single_email.html:7
109 | msgid "Username:"
110 | msgstr "Пользователь:"
111 |
112 | #: templates/emailauth/account.html:11
113 | #: templates/emailauth/account_single_email.html:11
114 | msgid "First name:"
115 | msgstr "Имя:"
116 |
117 | #: templates/emailauth/account.html:15
118 | msgid "Default email:"
119 | msgstr "Электронный адрес по умолчанию"
120 |
121 | #: templates/emailauth/account.html:19
122 | msgid "Extra emails"
123 | msgstr "Дополнительные адреса"
124 |
125 | #: templates/emailauth/account.html:25
126 | msgid "Set as default"
127 | msgstr "Установить по умоллчанию"
128 |
129 | #: templates/emailauth/account.html:26
130 | msgid "Delete"
131 | msgstr "Удалить"
132 |
133 | #: templates/emailauth/account.html:32
134 | msgid "Unverified emails"
135 | msgstr "Неподтвержденные адерса"
136 |
137 | #: templates/emailauth/account.html:36
138 | msgid "resend verification email"
139 | msgstr "Отправить подтверждение ещё раз"
140 |
141 | #: templates/emailauth/account.html:41
142 | msgid "Add email"
143 | msgstr "Добавить электронный адрес"
144 |
145 | #: templates/emailauth/account_single_email.html:15
146 | msgid "Email:"
147 | msgstr "Электронный адрес:"
148 |
149 | #: templates/emailauth/account_single_email.html:19
150 | msgid "Change email"
151 | msgstr "Изменить электронный адрес"
152 |
153 | #: templates/emailauth/add_email.html:4
154 | msgid "Add another email to your account"
155 | msgstr "Добавтиь ещё один электронный адрес к профилю"
156 |
157 | #: templates/emailauth/add_email.html:9
158 | #: templates/emailauth/change_email.html:9
159 | #: templates/emailauth/request_password.html:9
160 | #: templates/emailauth/reset_password.html:9
161 | msgid "submit"
162 | msgstr "Отправить"
163 |
164 | #: templates/emailauth/change_email.html:4
165 | msgid "Set new email address"
166 | msgstr "Установить новый электронный адрес"
167 |
168 | #: templates/emailauth/delete_email.html:4
169 | msgid "Delete your email"
170 | msgstr "Удалить электронный адрес"
171 |
172 | #: templates/emailauth/delete_email.html:6
173 | msgid "Yes"
174 | msgstr "Да"
175 |
176 | #: templates/emailauth/delete_email.html:7
177 | #: templates/emailauth/set_default_email.html:7
178 | msgid "No"
179 | msgstr "Нет"
180 |
181 | #: templates/emailauth/logged_out.html:4
182 | msgid "You are logged out!"
183 | msgstr "Вы вышли из системы"
184 |
185 | #: templates/emailauth/login.html:4 templates/emailauth/login.html.py:9
186 | #: templates/emailauth/loginform.html:12
187 | msgid "Login"
188 | msgstr "Вход в систему"
189 |
190 | #: templates/emailauth/login.html:11 templates/emailauth/loginform.html:14
191 | #: templates/emailauth/request_password.html:4
192 | msgid "Forgot your password?"
193 | msgstr "Забыли пароль?"
194 |
195 | #: templates/emailauth/loginform.html:4
196 | msgid "Welcome"
197 | msgstr "Добро пожаловать"
198 |
199 | #: templates/emailauth/loginform.html:5
200 | msgid "Account"
201 | msgstr "Профиль"
202 |
203 | #: templates/emailauth/loginform.html:6
204 | msgid "Logout"
205 | msgstr "Выход"
206 |
207 | #: templates/emailauth/loginform.html:13 templates/emailauth/register.html:10
208 | msgid "Register"
209 | msgstr "Зарегистрироваться"
210 |
211 | #: templates/emailauth/register.html:5
212 | msgid "Registration"
213 | msgstr "Регистрация"
214 |
215 | #: templates/emailauth/reset_password.html:4
216 | msgid "Set new password"
217 | msgstr "Сменить пароль"
218 |
219 | #: templates/emailauth/reset_password_continue.html:4
220 | msgid "Confirm your email"
221 | msgstr "Подтверждение электронного адреса"
222 |
223 | #: templates/emailauth/reset_password_continue.html:5
224 | #, python-format
225 | msgid ""
226 | "\n"
227 | " We've sent an email to %(email)s"
228 | "strong> containing a link\n"
229 | " you'll need to follow to reset your password. You should receive "
230 | "the\n"
231 | " email within the next few minutes.\n"
232 | " "
233 | msgstr ""
234 | "\n"
235 | " На Ваш адрес %(email)s "
236 | "мы отправили письмо, содержащее ссылку с кодом \n"
237 | " для сброса пароля. Письмо будет доставлено \n"
238 | " в течение нескольких минут.\n"
239 | " "
240 |
241 | #: templates/emailauth/reset_password_continue.html:11
242 | msgid "Didn't get the email?"
243 | msgstr "Не получили письмо?"
244 |
245 | #: templates/emailauth/reset_password_continue.html:13
246 | msgid ""
247 | "Below are some of the most common reasons you might not be getting the "
248 | "message:"
249 | msgstr "Это могло произойти по следующим причинам:"
250 |
251 | #: templates/emailauth/reset_password_continue.html:15
252 | msgid "First, be patient, sometimes it takes a while for the email to arrive."
253 | msgstr "Во-первых, будьте терпеливы, иногда письмо может задержаться в пути"
254 |
255 | #: templates/emailauth/reset_password_continue.html:16
256 | msgid "Check above to ensure that you entered your email address correctly."
257 | msgstr "Проверьте правильность ввода электронного адреса"
258 |
259 | #: templates/emailauth/reset_password_continue.html:17
260 | msgid ""
261 | "Check your junk email box, the message might have been filtered as junk."
262 | msgstr "Проверьте, не было ли письмо отфильтровано как спам"
263 |
264 | #: templates/emailauth/set_default_email.html:4
265 | #, python-format
266 | msgid "Set %(email)s as your new default email?"
267 | msgstr "Установить адрес %(email)s как основной?"
268 |
269 | #: templates/emailauth/verify.html:5
270 | msgid "Verified"
271 | msgstr "Подтверждено"
272 |
273 | #: templates/emailauth/verify.html:7
274 | msgid "Not verified"
275 | msgstr "Электронный адрес не подтвержден"
276 |
--------------------------------------------------------------------------------
/emailauth/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/emailauth/management/__init__.py
--------------------------------------------------------------------------------
/emailauth/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/emailauth/management/commands/__init__.py
--------------------------------------------------------------------------------
/emailauth/management/commands/cleanupemailauth.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import NoArgsCommand
2 |
3 | from emailauth.models import UserEmail
4 |
5 |
6 | class Command(NoArgsCommand):
7 | help = "Delete expired UserEmail objects from the database"
8 |
9 | def handle_noargs(self, **options):
10 | UserEmail.objects.delete_expired()
11 |
--------------------------------------------------------------------------------
/emailauth/models.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import random
3 |
4 | import django.core.mail
5 |
6 | from django.db import models
7 | from django.contrib.auth.models import User
8 | from django.contrib.sites.models import Site
9 |
10 | from django.template.loader import render_to_string
11 |
12 | from django.utils.hashcompat import sha_constructor
13 | from django.utils.translation import ugettext_lazy as _
14 | import django.core.mail
15 |
16 | from django.conf import settings
17 |
18 | from emailauth.utils import email_verification_days, use_automaintenance
19 |
20 | class UserEmailManager(models.Manager):
21 | def make_random_key(self, email):
22 | salt = sha_constructor(str(random.random())).hexdigest()[:5]
23 | key = sha_constructor(salt + email).hexdigest()
24 | return key
25 |
26 | def create_unverified_email(self, email, user=None):
27 | if use_automaintenance():
28 | self.delete_expired()
29 |
30 | email_obj = UserEmail(email=email, user=user, default=user is None,
31 | verification_key=self.make_random_key(email))
32 | return email_obj
33 |
34 | def verify(self, verification_key):
35 | try:
36 | email = self.get(verification_key=verification_key)
37 | except self.model.DoesNotExist:
38 | return None
39 | if not email.verification_key_expired():
40 | email.verification_key = self.model.VERIFIED
41 | email.verified = True
42 | email.save()
43 | return email
44 |
45 | def delete_expired(self):
46 | date_threshold = (datetime.datetime.now() -
47 | datetime.timedelta(days=email_verification_days()))
48 | expired_emails = self.filter(code_creation_date__lt=date_threshold)
49 |
50 | for email in expired_emails:
51 | if not email.verified:
52 | user = email.user
53 | emails = user.useremail_set.all()
54 | if not user.is_active:
55 | user.delete()
56 | else:
57 | email.delete()
58 |
59 |
60 | class UserEmail(models.Model):
61 | class Meta:
62 | verbose_name = _('user email')
63 | verbose_name_plural = _('user emails')
64 |
65 | VERIFIED = 'ALREADY_VERIFIED'
66 |
67 | objects = UserEmailManager()
68 |
69 | user = models.ForeignKey(User, null=True, blank=True, verbose_name=_('user'))
70 | default = models.BooleanField(default=False)
71 | email = models.EmailField(unique=True)
72 | verified = models.BooleanField(default=False)
73 | code_creation_date = models.DateTimeField(default=datetime.datetime.now)
74 | verification_key = models.CharField(_('verification key'), max_length=40)
75 |
76 | def __init__(self, *args, **kwds):
77 | super(UserEmail, self).__init__(*args, **kwds)
78 | self._original_default = self.default
79 |
80 | def __unicode__(self):
81 | return self.email
82 |
83 | def save(self, *args, **kwds):
84 | super(UserEmail, self).save(*args, **kwds)
85 | if self.default and not self._original_default:
86 | self.user.email = self.email
87 | self.user.save()
88 | for email in self.__class__.objects.filter(user=self.user):
89 | if email.id != self.id and email.default:
90 | email.default = False
91 | email.save()
92 |
93 | def make_new_key(self):
94 | self.verification_key = self.__class__.objects.make_random_key(
95 | self.email)
96 | self.code_creation_date = datetime.datetime.now()
97 |
98 | def send_verification_email(self, first_name=None):
99 | current_site = Site.objects.get_current()
100 |
101 | subject = render_to_string('emailauth/verification_email_subject.txt',
102 | {'site': current_site})
103 | # Email subject *must not* contain newlines
104 | subject = ''.join(subject.splitlines())
105 |
106 | emails = set()
107 | if self.user is not None:
108 | for email in self.__class__.objects.filter(user=self.user):
109 | emails.add(email.email)
110 | emails.add(self.email)
111 | first_email = len(emails) == 1
112 |
113 | if first_name is None:
114 | first_name = self.user.first_name
115 |
116 | message = render_to_string('emailauth/verification_email.txt', {
117 | 'verification_key': self.verification_key,
118 | 'expiration_days': email_verification_days(),
119 | 'site': current_site,
120 | 'first_name': first_name,
121 | 'first_email': first_email,
122 | })
123 |
124 | self.code_creation_date = datetime.datetime.now()
125 |
126 | django.core.mail.send_mail(subject, message,
127 | settings.DEFAULT_FROM_EMAIL, [self.email])
128 |
129 |
130 | def verification_key_expired(self):
131 | expiration_date = datetime.timedelta(days=email_verification_days())
132 | return (self.verification_key == self.VERIFIED or
133 | (self.code_creation_date + expiration_date <= datetime.datetime.now()))
134 |
135 | verification_key_expired.boolean = True
136 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/account.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Account properties' %}
5 |
6 |
7 | {% trans 'First name:' %}
8 | {{ user.first_name }}
9 |
10 |
11 | {% trans 'Default email:' %}
12 | {{ user.email }}
13 |
14 |
15 | {% trans 'Extra emails' %}
16 | {% if extra_emails %}
17 |
26 | {% endif %}
27 | {% if unverified_emails %}
28 | {% trans 'Unverified emails' %}
29 |
36 | {% endif %}
37 | {% trans 'Add email' %}
38 |
39 | {% endblock %}
40 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/account_single_email.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Account properties' %}
5 |
6 |
7 | {% trans 'First name:' %}
8 | {{ user.first_name }}
9 |
10 |
11 | {% trans 'Email:'%}
12 | {{ user.email }}
13 |
14 |
15 | {% trans 'Change email' %}
16 |
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/add_email.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Add another email to your account' %}
5 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/add_email_continue.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/register_continue.html" %}
2 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/base.html:
--------------------------------------------------------------------------------
1 | {# Customize this template as needed for integration with your site's templates. #}
2 |
3 | {# Default template integrates with emailauth's example project. #}
4 |
5 | {% extends "master.html" %}
6 |
7 | {% block content %}
8 | {% block emailauth_content %}
9 | {% endblock %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/change_email.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Set new email address' %}
5 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/change_email_continue.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/register_continue.html" %}
2 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/delete_email.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Delete your email' %} {{ email }}?
5 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/logged_out.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'You are logged out!' %}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/login.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Login' %}
5 |
11 | {% trans 'Forgot your password?' %}
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/loginform.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
18 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/register.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Registration' %}
5 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/register_continue.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/reset_password_continue.html" %}
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/request_password.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Forgot your password?' %}
5 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/request_password_email.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% blocktrans %}Dear {{ first_name }},{% endblocktrans %}
2 |
3 | You (or someone posing as you) has requested a password reset for your account on {{ site.name }}
4 |
5 | To set new password please follow this link:
6 | http://{{ site.domain }}{% url emailauth_reset_password reset_code %}
7 |
8 | If you hasn't request a password reset, just ignore this email and the link above will expire in {{ expiration_days }} days.
9 |
10 | {# vim: set ft=django: #}
11 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/request_password_email_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}[{{ site }}]{% trans 'Password reset' %}
2 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/reset_password.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Set new password' %}
5 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/reset_password_continue.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% trans 'Confirm your email' %}
5 | {% blocktrans %}
6 | We've sent an email to {{ email }} containing a link
7 | you'll need to follow to reset your password. You should receive the
8 | email within the next few minutes.
9 | {% endblocktrans %}
10 |
11 | {% trans "Didn't get the email?" %}
12 |
13 | {% trans 'Below are some of the most common reasons you might not be getting the message:' %}
14 |
15 | {% trans 'First, be patient, sometimes it takes a while for the email to arrive.' %}
16 | {% trans 'Check above to ensure that you entered your email address correctly.' %}
17 | {% trans 'Check your junk email box, the message might have been filtered as junk.' %}
18 |
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/set_default_email.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% blocktrans %}Set {{ email }} as your new default email?{% endblocktrans %}
5 |
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/verification_email.txt:
--------------------------------------------------------------------------------
1 | Dear {{ first_name }},
2 |
3 | {% if first_email %}
4 | To activate your account on {{ site.name }} please follow this link:{% else %}To verify your email for your account on {{ site.name }} please follow this link:{% endif %}
5 | http://{{ site.domain }}{% url emailauth_verify verification_key %}
6 | The link above will expire in {{ expiration_days }} days.
7 | {# vim: set ft=django: #}
8 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/verification_email_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}[{{ site }}]{% trans 'Please confirm your email' %}
2 |
--------------------------------------------------------------------------------
/emailauth/templates/emailauth/verify.html:
--------------------------------------------------------------------------------
1 | {% extends "emailauth/base.html" %}{% load i18n %}
2 |
3 | {% block emailauth_content %}
4 | {% if email %}
5 | {% trans 'Verified' %}
6 | {% else %}
7 | {% trans 'Not verified' %}
8 | {% endif %}
9 | {% endblock %}
10 |
11 |
--------------------------------------------------------------------------------
/emailauth/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/emailauth/templatetags/__init__.py
--------------------------------------------------------------------------------
/emailauth/templatetags/emailauth_tags.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django import template
3 | from emailauth.forms import LoginForm
4 |
5 | register = template.Library()
6 |
7 | @register.inclusion_tag('emailauth/loginform.html', takes_context=True)
8 | def loginform(context):
9 | form = LoginForm()
10 | user = context['request'].user
11 | return locals()
--------------------------------------------------------------------------------
/emailauth/tests.py:
--------------------------------------------------------------------------------
1 | import re
2 | from datetime import datetime, timedelta
3 |
4 | from django.test.client import Client
5 | from django.test.testcases import TestCase
6 | from django.core import mail
7 | from django.contrib.auth.models import User
8 | from django.conf import settings
9 |
10 | from emailauth.models import UserEmail
11 | from emailauth.utils import email_verification_days
12 |
13 |
14 | class Status:
15 | OK = 200
16 | REDIRECT = 302
17 | NOT_FOUND = 404
18 |
19 |
20 | class BaseTestCase(TestCase):
21 | def assertStatusCode(self, response, status_code=200):
22 | self.assertEqual(response.status_code, status_code)
23 |
24 | def checkSimplePage(self, path, params={}):
25 | client = Client()
26 | response = client.get(path, params)
27 | self.assertStatusCode(response)
28 |
29 | def createActiveUser(self, username='username', email='user@example.com',
30 | password='password'):
31 |
32 | user = User(username=username, email=email, is_active=True)
33 | user.first_name = 'John'
34 | user.set_password(password)
35 | user.save()
36 |
37 | user_email = UserEmail(user=user, email=email, verified=True,
38 | default=True, verification_key=UserEmail.VERIFIED)
39 | user_email.save()
40 | return user, user_email
41 |
42 | def getLoggedInClient(self, email='user@example.com', password='password'):
43 | client = Client()
44 | client.login(username=email, password=password)
45 | return client
46 |
47 |
48 | class RegisterTest(BaseTestCase):
49 | def testRegisterGet(self):
50 | self.checkSimplePage('/register/')
51 |
52 | def testRegisterPost(self):
53 | client = Client()
54 | response = client.post('/register/', {
55 | 'email': 'user@example.com',
56 | 'first_name': 'John',
57 | 'password1': 'password',
58 | 'password2': 'password',
59 | })
60 | self.assertRedirects(response, '/register/continue/user%40example.com/')
61 |
62 | self.assertEqual(len(mail.outbox), 1)
63 |
64 | email = mail.outbox[0]
65 |
66 | addr_re = re.compile(r'.*http://.*?(/\S*/)', re.UNICODE | re.MULTILINE)
67 | verification_url = addr_re.search(email.body).groups()[0]
68 |
69 | response = client.get(verification_url)
70 |
71 | self.assertRedirects(response, '/account/')
72 |
73 | response = client.post('/login/', {
74 | 'email': 'user@example.com',
75 | 'password': 'password',
76 | })
77 |
78 | self.assertRedirects(response, '/account/')
79 |
80 | user = User.objects.get(email='user@example.com')
81 | self.assertEqual(user.first_name, 'John')
82 |
83 | def testRegisterSame(self):
84 | user, user_email = self.createActiveUser()
85 | client = Client()
86 | response = client.post('/register/', {
87 | 'email': user_email.email,
88 | 'first_name': 'John',
89 | 'password1': 'password',
90 | 'password2': 'password',
91 | })
92 | self.assertContains(response, 'This email is already taken')
93 |
94 | email_obj = UserEmail.objects.create_unverified_email(
95 | 'user@example.org', user)
96 | email_obj.save()
97 |
98 | response = client.post('/register/', {
99 | 'email': 'user@example.org',
100 | 'first_name': 'John',
101 | 'password1': 'password',
102 | 'password2': 'password',
103 | })
104 | self.assertContains(response, 'This email is already taken')
105 |
106 |
107 |
108 | class LoginTest(BaseTestCase):
109 | def testLoginGet(self):
110 | self.checkSimplePage('/login/')
111 |
112 | def testLoginFail(self):
113 | user, user_email = self.createActiveUser()
114 | client = Client()
115 | response = client.post('/login/', {
116 | 'email': 'user@example.com',
117 | 'password': 'wrongpassword',
118 | })
119 | self.assertStatusCode(response, Status.OK)
120 |
121 |
122 | class PasswordResetTest(BaseTestCase):
123 | def prepare(self):
124 | user, user_email = self.createActiveUser()
125 |
126 | client = Client()
127 | response = client.post('/resetpassword/', {
128 | 'email': user_email.email,
129 | })
130 |
131 | self.assertRedirects(response,
132 | '/resetpassword/continue/user%40example.com/')
133 |
134 | email = mail.outbox[0]
135 | addr_re = re.compile(r'.*http://.*?(/\S*/)', re.UNICODE | re.MULTILINE)
136 | reset_url = addr_re.search(email.body).groups()[0]
137 | return reset_url, user_email
138 |
139 |
140 | def testPasswordReset(self):
141 | reset_url, user_email = self.prepare()
142 | client = Client()
143 |
144 | self.checkSimplePage(reset_url)
145 |
146 | response = client.post(reset_url, {
147 | 'password1': 'newpassword',
148 | 'password2': 'newpassword',
149 | })
150 |
151 | self.assertRedirects(response, '/account/')
152 |
153 | user = User.objects.get(email=user_email.email)
154 | self.assertTrue(user.check_password('newpassword'))
155 |
156 | response = client.get(reset_url)
157 | self.assertStatusCode(response, Status.NOT_FOUND)
158 |
159 |
160 | def testPasswordResetFail(self):
161 | reset_url, user_email = self.prepare()
162 | client = Client()
163 | user_email.verification_key = UserEmail.VERIFIED
164 | user_email.save()
165 |
166 | response = client.get(reset_url)
167 | self.assertStatusCode(response, Status.NOT_FOUND)
168 |
169 |
170 | def testPasswordResetFail2(self):
171 | reset_url, user_email = self.prepare()
172 | client = Client()
173 | user_email.code_creation_date = (datetime.now() -
174 | timedelta(days=email_verification_days() + 1))
175 | user_email.save()
176 |
177 | response = client.get(reset_url)
178 | self.assertStatusCode(response, Status.NOT_FOUND)
179 |
180 |
181 | class TestAddEmail(BaseTestCase):
182 | def setUp(self):
183 | self.user, self.user_email = self.createActiveUser()
184 | self.client = self.getLoggedInClient()
185 |
186 | def testAddEmailGet(self):
187 | response = self.client.get('/account/addemail/')
188 | self.assertStatusCode(response, Status.OK)
189 |
190 | def testAddEmail(self):
191 | response = self.client.post('/account/addemail/', {
192 | 'email': 'user@example.org',
193 | })
194 | self.assertRedirects(response, '/account/addemail/continue/user%40example.org/')
195 |
196 | self.assertEqual(len(mail.outbox), 1)
197 |
198 | email = mail.outbox[0]
199 |
200 | addr_re = re.compile(r'.*http://.*?(/\S*/)', re.UNICODE | re.MULTILINE)
201 | verification_url = addr_re.search(email.body).groups()[0]
202 |
203 | response = self.client.get(verification_url)
204 |
205 | self.assertRedirects(response, '/account/')
206 |
207 | client = Client()
208 | response = client.post('/login/', {
209 | 'email': 'user@example.org',
210 | 'password': 'password',
211 | })
212 |
213 | self.assertRedirects(response, '/account/')
214 |
215 | def testAddSameEmail(self):
216 | response = self.client.post('/account/addemail/', {
217 | 'email': 'user@example.com',
218 | })
219 | self.assertStatusCode(response, Status.OK)
220 |
221 | response = self.client.post('/account/addemail/', {
222 | 'email': 'user@example.org',
223 | })
224 | self.assertRedirects(response,
225 | '/account/addemail/continue/user%40example.org/')
226 |
227 | response = self.client.post('/account/addemail/', {
228 | 'email': 'user@example.org',
229 | })
230 | self.assertStatusCode(response, Status.OK)
231 |
232 |
233 | class TestDeleteEmail(BaseTestCase):
234 | def setUp(self):
235 | self.user, self.user_email = self.createActiveUser()
236 | self.client = self.getLoggedInClient()
237 |
238 | def testDeleteEmail(self):
239 | user = self.user
240 | user_email = UserEmail(user=user, email='email@example.org', verified=True,
241 | default=False, verification_key=UserEmail.VERIFIED)
242 | user_email.save()
243 |
244 | response = self.client.post('/account/deleteemail/%s/' % user_email.id, {
245 | 'yes': 'yes',
246 | })
247 |
248 | self.assertRedirects(response, '/account/')
249 |
250 | user_emails = UserEmail.objects.filter(user=self.user)
251 | self.assertEqual(len(user_emails), 1)
252 |
253 | response = self.client.post('/account/deleteemail/%s/' % user_emails[0].id, {
254 | 'yes': 'yes',
255 | })
256 |
257 | self.assertStatusCode(response, Status.OK)
258 |
259 |
260 | class TestSetDefaultEmail(BaseTestCase):
261 | def setUp(self):
262 | self.user, self.user_email = self.createActiveUser()
263 | self.client = self.getLoggedInClient()
264 |
265 | def testSetDefaultEmailGet(self):
266 | response = self.client.get('/account/setdefaultemail/%s/' %
267 | self.user_email.id)
268 | self.assertStatusCode(response, Status.OK)
269 |
270 | def testSetDefaultEmail(self):
271 | user = self.user
272 | user_email = UserEmail(user=user, email='user@example.org', verified=True,
273 | default=False, verification_key=UserEmail.VERIFIED)
274 | user_email.save()
275 |
276 | response = self.client.post('/account/setdefaultemail/%s/' % user_email.id, {
277 | 'yes': 'yes',
278 | })
279 |
280 | self.assertRedirects(response, '/account/')
281 |
282 | new_default_email = user_email.email
283 |
284 | for email in UserEmail.objects.filter():
285 | self.assertEqual(email.default, email.email == new_default_email)
286 |
287 | user = User.objects.get(id=self.user.id)
288 | self.assertEqual(user.email, new_default_email)
289 |
290 | def testSetDefaultUnverifiedEmail(self):
291 | user = self.user
292 | user_email = UserEmail(user=user, email='user@example.org', verified=False,
293 | default=False, verification_key=UserEmail.VERIFIED)
294 | user_email.save()
295 |
296 | response = self.client.post('/account/setdefaultemail/%s/' % user_email.id, {
297 | 'yes': 'yes',
298 | })
299 | self.assertStatusCode(response, Status.NOT_FOUND)
300 |
301 | class TestDeleteEmail(BaseTestCase):
302 | def setUp(self):
303 | self.user, self.user_email = self.createActiveUser()
304 | self.client = self.getLoggedInClient()
305 |
306 | def testDeleteEmail(self):
307 | user_email = UserEmail(user=self.user, email='user@example.org', verified=True,
308 | default=False, verification_key=UserEmail.VERIFIED)
309 | user_email.save()
310 |
311 | page_url = '/account/deleteemail/%s/' % user_email.id
312 |
313 | response = self.client.get(page_url)
314 | self.assertStatusCode(response, Status.OK)
315 |
316 | response = self.client.post(page_url, {'yes': 'yes'})
317 | self.assertRedirects(response, '/account/')
318 |
319 | def testDeleteUnverifiedEmail(self):
320 | user_email = UserEmail(user=self.user, email='user@example.org', verified=False,
321 | default=False, verification_key=UserEmail.VERIFIED)
322 | user_email.save()
323 |
324 | response = self.client.post('/account/deleteemail/%s/' % user_email.id, {
325 | 'yes': 'yes',
326 | })
327 | self.assertStatusCode(response, Status.NOT_FOUND)
328 |
329 |
330 | class TestAccountSingleEmail(BaseTestCase):
331 | def setUp(self):
332 | self.user, self.user_email = self.createActiveUser()
333 | self.client = self.getLoggedInClient()
334 | settings.EMAILAUTH_USE_SINGLE_EMAIL = True
335 |
336 | def tearDown(self):
337 | settings.EMAILAUTH_USE_SINGLE_EMAIL = False
338 |
339 | def testAccountGet(self):
340 | response = self.client.get('/account/')
341 | self.assertStatusCode(response, Status.OK)
342 |
343 | class TestChangeEmail(BaseTestCase):
344 | def setUp(self):
345 | self.user, self.user_email = self.createActiveUser()
346 | self.client = self.getLoggedInClient()
347 | settings.EMAILAUTH_USE_SINGLE_EMAIL = True
348 |
349 | def tearDown(self):
350 | settings.EMAILAUTH_USE_SINGLE_EMAIL = False
351 |
352 | def testEmailChangeWrongMode(self):
353 | settings.EMAILAUTH_USE_SINGLE_EMAIL = False
354 | response = self.client.get('/account/changeemail/')
355 | self.assertStatusCode(response, Status.NOT_FOUND)
356 |
357 | def testEmailChange(self):
358 | response = self.client.get('/account/changeemail/')
359 | self.assertStatusCode(response, Status.OK)
360 |
361 | response = self.client.post('/account/changeemail/', {
362 | 'email': 'user@example.org',
363 | })
364 |
365 | self.assertRedirects(response,
366 | '/account/changeemail/continue/user%40example.org/')
367 |
368 | self.assertEqual(len(mail.outbox), 1)
369 |
370 | email = mail.outbox[0]
371 |
372 | addr_re = re.compile(r'.*http://.*?(/\S*/)', re.UNICODE | re.MULTILINE)
373 | verification_url = addr_re.search(email.body).groups()[0]
374 |
375 | response = self.client.get(verification_url)
376 |
377 | self.assertRedirects(response, '/account/')
378 |
379 | client = Client()
380 | response = client.post('/login/', {
381 | 'email': 'user@example.org',
382 | 'password': 'password',
383 | })
384 |
385 | self.assertRedirects(response, '/account/')
386 |
387 | user = User.objects.get(id=self.user.id)
388 | self.assertEqual(user.email, 'user@example.org')
389 |
390 | client = Client()
391 | response = client.post('/login/', {
392 | 'email': 'user@example.com',
393 | 'password': 'password',
394 | })
395 | self.assertStatusCode(response, Status.OK)
396 |
397 |
398 | class TestResendEmail(BaseTestCase):
399 | def setUp(self):
400 | self.user, self.user_email = self.createActiveUser()
401 | self.client = self.getLoggedInClient()
402 |
403 | def testResendEmail(self):
404 | user = self.user
405 | user_email = UserEmail(user=user, email='user@example.org', verified=False,
406 | default=False, verification_key='abcdef')
407 | user_email.save()
408 |
409 | response = self.client.get('/account/resendemail/%s/' % user_email.id)
410 | self.assertRedirects(response,
411 | '/account/addemail/continue/user%40example.org/')
412 | self.assertEqual(len(mail.outbox), 1)
413 |
414 |
415 | class TestCleanup(BaseTestCase):
416 | def testCleanup(self):
417 | user1 = User(username='user1', email='user1@example.com', is_active=True)
418 | user1.save()
419 |
420 | old_enough = (datetime.now() - timedelta(days=email_verification_days() + 1))
421 | not_old_enough = (datetime.now() -
422 | timedelta(days=email_verification_days() - 1))
423 |
424 | email1 = UserEmail(user=user1, email='user1@example.com',
425 | verified=True, default=True,
426 | verification_key=UserEmail.VERIFIED + 'asd',
427 | code_creation_date=old_enough)
428 | email1.save()
429 |
430 | user2 = User(username='user2', email='user2@example.com', is_active=False)
431 | user2.save()
432 |
433 | email2 = UserEmail(user=user2, email='user2@example.com',
434 | verified=False, default=True,
435 | verification_key='key1',
436 | code_creation_date=old_enough)
437 | email2.save()
438 |
439 | user3 = User(username='user3', email='user3@example.com', is_active=False)
440 | user3.save()
441 |
442 | email3 = UserEmail(user=user3, email='user3@example.com',
443 | verified=False, default=True,
444 | verification_key='key2',
445 | code_creation_date=not_old_enough)
446 | email3.save()
447 |
448 | UserEmail.objects.delete_expired()
449 |
450 | user_ids = [user.id for user in User.objects.all()]
451 | user_email_ids = [user_email.id for user_email in UserEmail.objects.all()]
452 |
453 | self.assertEqual(list(sorted(user_ids)), list(sorted([user1.id, user3.id])))
454 | self.assertEqual(list(sorted(user_email_ids)), list(sorted([email1.id, email3.id])))
455 |
--------------------------------------------------------------------------------
/emailauth/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 |
3 | import emailauth.views
4 |
5 | urlpatterns = patterns('',
6 | url(r'^account/$', 'emailauth.views.account', name='emailauth_account'),
7 |
8 | url(r'^register/$', 'emailauth.views.register',
9 | name='register'),
10 |
11 | url(r'^register/continue/(?P.+)/$',
12 | 'emailauth.views.register_continue',
13 | name='emailauth_register_continue'),
14 |
15 | url(r'^verify/(?P\w+)/$', 'emailauth.views.verify',
16 | name='emailauth_verify'),
17 |
18 | url(r'^resetpassword/$', 'emailauth.views.request_password_reset',
19 | name='emailauth_request_password_reset'),
20 | url(r'^resetpassword/continue/(?P.+)/$',
21 | 'emailauth.views.request_password_reset_continue',
22 | name='emailauth_request_password_reset_continue'),
23 | url(r'^resetpassword/(?P\w+)/$',
24 | 'emailauth.views.reset_password', name='emailauth_reset_password'),
25 |
26 | url(r'^account/addemail/$', 'emailauth.views.add_email',
27 | name='emailauth_add_email'),
28 | url(r'^account/addemail/continue/(?P.+)/$',
29 | 'emailauth.views.add_email_continue',
30 | name='emailauth_add_email_continue'),
31 | url(r'^account/resendemail/(\d+)/$',
32 | 'emailauth.views.resend_verification_email',
33 | name='emailauth_resend_verification_email'),
34 |
35 | url(r'^account/changeemail/$', 'emailauth.views.change_email',
36 | name='emailauth_change_email'),
37 | url(r'^account/changeemail/continue/(?P.+)/$',
38 | 'emailauth.views.change_email_continue',
39 | name='emailauth_change_email_continue'),
40 |
41 | url(r'^account/deleteemail/(\d+)/$', 'emailauth.views.delete_email',
42 | name='emailauth_delete_email'),
43 |
44 | url(r'^account/setdefaultemail/(\d+)/$',
45 | 'emailauth.views.set_default_email',
46 | name='emailauth_set_default_email'),
47 |
48 | url(r'^login/$', 'emailauth.views.login', name='login'),
49 | url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': '/',
50 | 'template_name': 'logged_out.html'}, name='logout'),
51 | )
52 |
--------------------------------------------------------------------------------
/emailauth/utils.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.http import Http404
3 | from django.utils.functional import curry
4 |
5 | def email_verification_days():
6 | return getattr(settings, 'EMAILAUTH_VERIFICATION_DAYS', 3)
7 |
8 | def use_single_email():
9 | return getattr(settings, 'EMAILAUTH_USE_SINGLE_EMAIL', True)
10 |
11 | def use_automaintenance():
12 | return getattr(settings, 'EMAILAUTH_USE_AUTOMAINTENANCE', True)
13 |
14 | def require_emailauth_mode(func, emailauth_use_singe_email):
15 | def wrapper(*args, **kwds):
16 | if use_single_email() == emailauth_use_singe_email:
17 | return func(*args, **kwds)
18 | else:
19 | raise Http404()
20 | return wrapper
21 |
22 | requires_single_email_mode = curry(require_emailauth_mode,
23 | emailauth_use_singe_email=True)
24 |
25 | requires_multi_emails_mode = curry(require_emailauth_mode,
26 | emailauth_use_singe_email=False)
27 |
--------------------------------------------------------------------------------
/emailauth/views.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta
2 | from urllib import urlencode, quote_plus
3 |
4 | import django.core.mail
5 | from django.conf import settings
6 | from django.contrib.auth import REDIRECT_FIELD_NAME
7 | from django.contrib.auth.models import User
8 | from django.contrib.sites.models import Site, RequestSite
9 | from django.core.urlresolvers import reverse
10 | from django.http import HttpResponseRedirect, Http404
11 | from django.shortcuts import render_to_response, get_object_or_404
12 | from django.template import RequestContext
13 | from django.template.loader import render_to_string
14 | from django.contrib.auth.decorators import login_required
15 | from django import forms
16 | import django.forms.forms
17 | import django.forms.util
18 |
19 | from django.utils.translation import ugettext_lazy as _
20 |
21 | from emailauth.forms import (LoginForm, RegistrationForm,
22 | PasswordResetRequestForm, PasswordResetForm, AddEmailForm, DeleteEmailForm,
23 | ConfirmationForm)
24 | from emailauth.models import UserEmail
25 |
26 | from emailauth.utils import (use_single_email, requires_single_email_mode,
27 | requires_multi_emails_mode, email_verification_days)
28 |
29 |
30 | def login(request, template_name='emailauth/login.html',
31 | redirect_field_name=REDIRECT_FIELD_NAME):
32 |
33 | redirect_to = request.REQUEST.get(redirect_field_name, '')
34 |
35 | if request.method == 'POST':
36 | form = LoginForm(request.POST)
37 | if form.is_valid():
38 | from django.contrib.auth import login
39 | login(request, form.get_user())
40 |
41 | if request.get_host() == 'testserver':
42 | if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
43 | redirect_to = settings.LOGIN_REDIRECT_URL
44 | return HttpResponseRedirect(redirect_to)
45 |
46 | request.session.set_test_cookie()
47 |
48 | return HttpResponseRedirect(settings.LOGIN_URL + '?' + urlencode({
49 | 'testcookiesupport': '',
50 | redirect_field_name: redirect_to,
51 | }))
52 | elif 'testcookiesupport' in request.GET:
53 | if request.session.test_cookie_worked():
54 | request.session.delete_test_cookie()
55 | if not redirect_to or '//' in redirect_to or ' ' in redirect_to:
56 | redirect_to = settings.LOGIN_REDIRECT_URL
57 | return HttpResponseRedirect(redirect_to)
58 | else:
59 | form = LoginForm()
60 | errorlist = forms.util.ErrorList()
61 | errorlist.append(_("Your Web browser doesn't appear to "
62 | "have cookies enabled. Cookies are required for logging in."))
63 | form._errors = forms.util.ErrorDict()
64 | form._errors[forms.forms.NON_FIELD_ERRORS] = errorlist
65 | else:
66 | form = LoginForm()
67 |
68 | if Site._meta.installed:
69 | current_site = Site.objects.get_current()
70 | else:
71 | current_site = RequestSite(request)
72 |
73 | return render_to_response(template_name, {
74 | 'form': form,
75 | redirect_field_name: redirect_to,
76 | 'site_name': current_site.name,
77 | },
78 | context_instance=RequestContext(request))
79 |
80 |
81 | @login_required
82 | def account(request, template_name=None):
83 | context = RequestContext(request)
84 |
85 | if template_name is None:
86 | if use_single_email():
87 | template_name = 'emailauth/account_single_email.html'
88 | else:
89 | template_name = 'emailauth/account.html'
90 |
91 | # Maybe move this emails into context processors?
92 | extra_emails = UserEmail.objects.filter(user=request.user, default=False,
93 | verified=True)
94 | unverified_emails = UserEmail.objects.filter(user=request.user,
95 | default=False, verified=False)
96 |
97 | return render_to_response(template_name,
98 | {
99 | 'extra_emails': extra_emails,
100 | 'unverified_emails': unverified_emails,
101 | },
102 | context_instance=context)
103 |
104 |
105 | def get_max_length(model, field_name):
106 | field = model._meta.get_field_by_name(field_name)[0]
107 | return field.max_length
108 |
109 |
110 | def default_register_callback(form, email):
111 | data = form.cleaned_data
112 | user = User()
113 | user.first_name = data['first_name']
114 | user.is_active = False
115 | user.email = email.email
116 | user.set_password(data['password1'])
117 | user.save()
118 | user.username = ('id_%d_%s' % (user.id, user.email))[
119 | :get_max_length(User, 'username')]
120 | user.save()
121 | email.user = user
122 |
123 |
124 | def register(request, callback=default_register_callback):
125 | if request.method == 'POST':
126 | form = RegistrationForm(request.POST)
127 | if form.is_valid():
128 | email_obj = UserEmail.objects.create_unverified_email(
129 | form.cleaned_data['email'])
130 | email_obj.send_verification_email(form.cleaned_data['first_name'])
131 |
132 | if callback is not None:
133 | callback(form, email_obj)
134 |
135 | site = Site.objects.get_current()
136 | email_obj.user.message_set.create(message='Welcome to %s.' % site.name)
137 |
138 | email_obj.save()
139 | return HttpResponseRedirect(reverse('emailauth_register_continue',
140 | args=[quote_plus(email_obj.email)]))
141 | else:
142 | form = RegistrationForm()
143 |
144 | return render_to_response('emailauth/register.html', {'form': form},
145 | RequestContext(request))
146 |
147 |
148 | def register_continue(request, email,
149 | template_name='emailauth/register_continue.html'):
150 |
151 | return render_to_response(template_name, {'email': email},
152 | RequestContext(request))
153 |
154 |
155 | def default_verify_callback(request, email):
156 | email.user.is_active = True
157 | email.user.save()
158 |
159 | if request.user.is_anonymous():
160 | from django.contrib.auth import login
161 | user = email.user
162 | user.backend = 'emailauth.backends.EmailBackend'
163 | login(request, user)
164 | return HttpResponseRedirect(settings.LOGIN_REDIRECT_URL)
165 | else:
166 | return HttpResponseRedirect(reverse('emailauth_account'))
167 |
168 |
169 | def verify(request, verification_key, template_name='emailauth/verify.html',
170 | extra_context=None, callback=default_verify_callback):
171 |
172 | verification_key = verification_key.lower() # Normalize before trying anything with it.
173 | email = UserEmail.objects.verify(verification_key)
174 |
175 |
176 | if email is not None:
177 | email.user.message_set.create(message=_('%s email confirmed.') % email.email)
178 |
179 | if use_single_email():
180 | email.default = True
181 | email.save()
182 | UserEmail.objects.filter(user=email.user, default=False).delete()
183 |
184 | if email is not None and callback is not None:
185 | cb_result = callback(request, email)
186 | if cb_result is not None:
187 | return cb_result
188 |
189 | context = RequestContext(request)
190 | if extra_context is not None:
191 | for key, value in extra_context.items():
192 | context[key] = value() if callable(value) else value
193 |
194 | return render_to_response(template_name,
195 | {
196 | 'email': email,
197 | 'expiration_days': email_verification_days(),
198 | },
199 | context_instance=context)
200 |
201 |
202 | def request_password_reset(request,
203 | template_name='emailauth/request_password.html'):
204 |
205 | if request.method == 'POST':
206 | form = PasswordResetRequestForm(request.POST)
207 | if form.is_valid():
208 | email = form.cleaned_data['email']
209 | user_email = UserEmail.objects.get(email=email)
210 | user_email.make_new_key()
211 | user_email.save()
212 |
213 | current_site = Site.objects.get_current()
214 |
215 | subject = render_to_string(
216 | 'emailauth/request_password_email_subject.txt',
217 | {'site': current_site})
218 | # Email subject *must not* contain newlines
219 | subject = ''.join(subject.splitlines())
220 |
221 | message = render_to_string('emailauth/request_password_email.txt', {
222 | 'reset_code': user_email.verification_key,
223 | 'expiration_days': email_verification_days(),
224 | 'site': current_site,
225 | 'first_name': user_email.user.first_name,
226 | })
227 |
228 | django.core.mail.send_mail(subject, message,
229 | settings.DEFAULT_FROM_EMAIL, [email])
230 |
231 | return HttpResponseRedirect(
232 | reverse('emailauth_request_password_reset_continue',
233 | args=[quote_plus(email)]))
234 | else:
235 | form = PasswordResetRequestForm()
236 |
237 | context = RequestContext(request)
238 | return render_to_response(template_name,
239 | {
240 | 'form': form,
241 | 'expiration_days': email_verification_days(),
242 | },
243 | context_instance=context)
244 |
245 |
246 | def request_password_reset_continue(request, email,
247 | template_name='emailauth/reset_password_continue.html'):
248 |
249 | return render_to_response(template_name,
250 | {'email': email},
251 | context_instance=RequestContext(request))
252 |
253 |
254 | def reset_password(request, reset_code,
255 | template_name='emailauth/reset_password.html'):
256 |
257 | user_email = get_object_or_404(UserEmail, verification_key=reset_code)
258 | if (user_email.verification_key == UserEmail.VERIFIED or
259 | user_email.code_creation_date +
260 | timedelta(days=email_verification_days()) < datetime.now()):
261 |
262 | raise Http404()
263 |
264 | if request.method == 'POST':
265 | form = PasswordResetForm(request.POST)
266 | if form.is_valid():
267 | user = user_email.user
268 | user.set_password(form.cleaned_data['password1'])
269 | user.save()
270 |
271 | user_email.verification_key = UserEmail.VERIFIED
272 | user_email.save()
273 |
274 | from django.contrib.auth import login
275 | user.backend = 'emailauth.backends.EmailBackend'
276 | login(request, user)
277 | return HttpResponseRedirect(reverse('emailauth_account'))
278 | else:
279 | form = PasswordResetForm()
280 |
281 | context = RequestContext(request)
282 | return render_to_response(template_name,
283 | {'form': form},
284 | context_instance=context)
285 |
286 |
287 | @requires_multi_emails_mode
288 | @login_required
289 | def add_email(request, template_name='emailauth/add_email.html'):
290 | if request.method == 'POST':
291 | form = AddEmailForm(request.POST)
292 | if form.is_valid():
293 | email_obj = UserEmail.objects.create_unverified_email(
294 | form.cleaned_data['email'], user=request.user)
295 | email_obj.send_verification_email()
296 | email_obj.save()
297 | return HttpResponseRedirect(reverse('emailauth_add_email_continue',
298 | args=[quote_plus(email_obj.email)]))
299 | else:
300 | form = AddEmailForm()
301 |
302 | context = RequestContext(request)
303 | return render_to_response(template_name,
304 | {'form': form},
305 | context_instance=context)
306 |
307 |
308 | @requires_multi_emails_mode
309 | @login_required
310 | def add_email_continue(request, email,
311 | template_name='emailauth/add_email_continue.html'):
312 |
313 | return render_to_response(template_name,
314 | {'email': email},
315 | context_instance=RequestContext(request))
316 |
317 |
318 | @requires_single_email_mode
319 | @login_required
320 | def change_email(request, template_name='emailauth/change_email.html'):
321 | if request.method == 'POST':
322 | form = AddEmailForm(request.POST)
323 | if form.is_valid():
324 | UserEmail.objects.filter(user=request.user, default=False).delete()
325 |
326 | email_obj = UserEmail.objects.create_unverified_email(
327 | form.cleaned_data['email'], user=request.user)
328 | email_obj.send_verification_email()
329 | email_obj.save()
330 |
331 | return HttpResponseRedirect(reverse('emailauth_change_email_continue',
332 | args=[quote_plus(email_obj.email)]))
333 | else:
334 | form = AddEmailForm()
335 |
336 | context = RequestContext(request)
337 | return render_to_response(template_name,
338 | {'form': form},
339 | context_instance=context)
340 |
341 |
342 | @requires_single_email_mode
343 | @login_required
344 | def change_email_continue(request, email,
345 | template_name='emailauth/change_email_continue.html'):
346 |
347 | return render_to_response(template_name,
348 | {'email': email},
349 | context_instance=RequestContext(request))
350 |
351 |
352 | @requires_multi_emails_mode
353 | @login_required
354 | def delete_email(request, email_id,
355 | template_name='emailauth/delete_email.html'):
356 |
357 | user_email = get_object_or_404(UserEmail, id=email_id, user=request.user,
358 | verified=True)
359 |
360 | if request.method == 'POST':
361 | form = DeleteEmailForm(request.user, request.POST)
362 | if form.is_valid():
363 | user_email.delete()
364 |
365 | # Not really sure, where I should redirect from here...
366 | return HttpResponseRedirect(reverse('emailauth_account'))
367 | else:
368 | form = DeleteEmailForm(request.user)
369 |
370 | context = RequestContext(request)
371 | return render_to_response(template_name,
372 | {'form': form, 'email': user_email},
373 | context_instance=context)
374 |
375 |
376 | @requires_multi_emails_mode
377 | @login_required
378 | def set_default_email(request, email_id,
379 | template_name='emailauth/set_default_email.html'):
380 |
381 | user_email = get_object_or_404(UserEmail, id=email_id, user=request.user,
382 | verified=True)
383 |
384 | if request.method == 'POST':
385 | form = ConfirmationForm(request.POST)
386 | if form.is_valid():
387 | user_email.default = True
388 | user_email.save()
389 | return HttpResponseRedirect(reverse('emailauth_account'))
390 | else:
391 | form = ConfirmationForm()
392 |
393 | context = RequestContext(request)
394 | return render_to_response(template_name,
395 | {'form': form, 'email': user_email},
396 | context_instance=context)
397 |
398 |
399 | @login_required
400 | def resend_verification_email(request, email_id):
401 | user_email = get_object_or_404(UserEmail, id=email_id, user=request.user,
402 | verified=False)
403 | user_email.send_verification_email()
404 |
405 | return HttpResponseRedirect(reverse('emailauth_add_email_continue',
406 | args=[quote_plus(user_email.email)]))
407 |
408 |
--------------------------------------------------------------------------------
/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/__init__.py
--------------------------------------------------------------------------------
/example/emailauth.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/emailauth.db
--------------------------------------------------------------------------------
/example/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from os import path, environ
4 | from os.path import abspath, dirname, join
5 | import sys
6 |
7 | example_dir = dirname(abspath(__file__))
8 | emailauth_dir = dirname(example_dir)
9 |
10 | sys.path.insert(0, example_dir)
11 | sys.path.insert(0, emailauth_dir)
12 |
13 | from django.core.management import execute_manager
14 | try:
15 | import settings # Assumed to be in the same directory.
16 | except ImportError:
17 | import sys
18 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
19 | sys.exit(1)
20 |
21 | if __name__ == "__main__":
22 | execute_manager(settings)
23 |
--------------------------------------------------------------------------------
/example/media/css/clearfix.css:
--------------------------------------------------------------------------------
1 | .clearfix:after {
2 | content: ".";
3 | display: block;
4 | height: 0;
5 | clear: both;
6 | visibility: hidden;
7 | }
8 |
9 | .clearfix {display: inline-block;}
10 |
11 | /* Hides from IE-mac \*/
12 | * html .clearfix {height: 1%;}
13 | .clearfix {display: block;}
14 | /* End hide from IE-mac */
15 |
--------------------------------------------------------------------------------
/example/media/css/fancy.css:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------
2 |
3 | fancy-type.css
4 | * Lots of pretty advanced classes for manipulating text.
5 |
6 | See the Readme file in this folder for additional instructions.
7 |
8 | -------------------------------------------------------------- */
9 |
10 | /* Indentation instead of line shifts for sibling paragraphs. */
11 | /*p + p { text-indent:2em; margin-top:-1.5em; }*/
12 | /*form p + p { text-indent: 0; } [> Don't want this in forms. <]*/
13 |
14 |
15 | /* For great looking type, use this code instead of asdf:
16 | asdf
17 | Best used on prepositions and ampersands. */
18 |
19 | .alt {
20 | color: #666;
21 | font-family: "Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua", Georgia, serif;
22 | font-style: italic;
23 | font-weight: normal;
24 | }
25 |
26 |
27 | /* For great looking quote marks in titles, replace "asdf" with:
28 | “ asdf”
29 | (That is, when the title starts with a quote mark).
30 | (You may have to change this value depending on your font size). */
31 |
32 | .dquo { margin-left: -.5em; }
33 |
34 |
35 | /* Reduced size type with incremental leading
36 | (http://www.markboulton.co.uk/journal/comments/incremental_leading/)
37 |
38 | This could be used for side notes. For smaller type, you don't necessarily want to
39 | follow the 1.5x vertical rhythm -- the line-height is too much.
40 |
41 | Using this class, it reduces your font size and line-height so that for
42 | every four lines of normal sized type, there is five lines of the sidenote. eg:
43 |
44 | New type size in em's:
45 | 10px (wanted side note size) / 12px (existing base size) = 0.8333 (new type size in ems)
46 |
47 | New line-height value:
48 | 12px x 1.5 = 18px (old line-height)
49 | 18px x 4 = 72px
50 | 72px / 5 = 14.4px (new line height)
51 | 14.4px / 10px = 1.44 (new line height in em's) */
52 |
53 | p.incr, .incr p {
54 | font-size: 10px;
55 | line-height: 1.44em;
56 | margin-bottom: 1.5em;
57 | }
58 |
59 |
60 | /* Surround uppercase words and abbreviations with this class.
61 | Based on work by Jørgen Arnor Gårdsø Lom [http://twistedintellect.com/] */
62 |
63 | .caps {
64 | font-variant: small-caps;
65 | letter-spacing: 1px;
66 | text-transform: lowercase;
67 | font-size:1.2em;
68 | line-height:1%;
69 | font-weight:bold;
70 | padding:0 2px;
71 | }
72 |
--------------------------------------------------------------------------------
/example/media/css/forms.css:
--------------------------------------------------------------------------------
1 | .formfield {
2 | margin-top: 9px;
3 | }
4 |
5 | .fieldname {
6 | width: 200px;
7 | float: left;
8 | padding-top: 3px;
9 | padding-left: 20px;
10 | }
11 |
12 | .fieldnamefloat {
13 | float: left;
14 | padding-top: 3px;
15 | padding-left: 20px;
16 | padding-right: 10px;
17 | }
18 |
19 | .fieldnameflow {
20 | padding-top: 3px;
21 | padding-left: 20px;
22 | }
23 |
24 | .fieldname em {
25 | color: #bf1600;
26 | margin: 0px 0px 0px 5px;
27 | }
28 |
29 | .checkboxfield {
30 | float: left;
31 | padding-left: 20px;
32 | padding-top: 1px;
33 | width: 30px;
34 | }
35 |
36 | em.required {
37 | color: #bf1600;
38 | }
39 |
40 | .fieldvalue {
41 | float: left;
42 | }
43 |
44 | .fieldvalue.flow {
45 | padding-left: 20px;
46 | float: none;
47 | }
48 |
49 | .checkboxlabel {
50 | float: left;
51 | }
52 |
53 | .formfield .help {
54 | margin-top: 9px;
55 | width: 300px;
56 | }
57 |
58 | .checkboxlabel .help {
59 | margin-top: 9px;
60 | width: 470px;
61 | }
62 |
63 | .fieldvalue input {
64 | width: 150px;
65 | }
66 |
67 | .fieldvalue select {
68 | width: 150px;
69 | }
70 |
71 | .selectmultiple {
72 | height: 20em;
73 | }
74 |
75 | .fieldvalue input.long, .fieldvalue select.long, .fieldvalue textarea {
76 | width: 300px;
77 | }
78 |
79 | .fieldvalue input.short, .fieldvalue select.short {
80 | width: 75px;
81 | }
82 |
83 | .requiredfootnote {
84 | margin: 9px 0px;
85 | padding-left: 20px;
86 | }
87 |
88 | .submitrow {
89 | padding-left: 20px;
90 | }
91 |
92 | ul.errorlist {
93 | margin: 0px;
94 | list-style-type: none;
95 | padding: 2px 0px 2px 20px;
96 | color: #bf1600;
97 | background: url(/media/img/exclamation_white.png) no-repeat;
98 | }
99 |
100 | ul.errorlist li {
101 | font-size: 9pt;
102 | }
103 |
104 | .msg_error, .msg_info, .msg_question, .msg_success {
105 | background-position: 9px 7px;
106 | background-repeat: no-repeat;
107 | color: black;
108 | font-weight: bold;
109 | margin: 10px 0px 20px;
110 | padding: 6px 14px 6px 36px;
111 | vertical-align: middle;
112 | border: 2px solid #ddd;
113 | background-repeat: no-repeat;
114 | -moz-border-radius: 5px;
115 | border-radius: 5px;
116 | -webkit-border-radius: 5px;
117 | }
118 |
119 |
120 | .msg_error {
121 | background-color: #FBE3E4;
122 | color: #8a1f11;
123 | border-color: #FBC2C4;
124 | background-image: url(/media/img/error.png);
125 | }
126 |
127 | .msg_info {
128 | background-color: #E6EFC2;
129 | color: #264409;
130 | border-color: #C6D880;
131 | background-image: url(/media/img/info.png);
132 | }
133 |
134 | .msg_success {
135 | background-color: #E6EFC2;
136 | color: #264409;
137 | border-color: #C6D880;
138 | background-image: url(/media/img/success.png);
139 | }
140 |
141 | .msg_question {
142 | background-color:#D4F4CE;
143 | background-image: url(/media/img/question.png);
144 | border:1px solid #B9D6B4;
145 | }
146 |
147 | form table th {
148 | vertical-align: baseline;
149 | padding-top: 7px;
150 | }
151 |
--------------------------------------------------------------------------------
/example/media/css/ie6.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/css/ie6.css
--------------------------------------------------------------------------------
/example/media/css/reset.css:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------
2 |
3 | reset.css
4 | * Resets default browser CSS.
5 |
6 | -------------------------------------------------------------- */
7 |
8 | html, body, div, span, object, iframe,
9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
10 | a, abbr, acronym, address, code,
11 | del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
12 | fieldset, form, label, legend,
13 | table, caption, tbody, tfoot, thead, tr, th, td {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-weight: inherit;
18 | font-style: inherit;
19 | font-size: 100%;
20 | font-family: inherit;
21 | vertical-align: baseline;
22 | }
23 |
24 | body {
25 | line-height: 1.5;
26 | }
27 |
28 | /* Tables still need 'cellspacing="0"' in the markup. */
29 | table { border-collapse: separate; border-spacing: 0; }
30 | caption, th, td { text-align: left; font-weight: normal; }
31 | table, td, th { vertical-align: middle; }
32 |
33 | /* Remove possible quote marks (") from , . */
34 | blockquote:before, blockquote:after, q:before, q:after { content: ""; }
35 | blockquote, q { quotes: "" ""; }
36 |
37 | /* Remove annoying border on linked images. */
38 | a img { border: none; }
39 |
--------------------------------------------------------------------------------
/example/media/css/site.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | }
4 |
5 | html, body {
6 | height: 100%;
7 | }
8 |
9 | .wrapper {
10 | min-height: 100%;
11 | height: auto !important;
12 | height: 100%;
13 | margin: 0 auto -6em;
14 | }
15 |
16 | .footer, .push {
17 | height: 6em;
18 | }
19 |
20 | .header {
21 | background: #ddd;
22 | border-bottom: 2px solid #aaa;
23 | margin: 0px 0px 18px 0px;
24 | }
25 |
26 | .header h1 {
27 | font-size: 4em;
28 | float: left;
29 | }
30 |
31 | .header h1 a {
32 | text-decoration: none;
33 | }
34 |
35 | .logarea {
36 | float: right;
37 | line-height: 4em;
38 | }
39 |
40 | .logarea a, .logarea span {
41 | vertical-align: bottom;
42 | line-height: 1em;
43 | font-size: 14pt;
44 | }
45 |
46 | .footer {
47 | background: #ddd;
48 | text-align: center;
49 | }
50 |
51 | .innerfooter {
52 | border-top: 2px solid #aaa;
53 | padding: 9px 0px 0px 0px;
54 | }
55 |
56 | .content {
57 | padding-bottom: 36px;
58 | }
59 |
60 | .pagewidth {
61 | width: 960px;
62 | margin: 0px auto;
63 | }
64 |
--------------------------------------------------------------------------------
/example/media/css/typography.css:
--------------------------------------------------------------------------------
1 | /* --------------------------------------------------------------
2 |
3 | typography.css
4 | * Sets up some sensible default typography.
5 |
6 | -------------------------------------------------------------- */
7 |
8 | /* Default font settings.
9 | The font-size percentage is of 16px. (0.75 * 16px = 12px) */
10 | body {
11 | font-size: 75%;
12 | color: #222;
13 | background: #fff;
14 | font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
15 | }
16 |
17 |
18 | /* Headings
19 | -------------------------------------------------------------- */
20 |
21 | h1,h2,h3,h4,h5,h6 { font-weight: normal; color: #111; }
22 |
23 | h1 { font-size: 3em; line-height: 1; margin-bottom: 0.5em; }
24 | h2 { font-size: 2em; margin-bottom: 0.75em; }
25 | h3 { font-size: 1.5em; line-height: 1; margin-bottom: 1em; }
26 | h4 { font-size: 1.2em; line-height: 1.25; margin-bottom: 1.25em; }
27 | h5 { font-size: 1em; font-weight: bold; margin-bottom: 1.5em; }
28 | h6 { font-size: 1em; font-weight: bold; }
29 |
30 | h1 img, h2 img, h3 img,
31 | h4 img, h5 img, h6 img {
32 | margin: 0;
33 | }
34 |
35 |
36 | /* Text elements
37 | -------------------------------------------------------------- */
38 |
39 | p { margin: 0 0 1.5em; }
40 | p img.left { float: left; margin: 1.5em 1.5em 1.5em 0; padding: 0; }
41 | p img.right { float: right; margin: 1.5em 0 1.5em 1.5em; }
42 |
43 | a:focus,
44 | a:hover { color: #000; }
45 | a { color: #009; text-decoration: underline; }
46 |
47 | blockquote { margin: 1.5em; color: #666; font-style: italic; }
48 | strong { font-weight: bold; }
49 | em,dfn { font-style: italic; }
50 | dfn { font-weight: bold; }
51 | sup, sub { line-height: 0; }
52 |
53 | abbr,
54 | acronym { border-bottom: 1px dotted #666; }
55 | address { margin: 0 0 1.5em; font-style: italic; }
56 | del { color:#666; }
57 |
58 | pre { margin: 1.5em 0; white-space: pre; }
59 | pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
60 |
61 |
62 | /* Lists
63 | -------------------------------------------------------------- */
64 |
65 | li ul,
66 | li ol { margin:0 1.5em; }
67 | ul, ol { margin: 0 1.5em 1.5em 1.5em; }
68 |
69 | ul { list-style-type: disc; }
70 | ol { list-style-type: decimal; }
71 |
72 | dl { margin: 0 0 1.5em 0; }
73 | dl dt { font-weight: bold; }
74 | dd { margin-left: 1.5em;}
75 |
76 |
77 | /* Tables
78 | -------------------------------------------------------------- */
79 |
80 | table { margin-bottom: 1.4em; }
81 | th { font-weight: bold; }
82 | thead th { background: #c3d9ff; }
83 | th,td,caption { padding: 4px 10px 4px 5px; }
84 | tr.even td { background: #e5ecf9; }
85 | tfoot { font-style: italic; }
86 | caption { background: #eee; }
87 |
88 |
89 | /* Misc classes
90 | -------------------------------------------------------------- */
91 |
92 | .small { font-size: .8em; margin-bottom: 1.875em; line-height: 1.875em; }
93 | .large { font-size: 1.2em; line-height: 2.5em; margin-bottom: 1.25em; }
94 | .hide { display: none; }
95 |
96 | .quiet { color: #666; }
97 | .loud { color: #000; }
98 | .highlight { background:#ff0; }
99 | .added { background:#060; color: #fff; }
100 | .removed { background:#900; color: #fff; }
101 |
102 | .first { margin-left:0; padding-left:0; }
103 | .last { margin-right:0; padding-right:0; }
104 | .top { margin-top:0; padding-top:0; }
105 | .bottom { margin-bottom:0; padding-bottom:0; }
106 |
--------------------------------------------------------------------------------
/example/media/img/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/img/error.png
--------------------------------------------------------------------------------
/example/media/img/exclamation_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/img/exclamation_white.png
--------------------------------------------------------------------------------
/example/media/img/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/img/info.png
--------------------------------------------------------------------------------
/example/media/img/question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/img/question.png
--------------------------------------------------------------------------------
/example/media/img/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redvasily/django-emailauth/1c3c977f361e63eb6e4bd2fa32f6d8af78f74f31/example/media/img/success.png
--------------------------------------------------------------------------------
/example/middleware.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.contrib.sites.models import Site
3 |
4 | class CurrentSiteMiddleware(object):
5 | def process_request(self, request):
6 | site = Site.objects.get(id=settings.SITE_ID)
7 | if site.domain != request.get_host():
8 | site.domain = request.get_host()
9 | site.save()
10 |
--------------------------------------------------------------------------------
/example/settings.py:
--------------------------------------------------------------------------------
1 | from os.path import join, dirname, abspath
2 |
3 | DEBUG = True
4 | TEMPLATE_DEBUG = DEBUG
5 |
6 | PROJECT_ROOT = dirname(abspath(__file__))
7 |
8 | ADMINS = (
9 | # ('Your Name', 'your_email@domain.com'),
10 | )
11 |
12 | MANAGERS = ADMINS
13 |
14 | # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
15 | DATABASE_ENGINE = 'sqlite3'
16 |
17 | # Or path to database file if using sqlite3.
18 | DATABASE_NAME = join(PROJECT_ROOT, 'emailauth.db')
19 |
20 | # Not used with sqlite3.
21 | DATABASE_USER = ''
22 |
23 | # Not used with sqlite3.
24 | DATABASE_PASSWORD = ''
25 |
26 | # Set to empty string for localhost. Not used with sqlite3.
27 | DATABASE_HOST = ''
28 |
29 | # Set to empty string for default. Not used with sqlite3.
30 | DATABASE_PORT = ''
31 |
32 | # Local time zone for this installation. Choices can be found here:
33 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
34 | # although not all choices may be available on all operating systems.
35 | # If running in a Windows environment this must be set to the same as your
36 | # system time zone.
37 | TIME_ZONE = 'America/Chicago'
38 |
39 | # Language code for this installation. All choices can be found here:
40 | # http://www.i18nguy.com/unicode/language-identifiers.html
41 | LANGUAGE_CODE = 'en-us'
42 |
43 | SITE_ID = 1
44 |
45 | # If you set this to False, Django will make some optimizations so as not
46 | # to load the internationalization machinery.
47 | USE_I18N = True
48 |
49 | # Absolute path to the directory that holds media.
50 | # Example: "/home/media/media.lawrence.com/"
51 | MEDIA_ROOT = join(PROJECT_ROOT, 'media')
52 |
53 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
54 | # trailing slash if there is a path component (optional in other cases).
55 | # Examples: "http://media.lawrence.com", "http://example.com/media/"
56 | MEDIA_URL = '/media/'
57 |
58 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
59 | # trailing slash.
60 | # Examples: "http://foo.com/media/", "/media/".
61 | ADMIN_MEDIA_PREFIX = '/media/admin/'
62 |
63 | # Make this unique, and don't share it with anybody.
64 | SECRET_KEY = '-&umm_!rboq^$-ye%v+4rp^+@a&dqou&d=%psw(xvfh)y%p2q-'
65 |
66 | # List of callables that know how to import templates from various sources.
67 | TEMPLATE_LOADERS = (
68 | 'django.template.loaders.filesystem.load_template_source',
69 | 'django.template.loaders.app_directories.load_template_source',
70 | # 'django.template.loaders.eggs.load_template_source',
71 | )
72 |
73 | MIDDLEWARE_CLASSES = (
74 | 'django.middleware.common.CommonMiddleware',
75 | 'django.contrib.sessions.middleware.SessionMiddleware',
76 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
77 | 'example.middleware.CurrentSiteMiddleware',
78 | )
79 |
80 | ROOT_URLCONF = 'example.urls'
81 |
82 | TEMPLATE_DIRS = (
83 | join(PROJECT_ROOT, 'templates'),
84 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
85 | # Always use forward slashes, even on Windows.
86 | # Don't forget to use absolute paths, not relative paths.
87 | )
88 |
89 | INSTALLED_APPS = (
90 | 'django.contrib.auth',
91 | 'django.contrib.contenttypes',
92 | 'django.contrib.sessions',
93 | 'django.contrib.sites',
94 | 'django.contrib.admin',
95 | 'emailauth',
96 | )
97 |
98 | AUTHENTICATION_BACKENDS = (
99 | 'emailauth.backends.EmailBackend',
100 | 'emailauth.backends.FallbackBackend',
101 | )
102 |
103 | LOGIN_REDIRECT_URL = '/account/'
104 | LOGIN_URL = '/login/'
105 |
106 | EMAILAUTH_USE_SINGLE_EMAIL = False
107 |
108 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
109 |
--------------------------------------------------------------------------------
/example/settings_singleemail.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 | EMAILAUTH_USE_SINGLE_EMAIL = True
4 |
--------------------------------------------------------------------------------
/example/templates/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Emailauth example project
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 404
13 |
14 |
15 |
16 | {# vim: set ft=htmldjango: #}
17 |
--------------------------------------------------------------------------------
/example/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "master.html" %}
2 |
3 | {% block content %}
4 | {{ content|safe }}
5 | {% endblock %}
6 |
--------------------------------------------------------------------------------
/example/templates/master.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Emailauth example project
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
38 |
39 |
{% block title %}{{ title }}{% endblock %}
40 |
41 | {% if messages %}
42 | {% for message in messages %}
43 |
{{ message }}
44 | {% endfor %}
45 | {% endif %}
46 |
47 | {% block content %}
48 | {% endblock %}
49 |
50 |
51 |
52 |
53 |
62 |
63 | {#{% block logarea %}#}
64 | {# {% if user.is_authenticated %}#}
65 | {# Welcome, {{ user.first_name }}. #}
66 | {# Username: {{ user.username }} #}
67 | {# Email: {{ user.email }} #}
68 | {# Logout #}
69 | {# {% else %}#}
70 | {# Register | Login #}
71 | {# {% endif %}#}
72 | {#{% endblock %}#}
73 |
74 | {#{% block content %}#}
75 | {#{% endblock %}#}
76 |
77 |
78 |
79 | {# vim: set ft=htmldjango: #}
80 |
--------------------------------------------------------------------------------
/example/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import *
2 | from django.conf import settings
3 | from django.views.generic.simple import direct_to_template
4 |
5 | from django.contrib import admin
6 | admin.autodiscover()
7 |
8 | urlpatterns = patterns('',
9 | url(r'^$', 'example.views.index', name='index'),
10 | (r'', include('emailauth.urls')),
11 | (r'^admin/(.*)', admin.site.root),
12 | )
13 |
14 | urlpatterns += patterns('',
15 | (r'^media/(?P.*)$', 'django.views.static.serve',
16 | {
17 | 'document_root': settings.MEDIA_ROOT,
18 | 'show_indexes': True
19 | }
20 | ),
21 | )
22 |
--------------------------------------------------------------------------------
/example/views.py:
--------------------------------------------------------------------------------
1 | from os.path import dirname, abspath, join
2 |
3 | from django.shortcuts import render_to_response
4 | from django import template
5 | from django.template import RequestContext
6 | from django.contrib.markup.templatetags.markup import restructuredtext
7 |
8 | def index(request):
9 | readme_file = join(dirname(dirname(abspath(__file__))), 'README.rst')
10 | raw_content = open(readme_file).read()
11 | try:
12 | content = restructuredtext(raw_content)
13 | except template.TemplateSyntaxError:
14 | content = u'' + raw_content + u' '
15 |
16 | return render_to_response('index.html', {'content': content},
17 | context_instance=RequestContext(request))
18 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 | import os
3 |
4 | # Compile the list of packages available, because distutils doesn't have
5 | # an easy way to do this.
6 | packages, data_files = [], []
7 | root_dir = os.path.dirname(__file__)
8 | if root_dir:
9 | os.chdir(root_dir)
10 |
11 | for dirpath, dirnames, filenames in os.walk('emailauth'):
12 | # Ignore dirnames that start with '.'
13 | for i, dirname in enumerate(dirnames):
14 | if dirname.startswith('.'): del dirnames[i]
15 | if '__init__.py' in filenames:
16 | pkg = dirpath.replace(os.path.sep, '.')
17 | if os.path.altsep:
18 | pkg = pkg.replace(os.path.altsep, '.')
19 | packages.append(pkg)
20 | elif filenames:
21 | prefix = dirpath[len('emailauth/'):] # Strip package prefix
22 | for f in filenames:
23 | data_files.append(os.path.join(prefix, f))
24 |
25 | setup(name='django-emailauth',
26 | version='0.1',
27 | description='User email authentication application for Django',
28 | author='Vasily Sulatskov',
29 | author_email='redvasily@gmail.com',
30 | url='http://github.com/redvasily/django-emailauth/tree/master/',
31 | download_url='http://cloud.github.com/downloads/redvasily/django-emailauth/django-emailauth-0.1.tar.gz',
32 | package_dir={
33 | 'emailauth': 'emailauth',
34 | },
35 | packages=packages,
36 | package_data={
37 | 'emailauth': data_files
38 | },
39 | classifiers=[
40 | 'Development Status :: 4 - Beta',
41 | 'Environment :: Web Environment',
42 | 'Framework :: Django',
43 | 'Intended Audience :: Developers',
44 | 'License :: OSI Approved :: BSD License',
45 | 'Operating System :: OS Independent',
46 | 'Programming Language :: Python',
47 | 'Topic :: Utilities',
48 | ],
49 | )
50 |
--------------------------------------------------------------------------------