Repository: interlegis/sapl

Issue:
Criar tela para administração do usuário
Conforme verificado recentemente, permitir que o usuário acesse a tela 'admin' do Django mostrou-se uma fonte de problemas, pois permite que os "administradores" do SAPL alterem campos de tabelas que não devem ser alteradas.

Desta forma, vamos ter que implementar uma tela para cadastrar/excluir/desabilitar  usuários e atribuir perfis (ROLES), e daí vetar todo e qualquer acesso à tela admin. Apesar de termos congelado as mudanças para o 3.1, vejo que essa mudança precisa entrar ainda nesta versão para que possamos mitigar a quantidade de erro que o usuário pode realizar ao acessar a tela admin.


Discussion:

Resolving patch:
diff --git a/sapl/base/forms.py b/sapl/base/forms.py
--- a/sapl/base/forms.py
+++ b/sapl/base/forms.py
@@ -22,7 +22,7 @@
 from sapl.utils import (RANGE_ANOS, ChoiceWithoutValidationField,
                         ImageThumbnailFileInput, RangeWidgetOverride,
                         autor_label, autor_modal, models_with_gr_for_model,
-                        qs_override_django_filter)
+                        qs_override_django_filter, YES_NO_CHOICES)
 
 from .models import AppConfig, CasaLegislativa
 
@@ -40,6 +40,100 @@
     ('X', _('Excluir Usuário')),
 ]
 
+class UsuarioCreateForm(ModelForm):
+
+    username = forms.CharField(required=True, label="Nome de usuário")
+    firstname = forms.CharField(required=True, label="Nome")
+    lastname = forms.CharField(required=True, label="Sobrenome")
+    password1 = forms.CharField(required=True, widget=forms.PasswordInput, label='Senha')
+    password2 = forms.CharField(required=True, widget=forms.PasswordInput, label='Confirmar senha')
+    user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES, label="Usuário ativo?", initial='True')
+
+    ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
+
+    roles = forms.MultipleChoiceField(required=True, widget=forms.CheckboxSelectMultiple(), choices=ROLES)
+
+    class Meta:
+        model = get_user_model()
+        fields = ['username', 'firstname', 'lastname', 'email', 'password1', 'password2', 'user_active', 'roles']
+
+    def clean(self):
+        super(UsuarioCreateForm, self).clean()
+
+        if not self.is_valid():
+            return self.cleaned_data
+
+        data = self.cleaned_data
+        if data['password1'] != data['password2']:
+                raise ValidationError('Senhas informadas são diferentes')
+
+    def __init__(self, *args, **kwargs):
+
+        super(UsuarioCreateForm, self).__init__(*args, **kwargs)
+
+        row0 = to_row([('username', 12)])
+
+        row1 = to_row([('firstname', 6),
+                       ('lastname', 6)])
+
+        row2 = to_row([('email', 6),
+                       ('user_active', 6)])
+        row3 = to_row(
+            [('password1', 6),
+             ('password2', 6)])
+
+        row4 = to_row([(form_actions(label='Confirmar'), 6)])
+
+        self.helper = FormHelper()
+        self.helper.layout = Layout(
+            row0,
+            row1,
+            row3,
+            row2,
+            'roles',
+            row4)
+
+class UsuarioEditForm(ModelForm):
+    ROLES = [(g.id, g.name) for g in Group.objects.all().order_by('name')]
+
+    password1 = forms.CharField(required=False, widget=forms.PasswordInput, label='Senha')
+    password2 = forms.CharField(required=False, widget=forms.PasswordInput, label='Confirmar senha')
+    user_active = forms.ChoiceField(choices=YES_NO_CHOICES, required=True, label="Usuário ativo?", initial='True')
+    roles = forms.MultipleChoiceField(required=True, widget=forms.CheckboxSelectMultiple(), choices=ROLES)
+
+    class Meta:
+        model = get_user_model()
+        fields = ['email', 'password1', 'password2', 'user_active', 'roles']
+
+    def __init__(self, *args, **kwargs):
+
+        super(UsuarioEditForm, self).__init__(*args, **kwargs)
+
+        row1 = to_row([('email', 6),
+                       ('user_active', 6)])
+        row2 = to_row(
+            [('password1', 6),
+             ('password2', 6)])
+
+        row3 = to_row([(form_actions(label='Salvar Alterações'), 6)])
+
+        self.helper = FormHelper()
+        self.helper.layout = Layout(
+            row1,
+            row2,
+            'roles',
+            row3)
+
+    def clean(self):
+        super(UsuarioEditForm, self).clean()
+
+        if not self.is_valid():
+            return self.cleaned_data
+
+        data = self.cleaned_data
+        if data['password1'] and data['password1'] != data['password2']:
+                raise ValidationError('Senhas informadas são diferentes')
+
 
 class TipoAutorForm(ModelForm):
 
