Commits

Anonymous committed 045a935

Refactoring with class based view

  • Participants
  • Parent commits d5c4207

Comments (0)

Files changed (8)

src/accounts/admin.py

 # -*- coding: utf-8 -*- 
 
-from django import forms
 from django.contrib import admin
-from django.core.urlresolvers import reverse
-
 from models import *
 
-### Profile 
-class ProfileAdmin(admin.ModelAdmin):
-    list_display= ('id', 'user', 'name', 'given_name', 'family_name', 'middle_name', 'nickname', 'profile', 'picture', 'website', 'email', 'verified', 'gender', 'birthday', 'zoneinfo', 'locale', 'phone_number', 'address', 'updated_time')
-admin.site.register(Profile,ProfileAdmin)
+### SignupCode 
+class SignupCodeAdmin(admin.ModelAdmin):
+    list_display =  tuple([ f.name for f in SignupCode._meta.fields ] )
+    
+admin.site.register(SignupCode,SignupCodeAdmin)
+
+### SignupCodeResult 
+class SignupCodeResultAdmin(admin.ModelAdmin):
+    list_display =  tuple([ f.name for f in SignupCodeResult._meta.fields ] )
+
+admin.site.register(SignupCodeResult,SignupCodeResultAdmin)
+
+

src/accounts/forms.py

 #            if not request.session.test_cookie_worked():
 #                raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
         auth_login(request,self.user_cache)
+
+#####
+# code from django-user-accounts forms.py
+
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+from django.contrib import auth
+
+
+class SignupForm(forms.Form):
+    pass
+
+
+class LoginForm(forms.Form):
+    
+    password = forms.CharField(
+        label=_("Password"),
+        widget=forms.PasswordInput(render_value=False)
+    )
+    remember = forms.BooleanField(
+        label = _("Remember Me"),
+        required = False
+    )
+    user = None
+    
+    def clean(self):
+        if self._errors:
+            return
+        user = auth.authenticate(**self.user_credentials())
+        if user:
+            if user.is_active:
+                self.user = user
+            else:
+                raise forms.ValidationError(_("This account is inactive."))
+        else:
+            raise forms.ValidationError(self.authentication_fail_message)
+        return self.cleaned_data
+    
+    def user_credentials(self):
+        raise NotImplementedError("LoginForm must be subclassed to provide all user credentials")
+
+
+class LoginUsernameForm(LoginForm):
+    
+    username = forms.CharField(label=_("Username"), max_length=30)
+    authentication_fail_message = _("The username and/or password you specified are not correct.")
+    
+    def __init__(self, *args, **kwargs):
+        super(LoginUsernameForm, self).__init__(*args, **kwargs)
+        self.fields.keyOrder = ["username", "password", "remember"]
+    
+    def user_credentials(self):
+        return {
+            "username": self.cleaned_data["username"],
+            "password": self.cleaned_data["password"],
+        }
+
+
+class LoginEmailForm(LoginForm):
+    
+    email = forms.EmailField(label=_("Email"))
+    authentication_fail_message = _("The email address and/or password you specified are not correct.")
+    
+    def __init__(self, *args, **kwargs):
+        super(LoginEmailForm, self).__init__(*args, **kwargs)
+        self.fields.keyOrder = ["email", "password", "remember"]
+    
+    def user_credentials(self):
+        return {
+            "email": self.cleaned_data["email"],
+            "password": self.cleaned_data["password"],
+        }

src/accounts/models.py

-# -*- coding: utf-8 -*-
+## based on django-user-accounts/accounts/models.py
+#
+import datetime
+import hashlib
+import random
+from datetime import datetime
 
-from django.db.models import AutoField,Sum,Max ,Q
+from django.conf import settings
+from django.core.mail import send_mail
 from django.db import models
+from django.template.loader import render_to_string
 from django.contrib.auth.models import User
-from django.contrib.auth import authenticate
-from django.core.urlresolvers import reverse
+from django.contrib.sites.models import Site
 
-from datetime import datetime,timedelta
-import pytz
-import pyrfc3339
-import re
-import urllib,urllib2
-import uuid 
-import settings
-#import jwt
-import json
+from accounts.signals import signup_code_sent, signup_code_used
 
