Merge pull request #146 from d1ffuz0r/issue_101

#101 #103 added change password, login, logout views
This commit is contained in:
Daniel Greenfeld 2013-05-30 23:25:39 -07:00
commit 9e2349be7f
10 changed files with 271 additions and 10 deletions

View file

@ -125,6 +125,18 @@ class Admin2(object):
view=self.index_view.as_view(**self.get_index_kwargs()),
name='dashboard'
),
url(regex='^auth/user/(?P<pk>\d+)/update/password/$',
view=views.PasswordChangeView.as_view(),
name='password_change'
),
url(regex='^password_change_done/$',
view=views.PasswordChangeDoneView.as_view(),
name='password_change_done'
),
url(regex='^logout/$',
view=views.LogoutView.as_view(),
name='logout'
),
url(
regex=r'^(?P<app_label>\w+)/$',
view=self.app_index_view.as_view(**self.get_app_index_kwargs()),
@ -136,6 +148,7 @@ class Admin2(object):
name='api-index'
),
)
for model, model_admin in self.registry.iteritems():
model_options = utils.model_options(model)
urlpatterns += patterns('',

View file

@ -0,0 +1,25 @@
{% extends "admin2/bootstrap/base.html" %}
{% load i18n %}
{% load admin2_tags %}
{% block content %}
<div class="row">
<div class="span12">
{% if form.errors %}
<p class="error-note">
{% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="this_is_the_login_form" value="1" />
<input type="hidden" name="next" value="{{ next }}" />
<button class="btn btn-small btn-success" type="submit">{% trans "Log in" %}</button>
</form>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,18 @@
{% extends "admin2/bootstrap/base.html" %}
{% load i18n %}
{% load admin2_tags %}
{% block breadcrumbs %}
<li><a href="{% url "admin2:dashboard" %}">Home</a> <span class="divider">/</span></li>
<li class="active">{% trans "Logout" %}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="span12">
<p>{% trans "Thanks for spending some quality time with the Web site today." %}</p>
<p><p><a href="{% url 'admin2:dashboard' %}">{% trans 'Log in again' %}</a></p></p>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,20 @@
{% extends "admin2/bootstrap/base.html" %}
{% load i18n %}
{% load admin2_tags %}
{% block title %}{% trans 'Password change successful' %}{% endblock %}
{% block page_title %}{% trans 'Password change successful' %}{% endblock %}
{% block breadcrumbs %}
<li><a href="{% url "admin2:dashboard" %}">Home</a> <span class="divider">/</span></li>
<li class="active">{% trans "Password change successful" %}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="span12">
<p>{% trans 'Your password was changed.' %}</p>
</div>
</div>
{% endblock content %}

View file

@ -0,0 +1,33 @@
{% extends "admin2/bootstrap/base.html" %}
{% load i18n %}
{% load admin2_tags %}
{% block page_title %}{% trans "Password change" %}: {{ form.user }}{% endblock %}
{% block breadcrumbs %}
<li><a href="{% url "admin2:dashboard" %}">Home</a> <span class="divider">/</span></li>
<li class="active">{% trans "Password change" %} <span class="divider">/</span></li>
<li class="active">{{ form.user }}</li>
{% endblock %}
{% block content %}
<div class="row">
<div class="span12">
<p>Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.</p>
{% if form.errors %}
<p class="error-note">
{% blocktrans count counter=form.errors.items|length %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
</p>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-small btn-success" type="submit" name="_save">{% trans "Change my password" %}</button>
</form>
</div>
</div>
{% endblock content %}

View file

@ -13,16 +13,18 @@
<div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="brand" href="{% url 'admin2:dashboard' %}">Django-Admin2</a>
<a tabindex="1" class="brand" href="{% url 'admin2:dashboard' %}">Django-Admin2</a>
<ul class="nav pull-right">
<li><a tabindex="-1" href="{% url 'admin2:api-index' %}">{% trans "API" %}</a></li>
<li><a tabindex="2" href="{% url 'admin2:api-index' %}">{% trans "API" %}</a></li>
{% if docsroot %}
<li><a href="{{ docsroot }}">{% trans 'Documentation' %}</a></li>
<li><a tabindex="3" href="{{ docsroot }}">{% trans 'Documentation' %}</a></li>
{% endif %}
{% if user.is_authenticated %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<a tabindex="4" href="#" class="dropdown-toggle" data-toggle="dropdown">
{% if user.get_full_name %}
{{ user.get_full_name }}
{% else %}
@ -34,11 +36,12 @@
</a>
<ul class="dropdown-menu">
{% if user.has_usable_password %}
<li><a tabindex="-1" href="TODO">{% trans "Change password" %}</a></li>
<li><a href="{% url 'admin2:password_change' user.id %}">{% trans "Change password" %}</a></li>
{% endif %}
<li><a tabindex="-1" href="TODO">{% trans "Log out" %}</a></li>
<li><a href="{% url 'admin2:logout' %}">{% trans "Log out" %}</a></li>
</ul>
</li>
{% endif %}
</ul>
</div>
</div>

View file

@ -32,4 +32,4 @@ class Admin2Test(TestCase):
def test_get_urls(self):
self.admin2.register(Thing)
self.assertEquals(5, len(self.admin2.get_urls()))
self.assertEquals(8, len(self.admin2.get_urls()))

View file

@ -2,9 +2,9 @@ import os
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse, reverse_lazy
from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect
from braces.views import AccessMixin
from . import constants, permissions
@ -14,6 +14,7 @@ from .utils import admin2_urlname, model_options
class PermissionMixin(AccessMixin):
do_not_call_in_templates = True
permission_classes = (permissions.IsStaffPermission,)
login_url = reverse_lazy('admin2:dashboard')
def __init__(self, **kwargs):
self.permissions = [
@ -60,6 +61,8 @@ class Admin2Mixin(PermissionMixin):
model_name = None
app_label = None
index_path = reverse_lazy('admin2:dashboard')
def get_template_names(self):
return [os.path.join(constants.ADMIN2_THEME_DIRECTORY, self.default_template_name)]
@ -74,6 +77,27 @@ class Admin2Mixin(PermissionMixin):
return self.form_class
return modelform_factory(self.get_model())
def is_user(self, request):
return hasattr(request, 'user') and not (request.user.is_active and
request.user.is_staff)
def dispatch(self, request, *args, **kwargs):
if self.is_user(request):
from .views import LoginView
if request.path == reverse('admin2:logout'):
return HttpResponseRedirect(self.index_path)
if request.path == self.index_path:
extra = {
'next': request.GET.get('next', self.index_path)
}
return LoginView().dispatch(request, extra_context=extra,
*args, **kwargs)
return super(Admin2Mixin, self).dispatch(request, *args, **kwargs)
class AdminModel2Mixin(Admin2Mixin):
model_admin = None

View file

@ -1,4 +1,10 @@
from django.core.urlresolvers import reverse
from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.forms import (PasswordChangeForm,
AdminPasswordChangeForm)
from django.contrib.auth.views import (logout as auth_logout,
login as auth_login)
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect
from django.utils.encoding import force_text
from django.utils.text import capfirst
@ -128,3 +134,53 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
'deletable_objects': collector.nested(_format_callback)
})
return context
class PasswordChangeView(Admin2Mixin, generic.UpdateView):
default_template_name = 'auth/password_change_form.html'
form_class = AdminPasswordChangeForm
admin_form_class = PasswordChangeForm
model = get_user_model()
success_url = reverse_lazy('admin2:password_change_done')
def get_form_kwargs(self, **kwargs):
data = {'user': self.get_object()}
if self.request.method in ('POST', 'PUT'):
data.update({
'data': self.request.POST
})
return data
def get_form_class(self):
if self.request.user == self.get_object():
return self.admin_form_class
return super(PasswordChangeView, self).get_form_class()
class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView):
default_template_name = 'auth/password_change_done.html'
class LoginView(Admin2Mixin, generic.TemplateView):
default_template_name = 'auth/login.html'
authentication_form = AdminAuthenticationForm
def dispatch(self, request, *args, **kwargs):
return auth_login(request,
authentication_form=self.authentication_form,
template_name=self.get_template_names(),
*args, **kwargs)
class LogoutView(Admin2Mixin, generic.TemplateView):
default_template_name = 'auth/logout.html'
def get(self, request, *args, **kwargs):
return auth_logout(request, template_name=self.get_template_names(),
*args, **kwargs)

View file

@ -160,3 +160,72 @@ class PostDeleteActionTest(BaseIntegrationTest):
post_data, follow=True)
self.assertContains(response, "Successfully deleted 2 posts")
class TestAuthViews(TestCase):
def setUp(self):
self.client = Client()
self.user = get_user_model()(username='user', is_staff=True,
is_superuser=True)
self.user.set_password("password")
self.user.save()
def test_login_required_redirect_to_index(self):
index_path = reverse('admin2:dashboard') + '?next=/admin2/blog/post/'
target_path = reverse('admin2:blog_post_index')
self.assertRedirects(self.client.get(target_path), index_path)
def test_login_required_logined_successful(self):
index_path = reverse('admin2:dashboard')
self.client.login(username=self.user.username,
password='password')
self.assertContains(self.client.get(index_path),
reverse('admin2:blog_post_index'))
def test_change_password_for_myself(self):
self.client.login(username=self.user.username,
password='password')
request = self.client.post(reverse('admin2:password_change',
kwargs={'pk': self.user.pk}),
{'old_password': 'password',
'new_password1': 'user',
'new_password2': 'user'})
self.assertRedirects(request, reverse('admin2:password_change_done'))
self.client.logout()
self.assertFalse(self.client.login(username=self.user.username,
password='password'))
self.assertTrue(self.client.login(username=self.user.username,
password='user'))
def test_change_password(self):
self.client.login(username=self.user.username,
password='password')
new_user = get_user_model()(username='new_user')
new_user.set_password("new_user")
new_user.save()
request = self.client.post(reverse('admin2:password_change',
kwargs={'pk': new_user.pk}),
{'old_password': 'new_user',
'password1': 'new_user_password',
'password2': 'new_user_password'})
self.assertRedirects(request, reverse('admin2:password_change_done'))
self.client.logout()
self.assertFalse(self.client.login(username=new_user.username,
password='new_user'))
self.assertTrue(self.client.login(username=new_user.username,
password='new_user_password'))
def test_logout(self):
self.client.login(username=self.user.username,
password='password')
logout_path = reverse('admin2:logout')
request = self.client.get(logout_path)
self.assertContains(request, 'Log in again')
index_path = reverse('admin2:dashboard') + '?next=/admin2/blog/post/'
target_path = reverse('admin2:blog_post_index')
self.assertRedirects(self.client.get(target_path), index_path)