Commits

Anonymous committed 80b610c

initial commit

Comments (0)

Files changed (17)

+# use glob syntax.
+syntax: glob
+
+*.pyc
+*.swp
+*.orig
+*.DS_Store
+*.log
+*.cache
+.svn*
+.git*
+*.egg-info
+Thumbs.db
+Copyright (c) 2012 Jason Christa and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    3. Neither the name of this project nor the names of its contributors may
+       be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+recursive-include docs *
+include LICENSE
+include README
Empty file added.

accounts/__init__.py

+__version_info__ = {
+    'major': 0,
+    'minor': 1,
+    'micro': 0,
+    'releaselevel': 'alpha',
+    'serial': 1
+}
+
+def get_version(short=False):
+    assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
+    vers = ["%(major)i.%(minor)i" % __version_info__, ]
+    if __version_info__['micro']:
+        vers.append(".%(micro)i" % __version_info__)
+    if __version_info__['releaselevel'] != 'final' and not short:
+        vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
+    return ''.join(vers)
+
+__version__ = get_version()

accounts/auth_backends.py

+from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import User
+import re
+
+
+email_re = re.compile(
+    r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
+    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string
+    r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain
+
+
+class EmailBackend(ModelBackend):
+    """Authenticate using email only"""
+    def authenticate(self, username=None, password=None):
+        if email_re.search(username):
+            try:
+                user = User.objects.get(email=username)
+                if user.check_password(password):
+                    return user
+            except User.DoesNotExist:
+                pass
+        return None

accounts/forms.py

+from django import forms
+from django.contrib.auth.models import User
+
+
+class UserForm(forms.ModelForm):
+    username = forms.RegexField(max_length=30,
+        regex=r'^[\w.@+-]+$',
+        error_messages = {
+            'invalid': u'This value may contain only letters, numbers and @/./+/-/_ characters.'})
+    password = forms.CharField(max_length=100, required=False, widget=forms.PasswordInput())
+    password2 = forms.CharField(max_length=100, required=False, widget=forms.PasswordInput(), label=u'Confirm Password')
+
+    class Meta:
+        model = User
+        fields = ('username', 'email')
+
+    def clean(self):
+        cleaned_data = self.cleaned_data
+        password = cleaned_data.get('password')
+        password2 = cleaned_data.get('password2')
+        if password != password2:
+            self._errors['password'] = self.error_class([u'Passwords do not match.'])
+            del cleaned_data['password']
+            del cleaned_data['password2']
+        return cleaned_data
+
+    def save(self, *args, **kwargs):
+        password = self.cleaned_data.get('password')
+        if password:
+            self.instance.set_password(password)
+        return super(UserForm, self).save(*args, **kwargs)

accounts/models.py

Empty file added.

accounts/templates/accounts/login.html

+{% extends "base.html" %}
+
+{% block title %}
+    Login | {{block.super}}
+{% endblock %}
+
+
+{% block content %}
+    <h1>Login</h1>
+
+    <form action="" method="post">
+        {% csrf_token %}
+        <table>
+            {{form.as_table}}
+        </table>
+        <input class="btn" type="submit" value="Login" />
+    </form>
+    <p><a href="{% url auth_password_reset %}" >Forgot your password?</a></p>
+{% endblock %}

accounts/templates/accounts/password_reset_confirm.html

+{% extends "base.html" %}
+
+{% block title %}
+    Set Password | {{block.super}}
+{% endblock %}
+
+
+{% block content %}
+    <h1>Set Password</h1>
+
+    <form action="" method="post">
+        {% csrf_token %}
+        <table>
+            <tbody>
+                {{form.as_table}}
+            </tbody>
+            <tfoot>
+                <tr>
+                    <td colspan="2">
+                        <input class="btn" type="submit" value="Change Password" />
+                    </td>
+                </tr>
+            </tfoot>
+        </table>
+    </form>
+{% endblock %}

accounts/templates/accounts/password_reset_email.html

+A request has been made at MySite({{protocol}}://{{domain}}) to reset your password. If you are not the originator of this request, you can safely ignore this e-mail. Your password will NOT be changed until a new one is entered at this link: {{protocol}}://{{domain}}{% url auth_password_reset_confirm uid token %}.

accounts/templates/accounts/password_reset_form.html

+{% extends "base.html" %}
+
+{% block title %}
+    Reset Password | {{block.super}}
+{% endblock %}
+
+{% block content %}
+    <h1>Reset Password</h1>
+
+    <form action="" method="post">
+        {% csrf_token %}
+        <table>
+            <tbody>
+                {{form}}
+            </tbody>
+            <tfoot>
+                <tr>
+                    <td colspan="2">
+                        <input class="btn" type="submit" value="Send Email" />
+                    </td>
+                </tr>
+            </tfoot>
+        </table>
+    </form>
+{% endblock %}

accounts/templates/accounts/user_form.html

+{% extends "base.html" %}
+
+{% block title %}
+    User Profile | {{block.super}}
+{% endblock %}
+
+{% block content %}
+    <h1>User Profile</h1>
+
+    <form action="" method="post">
+        {% csrf_token %}
+        {{form.as_p}}
+        <input class="btn" type="submit" value="Update" />
+    </form>
+{% endblock %}
+from django.conf.urls import patterns, include, url
+from accounts import views
+
+
+urlpatterns = patterns('accounts.views',
+    url(r'^login/$', views.Login.as_view(), name='auth_login'),
+    url(r'^logout/$', views.Logout.as_view(), name='auth_logout'),
+    url(r'^password/reset/$', views.PasswordReset.as_view(), name='auth_password_reset'),
+    url(r'^password/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', views.PasswordResetConfirm.as_view(), name='auth_password_reset_confirm'),
+    url(r'^profile/$', views.UserUpdate.as_view(), name='accounts_profile'),
+)

accounts/views.py

+from django.http import Http404
+from django.views.generic.edit import FormView, UpdateView
+from django.views.generic.base import RedirectView
+from django.core.urlresolvers import reverse_lazy
+from django.contrib import messages
+from django.views.decorators.debug import sensitive_post_parameters
+from django.views.decorators.cache import never_cache
+from django.views.decorators.csrf import csrf_protect
+from django.contrib.auth.decorators import login_required
+from django.utils.decorators import method_decorator
+from django.contrib.auth import REDIRECT_FIELD_NAME, login, logout
+from django.contrib.auth.tokens import default_token_generator
+from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm
+from accounts.forms import UserForm
+from django.contrib.auth.models import User
+import urlparse
+from django.utils.http import base36_to_int
+from django.conf import settings
+
+
+class Login(FormView):
+    form_class = AuthenticationForm
+    redirect_field_name = REDIRECT_FIELD_NAME
+    template_name = 'accounts/login.html'
+
+    def get_success_url(self):
+        redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
+        netloc = urlparse.urlparse(redirect_to)[1]
+        if not redirect_to:
+            redirect_to = settings.LOGIN_REDIRECT_URL
+        elif netloc and netloc != self.request.get_host():
+            redirect_to = settings.LOGIN_REDIRECT_URL
+        return redirect_to
+
+    def form_valid(self, form):
+        login(self.request, form.get_user())
+
+        if self.request.session.test_cookie_worked():
+            self.request.session.delete_test_cookie()
+        return super(Login, self).form_valid(form)
+
+    #@method_decorator(sensitive_post_parameters())
+    @method_decorator(csrf_protect)
+    @method_decorator(never_cache)
+    def dispatch(self, *args, **kwargs):
+        return super(Login, self).dispatch(*args, **kwargs)
+
+
+class Logout(RedirectView):
+    redirect_field_name = REDIRECT_FIELD_NAME
+
+    def get_redirect_url(self):
+        redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
+        if redirect_to:
+            netloc = urlparse.urlparse(redirect_to)[1]
+            if netloc and netloc != self.request.get_host():
+                redirect_to = ''
+        return redirect_to or '/'
+
+    def get(self, request, *args, **kwargs):
+        logout(request)
+        messages.success(request, u'You have been logged out.')
+        return super(Logout, self).get(request, *args, **kwargs)
+
+
+class PasswordReset(FormView):
+    form_class = PasswordResetForm
+    token_generator = default_token_generator
+    template_name = 'accounts/password_reset_form.html'
+    email_template_name='accounts/password_reset_email.html'
+    subject_template_name='accounts/password_reset_subject.txt'
+    from_email = None
+    success_url = '/'
+
+    def form_valid(self, form):
+        form.save(use_https=self.request.is_secure(),
+                  token_generator=self.token_generator,
+                  from_email=self.from_email,
+                  email_template_name=self.email_template_name,
+                  subject_template_name=self.subject_template_name,
+                  request=self.request)
+        messages.success(self.request, u'Check your email for the password reset link.')
+        return super(PasswordReset, self).form_valid(form)
+
+    @method_decorator(csrf_protect)
+    def dispatch(self, *args, **kwargs):
+        return super(PasswordReset, self).dispatch(*args, **kwargs)
+
+
+class PasswordResetConfirm(FormView):
+    form_class = SetPasswordForm
+    token_generator = default_token_generator
+    template_name = 'accounts/password_reset_confirm.html'
+    success_url = reverse_lazy('')
+
+    def get_user(self, uid=None):
+        if not hasattr(self, '_user'):
+            if uid is None:
+                uidb36 = self.kwargs.get('uidb36', 0)
+                uid = base36_to_int(uidb36)
+            try:
+                self._user = User.objects.get(pk=uid)
+            except User.DoesNotExsit:
+                self._user = None
+        return self._user
+
+    def get_form_kwargs(self):
+        kwargs = super(PasswordResetConfirm, self).get_form_kwargs()
+        kwargs['user'] = self.get_user()
+        return kwargs
+
+    def form_valid(self, form):
+        form.save()
+        messages.success(self.request, u'Password reset.')
+        return super(PasswordResetConfirm, self).form_valid(form)
+
+    #@method_decorator(sensitive_post_parameters())
+    @method_decorator(never_cache)
+    def dispatch(self, *args, **kwargs):
+        uidb36 = kwargs.get('uidb36', 0)
+        uid = base36_to_int(uidb36)
+        user = self.get_user(uid)
+        token = kwargs.get('token', '')
+        if self.token_generator.check_token(user, token):
+            return super(PasswordResetConfirm, self).dispatch(*args, **kwargs)
+        else:
+            raise Http404
+
+
+class UserUpdate(UpdateView):
+    model = User
+    form_class = UserForm
+    template_name = 'accounts/user_form.html'
+    success_url = '/'
+
+    def get_object(self, queryset=None):
+        return self.request.user
+
+    def form_valid(self, *args, **kwargs):
+        messages.success(self.request, u'User account updated')
+        return super(UserUpdate, self).form_valid(*args, **kwargs)
+
+    @method_decorator(login_required)
+    def dispatch(self, *args, **kwargs):
+        return super(UserUpdate, self).dispatch(*args, **kwargs)
+Django==1.3
+import os
+try:
+    from setuptools import setup, find_packages
+except ImportError:
+    from distutils.core import setup, find_packages
+
+
+def read_file(filename):
+    """Read a file into a string"""
+    path = os.path.abspath(os.path.dirname(__file__))
+    filepath = os.path.join(path, filename)
+    try:
+        return open(filepath).read()
+    except IOError:
+        return ''
+
+
+def get_readme():
+    """Return the README file contents. Supports text,rst, and markdown"""
+    for name in ('README', 'README.rst', 'README.md'):
+        if os.path.exists(name):
+            return read_file(name)
+    return ''
+
+setup(
+    name = 'Django Accounts',
+    version = __import__('accounts').get_version().replace(' ', '-'),
+    url = 'https://bitbucket.org/nextscreenlabs/django-accounts',
+    author = 'Jason Christa',
+    author_email = 'jason@zeitcode.com',
+    description = 'Account login, logout, and password reset.',
+    long_description = get_readme(),
+    packages = find_packages(exclude=['tests']),
+    include_package_data = True,
+    install_requires = read_file('requirements.txt'),
+    classifiers = [
+        'Environment :: Web Environment',
+        'License :: OSI Approved :: BSD Liscense',
+        'Framework :: Django',
+        'Programming Language :: Python',
+    ],
+)