mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
Refactored 'users' module
- allows editing users (currently the groups and username) - allows any logged-in user to change the passwords of any other users - improved url highlighting of subsubmenu
This commit is contained in:
parent
733a4fd139
commit
4b3b3c666a
@ -19,9 +19,16 @@
|
|||||||
Plinth module to manage users
|
Plinth module to manage users
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from . import users
|
from gettext import gettext as _
|
||||||
from .users import init
|
from plinth import cfg
|
||||||
|
|
||||||
__all__ = ['users', 'init']
|
|
||||||
|
def init():
|
||||||
|
"""Intialize the user module"""
|
||||||
|
menu = cfg.main_menu.get('system:index')
|
||||||
|
menu.add_urlname(_('Users and Groups'), 'glyphicon-user', 'users:index',
|
||||||
|
15)
|
||||||
|
|
||||||
|
__all__ = ['init']
|
||||||
|
|
||||||
depends = ['plinth.modules.system']
|
depends = ['plinth.modules.system']
|
||||||
|
|||||||
30
plinth/modules/users/forms.py
Normal file
30
plinth/modules/users/forms.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class UserForm(forms.ModelForm):
|
||||||
|
"""Form to change one user"""
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ('username', 'groups')
|
||||||
|
widgets = {
|
||||||
|
'username': forms.widgets.TextInput(attrs={'style': 'width: 50%'}),
|
||||||
|
'groups': forms.widgets.CheckboxSelectMultiple(),
|
||||||
|
}
|
||||||
46
plinth/modules/users/templates/users_change_password.html
Normal file
46
plinth/modules/users/templates/users_change_password.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Change password of {{ form.user.username }}</h3>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form|bootstrap }}
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="Save Password"/>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_js %}
|
||||||
|
<script>
|
||||||
|
$('#id_password1').focus();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -22,14 +22,18 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<form class="form" method="post">
|
<div class="row">
|
||||||
{% csrf_token %}
|
<div class="col-sm-8">
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
{{ form|bootstrap }}
|
{{ form|bootstrap }}
|
||||||
|
|
||||||
<input type="submit" class="btn btn-primary" value="Add User"/>
|
<input type="submit" class="btn btn-primary" value="Create User"/>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -37,7 +41,7 @@
|
|||||||
|
|
||||||
<div class="well sidebar">
|
<div class="well sidebar">
|
||||||
|
|
||||||
<h3>Add User</h3>
|
<h3>Create User</h3>
|
||||||
|
|
||||||
<p>Adding a user via this administrative
|
<p>Adding a user via this administrative
|
||||||
interface <strong>might</strong> create a system user. For
|
interface <strong>might</strong> create a system user. For
|
||||||
@ -48,3 +52,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_js %}
|
||||||
|
<script>
|
||||||
|
$('#id_username').focus();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -22,17 +22,19 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h3>Delete Users</h3>
|
<h3>Delete User {{ object.username }}</h3>
|
||||||
|
<p>
|
||||||
<p>Deleting users is permanent!</p>
|
Deleting is permanent. Are you sure?
|
||||||
|
</p>
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<input type="submit" class="btn btn-md btn-primary"
|
||||||
{{ form|bootstrap }}
|
value="Delete {{ object.username }}"/>
|
||||||
|
<a href="{% url 'users:index' %}" role="button"
|
||||||
<input type="submit" class="btn btn-primary" value="Delete User"/>
|
class="btn btn-md btn-primary">
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
52
plinth/modules/users/templates/users_list.html
Normal file
52
plinth/modules/users/templates/users_list.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Users</h3>
|
||||||
|
<p>
|
||||||
|
You can edit or delete users here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="list-group">
|
||||||
|
{% for user in object_list %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<a href="{% url 'users:edit' user.username %}"
|
||||||
|
style="display:inline-block; width:75%;"
|
||||||
|
title="Edit user {{ user.username }}">
|
||||||
|
{{ user.username }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{% url 'users:delete' user.username %}"
|
||||||
|
class="btn btn-default btn-xs pull-right"
|
||||||
|
role="button" title="Delete user {{ user.username }}">
|
||||||
|
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
58
plinth/modules/users/templates/users_update.html
Normal file
58
plinth/modules/users/templates/users_update.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block page_head %}
|
||||||
|
<style type="text/css">
|
||||||
|
.multiple-checkbox li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h3>Edit {{ object.username }}</h3>
|
||||||
|
|
||||||
|
<p style='font-size:larger'>
|
||||||
|
Use the
|
||||||
|
<a href='{% url 'users:change_password' object.username %}'>
|
||||||
|
Change password form
|
||||||
|
</a> to change the password of {{ object.username }}.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<form class="form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{{ form|bootstrap }}
|
||||||
|
|
||||||
|
<input type="submit" class="btn btn-primary" value="Save Changes"/>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block page_js %}
|
||||||
|
<script>
|
||||||
|
$('#id_username').focus();
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -20,15 +20,17 @@ URLs for the Users module
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from django.views.generic import RedirectView
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns( # pylint: disable-msg=C0103
|
urlpatterns = patterns(
|
||||||
'plinth.modules.users.users',
|
'plinth.modules.users.views',
|
||||||
# create an index page (that only forwards) to have correct highlighting
|
url(r'^sys/users/$', views.UserList.as_view(), name='index'),
|
||||||
# of submenu items
|
url(r'^sys/users/create/$', views.UserCreate.as_view(), name='create'),
|
||||||
url(r'^sys/users/$', RedirectView.as_view(pattern_name='users:add'),
|
url(r'^sys/users/edit/(?P<slug>[\w.@+-]+)$', views.UserUpdate.as_view(),
|
||||||
name='index'),
|
name='edit'),
|
||||||
url(r'^sys/users/add/$', 'add', name='add'),
|
url(r'^sys/users/delete/(?P<slug>[\w.@+-]+)$', views.UserDelete.as_view(),
|
||||||
url(r'^sys/users/edit/$', 'edit', name='edit'),
|
name='delete'),
|
||||||
|
url(r'^sys/users/change_password/(?P<slug>[\w.@+-]+)$',
|
||||||
|
views.UserChangePassword.as_view(), name='change_password'),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,160 +0,0 @@
|
|||||||
#
|
|
||||||
# This file is part of Plinth.
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Affero General Public License as
|
|
||||||
# published by the Free Software Foundation, either version 3 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program 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 Affero General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.core import validators
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from gettext import gettext as _
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from plinth import cfg
|
|
||||||
from plinth.modules.lib.auth import add_user
|
|
||||||
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
subsubmenu = {'title': _('Users and Groups'),
|
|
||||||
'items': [{'url': reverse_lazy('users:add'),
|
|
||||||
'text': _('Add User')},
|
|
||||||
{'url': reverse_lazy('users:edit'),
|
|
||||||
'text': _('Delete Users')}]}
|
|
||||||
|
|
||||||
|
|
||||||
def init():
|
|
||||||
"""Intialize the module"""
|
|
||||||
menu = cfg.main_menu.get('system:index')
|
|
||||||
menu.add_urlname(_('Users and Groups'), 'glyphicon-user', 'users:index',
|
|
||||||
15)
|
|
||||||
|
|
||||||
|
|
||||||
class UserAddForm(forms.Form): # pylint: disable-msg=W0232
|
|
||||||
"""Form to add a new user"""
|
|
||||||
|
|
||||||
username = forms.CharField(
|
|
||||||
label=_('Username'),
|
|
||||||
help_text=_('Must be lower case alphanumeric and start with \
|
|
||||||
and alphabet'),
|
|
||||||
validators=[
|
|
||||||
validators.RegexValidator(r'^[a-z][a-z0-9]*$',
|
|
||||||
_('Invalid username'))])
|
|
||||||
|
|
||||||
password = forms.CharField(label=_('Password'),
|
|
||||||
widget=forms.PasswordInput())
|
|
||||||
full_name = forms.CharField(label=_('Full name'), required=False)
|
|
||||||
email = forms.EmailField(label=_('Email'), required=False)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add(request):
|
|
||||||
"""Serve the form"""
|
|
||||||
form = None
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = UserAddForm(request.POST, prefix='user')
|
|
||||||
# pylint: disable-msg=E1101
|
|
||||||
if form.is_valid():
|
|
||||||
_add_user(request, form.cleaned_data)
|
|
||||||
form = UserAddForm(prefix='user')
|
|
||||||
else:
|
|
||||||
form = UserAddForm(prefix='user')
|
|
||||||
|
|
||||||
return TemplateResponse(request, 'users_add.html',
|
|
||||||
{'title': _('Add User'),
|
|
||||||
'form': form,
|
|
||||||
'subsubmenu': subsubmenu})
|
|
||||||
|
|
||||||
|
|
||||||
def _add_user(request, data):
|
|
||||||
"""Add a user"""
|
|
||||||
if User.objects.filter(username=data['username']).exists():
|
|
||||||
messages.error(request, _('User "{username}" already exists').format(
|
|
||||||
username=data['username']))
|
|
||||||
return
|
|
||||||
|
|
||||||
add_user(data['username'], data['password'], data['full_name'],
|
|
||||||
data['email'], False)
|
|
||||||
messages.success(request, _('User "{username}" added').format(
|
|
||||||
username=data['username']))
|
|
||||||
|
|
||||||
|
|
||||||
class UserEditForm(forms.Form): # pylint: disable-msg=W0232
|
|
||||||
"""Form to edit/delete a user"""
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# pylint: disable-msg=E1002
|
|
||||||
super(forms.Form, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
for user in User.objects.all():
|
|
||||||
label = '%s (%s)' % (user.first_name, user.username)
|
|
||||||
field = forms.BooleanField(label=label, required=False)
|
|
||||||
# pylint: disable-msg=E1101
|
|
||||||
self.fields['delete_user_' + user.username] = field
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def edit(request):
|
|
||||||
"""Serve the edit form"""
|
|
||||||
form = None
|
|
||||||
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = UserEditForm(request.POST, prefix='user')
|
|
||||||
# pylint: disable-msg=E1101
|
|
||||||
if form.is_valid():
|
|
||||||
_apply_edit_changes(request, form.cleaned_data)
|
|
||||||
form = UserEditForm(prefix='user')
|
|
||||||
else:
|
|
||||||
form = UserEditForm(prefix='user')
|
|
||||||
|
|
||||||
return TemplateResponse(request, 'users_edit.html',
|
|
||||||
{'title': _('Delete Users'),
|
|
||||||
'form': form,
|
|
||||||
'subsubmenu': subsubmenu})
|
|
||||||
|
|
||||||
|
|
||||||
def _apply_edit_changes(request, data):
|
|
||||||
"""Apply form changes"""
|
|
||||||
for field, value in data.items():
|
|
||||||
if not value:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not field.startswith('delete_user_'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
username = field.split('delete_user_')[1]
|
|
||||||
|
|
||||||
requesting_user = request.user.username
|
|
||||||
LOGGER.info('%s asked to delete %s', requesting_user, username)
|
|
||||||
|
|
||||||
if username == requesting_user:
|
|
||||||
messages.error(
|
|
||||||
request, _('Can not delete current account - "%s"') % username)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not User.objects.filter(username=username).exists():
|
|
||||||
messages.error(request, _('User "%s" does not exist') % username)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
User.objects.filter(username=username).delete()
|
|
||||||
messages.success(request, _('User "%s" deleted') % username)
|
|
||||||
except IOError as exception:
|
|
||||||
messages.error(request, _('Error deleting "%s" - %s') %
|
|
||||||
(username, exception))
|
|
||||||
128
plinth/modules/users/views.py
Normal file
128
plinth/modules/users/views.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth import update_session_auth_hash
|
||||||
|
from django.contrib.auth.forms import UserCreationForm, AdminPasswordChangeForm
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
|
from django.views.generic.edit import (CreateView, DeleteView, UpdateView,
|
||||||
|
FormView)
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from gettext import gettext as _
|
||||||
|
from .forms import UserForm
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: we do not use the title anymore, and 'items' is also a python keyword.
|
||||||
|
# For all subsubmenus: let's remove title and just use the items list directly.
|
||||||
|
# Make sure to update the tests too.
|
||||||
|
subsubmenu = {'title': _('Users and Groups'),
|
||||||
|
'items': [{'url': reverse_lazy('users:index'),
|
||||||
|
'text': _('Users')},
|
||||||
|
{'url': reverse_lazy('users:create'),
|
||||||
|
'text': _('Create User')}]}
|
||||||
|
|
||||||
|
|
||||||
|
class PlinthContextMixin():
|
||||||
|
"""Add 'subsubmenu' and 'title' to the context of class-based views"""
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""Use self.title and the module-level subsubmenu"""
|
||||||
|
context = super(PlinthContextMixin, self).get_context_data(**kwargs)
|
||||||
|
context['subsubmenu'] = subsubmenu
|
||||||
|
context['title'] = getattr(self, 'title', '')
|
||||||
|
return context
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(PlinthContextMixin, SuccessMessageMixin, CreateView):
|
||||||
|
form_class = UserCreationForm
|
||||||
|
template_name = 'users_create.html'
|
||||||
|
model = User
|
||||||
|
success_message = "%(username)s was created successfully"
|
||||||
|
success_url = reverse_lazy('users:create')
|
||||||
|
title = _('Create User')
|
||||||
|
|
||||||
|
|
||||||
|
class UserList(PlinthContextMixin, ListView):
|
||||||
|
model = User
|
||||||
|
template_name = 'users_list.html'
|
||||||
|
title = _('Edit or Delete User')
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(PlinthContextMixin, SuccessMessageMixin, UpdateView):
|
||||||
|
template_name = 'users_update.html'
|
||||||
|
form_class = UserForm
|
||||||
|
model = User
|
||||||
|
slug_field = "username"
|
||||||
|
success_message = "User %(username)s was changed successfully"
|
||||||
|
fields = ['username', 'password']
|
||||||
|
exclude = ('last_login', 'email', 'first_name', 'last_name')
|
||||||
|
title = _('Edit User')
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('users:edit', kwargs={'slug': self.object.username})
|
||||||
|
|
||||||
|
|
||||||
|
class UserDelete(PlinthContextMixin, DeleteView):
|
||||||
|
"""Handle deleting users, showing a confirmation dialog first
|
||||||
|
|
||||||
|
On GET, display a confirmation page
|
||||||
|
on POST, delete the user
|
||||||
|
"""
|
||||||
|
template_name = 'users_delete.html'
|
||||||
|
model = User
|
||||||
|
slug_field = "username"
|
||||||
|
success_url = reverse_lazy('users:index')
|
||||||
|
title = _('Delete User')
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Set the success message of deleting the user.
|
||||||
|
|
||||||
|
The SuccessMessageMixin doesn't work with the DeleteView on Django1.7,
|
||||||
|
so set the success message manually here.
|
||||||
|
"""
|
||||||
|
message = _("User %s was deleted" % self.kwargs['slug'])
|
||||||
|
output = super(UserDelete, self).delete(*args, **kwargs)
|
||||||
|
messages.success(self.request, message)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class UserChangePassword(PlinthContextMixin, SuccessMessageMixin, FormView):
|
||||||
|
template_name = 'users_change_password.html'
|
||||||
|
form_class = AdminPasswordChangeForm
|
||||||
|
slug_field = "username"
|
||||||
|
model = User
|
||||||
|
title = _('Create User')
|
||||||
|
success_message = _("The password was changed successfully")
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
"""Make the user object available to the form"""
|
||||||
|
kwargs = super(UserChangePassword, self).get_form_kwargs()
|
||||||
|
kwargs['user'] = User.objects.get(username=self.kwargs['slug'])
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('users:edit', kwargs={'slug': self.kwargs['slug']})
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
if form.user == self.request.user:
|
||||||
|
update_session_auth_hash(self.request, form.user)
|
||||||
|
return super(UserChangePassword, self).form_valid(form)
|
||||||
@ -1,4 +1,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load plinth_extras %}
|
||||||
|
|
||||||
{% comment %}
|
{% comment %}
|
||||||
#
|
#
|
||||||
# This file is part of Plinth.
|
# This file is part of Plinth.
|
||||||
@ -145,14 +147,7 @@
|
|||||||
|
|
||||||
{% block subsubmenu %}
|
{% block subsubmenu %}
|
||||||
{% if subsubmenu %}
|
{% if subsubmenu %}
|
||||||
<ul class="nav nav-tabs">
|
{% show_subsubmenu subsubmenu %}
|
||||||
{% for urlitem in subsubmenu.items %}
|
|
||||||
<li {% if urlitem.url == request.path %} class="active"{% endif %}
|
|
||||||
role="presentation">
|
|
||||||
<a href="{{ urlitem.url }}">{{ urlitem.text }}</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
27
plinth/templates/subsubmenu.html
Normal file
27
plinth/templates/subsubmenu.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% comment %}
|
||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
{% for urlitem in subsubmenu.items %}
|
||||||
|
<li {% if urlitem.active %} class="active"{% endif %}
|
||||||
|
role="presentation">
|
||||||
|
<a href="{{ urlitem.url }}">{{ urlitem.text }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
0
plinth/templatetags/__init__.py
Normal file
0
plinth/templatetags/__init__.py
Normal file
64
plinth/templatetags/plinth_extras.py
Normal file
64
plinth/templatetags/plinth_extras.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#
|
||||||
|
# This file is part of Plinth.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
def mark_active_menuitem(menu, path):
|
||||||
|
"""Mark the best-matching menu item with 'active'
|
||||||
|
|
||||||
|
Input: a menu dict in the form of:
|
||||||
|
{'title': 'x',
|
||||||
|
'items': [{'url': 'a/b', 'text': 'myUrl'}, {'url': ...}]
|
||||||
|
}
|
||||||
|
|
||||||
|
Output: The same dictionary; the best-matching URL dict gets the value
|
||||||
|
'active': True. All other URL dicts get the value 'active': False.
|
||||||
|
|
||||||
|
Note: this sets the 'active' values on the menu itself, not on a copy.
|
||||||
|
"""
|
||||||
|
best_match = ''
|
||||||
|
best_match_item = None
|
||||||
|
|
||||||
|
for urlitem in menu['items']:
|
||||||
|
urlitem['active'] = False
|
||||||
|
# TODO: use a more suitable function instead of os.path.commonprefix
|
||||||
|
match = os.path.commonprefix([urlitem['url'], path])
|
||||||
|
# In case of 'xx/create' and 'xx/change' we'd have 'xx/c' as prefix.
|
||||||
|
# That's a wrong match, skip it.
|
||||||
|
match_clean = match.rpartition('/')[0]
|
||||||
|
if (len(match_clean) + 1) < len(match):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(match_clean) > len(best_match):
|
||||||
|
best_match = match
|
||||||
|
best_match_item = urlitem
|
||||||
|
|
||||||
|
if best_match_item:
|
||||||
|
best_match_item['active'] = True
|
||||||
|
|
||||||
|
return menu
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('subsubmenu.html', takes_context=True)
|
||||||
|
def show_subsubmenu(context, menudata):
|
||||||
|
"""Mark the active menu item and display the subsubmenu"""
|
||||||
|
menudata = mark_active_menuitem(menudata, context['request'].path)
|
||||||
|
return {'subsubmenu': menudata}
|
||||||
Loading…
x
Reference in New Issue
Block a user