Newer
Older

Carlos Vega
committed
from dateutil.relativedelta import relativedelta
from django.db.models.signals import post_save
from django.dispatch import receiver

Carlos Vega
committed
from django.db import transaction
from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES_CONTROL

Carlos Vega
committed
from web.models import Study
import logging
logger = logging.getLogger(__name__)
subject = models.ForeignKey("web.StudySubject", on_delete=models.CASCADE, verbose_name='Subject'
)
datetime_begin = models.DateTimeField(
verbose_name='Visit starts at'
)
datetime_end = models.DateTimeField(
verbose_name='Visit ends at'
) # Deadline before which all appointments need to be scheduled
is_finished = models.BooleanField(
verbose_name='Has ended',
default=False
)
post_mail_sent = models.BooleanField(choices=BOOL_CHOICES,
verbose_name='Post mail sent',
default=False
)
appointment_types = models.ManyToManyField("web.AppointmentType",
verbose_name='Requested appointments',
blank=True,
)
# this value is automatically computed by signal handled by
# update_visit_number method
visit_number = models.IntegerField(
verbose_name='Visit number',
default=1
)
Piotr Gawron
committed
return "%s %s" % (self.subject.subject.first_name, self.subject.subject.last_name)
Piotr Gawron
committed
return "%s %s" % (self.subject.subject.first_name, self.subject.subject.last_name)
def mark_as_finished(self):
self.is_finished = True
self.save()
Piotr Gawron
committed
create_follow_up = True
if self.subject.subject.dead:
create_follow_up = False
elif self.subject.resigned:
create_follow_up = False
elif self.subject.excluded:
create_follow_up = False
Piotr Gawron
committed
elif not self.subject.study.auto_create_follow_up:
create_follow_up = False
if create_follow_up:
visit_started = Visit.objects.filter(
subject=self.subject).filter(visit_number=1)[0].datetime_begin
follow_up_number = Visit.objects.filter(
subject=self.subject).count() + 1

Carlos Vega
committed
study = self.subject.study
Piotr Gawron
committed
if self.subject.type == SUBJECT_TYPE_CHOICES_CONTROL:

Carlos Vega
committed
args = {study.default_delta_time_for_follow_up_units: study.default_delta_time_for_control_follow_up}
else:
args = {study.default_delta_time_for_follow_up_units: study.default_delta_time_for_patient_follow_up}
time_to_next_visit = relativedelta(**args) * (follow_up_number - 1) #calculated from first visit
Piotr Gawron
committed
logger.warn('new visit: {} {} {}'.format(args, relativedelta(**args), time_to_next_visit))
Visit.objects.create(
subject=self.subject,
datetime_begin=visit_started + time_to_next_visit,
datetime_end=visit_started + time_to_next_visit + relativedelta(months=study.default_visit_duration_in_months)
@receiver(post_save, sender=Visit)

Carlos Vega
committed
def check_visit_number(sender, instance, created, **kwargs):
# no other solution to ensure the visit_number is in cronological order than to sort the whole list if there are future visits
if visit.subject is not None: #not sure if select_for_update has an effect, the tests work as well without it

Carlos Vega
committed
#new visit, sort only future visit respect to the new one
if created:
visits_before = Visit.objects.select_for_update().filter(subject=visit.subject).filter(datetime_begin__lt=visit.datetime_begin).count()
# we need to sort the future visits respect to the new one, if any
visits = Visit.objects.select_for_update().filter(subject=visit.subject).filter(datetime_begin__gte=visit.datetime_begin).order_by('datetime_begin','datetime_end')
with transaction.atomic(): #not sure if it has an effect, the tests work as well without it
for i, v in enumerate(visits):
expected_visit_number = (visits_before + i + 1)
if v.visit_number != expected_visit_number:
Visit.objects.filter(id=v.id).update(visit_number=expected_visit_number) # does not rise post_save, we avoid recursion
if v.id == visit.id: #if the iteration visit is the same that the instance that produced the signal call
#this ensures that the upper saved object is also updated, otherwise, refresh_from_db should be called
visit.visit_number = v.visit_number
else:
#if visits are modified, then, check everything
visits = Visit.objects.select_for_update().filter(subject=visit.subject).order_by('datetime_begin','datetime_end')
with transaction.atomic():
for i, v in enumerate(visits):
expected_visit_number = (i+1)
if v.visit_number != expected_visit_number: #update only those with wrong numbers
Visit.objects.filter(id=v.id).update(visit_number=expected_visit_number)