Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add table to track user facility allocation #1450

Merged
merged 5 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions care/users/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ class UsersConfig(AppConfig):
verbose_name = _("Users")

def ready(self):
try:
import care.users.signals # noqa F401
except ImportError:
pass
import care.users.signals # noqa F401
70 changes: 70 additions & 0 deletions care/users/migrations/0009_userfacilityallocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Generated by Django 4.2.2 on 2023-07-12 12:27

import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models


def fill_user_facility_allocation(apps, schema_editor):
UserFacilityAllocation = apps.get_model("users", "UserFacilityAllocation")
User = apps.get_model("users", "User")
users = User.objects.filter(home_facility__isnull=False)

to_create = [
UserFacilityAllocation(
user=user, facility=user.home_facility, start_date=user.date_joined
)
for user in users
]
UserFacilityAllocation.objects.bulk_create(to_create, batch_size=2000)


def reverse_fill_user_facility_allocation(apps, schema_editor):
UserFacilityAllocation = apps.get_model("users", "UserFacilityAllocation")
UserFacilityAllocation.objects.all().delete()


class Migration(migrations.Migration):
dependencies = [
("facility", "0370_merge_20230705_1500"),
("users", "0008_rename_skill_and_add_new_20230817_1937"),
]

operations = [
migrations.CreateModel(
name="UserFacilityAllocation",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("start_date", models.DateTimeField(default=django.utils.timezone.now)),
("end_date", models.DateTimeField(blank=True, null=True)),
(
"facility",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="facility.facility",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
),
migrations.RunPython(
fill_user_facility_allocation, reverse_fill_user_facility_allocation
),
]
14 changes: 14 additions & 0 deletions care/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _

from care.utils.models.base import BaseModel
Expand Down Expand Up @@ -359,3 +360,16 @@ def save(self, *args, **kwargs) -> None:
if self.district is not None:
self.state = self.district.state
super().save(*args, **kwargs)


class UserFacilityAllocation(models.Model):
"""
This model tracks the allocation of a user to a facility for metabase analytics.
"""

user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+")
facility = models.ForeignKey(
"facility.Facility", on_delete=models.CASCADE, related_name="+"
)
sainak marked this conversation as resolved.
Show resolved Hide resolved
start_date = models.DateTimeField(default=now)
end_date = models.DateTimeField(null=True, blank=True)
52 changes: 52 additions & 0 deletions care/users/signals.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import contextlib

from django.conf import settings
from django.core.mail import EmailMessage
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.utils.timezone import now
from django_rest_passwordreset.signals import reset_password_token_created

from .models import UserFacilityAllocation


@receiver(reset_password_token_created)
def password_reset_token_created(
Expand Down Expand Up @@ -40,3 +46,49 @@ def password_reset_token_created(
)
msg.content_subtype = "html" # Main content is now text/html
msg.send()


@receiver(pre_save, sender=settings.AUTH_USER_MODEL)
def save_fields_before_update(sender, instance, raw, using, update_fields, **kwargs):
if raw:
return

if instance.pk:
fields_to_save = {"home_facility"}
if update_fields:
fields_to_save &= set(update_fields)
if fields_to_save:
with contextlib.suppress(IndexError):
instance._previous_values = instance.__class__._base_manager.filter(
pk=instance.pk
).values(*fields_to_save)[0]


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def track_user_facility_allocation(
sender, instance, created, raw, using, update_fields, **kwargs
):
if raw or (update_fields and "home_facility" not in update_fields):
return

if created and instance.home_facility:
UserFacilityAllocation.objects.create(
user=instance, facility=instance.home_facility
)
return

last_home_facility = getattr(instance, "_previous_values", {}).get("home_facility")

if (
last_home_facility and instance.home_facility_id != last_home_facility
) or instance.deleted:
# this also includes the case when the user's new home facility is set to None
UserFacilityAllocation.objects.filter(
user=instance, facility=last_home_facility, end_date__isnull=True
).update(end_date=now())

if instance.home_facility_id and instance.home_facility_id != last_home_facility:
# create a new allocation if new home facility is changed
UserFacilityAllocation.objects.create(
user=instance, facility=instance.home_facility
)
88 changes: 88 additions & 0 deletions care/users/tests/test_user_homefacility_allocation_tracking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from care.users.models import User, UserFacilityAllocation
from care.utils.tests.test_base import TestBase


class TestUserFacilityAllocation(TestBase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.new_facility = cls.create_facility(cls.district)

@classmethod
def tearDownClass(cls):
cls.new_facility.delete()
super().tearDownClass()

def tearDown(self):
super().tearDown()
User._base_manager.filter(username="facility_allocation_test_user").delete()
UserFacilityAllocation.objects.all().delete()

def test_user_facility_allocation_is_created_when_user_is_created(self):
user = self.create_user(
self.district,
username="facility_allocation_test_user",
home_facility=self.facility,
)
self.assertTrue(UserFacilityAllocation.objects.filter(user=user).exists())

def test_user_facility_allocation_is_ended_when_home_facility_is_cleared(self):
user = self.create_user(
self.district,
username="facility_allocation_test_user",
home_facility=self.facility,
)
user.home_facility = None
user.save()
allocation = UserFacilityAllocation.objects.get(
user=user, facility=self.facility
)
self.assertIsNotNone(allocation.end_date)

def test_user_facility_allocation_is_ended_when_user_is_deleted(self):
user = self.create_user(
self.district,
username="facility_allocation_test_user",
home_facility=self.facility,
)
user.deleted = True
user.save()
allocation = UserFacilityAllocation.objects.get(
user=user, facility=self.facility
)
self.assertIsNotNone(allocation.end_date)

def test_user_facility_allocation_on_home_facility_changed(self):
user = self.create_user(
self.district,
username="facility_allocation_test_user",
home_facility=self.facility,
)
user.home_facility = self.new_facility
user.save()
allocation = UserFacilityAllocation.objects.get(
user=user, facility=self.facility
)
self.assertIsNotNone(allocation.end_date)
self.assertTrue(
UserFacilityAllocation.objects.filter(
user=user, facility=self.new_facility
).exists()
)

def test_user_facility_allocation_is_not_created_when_user_is_created_without_home_facility(
self,
):
user = self.create_user(self.district, username="facility_allocation_test_user")
self.assertFalse(UserFacilityAllocation.objects.filter(user=user).exists())

def test_user_facility_allocation_is_not_changed_when_update_fields_is_passed_without_home_facility(
self,
):
user = self.create_user(
self.district,
username="facility_allocation_test_user",
home_facility=self.facility,
)
user.save(update_fields=["last_login"])
self.assertEqual(UserFacilityAllocation.objects.filter(user=user).count(), 1)
Loading