this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add table to track user facility allocation (#1450)

* add table to track user facility allocation

* fixed edge cases

* update migration

Signed-off-by: Aakash Singh <mail@singhaakash.dev>

---------

Signed-off-by: Aakash Singh <mail@singhaakash.dev>

authored by

Aakash Singh and committed by
GitHub
ed4fcbee e83c10b2

+225 -4
+1 -4
care/users/apps.py
··· 7 7 verbose_name = _("Users") 8 8 9 9 def ready(self): 10 - try: 11 - import care.users.signals # noqa F401 12 - except ImportError: 13 - pass 10 + import care.users.signals # noqa F401
+70
care/users/migrations/0009_userfacilityallocation.py
··· 1 + # Generated by Django 4.2.2 on 2023-07-12 12:27 2 + 3 + import django.db.models.deletion 4 + import django.utils.timezone 5 + from django.conf import settings 6 + from django.db import migrations, models 7 + 8 + 9 + def fill_user_facility_allocation(apps, schema_editor): 10 + UserFacilityAllocation = apps.get_model("users", "UserFacilityAllocation") 11 + User = apps.get_model("users", "User") 12 + users = User.objects.filter(home_facility__isnull=False) 13 + 14 + to_create = [ 15 + UserFacilityAllocation( 16 + user=user, facility=user.home_facility, start_date=user.date_joined 17 + ) 18 + for user in users 19 + ] 20 + UserFacilityAllocation.objects.bulk_create(to_create, batch_size=2000) 21 + 22 + 23 + def reverse_fill_user_facility_allocation(apps, schema_editor): 24 + UserFacilityAllocation = apps.get_model("users", "UserFacilityAllocation") 25 + UserFacilityAllocation.objects.all().delete() 26 + 27 + 28 + class Migration(migrations.Migration): 29 + dependencies = [ 30 + ("facility", "0370_merge_20230705_1500"), 31 + ("users", "0008_rename_skill_and_add_new_20230817_1937"), 32 + ] 33 + 34 + operations = [ 35 + migrations.CreateModel( 36 + name="UserFacilityAllocation", 37 + fields=[ 38 + ( 39 + "id", 40 + models.BigAutoField( 41 + auto_created=True, 42 + primary_key=True, 43 + serialize=False, 44 + verbose_name="ID", 45 + ), 46 + ), 47 + ("start_date", models.DateTimeField(default=django.utils.timezone.now)), 48 + ("end_date", models.DateTimeField(blank=True, null=True)), 49 + ( 50 + "facility", 51 + models.ForeignKey( 52 + on_delete=django.db.models.deletion.CASCADE, 53 + related_name="+", 54 + to="facility.facility", 55 + ), 56 + ), 57 + ( 58 + "user", 59 + models.ForeignKey( 60 + on_delete=django.db.models.deletion.CASCADE, 61 + related_name="+", 62 + to=settings.AUTH_USER_MODEL, 63 + ), 64 + ), 65 + ], 66 + ), 67 + migrations.RunPython( 68 + fill_user_facility_allocation, reverse_fill_user_facility_allocation 69 + ), 70 + ]
+14
care/users/models.py
··· 5 5 from django.core.validators import MaxValueValidator, MinValueValidator 6 6 from django.db import models 7 7 from django.urls import reverse 8 + from django.utils.timezone import now 8 9 from django.utils.translation import gettext_lazy as _ 9 10 10 11 from care.utils.models.base import BaseModel ··· 359 360 if self.district is not None: 360 361 self.state = self.district.state 361 362 super().save(*args, **kwargs) 363 + 364 + 365 + class UserFacilityAllocation(models.Model): 366 + """ 367 + This model tracks the allocation of a user to a facility for metabase analytics. 368 + """ 369 + 370 + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="+") 371 + facility = models.ForeignKey( 372 + "facility.Facility", on_delete=models.CASCADE, related_name="+" 373 + ) 374 + start_date = models.DateTimeField(default=now) 375 + end_date = models.DateTimeField(null=True, blank=True)
+52
care/users/signals.py
··· 1 + import contextlib 2 + 1 3 from django.conf import settings 2 4 from django.core.mail import EmailMessage 5 + from django.db.models.signals import post_save, pre_save 3 6 from django.dispatch import receiver 4 7 from django.template.loader import render_to_string 8 + from django.utils.timezone import now 5 9 from django_rest_passwordreset.signals import reset_password_token_created 10 + 11 + from .models import UserFacilityAllocation 6 12 7 13 8 14 @receiver(reset_password_token_created) ··· 40 46 ) 41 47 msg.content_subtype = "html" # Main content is now text/html 42 48 msg.send() 49 + 50 + 51 + @receiver(pre_save, sender=settings.AUTH_USER_MODEL) 52 + def save_fields_before_update(sender, instance, raw, using, update_fields, **kwargs): 53 + if raw: 54 + return 55 + 56 + if instance.pk: 57 + fields_to_save = {"home_facility"} 58 + if update_fields: 59 + fields_to_save &= set(update_fields) 60 + if fields_to_save: 61 + with contextlib.suppress(IndexError): 62 + instance._previous_values = instance.__class__._base_manager.filter( 63 + pk=instance.pk 64 + ).values(*fields_to_save)[0] 65 + 66 + 67 + @receiver(post_save, sender=settings.AUTH_USER_MODEL) 68 + def track_user_facility_allocation( 69 + sender, instance, created, raw, using, update_fields, **kwargs 70 + ): 71 + if raw or (update_fields and "home_facility" not in update_fields): 72 + return 73 + 74 + if created and instance.home_facility: 75 + UserFacilityAllocation.objects.create( 76 + user=instance, facility=instance.home_facility 77 + ) 78 + return 79 + 80 + last_home_facility = getattr(instance, "_previous_values", {}).get("home_facility") 81 + 82 + if ( 83 + last_home_facility and instance.home_facility_id != last_home_facility 84 + ) or instance.deleted: 85 + # this also includes the case when the user's new home facility is set to None 86 + UserFacilityAllocation.objects.filter( 87 + user=instance, facility=last_home_facility, end_date__isnull=True 88 + ).update(end_date=now()) 89 + 90 + if instance.home_facility_id and instance.home_facility_id != last_home_facility: 91 + # create a new allocation if new home facility is changed 92 + UserFacilityAllocation.objects.create( 93 + user=instance, facility=instance.home_facility 94 + )
+88
care/users/tests/test_user_homefacility_allocation_tracking.py
··· 1 + from care.users.models import User, UserFacilityAllocation 2 + from care.utils.tests.test_base import TestBase 3 + 4 + 5 + class TestUserFacilityAllocation(TestBase): 6 + @classmethod 7 + def setUpClass(cls): 8 + super().setUpClass() 9 + cls.new_facility = cls.create_facility(cls.district) 10 + 11 + @classmethod 12 + def tearDownClass(cls): 13 + cls.new_facility.delete() 14 + super().tearDownClass() 15 + 16 + def tearDown(self): 17 + super().tearDown() 18 + User._base_manager.filter(username="facility_allocation_test_user").delete() 19 + UserFacilityAllocation.objects.all().delete() 20 + 21 + def test_user_facility_allocation_is_created_when_user_is_created(self): 22 + user = self.create_user( 23 + self.district, 24 + username="facility_allocation_test_user", 25 + home_facility=self.facility, 26 + ) 27 + self.assertTrue(UserFacilityAllocation.objects.filter(user=user).exists()) 28 + 29 + def test_user_facility_allocation_is_ended_when_home_facility_is_cleared(self): 30 + user = self.create_user( 31 + self.district, 32 + username="facility_allocation_test_user", 33 + home_facility=self.facility, 34 + ) 35 + user.home_facility = None 36 + user.save() 37 + allocation = UserFacilityAllocation.objects.get( 38 + user=user, facility=self.facility 39 + ) 40 + self.assertIsNotNone(allocation.end_date) 41 + 42 + def test_user_facility_allocation_is_ended_when_user_is_deleted(self): 43 + user = self.create_user( 44 + self.district, 45 + username="facility_allocation_test_user", 46 + home_facility=self.facility, 47 + ) 48 + user.deleted = True 49 + user.save() 50 + allocation = UserFacilityAllocation.objects.get( 51 + user=user, facility=self.facility 52 + ) 53 + self.assertIsNotNone(allocation.end_date) 54 + 55 + def test_user_facility_allocation_on_home_facility_changed(self): 56 + user = self.create_user( 57 + self.district, 58 + username="facility_allocation_test_user", 59 + home_facility=self.facility, 60 + ) 61 + user.home_facility = self.new_facility 62 + user.save() 63 + allocation = UserFacilityAllocation.objects.get( 64 + user=user, facility=self.facility 65 + ) 66 + self.assertIsNotNone(allocation.end_date) 67 + self.assertTrue( 68 + UserFacilityAllocation.objects.filter( 69 + user=user, facility=self.new_facility 70 + ).exists() 71 + ) 72 + 73 + def test_user_facility_allocation_is_not_created_when_user_is_created_without_home_facility( 74 + self, 75 + ): 76 + user = self.create_user(self.district, username="facility_allocation_test_user") 77 + self.assertFalse(UserFacilityAllocation.objects.filter(user=user).exists()) 78 + 79 + def test_user_facility_allocation_is_not_changed_when_update_fields_is_passed_without_home_facility( 80 + self, 81 + ): 82 + user = self.create_user( 83 + self.district, 84 + username="facility_allocation_test_user", 85 + home_facility=self.facility, 86 + ) 87 + user.save(update_fields=["last_login"]) 88 + self.assertEqual(UserFacilityAllocation.objects.filter(user=user).count(), 1)