+class SignupCode(models.Model):
+    
+    code = models.CharField(max_length=64, unique=True)
+    max_uses = models.PositiveIntegerField(default=0)
+    expiry = models.DateTimeField(null=True, blank=True)
+    inviter = models.ForeignKey(User, null=True, blank=True)
+    email = models.EmailField(blank=True, unique=True)
+    notes = models.TextField(blank=True)
+    sent = models.DateTimeField(null=True, blank=True)
+    created = models.DateTimeField(default=datetime.now, editable=False)
+    use_count = models.PositiveIntegerField(editable=False, default=0)
+    
+    def __unicode__(self):
+        return u"%s [%s]" % (self.email, self.code)
+    
+    @classmethod
+    def exists(cls, email):
+        return cls._default_manager.filter(email=email).exists()
+    
+    @classmethod
+    def create(cls, email, expiry):
+        expiry = datetime.now() + datetime.timedelta(hours=expiry)
+        bits = [email, str(random.SystemRandom().getrandbits(512))]
+        code = hashlib.sha256("".join(bits)).hexdigest()
+        return cls(code=code, email=email, max_uses=1, expiry=expiry)
+    
+    @classmethod
+    def check(cls, code):
+        if code:
+            try:
+                signup_code = cls._default_manager.get(code=code)
+            except cls.DoesNotExist:
+                return False
+            else:
+                # check max uses
+                if signup_code.max_uses and signup_code.max_uses < signup_code.use_count + 1:
+                    return False
+                else:
+                    if signup_code.expiry and datetime.now() > signup_code.expiry:
+                        return False
+                    else:
+                        return signup_code
+        else:
+            return False
+    
+    def calculate_use_count(self):
+        self.use_count = self.signupcoderesult_set.count()
+        self.save()
+    
+    def use(self, user):
+        """
+        Add a SignupCode result attached to the given user.
+        """
+        result = SignupCodeResult()
+        result.signup_code = self
+        result.user = user
+        result.save()
+        signup_code_used.send(sender=result.__class__, signup_code_result=result)
+    
+    def send(self):
+        current_site = Site.objects.get_current()
+        domain = unicode(current_site.domain)
+        ctx = {
+            "signup_code": self,
+            "domain": domain,
+        }
+        subject = render_to_string("account/email/invite_user_subject.txt", ctx)
+        message = render_to_string("account/email/invite_user.txt", ctx)
+        send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [self.email])
+        self.sent = datetime.now()
+        self.save()
+        signup_code_sent.send(sender=SignupCode, signup_code=self)
 
-def entity_id(request):
-    ''' Entity Identifier '''
-    return  "%s://%s/" % (request.META['wsgi.url_scheme'],request.META['HTTP_HOST']   )
 
-def gen_nonce():
-    ''' TODO: 
-    '''
-    return uuid.uuid1().hex
-
-class AbstractUserInfo(models.Model):
-    ''' :term:`OpenID Connect Lite` UserInfo fields
-        
-        - See  :ref:`connect_lite_table_1`
-    '''
-
-    name = models.CharField( u'name', max_length=40 , )
-    ''' User's full name in displayable form including all name parts, ordered according to user's locale and preferences. '''
-
-    given_name  = models.CharField( u'given name', max_length=20 , )
-    ''' Given name or first name of the user. '''
-
-    family_name  = models.CharField( u'family name', max_length=20 , )
-    ''' Given name or first name of the user. '''
-
-    middle_name  = models.CharField( u'middle name', max_length=20 , )
-    ''' Surname or last name of the user.  '''
-
-    nickname  = models.CharField( u'nickname', max_length=20 , )
-    ''' Casual name of the user that may or may not be the same as the given_name. 
-        For instance, a nickname value of Mike might be returned alongside a given_name value of Michael. '''
-
-    profile = models.CharField( u'Profile URL', max_length=200, )
-    ''' URL of user's profile page. '''
-
-    picture = models.CharField( u'Picture URL', max_length=200, )
-    ''' URL of the user's profile picture. '''
-
-    website = models.CharField( u'Website URL', max_length=200, )
-    ''' URL of user's web page or blog. '''
-
-    email= models.CharField( u'Email', max_length=200, )
-    ''' The user's preferred e-mail address. '''
-
-    verified =  models.BooleanField( u'Email is verified or not', default=False )
-    ''' True if the user's e-mail address has been verified; otherwise false. '''
-
-    gender=  models.CharField( u'Gender', max_length=6 , )
-    ''' The user's gender: female or male. '''
-
-    birthday = models.DateField( u'Birthday',  )
-    ''' string  The user's birthday, represented as a date string in MM/DD/YYYY format. The year MAY be 0000, indicating that it is omitted. '''
-
-    zoneinfo = models.CharField(u'ZoneInfo', max_length=30 ,default=settings.TIME_ZONE)
-    ''' String from zoneinfo [zoneinfo] timezone database. For example, Europe/Paris or America/Los_Angeles. '''
-
-    locale =  models.CharField(u'Locale', max_length=30 ,default=settings.LANGUAGE_CODE )
-    ''' The user's locale, represented as an RFC 5646 [:rfc:`5646`] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; Implementations MAY choose to accept this locale syntax as well.
-    '''
+class SignupCodeResult(models.Model):
     
