From 4b58cfe47e3ceefc2620abb4bdd2fcf46aa2a488 Mon Sep 17 00:00:00 2001 From: Julien BERNARD Date: Fri, 3 Sep 2021 10:53:21 -0400 Subject: [PATCH] Add user management Update user model --- src/lgr_auth/forms.py | 9 +++ src/lgr_auth/migrations/0001_initial.py | 13 ++- .../migrations/0002_update_user_roles.py | 26 ------ src/lgr_auth/models.py | 30 ++++++- .../templates/lgr_auth/user_update.html | 24 ++++++ src/lgr_auth/urls.py | 3 + src/lgr_auth/views.py | 39 +++++++++ src/lgr_idn_table_review/icann_tools/api.py | 3 +- .../icann_tools/models.py | 4 + src/lgr_idn_table_review/icann_tools/views.py | 3 +- .../list_reports.html | 16 ++-- src/lgr_idn_table_review/idn_tool/views.py | 1 - src/lgr_manage/forms.py | 6 -- .../templates/lgr_manage/user_management.html | 79 ++++++++++++++----- .../templates/lgr_manage/user_update.html | 44 +++++++++++ src/lgr_manage/urls.py | 6 +- src/lgr_manage/views/users.py | 32 +++++++- src/lgr_web/templates/_base.html | 15 +++- 18 files changed, 276 insertions(+), 77 deletions(-) delete mode 100644 src/lgr_auth/migrations/0002_update_user_roles.py create mode 100644 src/lgr_auth/templates/lgr_auth/user_update.html create mode 100644 src/lgr_auth/views.py create mode 100644 src/lgr_manage/templates/lgr_manage/user_update.html diff --git a/src/lgr_auth/forms.py b/src/lgr_auth/forms.py index 652b3c21..cf5deb2d 100644 --- a/src/lgr_auth/forms.py +++ b/src/lgr_auth/forms.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- from django.contrib.auth import get_user_model from django.contrib.auth.forms import PasswordResetForm +from django.forms import ModelForm + +from lgr_auth.models import LgrUser class LgrPasswordResetForm(PasswordResetForm): @@ -10,3 +13,9 @@ class LgrPasswordResetForm(PasswordResetForm): def get_users(self, email): return get_user_model()._default_manager.filter(email__iexact=email) + + +class UserForm(ModelForm): + class Meta: + model = LgrUser + fields = ['first_name', 'last_name', 'email', 'role'] \ No newline at end of file diff --git a/src/lgr_auth/migrations/0001_initial.py b/src/lgr_auth/migrations/0001_initial.py index c93ac35e..ce98d7df 100644 --- a/src/lgr_auth/migrations/0001_initial.py +++ b/src/lgr_auth/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 2.2.1 on 2021-02-09 00:52 +# Generated by Django 3.1.7 on 2021-09-02 16:15 from django.db import migrations, models +import django.utils.timezone import lgr_auth.models @@ -19,10 +20,16 @@ class Migration(migrations.Migration): ('password', models.CharField(max_length=128, verbose_name='password')), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), ('email', lgr_auth.models.UAEmailField(error_messages={'unique': 'An user with that email already exists.'}, help_text='Required. Valid email address', max_length=254, unique=True)), - ('role', models.PositiveSmallIntegerField(default=2)), + ('role', models.CharField(choices=[('User', 'User'), ('ICANN', 'ICANN'), ('Admin', 'Admin')], default='User', max_length=16)), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('is_active', models.BooleanField(default=True, verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ], options={ - 'abstract': False, + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'ordering': ['email'], }, ), ] diff --git a/src/lgr_auth/migrations/0002_update_user_roles.py b/src/lgr_auth/migrations/0002_update_user_roles.py deleted file mode 100644 index 6742a95c..00000000 --- a/src/lgr_auth/migrations/0002_update_user_roles.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.1.7 on 2021-08-10 13:01 - -from django.db import migrations, models - - -def update_roles(apps, schema_editor): - OldUser = apps.get_model('lgr_auth', 'lgruser') - OldUser.objects.filter(role=1).update(role='Admin') - OldUser.objects.filter(role=2).update(role='ICANN') - - -class Migration(migrations.Migration): - - dependencies = [ - ('lgr_auth', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='lgruser', - name='role', - field=models.CharField(choices=[('User', 'User'), ('ICANN', 'ICANN'), ('Admin', 'Admin')], default='User', - max_length=16), - ), - migrations.RunPython(update_roles) - ] diff --git a/src/lgr_auth/models.py b/src/lgr_auth/models.py index 566200ec..3e604cd1 100644 --- a/src/lgr_auth/models.py +++ b/src/lgr_auth/models.py @@ -5,6 +5,7 @@ from django.contrib.auth.base_user import BaseUserManager, AbstractBaseUser from django.core import validators from django.db import models +from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -63,6 +64,15 @@ class LgrUser(AbstractBaseUser): }) role = models.CharField(max_length=16, choices=((r.value, r.value) for r in LgrRole), null=False, default=LgrRole.USER.value) + first_name = models.CharField(_('first name'), max_length=150, blank=True) + last_name = models.CharField(_('last name'), max_length=150, blank=True) + is_active = models.BooleanField(_('active'), default=True) + date_joined = models.DateTimeField(_('date joined'), default=timezone.now) + + class Meta: + verbose_name = _('user') + verbose_name_plural = _('users') + ordering = ['email'] # The username field will be the email address USERNAME_FIELD = 'email' @@ -74,4 +84,22 @@ def is_icann(self): return self.role in (LgrRole.ICANN.value, LgrRole.ADMIN.value) def is_admin(self): - return self.role == LgrRole.ADMIN.value \ No newline at end of file + return self.role == LgrRole.ADMIN.value + + def enable(self, enable): + self.is_active = enable + self.save(update_fields=['is_active']) + + def enabled(self): + return self.is_active + + def get_full_name(self): + """ + Return the first_name plus the last_name, with a space in between. + """ + full_name = '%s %s' % (self.first_name, self.last_name) + return full_name.strip() + + def get_short_name(self): + """Return the short name for the user.""" + return self.first_name diff --git a/src/lgr_auth/templates/lgr_auth/user_update.html b/src/lgr_auth/templates/lgr_auth/user_update.html new file mode 100644 index 00000000..bec51595 --- /dev/null +++ b/src/lgr_auth/templates/lgr_auth/user_update.html @@ -0,0 +1,24 @@ +{% extends "_base.html" %} +{% load i18n %} + +{% block html_title %}{% trans 'LGR Tools user edition' %}{% endblock %} + +{% block navbar-right %} + {% trans "Switch mode" %} + {{ block.super }} +{% endblock %} + +{% block content-title %} + {% blocktrans with email=user.email %}Edit user {{ email }}{% endblocktrans %} +{% endblock %} + +{% block content %} +
+ {% csrf_token %} + {% include "lgr_editor/_form_field.html" with field=form.first_name control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.last_name control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.email control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.role control_width=8 %} + +
+{% endblock content %} diff --git a/src/lgr_auth/urls.py b/src/lgr_auth/urls.py index 10a3826f..23164a29 100644 --- a/src/lgr_auth/urls.py +++ b/src/lgr_auth/urls.py @@ -6,6 +6,7 @@ # patterns from django.contrib.auth.urls from lgr_auth.forms import LgrPasswordResetForm +from lgr_auth.views import LgrUserUpdateView urlpatterns = [ path('login/', views.LoginView.as_view(template_name='lgr_auth/login.html', extra_context={'reset_mode': True}), @@ -31,4 +32,6 @@ views.PasswordResetCompleteView.as_view(template_name='lgr_auth/password_reset_complete.html', extra_context={'reset_mode': True}), name='password_reset_complete'), + path('users/', LgrUserUpdateView.as_view(), name='lgr_update_user'), + ] diff --git a/src/lgr_auth/views.py b/src/lgr_auth/views.py new file mode 100644 index 00000000..54d6ce04 --- /dev/null +++ b/src/lgr_auth/views.py @@ -0,0 +1,39 @@ +#! /bin/env python +# -*- coding: utf-8 -*- +""" +views.py +""" +import logging + +from django.contrib import messages +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ +from django.views.generic import UpdateView + +from lgr_auth.forms import UserForm +from lgr_auth.models import LgrUser + +logger = logging.getLogger(__name__) +from django.contrib.auth.mixins import LoginRequiredMixin + + +class LgrUserUpdateView(LoginRequiredMixin, UpdateView): + model = LgrUser + form_class = UserForm + pk_url_kwarg = 'user_pk' + template_name = 'lgr_auth/user_update.html' + success_url_name = 'lgr_update_user' + + def get_queryset(self): + return LgrUser.objects.filter(pk=self.request.user.pk) + + def get_success_url(self): + return reverse(self.success_url_name, kwargs={'user_pk': self.kwargs[self.pk_url_kwarg]}) + + def form_valid(self, form): + messages.add_message(self.request, messages.SUCCESS, _('User updated')) + return super().form_valid(form) + + def form_invalid(self, form): + messages.add_message(self.request, messages.ERROR, _('Failed to update user')) + return super().form_invalid(form) diff --git a/src/lgr_idn_table_review/icann_tools/api.py b/src/lgr_idn_table_review/icann_tools/api.py index 4298a72e..e16c9a49 100644 --- a/src/lgr_idn_table_review/icann_tools/api.py +++ b/src/lgr_idn_table_review/icann_tools/api.py @@ -15,7 +15,6 @@ from lgr.core import LGR from lgr.tools.utils import download_file from lgr.utils import tag_to_language_script -from lgr_auth.models import LgrRole from lgr_idn_table_review.icann_tools.models import IdnReviewIcannReport, IANAIdnTable from lgr_models.models.lgr import RefLgr, RzLgrMember, RzLgr from lgr_session.api import LGRStorage @@ -40,7 +39,7 @@ def __init__(self, user): super().__init__(user, filter_on_user=False) def storage_can_read(self): - return self.user.role in [LgrRole.ICANN.value, LgrRole.ADMIN.value] + return self.user.is_icann() def get_icann_idn_repository_tables(): diff --git a/src/lgr_idn_table_review/icann_tools/models.py b/src/lgr_idn_table_review/icann_tools/models.py index e752071d..342f895e 100644 --- a/src/lgr_idn_table_review/icann_tools/models.py +++ b/src/lgr_idn_table_review/icann_tools/models.py @@ -12,6 +12,7 @@ from lgr.core import LGR from lgr.metadata import Metadata, Version from lgr.parser.heuristic_parser import HeuristicParser +from lgr_auth.models import LgrUser from lgr_models.models.lgr import LgrBaseModel from lgr_models.models.report import LGRReport from lgr_session.views import StorageType @@ -29,12 +30,15 @@ class IANAIdnTable(LgrBaseModel): """ Model for a IANA IDN table, not meant to be saved in the database """ + lgr_parser = HeuristicParser url = models.URLField() date = models.DateField() lang_script = models.CharField(max_length=16) version = models.DecimalField(max_digits=2, decimal_places=1) + # on delete user do nothing since this is unmanaged + owner = models.ForeignKey(to=LgrUser, on_delete=models.DO_NOTHING, related_name='+') class Meta: managed = False # do not manage this model, this is only used to create objects that won't be saved in the DB diff --git a/src/lgr_idn_table_review/icann_tools/views.py b/src/lgr_idn_table_review/icann_tools/views.py index ec09cc1b..bdd08be3 100644 --- a/src/lgr_idn_table_review/icann_tools/views.py +++ b/src/lgr_idn_table_review/icann_tools/views.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView -from lgr_auth.models import LgrRole from lgr_idn_table_review.icann_tools.api import LGRIcannStorage from lgr_web.views import INTERFACE_SESSION_MODE_KEY, Interfaces from .tasks import idn_table_review_task @@ -20,7 +19,7 @@ def setup(self, request, *args, **kwargs): self.storage = LGRIcannStorage(request.user) def test_func(self): - return self.request.user.role in [LgrRole.ICANN.value, LgrRole.ADMIN.value] + return self.request.user.is_icann() class IdnTableIcannModeView(BaseIcannView, TemplateView): diff --git a/src/lgr_idn_table_review/idn_tool/templates/lgr_idn_table_review_tool/list_reports.html b/src/lgr_idn_table_review/idn_tool/templates/lgr_idn_table_review_tool/list_reports.html index 9c624615..d2b99201 100644 --- a/src/lgr_idn_table_review/idn_tool/templates/lgr_idn_table_review_tool/list_reports.html +++ b/src/lgr_idn_table_review/idn_tool/templates/lgr_idn_table_review_tool/list_reports.html @@ -28,15 +28,17 @@ {% for reports_grouped in report_ids %}
  • {% with report=reports_grouped.list|first %} -
    - {{ report.report_id }} + {% csrf_token %} -
    + {{ report.report_id }} + + + {% endwith %}
  • {% empty %} diff --git a/src/lgr_idn_table_review/idn_tool/views.py b/src/lgr_idn_table_review/idn_tool/views.py index 6e9bd65e..8858f217 100644 --- a/src/lgr_idn_table_review/idn_tool/views.py +++ b/src/lgr_idn_table_review/idn_tool/views.py @@ -120,7 +120,6 @@ class IdnTableReviewDisplayIdnTable(IdnTableReviewViewMixin, SingleObjectMixin, def get(self, request, *args, **kwargs): idn_table = self.get_object(queryset=self.api.lgr_queryset()) - # FIXME: should distinct txt and xml LGRs content_type = 'text/plain' if idn_table.filename.endswith('xml'): content_type = 'text/xml' diff --git a/src/lgr_manage/forms.py b/src/lgr_manage/forms.py index 20cc25f2..85d19e09 100644 --- a/src/lgr_manage/forms.py +++ b/src/lgr_manage/forms.py @@ -5,7 +5,6 @@ from django.utils.translation import ugettext_lazy as _ from lgr_advanced.lgr_editor.forms import FILE_FIELD_ENCODING_HELP -from lgr_auth.models import LgrUser from lgr_models.models.lgr import RzLgr, RzLgrMember, RefLgr, MSR from lgr_web.utils import IANA_LANG_REGISTRY @@ -56,8 +55,3 @@ class Meta: 'name': _('Name'), } - -class UserCreateForm(forms.ModelForm): - class Meta: - model = LgrUser - fields = ['email', 'role'] diff --git a/src/lgr_manage/templates/lgr_manage/user_management.html b/src/lgr_manage/templates/lgr_manage/user_management.html index 2bf81a00..3be43a6e 100644 --- a/src/lgr_manage/templates/lgr_manage/user_management.html +++ b/src/lgr_manage/templates/lgr_manage/user_management.html @@ -4,26 +4,32 @@ {% block user-management-active %}active{% endblock %} {% block content-pane %} -

    {% trans 'Admin user' %}

    -
    - {% csrf_token %} - -
    -

    {% trans 'ICANN users' %}

    - +

    {% trans 'Existing Users' %}

    +
    + + + + {% for user in object_list %} + + + + - {% empty %} - - - {% endfor %}
    {% trans 'Name' %} {% trans 'Email' %}{% trans 'Enabled' %}{% trans 'Role' %} {% trans 'Actions' %}
    {{ user.get_full_name }} {{ user.email }} + {% if user.enabled %} + + {% else %} + + {% endif %} + {{ user.role }}
    {% csrf_token %} @@ -33,25 +39,56 @@

    {% trans 'ICANN users' %}

      -
    - {% csrf_token %} - -
    + {% if not user.is_admin %} +
    + + {% trans 'Edit' %} + + + +
    + {% elif user == request.user %} + + {% trans 'Edit' %} + + {% endif %}
    {% trans 'No ICANN user.' %}

    {% trans 'Create new ICANN user' %}

    {% csrf_token %} + {% include "lgr_editor/_form_field.html" with field=form.first_name control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.last_name control_width=8 %} {% include "lgr_editor/_form_field.html" with field=form.email control_width=8 %} {% include "lgr_editor/_form_field.html" with field=form.role control_width=8 %} diff --git a/src/lgr_manage/templates/lgr_manage/user_update.html b/src/lgr_manage/templates/lgr_manage/user_update.html new file mode 100644 index 00000000..0f9b8ac6 --- /dev/null +++ b/src/lgr_manage/templates/lgr_manage/user_update.html @@ -0,0 +1,44 @@ +{% extends "lgr_manage/_idn_table_review_admin_base.html" %} +{% load i18n %} + +{% block user-management-active %}active{% endblock %} + +{% block content-pane %} +

    {% blocktrans with email=object.email %}Edit user {{ email }}{% endblocktrans %}

    + + {% if not object.enabled %} + + {% endif %} + + + {% csrf_token %} + {% include "lgr_editor/_form_field.html" with field=form.first_name control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.last_name control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.email control_width=8 %} + {% include "lgr_editor/_form_field.html" with field=form.role control_width=8 %} + {% trans 'Back' %} + + {% if object.enabled %} + + {% trans 'Disable' %} + + {% endif %} + + {% trans 'Delete' %} + +
    +
    + {% csrf_token %} +
    +
    + {% csrf_token %} +
    +{% endblock content-pane %} diff --git a/src/lgr_manage/urls.py b/src/lgr_manage/urls.py index 8c168f1b..501d9d3e 100644 --- a/src/lgr_manage/urls.py +++ b/src/lgr_manage/urls.py @@ -4,7 +4,7 @@ from lgr_manage.views.msr import MSRView, MSRDeleteView, DisplayMSRView from lgr_manage.views.reference_lgr import RefLgrView, RefLgrDeleteView, DisplayRefLgrView from lgr_manage.views.rz_lgr import RzLgrView, RzLgrDeleteView, DisplayRzLgrView, DisplayRzLgrMemberView -from lgr_manage.views.users import LgrUserView, LgrUserDeleteView +from lgr_manage.views.users import LgrUserView, LgrUserDeleteView, LgrUserChangeStatusView, LgrUserAdminUpdateView urlpatterns = [ path('', RzLgrView.as_view(), name='lgr_admin_mode'), @@ -20,5 +20,7 @@ path('msr//delete', MSRDeleteView.as_view(), name='lgr_admin_delete_msr'), path('msr/', DisplayMSRView.as_view(), name='lgr_admin_display_msr'), path('users', LgrUserView.as_view(), name='lgr_admin_user_management'), - path('users//delete', LgrUserDeleteView.as_view(), name='lgr_admin_delete_user'), + path('users//update', LgrUserAdminUpdateView.as_view(), name='lgr_admin_update_user'), + path('users//delete', LgrUserDeleteView.as_view(), name='lgr_admin_delete_user'), + path('users//status', LgrUserChangeStatusView.as_view(), name='lgr_admin_change_user_status'), ] diff --git a/src/lgr_manage/views/users.py b/src/lgr_manage/views/users.py index 1586837a..3156ca82 100644 --- a/src/lgr_manage/views/users.py +++ b/src/lgr_manage/views/users.py @@ -3,15 +3,18 @@ from django.contrib import messages from django.urls import reverse_lazy from django.utils.translation import ugettext_lazy as _ +from django.views.generic import RedirectView +from django.views.generic.detail import SingleObjectMixin +from lgr_auth.forms import UserForm from lgr_auth.models import LgrUser, LgrRole -from lgr_manage.forms import UserCreateForm +from lgr_auth.views import LgrUserUpdateView from lgr_manage.views.common import BaseListAdminView, BaseAdminView class LgrUserListView(BaseListAdminView): model = LgrUser - queryset = LgrUser.objects.filter(role=LgrRole.ICANN.value).order_by('email') + queryset = LgrUser.objects.all() template_name = 'lgr_manage/user_management.html' def get_context_data(self, **kwargs): @@ -22,7 +25,7 @@ def get_context_data(self, **kwargs): class LgrUserCreateView(BaseAdminView, views.generic.CreateView): model = LgrUser - form_class = UserCreateForm + form_class = UserForm template_name = 'lgr_manage/user_management.html' success_url = reverse_lazy('lgr_admin_user_management') @@ -51,6 +54,29 @@ def post(self, request, *args, **kwargs): return view(request, *args, **kwargs) +class LgrUserAdminUpdateView(BaseAdminView, LgrUserUpdateView): + template_name = 'lgr_manage/user_update.html' + success_url_name = 'lgr_admin_update_user' + + def get_queryset(self): + return LgrUser.objects.exclude(role__exact=LgrRole.ADMIN) + + +class LgrUserChangeStatusView(BaseAdminView, SingleObjectMixin, RedirectView): + model = LgrUser + url = reverse_lazy('lgr_admin_user_management') + pk_url_kwarg = 'user_pk' + enable = None + + def get_redirect_url(self, *args, **kwargs): + return self.request.GET.get('next', super(LgrUserChangeStatusView, self).get_redirect_url(*args, **kwargs)) + + def post(self, request, *args, **kwargs): + user = self.get_object() + user.enable(not user.enabled()) + return super(LgrUserChangeStatusView, self).post(request, *args, **kwargs) + + class LgrUserDeleteView(BaseAdminView, views.generic.DeleteView): model = LgrUser success_url = reverse_lazy('lgr_admin_user_management') diff --git a/src/lgr_web/templates/_base.html b/src/lgr_web/templates/_base.html index 5fafa492..0cd05a15 100644 --- a/src/lgr_web/templates/_base.html +++ b/src/lgr_web/templates/_base.html @@ -63,11 +63,15 @@