diff --git a/administration/forms.py b/administration/forms.py index 0cf71a7098ad21de7b4a6f3b37db94f2d3294f6f..4d6da0c5e20705a6e5945754cc04eb17b81b8196 100644 --- a/administration/forms.py +++ b/administration/forms.py @@ -134,3 +134,15 @@ class PeriodForm(forms.Form): if start_date > end_date: self.add_error("end_date", "waktu selesai sebelum waktu mulai") + + +class EditAdminStatusForm(forms.ModelForm): + + is_active = forms.BooleanField(required=False) + + class Meta: + model = User + fields = ["is_active"] + + def __init__(self, *args, **kwargs): + super(EditAdminStatusForm, self).__init__(*args, **kwargs) diff --git a/administration/templates/edit_admin.html b/administration/templates/edit_admin.html new file mode 100644 index 0000000000000000000000000000000000000000..47e16ef164f42dc12bc70358741ae0ca3a566c9d --- /dev/null +++ b/administration/templates/edit_admin.html @@ -0,0 +1,27 @@ +{% extends 'administration/base_administrasi.html' %} +{% load static %} + +{% block content %} +<div class="card shadow mb-4"> + <div class="card-header py-3"> + <h6 class="m-0 font-weight-bold text-primary"> + Edit {{ page_title }} + </h6> + </div> + <div class="card-body"> + <form method="POST"> + {% csrf_token %} + {{ item.name }} + <div class="status d-flex align-items-baseline"> + {{ form.is_active }} <br> Active + </div> + <div class=""> + <button class="btn-sm btn-primary rounded p-12" type="submit"> + <i class="far fa-save" aria-hidden="true"></i> + Simpan + </button> + </div> + </form> + </div> +</div> +{% endblock %} \ No newline at end of file diff --git a/administration/templates/kelola_admin.html b/administration/templates/kelola_admin.html index ac52797935b91de8ceea83a1a23035a73b44bf85..1af6884bf2e244ff5cc323b2f94dfce561f236b9 100644 --- a/administration/templates/kelola_admin.html +++ b/administration/templates/kelola_admin.html @@ -27,30 +27,39 @@ <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <thead> <tr> - <th>Nama</th> - <th>NIK</th> - <th>Instansi</th> - <th>Pilihan</th> + <th scope="col">Nama</th> + <th scope="col">NIK</th> + <th scope="col">Instansi</th> + <th scope="col">Status</th> + <th scope="col">Pilihan</th> </tr> </thead> <tfoot> <tr> - <th>Nama</th> - <th>NIK</th> - <th>Instansi</th> - <th>Pilihan</th> + <th scope="col">Nama</th> + <th scope="col">NIK</th> + <th scope="col">Instansi</th> + <th scope="col">Status</th> + <th scope="col">Pilihan</th> </tr> </tr> </tfoot> <tbody> {% for current in users_list %} <tr> - <td>{{ current.name }}</td> + <td>{% if current.name %} {{ current.name }} {% else %} Superadmin {% endif %}</td> <td>{{ current.nik }}</td> <td>{{ current.instansi }}</td> + <td>{% if current.is_active %} Active {% else %} Inactive {% endif %}</td> <td class="verif-buttons"> <span> + {% if user.is_superuser %} + {% if not current.is_superuser %} + <a href="/administration/setting/admin/{{ current.id }}/edit" class="accept-button button-decoration" role="button">Edit</a> + {% endif %} + {% endif %} <a href="/administration/profil-admin/{{ current.id }}/" class="accept-button button-decoration">Detail</a> + {% if not current.is_superuser %} <button type="button" class="reject-button button-decoration" data-toggle="modal" data-target="#confirmModal{{ current.id }}">Hapus</button> <div class="modal fade" id="confirmModal{{ current.id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> @@ -80,6 +89,7 @@ </div> </div> </div> + {% endif %} </span> </td> </tr> diff --git a/administration/tests.py b/administration/tests.py index 69cef0626b45667a4442004559bad670a6def3bd..87122190ea2e86da9de75f86f6b27fe0e5c6d39b 100644 --- a/administration/tests.py +++ b/administration/tests.py @@ -5,9 +5,12 @@ from django.urls import resolve from administration import models, views from administration.utils import id_generator +from administration.forms import EditAdminStatusForm from app.models import Category, Materi +from authentication.models import User from bs4 import BeautifulSoup +EDIT_ENDPOINT = "/edit" class VerifikasiMateriTest(TestCase): def setUp(self): @@ -809,7 +812,7 @@ class EditVerificationTest(TestCase): **self.admin_credential, name="Admin", is_admin=True) models.VerificationSetting(title='test', description="desc test").save() self.verification = models.VerificationSetting.objects.first() - self.url = "/administration/setting/verification/" + str(self.verification.id) +"/edit" + self.url = "/administration/setting/verification/" + str(self.verification.id) + EDIT_ENDPOINT def test_edit_verification_admin_access(self): self.client.login(**self.admin_credential) @@ -891,7 +894,7 @@ class EditCategoryTest(TestCase): **self.admin_credential, name="Admin", is_admin=True) Category(name='test', description="desc test").save() self.category = Category.objects.first() - self.url = "/administration/setting/category/" + str(self.category.id) +"/edit" + self.url = "/administration/setting/category/" + str(self.category.id) + EDIT_ENDPOINT def test_edit_category_admin_access(self): self.client.login(**self.admin_credential) @@ -961,3 +964,150 @@ class EditCategoryTest(TestCase): self.assertTemplateUsed(response, self.template_name) self.assertContains(response, "Edit Kategori Materi") +class EditAdminStatusFormTests(TestCase): + def test_set_active_admin(self): + form = EditAdminStatusForm(data={"is_active": True}) + + self.assertEqual(form.is_valid(), True) + + def test_set_inactive_admin(self): + form = EditAdminStatusForm(data={"is_active": False}) + + self.assertEqual(form.is_valid(), True) + +class DeleteAdminTests(TestCase): + def setUp(self): + self.client = Client() + self.user_credential = { + "email": "user@gov.id", + "password": id_generator() + } + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.superuser_credential = { + "email": "superuser@gov.id", + "password": id_generator() + } + self.user = get_user_model().objects.create_user( + **self.user_credential, name="User", is_contributor=True) + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + self.superuser = get_user_model().objects.create_user( + **self.superuser_credential, name="Superuser", is_admin=True, is_superuser=True) + User(name='dummy_admin', is_admin=True, email="dummy_admin@example.com").save() + User(name='dummy_superuser', is_admin=True, is_superuser=True, email="dummy_superuser@example.com").save() + self.dummy_admin = User.objects.filter(name="dummy_admin").get() + self.dummy_superuser = User.objects.filter(name="dummy_superuser").get() + self.delete_url_dummy_admin = self.url_generator_delete_admin(self.dummy_admin.id) + self.delete_url_dummy_superuser = self.url_generator_delete_admin(self.dummy_superuser.id) + + def url_generator_delete_admin(self, id): + return "/administration/hapus-admin/" + str(id) + "/" + + def test_delete_admin_as_admin_user(self): + self.client.login(**self.admin_credential) + self.assertEqual(User.objects.filter( + name="dummy_admin").count(), 1) + + self.client.get(self.delete_url_dummy_admin) + + self.assertEqual(User.objects.filter( + name="dummy_admin").count(), 0) + + def test_cannot_delete_admin_as_user(self): + self.client.login(**self.user_credential) + response = self.client.get(self.delete_url_dummy_admin) + + self.assertEqual(response.status_code, 403) + + def test_cannot_delete_superuser_as_user_or_admin(self): + self.client.login(**self.user_credential) + response = self.client.get(self.delete_url_dummy_superuser) + + self.assertEqual(response.status_code, 403) + + self.client.login(**self.admin_credential) + response = self.client.get(self.delete_url_dummy_superuser) + + self.assertEqual(response.status_code, 403) + + def test_redirect_after_delet_admin(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.delete_url_dummy_admin) + + self.assertEqual(response.status_code, 302) + +class EditAdminStatusTests(TestCase): + def setUp(self): + self.client = Client() + self.admin_credential = { + "email": "admin@gov.id", + "password": id_generator() + } + self.superuser_credential = { + "email": "superuser@gov.id", + "password": id_generator() + } + self.admin = get_user_model().objects.create_user( + **self.admin_credential, name="Admin", is_admin=True) + self.superuser = get_user_model().objects.create_user( + **self.superuser_credential, name="Superuser", is_admin=True, is_superuser=True) + User(name='dummy_admin', is_admin=True, email="dummy_admin@example.com").save() + User(name='dummy_superuser', is_admin=True, is_superuser=True, email="dummy_superuser@example.com").save() + self.dummy_admin = User.objects.filter(name="dummy_admin").get() + self.dummy_superuser = User.objects.filter(name="dummy_superuser").get() + self.edit_url_dummy_admin = self.url_generator_edit_admin(self.dummy_admin.id) + self.edit_url_dummy_superuser = self.url_generator_edit_admin(self.dummy_superuser.id) + + def url_generator_edit_admin(self, id): + return "/administration/setting/admin/" + str(id) + EDIT_ENDPOINT + + def test_get_edit_admin_html_content(self): + self.client.login(**self.superuser_credential) + response = self.client.get(self.edit_url_dummy_admin) + self.assertContains(response, self.dummy_admin.name) + + response = self.client.get(self.edit_url_dummy_superuser) + self.assertContains(response, self.dummy_superuser.name) + + def test_cannot_edit_set_active_admin_as_admin(self): + self.client.login(**self.admin_credential) + self.client.get(self.edit_url_dummy_admin) + response = self.client.post(self.edit_url_dummy_admin, {"is_active": "on"}) + + self.assertEqual(response.status_code, 403) + + def test_edit_set_active_admin_as_superuser(self): + self.dummy_admin.is_active = False + self.client.login(**self.superuser_credential) + self.client.get(self.edit_url_dummy_admin) + response = self.client.post(self.edit_url_dummy_admin, {"is_active": "on"}) + self.dummy_admin.refresh_from_db() + + self.assertEqual(response.status_code, 302) + self.assertEqual(self.dummy_admin.is_active, True) + + def test_edit_set_inactive_admin_as_superuser(self): + self.dummy_admin.is_active = True + self.client.login(**self.superuser_credential) + self.client.get(self.edit_url_dummy_admin) + response = self.client.post(self.edit_url_dummy_admin, {"is_active": "false"}) + self.dummy_admin.refresh_from_db() + + self.assertEqual(response.status_code, 302) + self.assertEqual(self.dummy_admin.is_active, False) + + def test_cannot_edit_set_active_superuser_as_superuser(self): + self.client.login(**self.superuser_credential) + self.client.get(self.edit_url_dummy_superuser) + response = self.client.post(self.edit_url_dummy_superuser, {"is_active": "on"}) + + self.assertEqual(response.status_code, 403) + + def test_cannot_access_edit_page_as_user(self): + self.client.login(**self.admin_credential) + response = self.client.get(self.edit_url_dummy_superuser) + + self.assertEqual(response.status_code, 403) diff --git a/administration/urls.py b/administration/urls.py index 7b6388a06750a3ffeff1a255974421994b614ee0..c13a46c75274d3aa69e5f7b7ddc8d9effbcc247c 100644 --- a/administration/urls.py +++ b/administration/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from administration.views import VerificationView, DetailVerificationView, VerificationSettingView, CategorySettingView, KelolaKontributorView, ProfileContributorAdminView, ProfileAdminAdministrationView, KelolaAdminView, RegistrasiAdminView, EditVerificationView, delete_admin, delete_contributor, delete_verification, StatisticsView, StatisticApiView, EditCategoryView, delete_category, generatedummy +from administration.views import VerificationView, DetailVerificationView, VerificationSettingView, CategorySettingView, KelolaKontributorView, ProfileContributorAdminView, ProfileAdminAdministrationView, KelolaAdminView, RegistrasiAdminView, EditVerificationView, delete_admin, delete_contributor, delete_verification, StatisticsView, StatisticApiView, EditCategoryView, EditAdminStatusView, delete_category, generatedummy app_name = "administration" @@ -22,6 +22,8 @@ urlpatterns = [ path("profil-admin/<int:pk>/", ProfileAdminAdministrationView.as_view()), path("kelola-admin/", KelolaAdminView.as_view()), path("kelola-admin/tambah/", RegistrasiAdminView.as_view()), + path("setting/admin/<int:pk>/edit", + EditAdminStatusView.as_view(), name="edit-admin-status"), path("hapus-admin/<int:pk>/", delete_admin), path("hapus-kontributor/<int:pk>/", delete_contributor), path("generate-dummy", generatedummy), diff --git a/administration/views.py b/administration/views.py index 894ff0ad555264c9ee85f1360e470cb9c84113d4..cd49c9fae60e3675adf3002d6fc8c3b5011eb88f 100644 --- a/administration/views.py +++ b/administration/views.py @@ -10,7 +10,7 @@ from django.contrib import messages from django.utils import timezone, lorem_ipsum from dateutil.relativedelta import relativedelta from administration.models import VerificationReport, VerificationSetting, DeletionHistory -from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm +from administration.forms import CategoryForm, VerificationSettingForm, RegistrasiAdminForm, PeriodForm, EditAdminStatusForm from app.models import Category, Materi, ViewStatistics, DownloadStatistics, Comment, Like, getRandomColor from authentication.models import User from datetime import datetime @@ -19,6 +19,7 @@ from administration.utils import generate_time_step from django.core import management +ADMINISTRATION_MANAGEMENT = "/administration/kelola-admin/" def get_start_end_date(period): if period == 'ALL_TIME': @@ -450,7 +451,7 @@ class RegistrasiAdminView(TemplateView): new_user.password = make_password(data["password"]) new_user.is_admin = True new_user.save() - return HttpResponseRedirect("/administration/kelola-admin/") + return HttpResponseRedirect(ADMINISTRATION_MANAGEMENT) else: context = self.get_context_data(**kwargs) context["form"] = form @@ -520,12 +521,42 @@ class EditCategoryView(TemplateView): context["form"] = form return self.render_to_response(context) +class EditAdminStatusView(TemplateView): + template_name = "edit_admin.html" + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated or not request.user.is_superuser: + raise PermissionDenied(request) + return super(EditAdminStatusView, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(EditAdminStatusView, self).get_context_data(**kwargs) + context["page_title"] = "Admin Status" + context["item"] = User.objects.get(id=kwargs["pk"]) + context["form"] = EditAdminStatusForm(instance=context["item"]) + return context + + def get(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + return self.render_to_response(context=context) + + def post(self, request, *args, **kwargs): + user_object = self.get_context_data(**kwargs)["item"] + if user_object.is_superuser: + raise PermissionDenied(request) + form = EditAdminStatusForm( + request.POST, instance=user_object) + form.save() + return HttpResponseRedirect(ADMINISTRATION_MANAGEMENT) def delete_admin(request, *args, **kwargs): if not request.user.is_authenticated or not request.user.is_admin: raise PermissionDenied(request) user_to_be_deleted = User.objects.get(pk=kwargs["pk"]) + if (user_to_be_deleted.is_superuser): + raise PermissionDenied(request) + DeletionHistory.objects.create( deleted_user_name=user_to_be_deleted.name, deleted_user_role="Admin", @@ -533,7 +564,7 @@ def delete_admin(request, *args, **kwargs): ) User.objects.filter(pk=kwargs["pk"]).delete() - return HttpResponseRedirect("/administration/kelola-admin/") + return HttpResponseRedirect(ADMINISTRATION_MANAGEMENT) def delete_contributor(request, *args, **kwargs): diff --git a/authentication/admin.py b/authentication/admin.py index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..764e6050529571755e7553a08f96681b893e35dd 100644 --- a/authentication/admin.py +++ b/authentication/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin +from .models import User -# Register your models here. +admin.site.register(User) \ No newline at end of file diff --git a/register/templates/index_admin.html b/register/templates/index_admin.html index 0e9e3a8891ac7fc58ceaf6c972c03ccaf862c8f9..aee58b799e6b8dabc051c790298218098bfd8c2c 100644 --- a/register/templates/index_admin.html +++ b/register/templates/index_admin.html @@ -34,6 +34,9 @@ <main> <div class="limiter"> + {% if message %} + <h1>{{ message }}</h1> + {% endif %} <div class="container-login100"> <div class="wrap-login100"> <form class="login100-form validate-form" method="POST" action=""> diff --git a/register/tests.py b/register/tests.py index d97744a303d6da3b12552a8d0cc3123b01bec08d..86be11ef80274b60d41936474c00295755de1cdc 100644 --- a/register/tests.py +++ b/register/tests.py @@ -2,6 +2,7 @@ from django.test import Client, TestCase from django.urls import resolve from authentication.models import User +from administration.utils import id_generator from register import views @@ -121,6 +122,7 @@ class RegisterPageTest(TestCase): class RegisterAdminTest(TestCase): def setUp(self): self.client = Client() + self.random_password = id_generator() def test_register_url_is_exist(self): # Positive tests @@ -208,8 +210,8 @@ class RegisterAdminTest(TestCase): "alamat": "bekasi", "email": "bob@company.com", "nomor_telpon": "087878726602", - "password": "1234", - "password2": "1234", + "password": self.random_password, + "password2": self.random_password, }, ) self.assertEqual(User.objects.all().count(), 1) @@ -222,10 +224,43 @@ class RegisterAdminTest(TestCase): "alamat": "bekasi", "email": "bob@company.com", "nomor_telpon": "087878726602", - "password": "1234", - "password2": "1234", + "password": self.random_password, + "password2": self.random_password, }, ) self.assertEqual(User.objects.all().count(), 1) self.assertIn( b"Email sudah digunakan untuk mendaftar akun.", response.content) + + def test_create_user_success_but_status_is_inactive(self): + self.client.post( + "/registrasi/admin/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": self.random_password, + "password2": self.random_password, + }, + ) + self.assertEqual(User.objects.filter(name="bob").get().is_active, False) + + def test_create_user_success_and_show_message_to_wait_for_internal_team(self): + response = self.client.post( + "/registrasi/admin/", + { + "name": "bob", + "instansi": "university", + "nik": "1706074940", + "alamat": "bekasi", + "email": "bob@company.com", + "nomor_telpon": "087878726602", + "password": self.random_password, + "password2": self.random_password, + }, + ) + self.assertEqual(User.objects.count(), 1) + self.assertIn("Please wait for our internal team to accept your admin account", response.content.decode()) diff --git a/register/views.py b/register/views.py index 2fcbf6d10a7653b125a59f2ee927f52e58087e6b..4d9d339882ddaea3f1583b7cd4a7342c214476ee 100644 --- a/register/views.py +++ b/register/views.py @@ -46,19 +46,20 @@ class RegistrasiAdmin(TemplateView): def post(self, request, *args, **kwargs): data = request.POST.copy() form = UserForm(request.POST) + context = self.get_context_data(**kwargs) + context["form"] = form if form.is_valid(): new_user = form.save(commit=False) new_user.password = make_password(data["password"]) new_user.is_admin = True + new_user.is_active = False new_user.save() - login(request, new_user) - return HttpResponseRedirect("/sukses-admin/") - else: - context = self.get_context_data(**kwargs) - context["form"] = form + context["message"] = "Please wait for our internal team to accept your admin account" return self.render_to_response(context) + + return self.render_to_response(context) def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) context["form"] = UserForm - return self.render_to_response(context=context) \ No newline at end of file + return self.render_to_response(context=context)