-    phone_number = models.CharField(u'Phone Number', max_length=15,default='' )
-    '''  The user's preferred telephone number. For example, +1 (425) 555-1212 or +56 (2) 687 2400. '''
-
-    address =  models.CharField(u'Address', max_length=15,default='' )
-    ''' The user's preferred address. The value of the address member is a JSON [:rfc:`4627`] structure containing some or all of these string-valued fields: formatted, street_address, locality, region, postal_code, and country. The street_address field MAY contain multiple lines if the address representation requires it. Implementations MAY return only a subset of the fields of an address, depending upon the information available and the user's privacy preferences. For example, the country and region might be returned without returning more fine-grained address information.
-    '''
-
-    updated_time = models.DateTimeField(u'Updated Time',default=datetime.now )
-    ''' Time the user's information was last updated, represented as a RFC 3339 [:rfc:`3339`] datetime. For example, 2011-01-03T23:58:42+0000. '''
-
-    class Meta:
-        abstract =True
-        verbose_name =u'OpenID UserInfo'
-        verbose_name_plural =u'OpenID UserInfo'
-
-class Profile(AbstractUserInfo):
-    ''' User's defualt profile   
-
-        - This model might be an application sepecific.
-        - This will be copied to OpenIDUserInfo .
-    '''
-    user = models.OneToOneField(User,verbose_name= u'System User ')
-    ''' User '''
-
-    def get_dict(self):
-        return dict([ (n,getattr(self,n)) for n in [ f.name for f in self._meta.fields if f.name not in ['user'] ]])
-
-    class Meta:
-        verbose_name =u'Profile'
-        verbose_name_plural =u'Profile'
-
+    signup_code = models.ForeignKey(SignupCode)
+    user = models.ForeignKey(User)
+    timestamp = models.DateTimeField(default=datetime.now)
+    
+    def save(self, **kwargs):
+        super(SignupCodeResult, self).save(**kwargs)
+        self.signup_code.calculate_use_count()

src/accounts/openid/userinfo.py