diff --git a/sapl/base/urls.py b/sapl/base/urls.py
--- a/sapl/base/urls.py
+++ b/sapl/base/urls.py
@@ -17,10 +17,18 @@
                     RelatorioMateriasPorAutorView,
                     RelatorioMateriasTramitacaoView,
                     RelatorioPresencaSessaoView, SaplSearchView,
-                    RelatorioDataFimPrazoTramitacaoView)
+                    RelatorioDataFimPrazoTramitacaoView, ListarUsuarioView, EditUsuarioView, CreateUsuarioView,
+                    DeleteUsuarioView)
 
 app_name = AppConfig.name
 
+admin_user = [
+    url(r'^sistema/usuario/$', ListarUsuarioView.as_view(), name='user_list'),
+    url(r'^sistema/usuario/create$', CreateUsuarioView.as_view(), name='user_create'),
+    url(r'^sistema/usuario/(?P<pk>\d+)/edit$', EditUsuarioView.as_view(), name='user_edit'),
+    url(r'^sistema/usuario/(?P<pk>\d+)/delete$', DeleteUsuarioView.as_view(), name='user_delete')
+]
+
 alterar_senha = [
     url(r'^sistema/alterar-senha/$',
         AlterarSenha.as_view(),
@@ -111,4 +119,4 @@
 
     url(r'^sistema/search/', SaplSearchView(), name='haystack_search'),
 
-] + recuperar_senha + alterar_senha
+] + recuperar_senha + alterar_senha + admin_user
diff --git a/sapl/base/views.py b/sapl/base/views.py
--- a/sapl/base/views.py
+++ b/sapl/base/views.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.contrib.auth import get_user_model, update_session_auth_hash
+from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.contrib.auth.models import Group
 from django.contrib.auth.tokens import default_token_generator
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
@@ -13,13 +14,13 @@
 from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import string_concat
-from django.views.generic import FormView
+from django.views.generic import FormView, ListView, DetailView, UpdateView, CreateView, DeleteView
 from django.views.generic.base import TemplateView
 from django_filters.views import FilterView
 from haystack.views import SearchView
 from sapl.base.forms import AutorForm, AutorFormForAdmin, TipoAutorForm
 from sapl.base.models import Autor, TipoAutor
-from sapl.crud.base import CrudAux
+from sapl.crud.base import (CrudAux, make_pagination)
 from sapl.materia.models import (Autoria, MateriaLegislativa,
                                  TipoMateriaLegislativa)
 from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
@@ -34,7 +35,7 @@
                     RelatorioMateriasPorAutorFilterSet,
                     RelatorioMateriasTramitacaoilterSet,
                     RelatorioPresencaSessaoFilterSet,
-                    RelatorioDataFimPrazoTramitacaoFilterSet)
+                    RelatorioDataFimPrazoTramitacaoFilterSet, UsuarioEditForm, UsuarioCreateForm)
 from .models import AppConfig, CasaLegislativa
 
 
