Fakultas Ilmu Komputer UI

Commit 1df27b44 authored by Dave Nathanael's avatar Dave Nathanael
Browse files

Merge branch 'jojo/reimplement-monitoring-account-log' into 'rework-model-revisions'

Rework Monitoring, Account, Log

See merge request !34
parents d75b2d76 3dac0125
Pipeline #41443 passed with stages
in 5 minutes
from django_filters import FilterSet, CharFilter
ACCOUNT_FILTERSET_FIELDS = (
"name",
"email",
"phone_number",
"area",
"is_admin",
"is_verified",
"is_active",
)
from apps.accounts.models import Account
ACCOUNT_SEARCH_FIELDS = (
"name",
"email",
"phone_number",
"area",
"is_admin",
"is_verified",
"is_active",
)
class AccountFilter(FilterSet):
username = CharFilter(field_name="user__username")
class Meta:
model = Account
fields = [
"username",
"name",
"email",
"phone_number",
"area",
"is_admin",
"is_verified",
]
ACCOUNT_ORDERING_FIELDS = (
"name",
"email",
"phone_number",
"area",
"is_admin",
"is_verified",
"is_active",
"created_at",
)
# Generated by Django 3.0.1 on 2020-04-18 18:56
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
("accounts", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="AccountHistory",
fields=[
(
"revision_id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("object_id", models.UUIDField(default=uuid.uuid4, editable=False)),
(
"action_type",
models.CharField(
choices=[
("Create", "Create"),
("Edit", "Edit"),
("Delete", "Delete"),
],
max_length=64,
),
),
("recorded_at", models.DateTimeField(auto_now_add=True)),
("user_id", models.IntegerField()),
("name", models.CharField(max_length=128)),
("email", models.EmailField(max_length=128)),
("phone_number", models.CharField(max_length=64)),
("area", models.CharField(max_length=128)),
("is_admin", models.BooleanField(default=False)),
("is_verified", models.BooleanField(default=False)),
("is_active", models.BooleanField(default=False)),
],
options={
"verbose_name_plural": "account histories",
"db_table": "account_history",
"ordering": ["-recorded_at"],
},
),
migrations.AlterModelOptions(
name="account",
options={"ordering": ["-created_at"], "verbose_name_plural": "accounts"},
),
migrations.AddField(
model_name="account",
name="deleted_at",
field=models.DateTimeField(blank=True, null=True),
),
]
# Generated by Django 3.0.1 on 2020-04-19 11:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("accounts", "0002_auto_20200419_0156"),
]
operations = [
migrations.AddField(
model_name="accounthistory",
name="author",
field=models.UUIDField(null=True),
),
]
# Generated by Django 3.0.1 on 2020-04-19 11:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("accounts", "0003_accounthistory_author"),
]
operations = [
migrations.AddField(
model_name="account",
name="author",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="account",
to="accounts.Account",
),
),
]
......@@ -2,10 +2,12 @@ import uuid
from django.contrib.auth.models import User
from django.db import models
from apps.commons.managers import SoftDeleteManager
from apps.commons.models import HistoryEnabledModel, HistoryModel
class Account(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.OneToOneField(User, on_delete=models.CASCADE)
class AccountHistory(HistoryModel):
user_id = models.IntegerField(null=False)
name = models.CharField(max_length=128)
email = models.EmailField(max_length=128)
phone_number = models.CharField(max_length=64)
......@@ -13,15 +15,46 @@ class Account(models.Model):
is_admin = models.BooleanField(default=False)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
author = models.UUIDField(null=True)
objects = models.Manager()
class Meta:
db_table = "account_history"
verbose_name_plural = "account histories"
ordering = ["-recorded_at"]
def __str__(self):
return f"[History] Rev. {self.revision_id} - {self.name}"
class Account(HistoryEnabledModel):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
email = models.EmailField(max_length=128)
phone_number = models.CharField(max_length=64)
area = models.CharField(max_length=128)
is_admin = models.BooleanField(default=False)
is_verified = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
author = models.ForeignKey(
"self",
blank=True,
null=True,
on_delete=models.DO_NOTHING,
related_name="account",
)
history_class = AccountHistory
relation_fields = ["user"]
objects = SoftDeleteManager()
class Meta:
db_table = "account"
verbose_name_plural = "accounts"
ordering = ["-created_at"]
def __str__(self):
if self.is_admin:
return f"[Admin] {self.user.username}"
return f"[Officer] {self.user.username}"
account_role = "[Admin]" if self.is_admin else "[Officer]"
return f"{account_role} {self.user.username}"
......@@ -5,7 +5,7 @@ from apps.accounts.models import Account
class AccountSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="user.username", required=False)
username = serializers.CharField(source="user.username", read_only=True)
class Meta:
model = Account
......@@ -47,3 +47,11 @@ class AccountRegisterSerializer(serializers.ModelSerializer):
def validate_password(self, value):
password_validation.validate_password(value)
return value
def to_representation(self, instance):
serializer = AccountSerializer(instance)
return serializer.data
def save(self):
account = self.context.get('request').user.account
super(AccountRegisterSerializer, self).save(author=account)
......@@ -51,33 +51,6 @@ class AccountViewTest(APITestCase):
self.assertIn('"user_1"', response_string)
self.assertIn('"user_2"', response_string)
def test_list_all_accounts_paginate_failed(self):
url = self.BASE_URL + "?page=100"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
response_string = response.rendered_content.decode("utf-8")
self.assertIn('"detail":"Invalid page."', response_string)
def test_list_all_accounts_filter_success(self):
url = self.BASE_URL + "?username=" + self.user_1.username
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_string = response.rendered_content.decode("utf-8")
self.assertIn('"count":1', response_string)
def test_list_all_accounts_filter_failed(self):
url = self.BASE_URL + "?username=1234567890"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
response_string = response.rendered_content.decode("utf-8")
self.assertIn('"count":0', response_string)
def test_retrieve_account_success(self):
url = self.BASE_URL + str(self.officer.id) + "/"
response = self.client.get(url)
......@@ -150,24 +123,6 @@ class AccountViewTest(APITestCase):
'"action_type":"{}"'.format(ACTIVITY_TYPE_CREATE), response_string
)
def test_username_lowercased(self):
url = self.BASE_URL
data = {
"name": self.faker.name(),
"username": "aBcDeFgH",
"password": "justpass",
"email": self.faker.email(),
"phone_number": self.faker.phone_number(),
"area": self.faker.city(),
"is_admin": False,
}
self.client.post(
path=url, data=data, format="json",
)
self.assertTrue(Account.objects.filter(user__username="abcdefgh").exists())
def test_create_new_account_fails_with_poor_password(self):
url = self.BASE_URL
_account_id = self.faker.email()
......@@ -233,7 +188,7 @@ class AccountViewTest(APITestCase):
response = self.client.delete(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
# Have account deletion log
response = self.client.get("/logs/")
......
......@@ -3,17 +3,22 @@ from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from apps.accounts.filters import AccountFilter
from apps.accounts.filters import (
ACCOUNT_FILTERSET_FIELDS,
ACCOUNT_ORDERING_FIELDS,
ACCOUNT_SEARCH_FIELDS,
)
from apps.accounts.models import Account
from apps.accounts.serializers import (
AccountSerializer,
AccountRegisterSerializer,
)
from apps.commons.permissions import (
IsSelfOrAdministrator,
IsAuthenticated,
CreateOnly,
)
from apps.constants import (
......@@ -22,85 +27,39 @@ from apps.constants import (
ACTIVITY_TYPE_EDIT,
ACTIVITY_TYPE_DELETE,
)
from apps.logs.models import Log
class AccountViewSet(viewsets.ViewSet):
queryset = Account.objects.all().select_related("user").order_by("-created_at")
filter_backends = (DjangoFilterBackend,)
permission_classes = [
IsSelfOrAdministrator | CreateOnly,
]
def list(self, request):
paginator = PageNumberPagination()
filtered_set = AccountFilter(request.GET, queryset=self.queryset).qs
context = paginator.paginate_queryset(filtered_set, request)
serializer = AccountSerializer(context, many=True)
return paginator.get_paginated_response(serializer.data)
def retrieve(self, request, pk=None):
instance = get_object_or_404(self.queryset, pk=pk)
self.check_object_permissions(request, instance)
serializer = AccountSerializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request):
serializer = AccountRegisterSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
class AccountViewSet(viewsets.ModelViewSet):
serializer_class = AccountSerializer
queryset = Account.objects.all()
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
filterset_fields = ACCOUNT_FILTERSET_FIELDS
permission_classes = (IsAuthenticated | CreateOnly, )
pagination_class = PageNumberPagination
search_fields = ACCOUNT_SEARCH_FIELDS
ordering_fields = ACCOUNT_ORDERING_FIELDS
def get_queryset(self):
user = self.request.user
account = Account.objects.filter(user=user)
if account.first().is_admin:
return Account.objects.all()
return account
def get_serializer_class(self):
if self.action in ['create']:
return AccountRegisterSerializer
return AccountSerializer
def perform_create(self, serializer):
username = serializer.validated_data.pop("username").lower()
password = serializer.validated_data.pop("password")
user = User.objects.create_user(username=username, password=password)
account = Account.objects.create(user=user, **serializer.validated_data)
# Add account creation log
Log.objects.create(
model_name=MODEL_NAME_ACCOUNT,
object_id=account.id,
action_type=ACTIVITY_TYPE_CREATE,
author=account,
)
Account.objects.create(user=user, **serializer.validated_data)
return Response(
AccountSerializer(account).data, status=status.HTTP_201_CREATED,
)
def update(self, request, pk=None):
instance = get_object_or_404(self.queryset, pk=pk)
self.check_object_permissions(request, instance)
serializer = AccountSerializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
# Add account update log
Log.objects.create(
model_name=MODEL_NAME_ACCOUNT,
object_id=pk,
action_type=ACTIVITY_TYPE_EDIT,
author=request.user.account,
)
return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, pk=None):
instance = get_object_or_404(self.queryset, pk=pk)
self.check_object_permissions(request, instance)
instance.delete()
# Add account deletion log
Log.objects.create(
model_name=MODEL_NAME_ACCOUNT,
object_id=pk,
action_type=ACTIVITY_TYPE_DELETE,
author=request.user.account,
)
serializer = AccountSerializer(instance=instance)
return Response(serializer.data, status=status.HTTP_200_OK)
def perform_destroy(self, instance):
instance.delete(author=self.request.user.account)
@action(detail=False, methods=["get"], url_path="me")
def profile(self, request):
......
......@@ -2,58 +2,130 @@ from django_filters.rest_framework import FilterSet
from apps.cases.models import InvestigationCase, MonitoringCase
CASE_SUBJECT_FILTERSET_FIELDS = [
CASE_SUBJECT_FILTERSET_FIELDS = (
"name",
"age",
"is_male",
"address",
"district",
"sub_district",
]
"author__name",
"created_at",
)
CASE_SUBJECT_SEARCH_FIELDS = [
CASE_SUBJECT_SEARCH_FIELDS = (
"name",
"age",
"address",
"district",
"sub_district",
"created_at",
]
"author__name",
)
CASE_SUBJECT_ORDERING_FIELDS = [
CASE_SUBJECT_ORDERING_FIELDS = (
"name",
"age",
"is_male",
"address",
"district",
"sub_district",
"author__name",
"created_at",
)
INVESTIGATION_CASE_FILTERSET_FIELDS = (
"case_subject__name",
"case_subject__age",
"case_subject__is_male",
"case_subject__address",
"case_subject__district",
"case_subject__sub_district",
"reference_case__case_relation",
"reference_case__medical_symptoms",
"reference_case__risk_factors",
"reference_case__is_referral_needed",
"reference_case__medical_facility_reference",
"reference_case__outcome",
"case_relation",
"medical_symptoms",
"risk_factors",
"is_referral_needed",
"medical_facility_reference",
"outcome",
"author__name",
"created_at",
)
INVESTIGATION_CASE_SEARCH_FIELDS = (
"case_subject__name",
"case_subject__age",
"case_subject__address",
"case_subject__district",
"case_subject__sub_district",
"case_subject__created_at",
"reference_case__case_relation",
"reference_case__medical_symptoms",
"reference_case__risk_factors",
"reference_case__medical_facility_reference",
"reference_case__outcome",
"reference_case__created_at",
"case_relation",
"medical_symptoms",
"risk_factors",
"medical_facility_reference",
"outcome",
"author__name",
)
INVESTIGATION_CASE_ORDERING_FIELDS = (
"case_subject__name",
"case_subject__age",
"case_subject__is_male",
"case_subject__address",
"case_subject__district",
"case_subject__sub_district",
"case_subject__created_at",
"reference_case__case_relation",
"reference_case__medical_symptoms",
"reference_case__risk_factors",
"reference_case__is_referral_needed",
"reference_case__medical_facility_reference",
"reference_case__outcome",
"reference_case__created_at",
"case_relation",
"medical_symptoms",
"risk_factors",
"is_referral_needed",
"medical_facility_reference",
"outcome",
"author__name",
"created_at",
)
MONITORING_CASE_FILTERSET_FIELDS = (
"is_referred",
"is_checked",
"regular_medicine_intake",
"treatment_start_date",
"treatment_end_date",
"author__name",
"created_at",
)
MONITORING_CASE_SEARCH_FIELDS = (
"is_referred",
"is_checked",
"regular_medicine_intake",
"treatment_start_date",
"treatment_end_date",
"author__name",
)
MONITORING_CASE_ORDERING_FIELDS = (
"is_referred",
"is_checked",
"regular_medicine_intake",
"treatment_start_date",
"treatment_end_date",
"author__name",
"created_at",
]
class InvestigationCaseFilter(FilterSet):
class Meta:
model = InvestigationCase
fields = [
"case_subject_id",
"reference_case_id",
"case_relation",
"medical_symptoms",