INITIAL
This commit is contained in:
commit
7ba937ef66
66 changed files with 1823 additions and 0 deletions
1
ccdb/users/__init__.py
Normal file
1
ccdb/users/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
37
ccdb/users/admin.py
Normal file
37
ccdb/users/admin.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
|
||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class MyUserChangeForm(UserChangeForm):
|
||||
class Meta(UserChangeForm.Meta):
|
||||
model = User
|
||||
|
||||
|
||||
class MyUserCreationForm(UserCreationForm):
|
||||
error_message = UserCreationForm.error_messages.update({
|
||||
'duplicate_username': 'This username has already been taken.'
|
||||
})
|
||||
|
||||
class Meta(UserCreationForm.Meta):
|
||||
model = User
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data["username"]
|
||||
try:
|
||||
User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
return username
|
||||
raise forms.ValidationError(self.error_messages['duplicate_username'])
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(AuthUserAdmin):
|
||||
form = MyUserChangeForm
|
||||
add_form = MyUserCreationForm
|
13
ccdb/users/middleware.py
Normal file
13
ccdb/users/middleware.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import pytz
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class TimezoneMiddleware(object):
|
||||
def process_request(self, request):
|
||||
if not request.user.is_anonymous():
|
||||
tzname = request.user.timezone
|
||||
if tzname:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
else:
|
||||
timezone.deactivate()
|
44
ccdb/users/migrations/0001_initial.py
Normal file
44
ccdb/users/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
import django.contrib.auth.models
|
||||
import django.core.validators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0006_require_contenttypes_0002'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
|
||||
('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)),
|
||||
('username', models.CharField(max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], verbose_name='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True)),
|
||||
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
|
||||
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
|
||||
('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)),
|
||||
('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)),
|
||||
('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)),
|
||||
('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)),
|
||||
('groups', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user')),
|
||||
('user_permissions', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', help_text='Specific permissions for this user.', related_query_name='user')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Name of User', blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'abstract': False,
|
||||
'verbose_name_plural': 'users',
|
||||
},
|
||||
managers=[
|
||||
(b'objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
26
ccdb/users/migrations/0002_timezone.py
Normal file
26
ccdb/users/migrations/0002_timezone.py
Normal file
File diff suppressed because one or more lines are too long
0
ccdb/users/migrations/__init__.py
Normal file
0
ccdb/users/migrations/__init__.py
Normal file
21
ccdb/users/models.py
Normal file
21
ccdb/users/models.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
import pytz
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
name = models.CharField(_("Name of User"), blank=True, max_length=255)
|
||||
timezone = models.CharField(_("Current Timezone"), max_length=255,
|
||||
default="UTC", choices=[(x, x) for x in pytz.common_timezones],
|
||||
blank=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('users:detail', kwargs={'username': self.username})
|
0
ccdb/users/tests/__init__.py
Normal file
0
ccdb/users/tests/__init__.py
Normal file
11
ccdb/users/tests/factories.py
Normal file
11
ccdb/users/tests/factories.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import factory
|
||||
|
||||
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
username = factory.Sequence(lambda n: 'user-{0}'.format(n))
|
||||
email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n))
|
||||
password = factory.PostGenerationMethodCall('set_password', 'password')
|
||||
|
||||
class Meta:
|
||||
model = 'users.User'
|
||||
django_get_or_create = ('username', )
|
39
ccdb/users/tests/test_admin.py
Normal file
39
ccdb/users/tests/test_admin.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from test_plus.test import TestCase
|
||||
|
||||
from ..admin import MyUserCreationForm
|
||||
|
||||
|
||||
class TestMyUserCreationForm(TestCase):
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
|
||||
def test_clean_username_success(self):
|
||||
# Instantiate the form with a new username
|
||||
form = MyUserCreationForm({
|
||||
'username': 'alamode',
|
||||
'password1': '123456',
|
||||
'password2': '123456',
|
||||
})
|
||||
# Run is_valid() to trigger the validation
|
||||
valid = form.is_valid()
|
||||
self.assertTrue(valid)
|
||||
|
||||
# Run the actual clean_username method
|
||||
username = form.clean_username()
|
||||
self.assertEqual('alamode', username)
|
||||
|
||||
def test_clean_username_false(self):
|
||||
# Instantiate the form with the same username as self.user
|
||||
form = MyUserCreationForm({
|
||||
'username': self.user.username,
|
||||
'password1': '123456',
|
||||
'password2': '123456',
|
||||
})
|
||||
# Run is_valid() to trigger the validation, which is going to fail
|
||||
# because the username is already taken
|
||||
valid = form.is_valid()
|
||||
self.assertFalse(valid)
|
||||
|
||||
# The form.errors dict should contain a single error called 'username'
|
||||
self.assertTrue(len(form.errors) == 1)
|
||||
self.assertTrue('username' in form.errors)
|
18
ccdb/users/tests/test_models.py
Normal file
18
ccdb/users/tests/test_models.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from test_plus.test import TestCase
|
||||
|
||||
|
||||
class TestUser(TestCase):
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
|
||||
def test__str__(self):
|
||||
self.assertEqual(
|
||||
self.user.__str__(),
|
||||
"testuser" # This is the default username for self.make_user()
|
||||
)
|
||||
|
||||
def test_get_absolute_url(self):
|
||||
self.assertEqual(
|
||||
self.user.get_absolute_url(),
|
||||
'/users/testuser/'
|
||||
)
|
66
ccdb/users/tests/test_views.py
Normal file
66
ccdb/users/tests/test_views.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
from django.test import RequestFactory
|
||||
|
||||
from test_plus.test import TestCase
|
||||
|
||||
from ..views import (
|
||||
UserRedirectView,
|
||||
UserUpdateView
|
||||
)
|
||||
|
||||
|
||||
class BaseUserTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.user = self.make_user()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
|
||||
class TestUserRedirectView(BaseUserTestCase):
|
||||
def test_get_redirect_url(self):
|
||||
# Instantiate the view directly. Never do this outside a test!
|
||||
view = UserRedirectView()
|
||||
# Generate a fake request
|
||||
request = self.factory.get('/fake-url')
|
||||
# Attach the user to the request
|
||||
request.user = self.user
|
||||
# Attach the request to the view
|
||||
view.request = request
|
||||
# Expect: '/users/testuser/', as that is the default username for
|
||||
# self.make_user()
|
||||
self.assertEqual(
|
||||
view.get_redirect_url(),
|
||||
'/users/testuser/'
|
||||
)
|
||||
|
||||
|
||||
class TestUserUpdateView(BaseUserTestCase):
|
||||
def setUp(self):
|
||||
# call BaseUserTestCase.setUp()
|
||||
super(TestUserUpdateView, self).setUp()
|
||||
# Instantiate the view directly. Never do this outside a test!
|
||||
self.view = UserUpdateView()
|
||||
# Generate a fake request
|
||||
request = self.factory.get('/fake-url')
|
||||
# Attach the user to the request
|
||||
request.user = self.user
|
||||
# Attach the request to the view
|
||||
self.view.request = request
|
||||
|
||||
def test_get_success_url(self):
|
||||
# Expect: '/users/testuser/', as that is the default username for
|
||||
# self.make_user()
|
||||
self.assertEqual(
|
||||
self.view.get_success_url(),
|
||||
'/users/testuser/'
|
||||
)
|
||||
|
||||
# TODO: write this test
|
||||
# def test_get_redirected(self):
|
||||
# Expect '/users/testuser01/' to redirect, because that isn't the
|
||||
# currently logged in user
|
||||
|
||||
def test_get_object(self):
|
||||
# Expect: self.user, as that is the request's user object
|
||||
self.assertEqual(
|
||||
self.view.get_object(),
|
||||
self.user
|
||||
)
|
14
ccdb/users/urls.py
Normal file
14
ccdb/users/urls.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(regex=r'^$', view=views.UserListView.as_view(), name='list'),
|
||||
url(regex=r'^redirect/$', view=views.UserRedirectView.as_view(), name='redirect'),
|
||||
url(regex=r'^(?P<username>[\w.@+-]+)/$', view=views.UserDetailView.as_view(), name='detail'),
|
||||
url(regex=r'^(?P<username>[\w.@+-]+)/update/$', view=views.UserUpdateView.as_view(), name='update'),
|
||||
]
|
51
ccdb/users/views.py
Normal file
51
ccdb/users/views.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from braces.views import LoginRequiredMixin
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserDetailView(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
# These next two lines tell the view to index lookups by username
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
||||
|
||||
|
||||
class UserRedirectView(LoginRequiredMixin, RedirectView):
|
||||
permanent = False
|
||||
|
||||
def get_redirect_url(self):
|
||||
return reverse("users:detail",
|
||||
kwargs={"username": self.request.user.username})
|
||||
|
||||
|
||||
class UserUpdateView(LoginRequiredMixin, UpdateView):
|
||||
fields = ['name', 'timezone']
|
||||
model = User
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.username != kwargs.pop("username", None):
|
||||
return redirect(reverse("users:detail",
|
||||
kwargs={"username": request.user.username}))
|
||||
return super(UserUpdateView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("users:detail",
|
||||
kwargs={"username": self.request.user.username})
|
||||
|
||||
def get_object(self):
|
||||
# Only get the User record for the user making the request
|
||||
return User.objects.get(username=self.request.user.username)
|
||||
|
||||
|
||||
class UserListView(LoginRequiredMixin, ListView):
|
||||
model = User
|
||||
# These next two lines tell the view to index lookups by username
|
||||
slug_field = "username"
|
||||
slug_url_kwarg = "username"
|
Loading…
Add table
Add a link
Reference in a new issue