+# -*- coding: utf-8 -*-
+
+from django.db.models import AutoField,Sum,Max ,Q
+from django.db import models
+from django.contrib.auth.models import User
+from django.contrib.auth import authenticate
+from django.core.urlresolvers import reverse
+
+from datetime import datetime,timedelta
+import pytz
+import pyrfc3339
+import re
+import urllib,urllib2
+import uuid 
+import settings
+#import jwt
+import json
+
+
+def entity_id(request):
+    ''' Entity Identifier '''
+    return  "%s://%s/" % (request.META['wsgi.url_scheme'],request.META['HTTP_HOST']   )
+
+def gen_nonce():
+    ''' TODO: 
+    '''
+    return uuid.uuid1().hex
+
+class AbstractUserInfo(models.Model):
+    ''' :term:`OpenID Connect Lite` UserInfo fields
+        
+        - See  :ref:`connect_lite_table_1`
+    '''
+
+    name = models.CharField( u'name', max_length=40 , )
+    ''' User's full name in displayable form including all name parts, ordered according to user's locale and preferences. '''
+
+    given_name  = models.CharField( u'given name', max_length=20 , )
+    ''' Given name or first name of the user. '''
+
+    family_name  = models.CharField( u'family name', max_length=20 , )
+    ''' Given name or first name of the user. '''
+
+    middle_name  = models.CharField( u'middle name', max_length=20 , )
+    ''' Surname or last name of the user.  '''
+
+    nickname  = models.CharField( u'nickname', max_length=20 , )
+    ''' Casual name of the user that may or may not be the same as the given_name. 
+        For instance, a nickname value of Mike might be returned alongside a given_name value of Michael. '''
+
+    profile = models.CharField( u'Profile URL', max_length=200, )
+    ''' URL of user's profile page. '''
+
+    picture = models.CharField( u'Picture URL', max_length=200, )
+    ''' URL of the user's profile picture. '''
+
+    website = models.CharField( u'Website URL', max_length=200, )
+    ''' URL of user's web page or blog. '''
+
+    email= models.CharField( u'Email', max_length=200, )
+    ''' The user's preferred e-mail address. '''
+
+    verified =  models.BooleanField( u'Email is verified or not', default=False )
+    ''' True if the user's e-mail address has been verified; otherwise false. '''
+
+    gender=  models.CharField( u'Gender', max_length=6 , )
+    ''' The user's gender: female or male. '''
+
+    birthday = models.DateField( u'Birthday',  )
+    ''' string  The user's birthday, represented as a date string in MM/DD/YYYY format. The year MAY be 0000, indicating that it is omitted. '''
+
+    zoneinfo = models.CharField(u'ZoneInfo', max_length=30 ,default=settings.TIME_ZONE)
+    ''' String from zoneinfo [zoneinfo] timezone database. For example, Europe/Paris or America/Los_Angeles. '''
+
+    locale =  models.CharField(u'Locale', max_length=30 ,default=settings.LANGUAGE_CODE )
+    ''' The user's locale, represented as an RFC 5646 [:rfc:`5646`] language tag. This is typically an ISO 639-1 Alpha-2 [ISO639‑1] language code in lowercase and an ISO 3166-1 Alpha-2 [ISO3166‑1] country code in uppercase, separated by a dash. For example, en-US or fr-CA. As a compatibility note, some implementations have used an underscore as the separator rather than a dash, for example, en_US; Implementations MAY choose to accept this locale syntax as well.
+    '''
+    
+    phone_number = models.CharField(u'Phone Number', max_length=15,default='' )
+    '''  The user's preferred telephone number. For example, +1 (425) 555-1212 or +56 (2) 687 2400. '''
+
+    address =  models.CharField(u'Address', max_length=15,default='' )
+    ''' The user's preferred address. The value of the address member is a JSON [:rfc:`4627`] structure containing some or all of these string-valued fields: formatted, street_address, locality, region, postal_code, and country. The street_address field MAY contain multiple lines if the address representation requires it. Implementations MAY return only a subset of the fields of an address, depending upon the information available and the user's privacy preferences. For example, the country and region might be returned without returning more fine-grained address information.
+    '''
+
+    updated_time = models.DateTimeField(u'Updated Time',default=datetime.now )
+    ''' Time the user's information was last updated, represented as a RFC 3339 [:rfc:`3339`] datetime. For example, 2011-01-03T23:58:42+0000. '''
+
+    class Meta:
+        abstract =True
+        verbose_name =u'OpenID UserInfo'
+        verbose_name_plural =u'OpenID UserInfo'
+
+class Profile(AbstractUserInfo):
+    ''' User's defualt profile   
+
+        - This model might be an application sepecific.
+        - This will be copied to OpenIDUserInfo .
+    '''
+    user = models.OneToOneField(User,verbose_name= u'System User ')
+    ''' User '''
+
+    def get_dict(self):
+        return dict([ (n,getattr(self,n)) for n in [ f.name for f in self._meta.fields if f.name not in ['user'] ]])
+
+    class Meta:
+        verbose_name =u'Profile'
+        verbose_name_plural =u'Profile'
+

src/accounts/signals.py

+import django.dispatch
+
+
+signup_code_sent = django.dispatch.Signal(providing_args=["signup_code"])
+signup_code_used = django.dispatch.Signal(providing_args=["signup_code_result"])

src/accounts/urls.py

 # -*- coding: utf-8 -*-
 from django.conf.urls.defaults import *
 
-urlpatterns = patterns('accounts.views',
-    
-    #: Password Authentication
-    url(r'^login/', 'login'   ,name='accounts_login'),
-    url(r'^logout/', 'logout' ,name='accounts_logout' ),
-    #: Profile
-    url(r'^profile/', 'profile' ,name='accounts_profile' ),
-    #: OpenID Authentication
-    url(r'^openid/', include("accounts.openid.views")),
-    url(r'^oauth/', include("accounts.oauth.views")),
+
+#urlpatterns = patterns('accounts.views',
+#    
+#    : Password Authentication
+#    url(r'^login/', 'login'   ,name='accounts_login'),
+#    url(r'^logout/', 'logout' ,name='accounts_logout' ),
+#
+#    : Profile
+#    url(r'^profile/', 'profile' ,name='accounts_profile' ),
+#
+#    : OpenID Authentication
+#    url(r'^openid/', include("accounts.openid.views")),
+#    url(r'^oauth/', include("accounts.oauth.views")),
+#)
+
+from accounts.views import SignupView, LoginView, LogoutView
+
+urlpatterns = patterns("",
+    url(r"^signup/$", SignupView.as_view(), name="account_signup"),
+    url(r"^login/$", LoginView.as_view(), name="account_login"),
+    url(r"^logout/$", LogoutView.as_view(), name="account_logout"),
 )

src/accounts/utils.py

+import urlparse
+
+def default_redirect(request, fallback_url, **kwargs):
+    redirect_field_name = kwargs.get("redirect_field_name", "next")
+    next = request.REQUEST.get(redirect_field_name)
+    if not next:
+        # try the session if available
+        if hasattr(request, "session"):
+            session_key_value = kwargs.get("session_key_value", "redirect_to")
+            next = request.session.get(session_key_value)
+    if next:
+        netloc = urlparse.urlparse(next)[1]
+        redirect_to = next
+        # security check -- don't allow redirection to a different host
+        if netloc and netloc != request.get_host():
+            redirect_to = fallback_url
+    else:
+        redirect_to = fallback_url
+    return redirect_to

src/accounts/views.py

                 { },
                     context_instance=template.RequestContext(request),)
     
+
+######
+
+from django.http import HttpResponse, HttpResponseNotFound, HttpResponseForbidden
+from django.shortcuts import redirect
+from django.views.generic.base import TemplateView
+from django.views.generic.edit import FormView
+
+from django.contrib import auth
+
+from django.conf import settings
+from accounts.forms import SignupForm, LoginUsernameForm
+from accounts.utils import default_redirect
+
+class SignupView(FormView):
+    
+    template_name = "account/signup.html"
+    form_class = SignupForm
+    
+    def get(self, *args, **kwargs):
+        if self.disabled():
+            return HttpResponseNotFound()
+        return super(SignupView, self).get(*args, **kwargs)
+    
+    def post(self, *args, **kwargs):
+        if self.disabled():
+            return HttpResponseNotFound()
+        return super(SignupView, self).post(*args, **kwargs)
+    
+    def disabled(self):
+        return settings.ACCOUNT_SIGNUP_DISABLED
+
+
+class LoginView(FormView):
+    
+    template_name = "accounts/login/page.html"
+    form_class = LoginUsernameForm
+    
+    def get(self, *args, **kwargs):
+        if self.request.user.is_authenticated():
+            return redirect(self.get_success_url())         #django.shortcut.redirect
+        return super(LoginView, self).get(*args, **kwargs)
+    
+    def post(self, *args, **kwargs):
+        if self.disabled():
+            return HttpResponseForbidden()
+        return super(LoginView, self).post(*args, **kwargs)
+    
+    def get_context_data(self, **kwargs):
+        ctx = kwargs
+        if self.disabled():
+            del ctx["form"]
+        return ctx
+    
+    def form_valid(self, form):
+        self.login_user(form)
+        return redirect(self.get_success_url())
+    
+    def get_success_url(self):
+        return default_redirect(self.request, 
+            settings.ACCOUNT_LOGIN_REDIRECT_URL)
+    
+    def disabled(self):
+        return  ""
+        #settings.ACCOUNT_LOGIN_DISABLED
+    
+    def login_user(self, form):
+        auth.login(self.request, form.user)
+        self.request.session.set_expiry(
+            60*60*24*365*10 if form.cleaned_data.get("remember") else 0
+        )
+
+
+class LogoutView(TemplateView):
+    
+    template_name = "accounts/logout/page.html"
+    
+    def get(self, *args, **kwargs):
+        if not self.request.user.is_authenticated():
+            return redirect(self.get_redirect_url())
+        return self.render_to_response({})
+    
+    def post(self, *args, **kwargs):
+        if self.request.user.is_authenticated():
+            auth.logout(self.request)
+        return redirect(self.get_redirect_url())
+    
+    def get_redirect_url(self):
+        return default_redirect(self.request, 
+                settings.ACCOUNT_LOGOUT_REDIRECT_URL)