[PATCH] Users can register for an account.
by Darryl L. Pierce
Users can go to /register/ to create a new account. Accounts must have
unique login names and email addresses.
Ensures that all required fields are provided.
When a well-formed user object is registered then it is saved and the
user is redirected to the login page.
Created a fixture for users.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
web/projxp/fixtures/users.yaml | 15 ++++
web/projxp/login/models.py | 3 +
web/projxp/login/tests.py | 23 ++++++
web/projxp/login/views.py | 31 +++++++
web/projxp/register/__init__.py | 27 ++++++
web/projxp/register/models.py | 34 ++++++++
web/projxp/register/registration.py | 50 ++++++++++++
web/projxp/register/tests.py | 114 +++++++++++++++++++++++++++
web/projxp/register/views.py | 54 +++++++++++++
web/projxp/settings.py | 5 +-
web/projxp/templates/register/login.html | 4 +
web/projxp/templates/register/register.html | 16 ++++
web/projxp/urls.py | 18 +++--
13 files changed, 386 insertions(+), 8 deletions(-)
create mode 100644 web/projxp/fixtures/users.yaml
create mode 100644 web/projxp/login/__init__.py
create mode 100644 web/projxp/login/models.py
create mode 100644 web/projxp/login/tests.py
create mode 100644 web/projxp/login/views.py
create mode 100644 web/projxp/register/__init__.py
create mode 100644 web/projxp/register/models.py
create mode 100644 web/projxp/register/registration.py
create mode 100644 web/projxp/register/tests.py
create mode 100644 web/projxp/register/views.py
create mode 100644 web/projxp/templates/register/login.html
create mode 100644 web/projxp/templates/register/register.html
diff --git a/web/projxp/fixtures/users.yaml b/web/projxp/fixtures/users.yaml
new file mode 100644
index 0000000..be9d451
--- /dev/null
+++ b/web/projxp/fixtures/users.yaml
@@ -0,0 +1,15 @@
+- fields:
+ date_joined: 2010-02-21 13:01:52.920958
+ email: mcpierce(a)gmail.com
+ first_name: Darryl
+ groups: []
+ is_active: true
+ is_staff: false
+ is_superuser: false
+ last_login: 2010-02-21 13:01:52.920917
+ last_name: Pierce
+ password: sha1$1a8a7$28eebe7fb5afc7d27aecd3ae0724a33d46c74d24
+ user_permissions: []
+ username: mcpierce
+ model: auth.user
+ pk: 1
diff --git a/web/projxp/login/__init__.py b/web/projxp/login/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web/projxp/login/models.py b/web/projxp/login/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/web/projxp/login/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/web/projxp/login/tests.py b/web/projxp/login/tests.py
new file mode 100644
index 0000000..2247054
--- /dev/null
+++ b/web/projxp/login/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/web/projxp/login/views.py b/web/projxp/login/views.py
new file mode 100644
index 0000000..944c674
--- /dev/null
+++ b/web/projxp/login/views.py
@@ -0,0 +1,31 @@
+# views.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ProjXP - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory,
+# and generally should not be modified by end users.
+
+from django.http import HttpResponse
+
+def login(request):
+ return HttpResponse("Not ready yet.")
diff --git a/web/projxp/register/__init__.py b/web/projxp/register/__init__.py
new file mode 100644
index 0000000..4deea80
--- /dev/null
+++ b/web/projxp/register/__init__.py
@@ -0,0 +1,27 @@
+# __init__.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ProjXP - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory,
+# and generally should not be modified by end users.
+
diff --git a/web/projxp/register/models.py b/web/projxp/register/models.py
new file mode 100644
index 0000000..0aff3b6
--- /dev/null
+++ b/web/projxp/register/models.py
@@ -0,0 +1,34 @@
+# models.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ProjXP - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory,
+# and generally should not be modified by end users.
+
+from django.db import models
+
+from django.contrib.auth.models import User
+
+class UserVerification(models.Model):
+ user = models.ForeignKey(User)
+ key = models.CharField(max_length=16, unique=True)
diff --git a/web/projxp/register/registration.py b/web/projxp/register/registration.py
new file mode 100644
index 0000000..2664fb6
--- /dev/null
+++ b/web/projxp/register/registration.py
@@ -0,0 +1,50 @@
+# registration.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from django.forms.util import ErrorList
+
+from django.contrib.auth.models import User
+
+class RegistrationForm(forms.Form):
+ username = forms.CharField(max_length=30)
+ email = forms.EmailField(max_length=75)
+ first_name = forms.CharField(max_length=30)
+ last_name = forms.CharField(max_length=30)
+ password = forms.CharField(max_length=128)
+ confirmpassword = forms.CharField(max_length=128)
+
+ def clean(self):
+ cleaned_data = self.cleaned_data
+ username = cleaned_data.get('username')
+ existing = User.objects.filter(username=username)
+ if len(existing) is not 0:
+ self._errors['username'] = ErrorList([u'\'%s\' is already used.' % username])
+
+ email = cleaned_data.get('email')
+ existing = User.objects.filter(email=email)
+ if len(existing) is not 0:
+ self._errors['email'] = ErrorList([u'\'%s\' is already used.' % email])
+
+ password = cleaned_data.get('password')
+ confirmation = cleaned_data.get('confirmpassword')
+ if password is not None and len(password) > 0 and confirmation is not None and len(confirmation) > 0:
+ if password != confirmation:
+ self._errors['confirmpassword'] = ErrorList([u'The passwords do not match.'])
+
+ return cleaned_data
diff --git a/web/projxp/register/tests.py b/web/projxp/register/tests.py
new file mode 100644
index 0000000..25ef6bb
--- /dev/null
+++ b/web/projxp/register/tests.py
@@ -0,0 +1,114 @@
+# tests.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ProjXP - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory,
+# and generally should not be modified by end users.
+
+from django.test import TestCase
+from django.test.client import Client
+
+from django.contrib.auth.models import User
+
+class RegistrationTest(TestCase):
+ fixtures = ['fixtures/users.yaml']
+
+ def setUp(self):
+ self.client = Client()
+ self.data = {
+ 'username' :'newuser',
+ 'email' :'newuser(a)localhost.com',
+ 'first_name' :'New',
+ 'last_name' :'User',
+ 'password' :'farkle',
+ 'confirmpassword':'farkle'
+ }
+
+ def test_register(self):
+ """Ensures that the register url works."""
+ response = self.client.get("/register/")
+ self.failUnlessEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'register/register.html')
+
+ def test_register_without_username(self):
+ """Ensures that registration requires a username."""
+ self.data['username'] = ''
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='username', errors=u'This field is required.')
+
+ def test_register_with_existing_username(self):
+ """Ensures that you cannot register with an existing username."""
+ existing = User.objects.all()[0]
+ self.data['username'] = existing.username
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='username', errors=u'\'%s\' is already used.' % self.data['username'])
+
+ def test_register_without_email(self):
+ """Ensures that an email address is required."""
+ self.data['email'] = ""
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='email', errors=u'This field is required.')
+
+ def test_register_with_existing_email(self):
+ """Ensures that you cannot register with an existing username."""
+ existing = User.objects.all()[0]
+ self.data['email'] = existing.email
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='email', errors=u'\'%s\' is already used.' % self.data['email'])
+
+ def test_register_without_first_name(self):
+ """Ensures that a first name is required."""
+ self.data['first_name'] = ''
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='first_name', errors=u'This field is required.')
+
+ def test_register_without_last_name(self):
+ """Ensures that a last name is required."""
+ self.data['last_name'] = ''
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='last_name', errors=u'This field is required.')
+
+ def test_register_without_password(self):
+ """Ensures that a password is required."""
+ self.data['password'] = ''
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='password', errors=u'This field is required.')
+
+ def test_register_without_password_confirmation(self):
+ """Ensures that a confirmation password is required."""
+ self.data['confirmpassword'] = ''
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='confirmpassword', errors=u'This field is required.')
+
+ def test_register_password_mismatch(self):
+ """Ensures that a password is required."""
+ self.data['password'] = str.swapcase(self.data['confirmpassword'])
+ response = self.client.post('/register/', data=self.data)
+ self.assertFormError(response, 'form', field='confirmpassword', errors=u'The passwords do not match.')
+
+ def test_ensure_user_registers(self):
+ """Ensures that registration works as expected."""
+ response = self.client.post('/register/', data=self.data)
+ self.assertRedirects(response, '/login/')
+ result = User.objects.get(username=self.data['username'])
+ assert result is not None
diff --git a/web/projxp/register/views.py b/web/projxp/register/views.py
new file mode 100644
index 0000000..3d89100
--- /dev/null
+++ b/web/projxp/register/views.py
@@ -0,0 +1,54 @@
+# views.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ProjXP - Pylons development environment configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+# This file is for deployment specific config options -- other configuration
+# that is always required for the app is done in the config directory,
+# and generally should not be modified by end users.
+
+from django.contrib.auth.models import User
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from registration import RegistrationForm
+
+def register(request):
+ """
+ Creates the user's entry in the database.
+ If the username is already in use then the user is redirected to
+ the login form with an error message that the username already exists.
+ """
+ if request.method == 'POST':
+ form = RegistrationForm(request.POST)
+ if form.is_valid():
+ user = User(username=form.cleaned_data.get('username'),
+ email=form.cleaned_data.get('email'),
+ first_name=form.cleaned_data.get('first_name'),
+ last_name=form.cleaned_data.get('last_name'))
+ user.set_password(form.cleaned_data.get('password'))
+ user.save()
+ return HttpResponseRedirect('/login/') # send the user to login
+ else:
+ form = RegistrationForm()
+
+ return render_to_response('register/register.html', {'form':form})
diff --git a/web/projxp/settings.py b/web/projxp/settings.py
index d6f9755..c5fbe11 100644
--- a/web/projxp/settings.py
+++ b/web/projxp/settings.py
@@ -37,7 +37,7 @@ ADMINS = (
MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
-DATABASE_NAME = 'projxp' # Or path to database file if using sqlite3.
+DATABASE_NAME = 'devdata.db' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
@@ -96,11 +96,14 @@ TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
+ "templates"
)
INSTALLED_APPS = (
+# 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
+ 'projxp.register',
)
diff --git a/web/projxp/templates/register/login.html b/web/projxp/templates/register/login.html
new file mode 100644
index 0000000..134ee2f
--- /dev/null
+++ b/web/projxp/templates/register/login.html
@@ -0,0 +1,4 @@
+<form action="/login/" method="POST">
+ <label for="username">Username:</label><input type="text" name="username" value="{{username}}" /><br />
+ <label form="password">Password:</label><input type="password" name="password" /><br />
+</form>
diff --git a/web/projxp/templates/register/register.html b/web/projxp/templates/register/register.html
new file mode 100644
index 0000000..58e4278
--- /dev/null
+++ b/web/projxp/templates/register/register.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+ <head>
+ <meta context="text/html; charset=UTF-8" htttp-equiv="content-type" />
+ <title>User Registration</title>
+ </head>
+
+ <body>
+ <form action="/register/" method="POST">
+ {{ form.as_p }}
+ <input type="submit" value="Register" />
+ </form>
+ </body>
+</html>
diff --git a/web/projxp/urls.py b/web/projxp/urls.py
index 64bc52e..82c8f10 100644
--- a/web/projxp/urls.py
+++ b/web/projxp/urls.py
@@ -32,13 +32,17 @@ from django.conf.urls.defaults import *
# admin.autodiscover()
urlpatterns = patterns('',
- # Example:
- # (r'^projxp/', include('projxp.foo.urls')),
+ # Example:
+ # (r'^projxp/', include('projxp.foo.urls')),
- # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
- # to INSTALLED_APPS to enable admin documentation:
- # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
+ # to INSTALLED_APPS to enable admin documentation:
+ # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
- # Uncomment the next line to enable the admin:
- # (r'^admin/', include(admin.site.urls)),
+ # Uncomment the next line to enable the admin:
+ # (r'^admin/', include(admin.site.urls)),
+
+ # User login and registration
+ (r'^login/$', 'projxp.login.views.login'),
+ (r'^register/$', 'projxp.register.views.register'),
)
--
1.6.6
14 years, 2 months
[PATCH] Users can register for an account.
by Darryl L. Pierce
Started the slow process of cleaning up the boiler plate TG2 web
appearance.
Added a "Register" link to the toolbar with the Login/Logout links.
Created the new RESTful controller, UserController. An unauthenticated
user can create a new account view the UserController.new method.
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
web/projxp/controllers/root.py | 4 +-
web/projxp/controllers/user.py | 58 ++++++++++++++++++++++++++
web/projxp/templates/authentication.html | 67 ------------------------------
web/projxp/templates/header.html | 18 ++++----
web/projxp/templates/master.html | 59 +++++++++++++-------------
web/projxp/templates/register.html | 26 +++++++++++
6 files changed, 125 insertions(+), 107 deletions(-)
create mode 100644 web/projxp/controllers/user.py
delete mode 100644 web/projxp/templates/authentication.html
create mode 100644 web/projxp/templates/register.html
diff --git a/web/projxp/controllers/root.py b/web/projxp/controllers/root.py
index a4458b7..8634bad 100644
--- a/web/projxp/controllers/root.py
+++ b/web/projxp/controllers/root.py
@@ -29,6 +29,7 @@ from projxp.model import DBSession, metadata
from projxp.controllers.error import ErrorController
from projxp import model
from projxp.controllers.secure import SecureController
+from projxp.controllers.user import UserController
__all__ = ['RootController']
@@ -48,10 +49,9 @@ class RootController(BaseController):
"""
secc = SecureController()
-
admin = Catwalk(model, DBSession)
-
error = ErrorController()
+ users = UserController()
@expose('projxp.templates.index')
def index(self):
diff --git a/web/projxp/controllers/user.py b/web/projxp/controllers/user.py
new file mode 100644
index 0000000..271fd98
--- /dev/null
+++ b/web/projxp/controllers/user.py
@@ -0,0 +1,58 @@
+# user.py
+# Copyright (C) 2010, Darryl L. Pierce
+#
+# This file is part of ProjXP.
+#
+# ProjXP is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# ProjXP is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ProjXP. If not, see <http://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+"""Provides RESTful methods for working with users."""
+
+# turbogears imports
+from tg import expose, redirect, request, validate
+from formencode.validators import NotEmpty
+
+# third party imports
+#from pylons.i18n import ugettext as _
+#from repoze.what import predicates
+
+# project specific imports
+from tg.controllers import RestController
+from projxp.model import DBSession, metadata
+from projxp.model.auth import User
+
+class UserController(RestController):
+ #Uncomment this line if your controller requires an authenticated user
+ # allow_only = authorize.not_anonymous()
+
+ @expose('projxp.templates.register')
+ def new(self, **kw):
+ # TODO need to write a unit test to enforce that authorized users cannot register
+ if request.identity:
+ redirect("/")
+ return dict(values=kw)
+
+ @validate({'login':NotEmpty,
+ 'email':NotEmpty,
+ 'password':NotEmpty,
+ 'confirmpassword':NotEmpty}, error_handler=new)
+ @expose()
+ def post(self, login, email, password, confirmpassword):
+ # TODO need to write a unit test to enforce that authorized users cannot register
+ if request.identity:
+ redirect("/")
+ user = User(user_name=login, email_address=email)
+ user.password = password
+ DBSession.add(user)
+ redirect('/login')
diff --git a/web/projxp/templates/authentication.html b/web/projxp/templates/authentication.html
deleted file mode 100644
index d86204b..0000000
--- a/web/projxp/templates/authentication.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude">
-
- <xi:include href="master.html" />
-
-<head>
- <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
- <title>Learning TurboGears 2.0: Quick guide to authentication.</title>
-</head>
-
-<body>
- ${sidebar_top()}
- ${sidebar_bottom()}
- <div id="getting_started">
- <h2>Authentication & Authorization in a TG2 site.</h2>
- <p>If you have access to this page, this means you have enabled authentication and authorization
- in the quickstart to create your project.</p>
- <p>
- The paster command will have created a few specific controllers for you. But before you
- go to play with those controllers you'll need to make sure your application has been
- properly bootstapped.
- This is dead easy, here is how to do this:
- </p>
-
- <span class="code">
- paster setup-app development.ini
- </span>
-
- <p>
- inside your application's folder and you'll get a database setup (using the preferences you have
- set in your development.ini file). This database will also have been prepopulated with some
- default logins/passwords so that you can test the secured controllers and methods.
- </p>
- <p>
- To change the comportement of this setup-app command you just need to edit the <span class="code">websetup.py</span> file.
- </p>
- <p>
- Now try to visiting the <a href="${tg.url('/manage_permission_only')}">manage_permission_only</a> URL. You will be challenged with a login/password form.
- </p>
- <p>
- Only managers are authorized to visit this method. You will need to log-in using:
- <p>
- <span class="code">
- login: manager
- </span>
- </p>
- <p>
- <span class="code">
- password: managepass
- </span>
- </p>
- </p>
- <p>
- Another protected resource is <a href="${tg.url('/editor_user_only')}">editor_user_only</a>. This one is protected by a different set of permissions.
- You will need to be <span class="code">editor</span> with a password of <span class="code">editpass</span> to be able to access it.
- </p>
- <p>
- The last kind of protected resource in this quickstarted app is a full so called <a href="${tg.url('/secc')}">secure controller</a>. This controller is protected globally.
- Instead of having a @require decorator on each method, we have set an allow_only attribute at the class level. All the methods in this controller will
- require the same level of access. You need to be manager to access <a href="${tg.url('/secc')}">secc</a> or <a href="${tg.url('/secc/some_where')}">secc/some_where</a>.
- </p>
- </div>
-</body>
-</html>
diff --git a/web/projxp/templates/header.html b/web/projxp/templates/header.html
index 0429cd0..119acb2 100644
--- a/web/projxp/templates/header.html
+++ b/web/projxp/templates/header.html
@@ -1,12 +1,12 @@
<html xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip="">
-<py:def function="header">
- <div id="header">
- <h1>
- Welcome to TurboGears 2
- <span class="subtitle">The Python web metaframework</span>
- </h1>
- </div>
-</py:def>
-</html>
\ No newline at end of file
+ <py:def function="header">
+ <div id="header">
+ <h1>
+ ProjXP
+ <span class="subtitle">The agile project management system</span>
+ </h1>
+ </div>
+ </py:def>
+</html>
diff --git a/web/projxp/templates/master.html b/web/projxp/templates/master.html
index e267395..21d9008 100644
--- a/web/projxp/templates/master.html
+++ b/web/projxp/templates/master.html
@@ -1,44 +1,45 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip="">
- <xi:include href="header.html" />
- <xi:include href="sidebars.html" />
- <xi:include href="footer.html" />
-<head py:match="head" py:attrs="select('@*')">
+ <xi:include href="header.html" />
+ <xi:include href="sidebars.html" />
+ <xi:include href="footer.html" />
+ <head py:match="head" py:attrs="select('@*')">
<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
<title py:replace="''">Your title goes here</title>
<meta py:replace="select('*')"/>
<link rel="stylesheet" type="text/css" media="screen" href="${tg.url('/css/style.css')}" />
-</head>
+ </head>
-<body py:match="body" py:attrs="select('@*')">
- ${header()}
- <ul id="mainmenu">
- <li class="first"><a href="${tg.url('/')}" class="${('', 'active')[defined('page') and page==page=='index']}">Welcome</a></li>
- <li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
- <li py:if="tg.auth_stack_enabled"><a href="${tg.url('/auth')}" class="${('', 'active')[defined('page') and page==page=='auth']}">Authentication</a></li>
- <li><a href="http://groups.google.com/group/turbogears">Contact</a></li>
- <span py:if="tg.auth_stack_enabled" py:strip="True">
+ <body py:match="body" py:attrs="select('@*')">
+ ${header()}
+ <ul id="mainmenu">
+ <li class="first"><a href="${tg.url('/')}" class="${('', 'active')[defined('page') and page==page=='index']}">Welcome</a></li>
+ <li><a href="${tg.url('/about')}" class="${('', 'active')[defined('page') and page==page=='about']}">About</a></li>
+ <li py:if="tg.auth_stack_enabled"><a href="${tg.url('/auth')}" class="${('', 'active')[defined('page') and page==page=='auth']}">Authentication</a></li>
+ <li><a href="http://groups.google.com/group/turbogears">Contact</a></li>
+ <span py:if="tg.auth_stack_enabled" py:strip="True">
<li py:if="not request.identity" id="login" class="loginlogout"><a href="${tg.url('/login')}">Login</a></li>
+ <li py:if="not request.identity" id="register" class="loginlogout"><a href="${tg.url('/register')}">Register</a></li>
<li py:if="request.identity" id="login" class="loginlogout"><a href="${tg.url('/logout_handler')}">Logout</a></li>
<li py:if="request.identity" id="admin" class="loginlogout"><a href="${tg.url('/admin')}">Admin</a></li>
- </span>
- </ul>
- <div id="content">
- <py:if test="defined('page')">
- <div class="currentpage">
- Now Viewing: <span py:replace="page"/>
- </div>
- </py:if>
- <py:with vars="flash=tg.flash_obj.render('flash', use_js=False)">
+ </span>
+ </ul>
+ <div id="content">
+ <py:if test="defined('page')">
+ <div class="currentpage">
+ Now Viewing: <span py:replace="page"/>
+ </div>
+ </py:if>
+ <py:with vars="flash=tg.flash_obj.render('flash', use_js=False)">
<div py:if="flash" py:content="XML(flash)" />
- </py:with>
- <div py:replace="select('*|text()')"/>
- <!-- End of content -->
- ${footer()}
- </div>
-</body>
+ </py:with>
+ <div py:replace="select('*|text()')"/>
+ <!-- End of content -->
+ ${footer()}
+ </div>
+ </body>
</html>
diff --git a/web/projxp/templates/register.html b/web/projxp/templates/register.html
new file mode 100644
index 0000000..b293e1b
--- /dev/null
+++ b/web/projxp/templates/register.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+
+<head>
+<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+<title>User Registration Form</title>
+</head>
+
+<body>
+<div id="loginform">
+<form action="./" method="POST">
+ <h2><span>Login</span></h2>
+ <label for="login">Username:</label><input type="text" id="login" name="login" class="text"></input><br />
+ <label for="email">Email Address:</label><input type="text" id="email" name="email" class="text"></input><br />
+ <label for="password">Password:</label><input type="password" id="password" name="password" class="text"></input><br />
+ <label for="confirmpassword">Confirm Password:</label><input type="password" id="confirmpassword" name="confirmpassword" class="text"></input>
+ <input type="submit" id="submit" value="Register" />
+</form>
+</div>
+</body>
+</html>
--
1.6.6
14 years, 2 months
[PATCH] Users must have a unique email address, a password and a display name.
by Darryl L. Pierce
Renamed the following tables:
* tg_user => users
* tg_group_permission => group_permissions
* tg_group => groups
* tg_user_group => user_groups
* tg_permission => permissions
Signed-off-by: Darryl L. Pierce <mcpierce(a)gmail.com>
---
web/projxp/model/__init__.py | 2 +-
web/projxp/model/auth.py | 86 ++++++++----------------------------------
2 files changed, 17 insertions(+), 71 deletions(-)
diff --git a/web/projxp/model/__init__.py b/web/projxp/model/__init__.py
index 02cfd34..d7ed3c4 100644
--- a/web/projxp/model/__init__.py
+++ b/web/projxp/model/__init__.py
@@ -1,5 +1,5 @@
# __init__.py
-# Copyright (C) 2010 Darryl L. Pierce
+# Copyright (C) 2010, Darryl L. Pierce
#
# This file is part of ProjXP.
#
diff --git a/web/projxp/model/auth.py b/web/projxp/model/auth.py
index 3b6a7f0..0963a2f 100644
--- a/web/projxp/model/auth.py
+++ b/web/projxp/model/auth.py
@@ -1,5 +1,5 @@
# auth.py
-# Copyright (C) 2010 Darryl L. Pierce
+# Copyright (C) 2010, Darryl L. Pierce
#
# This file is part of ProjXP.
#
@@ -45,66 +45,40 @@ from projxp.model import DeclarativeBase, metadata, DBSession
__all__ = ['User', 'Group', 'Permission']
-
-#{ Association tables
-
-
-# This is the association table for the many-to-many relationship between
-# groups and permissions. This is required by repoze.what.
-group_permission_table = Table('tg_group_permission', metadata,
- Column('group_id', Integer, ForeignKey('tg_group.group_id',
- onupdate="CASCADE", ondelete="CASCADE")),
- Column('permission_id', Integer, ForeignKey('tg_permission.permission_id',
- onupdate="CASCADE", ondelete="CASCADE"))
-)
-
-# This is the association table for the many-to-many relationship between
-# groups and members - this is, the memberships. It's required by repoze.what.
-user_group_table = Table('tg_user_group', metadata,
- Column('user_id', Integer, ForeignKey('tg_user.user_id',
- onupdate="CASCADE", ondelete="CASCADE")),
- Column('group_id', Integer, ForeignKey('tg_group.group_id',
- onupdate="CASCADE", ondelete="CASCADE"))
-)
-
-
-#{ The auth* model itself
-
+group_permission_table = Table('group_permissions', metadata,
+ Column('group_id', Integer, ForeignKey('groups.group_id',
+ onupdate="CASCADE", ondelete="CASCADE")),
+ Column('permission_id', Integer, ForeignKey('permissions.permission_id',
+ onupdate="CASCADE", ondelete="CASCADE"))
+ )
+user_group_table = Table('user_groups', metadata,
+ Column('user_id', Integer, ForeignKey('users.user_id',
+ onupdate="CASCADE", ondelete="CASCADE")),
+ Column('group_id', Integer, ForeignKey('groups.group_id',
+ onupdate="CASCADE", ondelete="CASCADE"))
+ )
class Group(DeclarativeBase):
"""
Group definition for :mod:`repoze.what`.
-
Only the ``group_name`` column is required by :mod:`repoze.what`.
-
"""
- __tablename__ = 'tg_group'
-
- #{ Columns
+ __tablename__ = 'groups'
group_id = Column(Integer, autoincrement=True, primary_key=True)
-
group_name = Column(Unicode(16), unique=True, nullable=False)
-
display_name = Column(Unicode(255))
-
created = Column(DateTime, default=datetime.now)
- #{ Relations
-
users = relation('User', secondary=user_group_table, backref='groups')
- #{ Special methods
-
def __repr__(self):
return '<Group: name=%s>' % self.group_name
def __unicode__(self):
return self.group_name
- #}
-
# The 'info' argument we're passing to the email_address and password columns
# contain metadata that Rum (http://python-rum.org/) can use generate an
@@ -115,37 +89,25 @@ class User(DeclarativeBase):
This is the user definition used by :mod:`repoze.who`, which requires at
least the ``user_name`` column.
-
"""
- __tablename__ = 'tg_user'
- #{ Columns
+ __tablename__ = 'users'
user_id = Column(Integer, autoincrement=True, primary_key=True)
-
user_name = Column(Unicode(16), unique=True, nullable=False)
-
email_address = Column(Unicode(255), unique=True, nullable=False,
info={'rum': {'field':'Email'}})
-
display_name = Column(Unicode(255))
-
_password = Column('password', Unicode(80),
info={'rum': {'field':'Password'}})
-
created = Column(DateTime, default=datetime.now)
- #{ Special methods
-
def __repr__(self):
return '<User: email="%s", display name="%s">' % (
self.email_address, self.display_name)
-
def __unicode__(self):
return self.display_name or self.user_name
- #{ Getters and setters
-
@property
def permissions(self):
"""Return a set of strings for the permissions granted."""
@@ -194,8 +156,6 @@ class User(DeclarativeBase):
password = synonym('_password', descriptor=property(_get_password,
_set_password))
- #}
-
def validate_password(self, password):
"""
Check the password against existing credentials.
@@ -218,33 +178,19 @@ class Permission(DeclarativeBase):
Permission definition for :mod:`repoze.what`.
Only the ``permission_name`` column is required by :mod:`repoze.what`.
-
"""
- __tablename__ = 'tg_permission'
-
- #{ Columns
+ __tablename__ = 'permissions'
permission_id = Column(Integer, autoincrement=True, primary_key=True)
-
permission_name = Column(Unicode(16), unique=True, nullable=False)
-
description = Column(Unicode(255))
- #{ Relations
-
groups = relation(Group, secondary=group_permission_table,
backref='permissions')
- #{ Special methods
-
def __repr__(self):
return '<Permission: name=%s>' % self.permission_name
def __unicode__(self):
return self.permission_name
-
- #}
-
-
-#}
--
1.6.6
14 years, 2 months