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@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