@@ -520,6 +521,118 @@ def get_context_data(self, **kwargs):
         return context
 
 
+class ListarUsuarioView(PermissionRequiredMixin, ListView):
+    model = get_user_model()
+    template_name = 'auth/user_list.html'
+    context_object_name = 'user_list'
+    permission_required = ('base.list_appconfig',)
+    paginate_by = 10
+
+    def get_queryset(self):
+        qs = super(ListarUsuarioView, self).get_queryset()
+        return qs.order_by('username')
+
+    def get_context_data(self, **kwargs):
+        context = super(ListarUsuarioView, self).get_context_data(**kwargs)
+        paginator = context['paginator']
+        page_obj = context['page_obj']
+        context['page_range'] = make_pagination(
+            page_obj.number, paginator.num_pages)
+        context['NO_ENTRIES_MSG'] = 'Nenhum usuário cadastrado.'
+        return context
+
+
+class CreateUsuarioView(PermissionRequiredMixin, CreateView):
+    model = get_user_model()
+    form_class = UsuarioCreateForm
+    success_message = 'Usuário criado com sucesso'
+    permission_required = ('base.add_appconfig',)
+
+    def get_success_url(self):
+        return reverse('sapl.base:user_list')
+
+    def form_valid(self, form):
+
+        data = form.cleaned_data
+
+        new_user = get_user_model().objects.create(username=data['username'], email=data['email'])
+        new_user.first_name = data['firstname']
+        new_user.last_name = data['lastname']
+        new_user.set_password(data['password1'])
+        new_user.is_superuser = False
+        new_user.is_staff = False
+        new_user.save()
+
+        groups = Group.objects.filter(id__in=data['roles'])
+        for g in groups:
+            g.user_set.add(new_user)
+
+        return HttpResponseRedirect(self.get_success_url())
+
+class DeleteUsuarioView(PermissionRequiredMixin, DeleteView):
+
+    model = get_user_model()
+    permission_required = ('base.delete_appconfig',)
+
+    def get_success_url(self):
+        return reverse('sapl.base:user_list')
+
+    def get(self, request, *args, **kwargs):
+        return self.post(request, *args, **kwargs)
+
+    def get_queryset(self):
+        qs = super(DeleteUsuarioView, self).get_queryset()
+        return qs.filter(id=self.kwargs['pk'])
+
+
+
+class EditUsuarioView(PermissionRequiredMixin, UpdateView):
+    model = get_user_model()
+    form_class = UsuarioEditForm
+    success_message = 'Usuário editado com sucesso'
+    permission_required = ('base.change_appconfig',)
+
+    def get_success_url(self):
+        return reverse('sapl.base:user_list')
+
+    def get_initial(self):
+        initial = super(EditUsuarioView, self).get_initial()
+
+        user = get_user_model().objects.get(id=self.kwargs['pk'])
+        roles = [str(g.id) for g in user.groups.all()]
+        initial['roles'] = roles
+        initial['user_active'] = user.is_active
+
+        return initial
+
+    def form_valid(self, form):
+
+        user = form.save(commit=False)
+        data = form.cleaned_data
+
+        # new_user.first_name = data['firstname']
+        # new_user.last_name = data['lastname']
+
+        if data['password1']:
+            user.set_password(data['password1'])
+
+        if data['user_active'] == 'True' and not user.is_active:
+            user.is_active = True
+        elif data['user_active'] == 'False' and user.is_active:
+            user.is_active = False
+
+        user.save()
+
+        for g in user.groups.all():
+            g.user_set.remove(user)
+
+        groups = Group.objects.filter(id__in=data['roles'])
+        for g in groups:
+            g.user_set.add(user)
+
+        return super(EditUsuarioView, self).form_valid(form)
+
+
 class CasaLegislativaCrud(CrudAux):
     model = CasaLegislativa
 


Test patch: