From 43d25c671fd877b4b9f6459e9c07c7a59d51573e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Grou=C3=A8s?= <valentin.groues@uni.lu> Date: Wed, 22 Mar 2017 22:44:50 +0100 Subject: [PATCH] split models into separate files --- smash/web/forms.py | 3 +- smash/web/models.py | 643 ------------------------ smash/web/models/__init__.py | 29 ++ smash/web/models/appointment.py | 112 +++++ smash/web/models/appointment_type.py | 64 +++ smash/web/models/avaibility.py | 32 ++ smash/web/models/constants.py | 16 + smash/web/models/flying_team.py | 38 ++ smash/web/models/holiday.py | 25 + smash/web/models/item.py | 29 ++ smash/web/models/language.py | 24 + smash/web/models/location.py | 17 + smash/web/models/room.py | 35 ++ smash/web/models/subject.py | 177 +++++++ smash/web/models/visit.py | 63 +++ smash/web/models/worker.py | 95 ++++ smash/web/tests/functions.py | 5 +- smash/web/tests/test_SubjectAddForm.py | 6 +- smash/web/tests/test_SubjectEditForm.py | 5 +- 19 files changed, 767 insertions(+), 651 deletions(-) delete mode 100644 smash/web/models.py create mode 100644 smash/web/models/__init__.py create mode 100644 smash/web/models/appointment.py create mode 100644 smash/web/models/appointment_type.py create mode 100644 smash/web/models/avaibility.py create mode 100644 smash/web/models/constants.py create mode 100644 smash/web/models/flying_team.py create mode 100644 smash/web/models/holiday.py create mode 100644 smash/web/models/item.py create mode 100644 smash/web/models/language.py create mode 100644 smash/web/models/location.py create mode 100644 smash/web/models/room.py create mode 100644 smash/web/models/subject.py create mode 100644 smash/web/models/visit.py create mode 100644 smash/web/models/worker.py diff --git a/smash/web/forms.py b/smash/web/forms.py index 35a319b8..5033f1a1 100644 --- a/smash/web/forms.py +++ b/smash/web/forms.py @@ -5,6 +5,7 @@ from django.forms import ModelForm, Form from django.utils.dates import MONTHS from models import Subject, Worker, Appointment, Visit, AppointmentType +from models.constants import SUBJECT_TYPE_CHOICES """ Possible redundancy, but if need arises, contents of forms can be easily customized @@ -249,6 +250,6 @@ class StatisticsForm(Form): self.fields['month'] = forms.ChoiceField(choices=MONTHS.items(), initial=month) self.fields['year'] = forms.ChoiceField(choices=year_choices, initial=year) choices = [(-1, "all")] - choices.extend(Subject.SUBJECT_TYPE_CHOICES.items()) + choices.extend(SUBJECT_TYPE_CHOICES.items()) self.fields['subject_type'] = forms.ChoiceField(choices=choices, initial="-1") self.fields['visit'] = forms.ChoiceField(choices=visit_choices, initial="-1") diff --git a/smash/web/models.py b/smash/web/models.py deleted file mode 100644 index 4fd299bd..00000000 --- a/smash/web/models.py +++ /dev/null @@ -1,643 +0,0 @@ -from __future__ import unicode_literals - -import datetime - -from django.contrib.auth.models import User -from django.db import models - - -def get_current_year(): - return datetime.datetime.now().year - - -BOOL_CHOICES = ((True, 'Yes'), (False, 'No')) - - -class Location(models.Model): - name = models.CharField(max_length=20) - - def __str__(self): - return "%s" % self.name - - def __unicode__(self): - return "%s" % self.name - - -class Language(models.Model): - name = models.CharField(max_length=20) - image = models.ImageField() - - def __str__(self): - return self.name - - def image_img(self): - if self.image: - return u'<img class="flag-icon" src="%s" />' % (self.image.url) - else: - return 'No image' - - image_img.short_description = 'Flag icon' - image_img.allow_tags = True - - -class Subject(models.Model): - SEX_CHOICES_MALE = 'M' - SEX_CHOICES_FEMALE = 'F' - - SEX_CHOICES = ( - (SEX_CHOICES_MALE, 'Male'), - (SEX_CHOICES_FEMALE, 'Female'), - ) - - SUBJECT_TYPE_CHOICES_CONTROL = 'C' - SUBJECT_TYPE_CHOICES = { - SUBJECT_TYPE_CHOICES_CONTROL: 'CONTROL', - 'P': 'PATIENT', - } - - def finish_all_visits(self): - visits = Visit.objects.filter(subject=self, is_finished=False) - for visit in visits: - visit.is_finished = True - visit.save() - - def finish_all_appointments(self): - appointments = Appointment.objects.filter(visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) - for appointment in appointments: - appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED - appointment.save() - - def mark_as_dead(self): - self.dead = True - self.save() - - self.finish_all_visits() - self.finish_all_appointments() - - def mark_as_rejected(self): - self.resigned = True - self.save() - - self.finish_all_visits() - self.finish_all_appointments() - - sex = models.CharField(max_length=1, - choices=SEX_CHOICES, - verbose_name='Sex' - ) - postponed = models.BooleanField(choices=BOOL_CHOICES, - verbose_name='Postponed', - default=False - ) - datetime_contact_reminder = models.DateField( - null=True, - blank=True, - verbose_name='Contact on', - ) - type = models.CharField(max_length=1, - choices=SUBJECT_TYPE_CHOICES.items(), - verbose_name='Type' - ) - - dead = models.BooleanField( - verbose_name='Dead', - default=False, - editable=False - ) - resigned = models.BooleanField( - verbose_name='Resigned', - default=False, - editable=False - ) - default_location = models.ForeignKey(Location, - verbose_name='Default appointment location', - ) - first_name = models.CharField(max_length=50, - verbose_name='First name' - ) - last_name = models.CharField(max_length=50, - verbose_name='Last name' - ) - languages = models.ManyToManyField(Language, - blank=True, - verbose_name='Known languages' - ) - default_written_communication_language = models.ForeignKey(Language, - null=True, - blank=True, - related_name="%(class)s_written_comunication", - verbose_name='Default language for document generation' - ) - phone_number = models.CharField(max_length=20, - null=True, - blank=True, - verbose_name='Phone number' - ) - phone_number_2 = models.CharField(max_length=20, - null=True, - blank=True, - verbose_name='Phone number 2' - ) - phone_number_3 = models.CharField(max_length=20, - null=True, - blank=True, - verbose_name='Phone number 3' - ) - email = models.EmailField( - null=True, - blank=True, - verbose_name='E-mail' - ) - date_born = models.DateField( - null=True, - blank=True, - verbose_name='Date of birth (YYYY-MM-DD)' - ) - address = models.CharField(max_length=255, - blank=True, - verbose_name='Address' - ) - postal_code = models.CharField(max_length=7, - blank=True, - verbose_name='Postal code' - ) - city = models.CharField(max_length=50, - blank=True, - verbose_name='City' - ) - country = models.CharField(max_length=50, - verbose_name='Country' - ) - screening_number = models.CharField(max_length=50, - unique=True, - verbose_name='Screening number' - ) - nd_number = models.CharField(max_length=6, - blank=True, - verbose_name='ND number' - ) - mpower_id = models.CharField(max_length=20, - blank=True, - verbose_name='MPower ID' - ) - comments = models.TextField(max_length=2000, - blank=True, - verbose_name='Comments' - ) - date_added = models.DateField(verbose_name='Added on', - auto_now=True - ) - referral = models.CharField(max_length=128, - null=True, - blank=True, - verbose_name='Referred by' - ) - diagnosis = models.CharField(max_length=128, - null=True, - blank=True, - verbose_name='Diagnosis' - ) - year_of_diagnosis = models.IntegerField( - default=0, - null=True, - blank=True, - verbose_name='Year of diagnosis (YYYY)' - ) - - def latest_visit(self): - visits = self.visit_set.all() - if len(visits) == 0: - return None - result = visits[0] - for visit in visits: - if visit.datetime_begin > result.datetime_begin: - result = visit - return result - - def __str__(self): - return "%s %s" % (self.first_name, self.last_name) - - def __unicode__(self): - return "%s %s" % (self.first_name, self.last_name) - - -class Item(models.Model): - is_fixed = models.BooleanField( - default=False, - verbose_name='Is the item fixed?' - ) - - disposable = models.BooleanField( - default=False, - verbose_name='Disposable set' - ) - - name = models.CharField(max_length=255, - verbose_name='Name' - ) - - def __str__(self): - return self.name - - def __unicode__(self): - return self.name - - -class Room(models.Model): - equipment = models.ManyToManyField(Item, - verbose_name='On-site equipment', - blank=True - ) - owner = models.CharField(max_length=50, - verbose_name='Owner' - ) - address = models.CharField(max_length=255, - verbose_name='Address' - ) - city = models.CharField(max_length=50, - verbose_name='City' - ) - room_number = models.IntegerField( - verbose_name='Room number' - ) - floor = models.IntegerField( - verbose_name='Floor' - ) - is_vehicle = models.BooleanField( - verbose_name='Is a vehicle?' - ) - - def __str__(self): - return "%d %s %s" % (self.room_number, self.address, self.city) - - def __unicode__(self): - return "%d %s %s" % (self.room_number, self.address, self.city) - - -class AppointmentType(models.Model): - DEFAULT_COLOR = '#cfc600' - DEFAULT_FONT_COLOR = '#00000' - - required_equipment = models.ManyToManyField(Item, - verbose_name='Required equipment', - blank=True - ) - code = models.CharField(max_length=20, - verbose_name='Appointment code' - ) - description = models.CharField(max_length=2000, - verbose_name='Appointment description' - ) - default_duration = models.IntegerField( - verbose_name='Default duration (in minutes)' - ) - calendar_color_priority = models.IntegerField( - verbose_name='Calendar color priority', - default=1 - ) - calendar_color = models.CharField(max_length=2000, - verbose_name='Calendar color', - default=DEFAULT_COLOR - ) - calendar_font_color = models.CharField(max_length=2000, - verbose_name='Calendar color', - default=DEFAULT_FONT_COLOR - ) - rest_time = models.IntegerField( - verbose_name='Suggested rest time', - default=0 - ) - can_be_parallelized = models.BooleanField( - verbose_name='Can be parallelized', - default=False - ) - REQ_ROLE_CHOICES = ( - ('DOCTOR', 'Doctor'), - ('NURSE', 'Nurse'), - ('PSYCHOLOGIST', 'Psychologist'), - ('ANY', 'Any') - ) - required_worker = models.CharField(max_length=20, choices=REQ_ROLE_CHOICES, - verbose_name='Type of worker required for appointment', - default='ANY' - ) - - class Meta: - ordering = ['description'] - - def __str__(self): - return self.description - - def __unicode__(self): - return self.description - - -class Worker(models.Model): - languages = models.ManyToManyField(Language, - verbose_name='Known languages' - ) - locations = models.ManyToManyField(Location, - verbose_name='Locations' - ) - appointments = models.ManyToManyField('Appointment', blank=True, - verbose_name='Appointments' - ) - user = models.OneToOneField(User, blank=True, null=True, - verbose_name='Username' - ) - first_name = models.CharField(max_length=50, - verbose_name='First name' - ) - last_name = models.CharField(max_length=50, - verbose_name='Last name' - ) - phone_number = models.CharField(max_length=20, - verbose_name='Phone number' - ) - unit = models.CharField(max_length=50, - verbose_name='Unit' - ) - email = models.EmailField( - verbose_name='E-mail' - ) - ROLE_CHOICES_SECRETARY = "SECRETARY" - ROLE_CHOICES = ( - ('DOCTOR', 'Doctor'), - ('NURSE', 'Nurse'), - ('PSYCHOLOGIST', 'Psychologist'), - ('TECHNICIAN', 'Technician'), - (ROLE_CHOICES_SECRETARY, 'Secretary') - ) - role = models.CharField(max_length=20, choices=ROLE_CHOICES, - verbose_name='Role' - ) - specialization = models.CharField(max_length=20, - verbose_name='Specialization' - ) - - def is_on_leave(self): - if len(self.holiday_set.filter(datetime_end__gt=datetime.datetime.now(), - datetime_start__lt=datetime.datetime.now())): - return True - return False - - @staticmethod - def get_by_user(the_user): - if isinstance(the_user, User): - workers = Worker.objects.filter(user=the_user) - if len(workers) > 0: - return workers[0] - else: - return None - elif isinstance(the_user, Worker): - return the_user - elif the_user is not None: - raise TypeError("Unknown class type: " + the_user.__class__.__name__) - else: - return None - - @staticmethod - def get_details(the_user): - if not the_user.is_authenticated: - return 'Guest', 'Test account' - - person = Worker.objects.filter(user=the_user) - - if len(person) == 0: - return the_user.get_full_name(), '<No worker information>' - else: - # For get_*_display, see: - # https://docs.djangoproject.com/en/1.10/topics/db/models/#field-options - return unicode(person[0]), person[0].get_role_display() - - def __str__(self): - return "%s %s" % (self.first_name, self.last_name) - - def __unicode__(self): - return "%s %s" % (self.first_name, self.last_name) - - -class FlyingTeam(models.Model): - # doctor = models.ForeignKey(Worker, related_name='FlyingTeamDoctor', - # verbose_name='Doctor' - # ) - # nurse = models.ForeignKey(Worker, related_name='FlyingTeamNurse', - # verbose_name='Nurse' - # ) - # psychologist = models.ForeignKey(Worker, related_name='FlyingTeamPsychologist', - # verbose_name='Psychologist' - # ) - # datetime_called = models.DateTimeField( - # verbose_name='Created on' - # ) - # datetime_until = models.DateTimeField( - # verbose_name='Disbanded on' - # ) - # - # def __str__(self): - # return "%s %s %s" % (self.doctor.last_name, self.nurse.last_name, self.psychologist.last_name) - # - # def __unicode__(self): - # return "%s %s %s" % (self.doctor.last_name, self.nurse.last_name, self.psychologist.last_name) - place = models.CharField(max_length=255, verbose_name='Place') - - def __str__(self): - return "%s" % self.place - - def __unicode__(self): - return "%s" % self.place - - -class Avaibility(models.Model): - person = models.ForeignKey(Worker, on_delete=models.CASCADE, - verbose_name='Worker' - ) - day_number = models.IntegerField( - verbose_name='Day of the week' - ) - available_from = models.TimeField( - verbose_name='Avaible since' - ) - available_till = models.TimeField( - verbose_name='Avaible until' - ) - is_current = models.BooleanField( - verbose_name='Is current?', - default=True - ) - - def __str__(self): - return "%d %s %s" % (self.day_number, self.person.last_name, self.person.first_name) - - def __unicode__(self): - return "%d %s %s" % (self.day_number, self.person.last_name, self.person.first_name) - - -class Holiday(models.Model): - person = models.ForeignKey(Worker, on_delete=models.CASCADE, - verbose_name='Worker' - ) - datetime_start = models.DateTimeField( - verbose_name='On leave since' - ) - datetime_end = models.DateTimeField( - verbose_name='On leave until' - ) - - def __str__(self): - return "%s %s" % (self.person.first_name, self.person.last_name) - - def __unicode__(self): - return "%s %s" % (self.person.first_name, self.person.last_name) - - -class Visit(models.Model): - subject = models.ForeignKey(Subject, 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(AppointmentType, - verbose_name='Requested appointments', - blank=True, - ) - - def __unicode__(self): - return "%s %s" % (self.subject.first_name, self.subject.last_name) - - def __str__(self): - return "%s %s" % (self.subject.first_name, self.subject.last_name) - - def follow_up_title(self): - count = Visit.objects.filter(subject=self.subject, datetime_begin__lt=self.datetime_begin).count() - return "Visit " + str(count + 1) - - def mark_as_finished(self): - self.is_finished = True - self.save() - - if (not self.subject.dead) and (not self.subject.resigned): - visit_started = self.datetime_begin - - time_to_next_visit = datetime.timedelta(days=365) - if self.subject.type == Subject.SUBJECT_TYPE_CHOICES_CONTROL: - time_to_next_visit = datetime.timedelta(days=365 * 3 + 366) - - Visit.objects.create( - subject=self.subject, - datetime_begin=visit_started + time_to_next_visit, - datetime_end=visit_started + time_to_next_visit + datetime.timedelta(days=93) - ) - - -class Appointment(models.Model): - APPOINTMENT_STATUS_SCHEDULED = 'SCHEDULED' - APPOINTMENT_STATUS_FINISHED = 'FINISHED' - APPOINTMENT_STATUS_CANCELLED = 'CANCELLED' - APPOINTMENT_STATUS_NO_SHOW = 'NO_SHOW' - APPOINTMENT_STATUS_CHOICES = { - APPOINTMENT_STATUS_SCHEDULED: 'Scheduled', - APPOINTMENT_STATUS_FINISHED: 'Finished', - APPOINTMENT_STATUS_CANCELLED: 'Cancelled', - APPOINTMENT_STATUS_NO_SHOW: 'No Show', - } - - flying_team = models.ForeignKey(FlyingTeam, - verbose_name='Flying team (if applicable)', - null=True, blank=True - ) - worker_assigned = models.ForeignKey(Worker, - verbose_name='Worker conducting the assessment (if applicable)', - null=True, blank=True - ) - appointment_types = models.ManyToManyField(AppointmentType, - verbose_name='Appointment types', - blank=True - ) - room = models.ForeignKey(Room, - verbose_name='Room ID', - null=True, - blank=True - ) - location = models.ForeignKey(Location, - verbose_name='Location', - ) - visit = models.ForeignKey(Visit, - verbose_name='Visit ID' - ) - comment = models.TextField(max_length=1024, - verbose_name='Comment', - null=True, - blank=True - ) - datetime_when = models.DateTimeField( - verbose_name='Appointment on', - null=True, blank=True - ) - length = models.IntegerField( - verbose_name='Appointment length (in minutes)' - ) # Potentially redundant; but can be used to manually adjust appointment's length - - status = models.CharField(max_length=20, choices=APPOINTMENT_STATUS_CHOICES.items(), - verbose_name='Status', - editable=False, - default=APPOINTMENT_STATUS_SCHEDULED - ) - - def mark_as_finished(self): - self.status = Appointment.APPOINTMENT_STATUS_FINISHED - self.save() - - def mark_as_cancelled(self): - self.status = Appointment.APPOINTMENT_STATUS_CANCELLED - self.save() - - def mark_as_no_show(self): - self.status = Appointment.APPOINTMENT_STATUS_NO_SHOW - self.save() - - def datetime_until(self): - if self.datetime_when is None: - return None - else: - return self.datetime_when + datetime.timedelta(minutes=max(self.length, 15)) - - def color(self): - result = AppointmentType.DEFAULT_COLOR - priority = 1000000 - for type in self.appointment_types.all(): - if type.calendar_color_priority < priority: - priority = type.calendar_color_priority - result = type.calendar_color - return result - - def font_color(self): - result = AppointmentType.DEFAULT_FONT_COLOR - priority = 1000000 - for type in self.appointment_types.all(): - if type.calendar_color_priority < priority: - priority = type.calendar_color_priority - result = type.calendar_font_color - return result - - def title(self): - if self.visit.subject.screening_number == "---": - return self.comment.replace("\n", ";").replace("\r", ";") - else: - title = self.visit.subject.first_name + " " + self.visit.subject.last_name + " type: " - for appointment_type in self.appointment_types.all(): - title += appointment_type.code + ", " - return title diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py new file mode 100644 index 00000000..553a5448 --- /dev/null +++ b/smash/web/models/__init__.py @@ -0,0 +1,29 @@ +# coding=utf-8 +from __future__ import unicode_literals + +import datetime + +from django.contrib.auth.models import User + +from flying_team import FlyingTeam +from location import Location +from room import Room +from visit import Visit +from worker import Worker +from appointment import Appointment +from appointment_type import AppointmentType +from avaibility import Avaibility +from holiday import Holiday +from item import Item +from language import Language +from subject import Subject + +__author__ = 'Valentin Grouès' + + +def get_current_year(): + return datetime.datetime.now().year + + +__all__ = [FlyingTeam, Appointment, AppointmentType, Avaibility, Holiday, Item, Language, Location, Room, Subject, + Visit, Worker] diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py new file mode 100644 index 00000000..62e42d9a --- /dev/null +++ b/smash/web/models/appointment.py @@ -0,0 +1,112 @@ +# coding=utf-8 +import datetime + +from django.db import models + +from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_FONT_COLOR +from . import FlyingTeam, Location, Room, Visit, Worker + +__author__ = 'Valentin Grouès' + + +class Appointment(models.Model): + class Meta: + app_label = 'web' + + APPOINTMENT_STATUS_SCHEDULED = 'SCHEDULED' + APPOINTMENT_STATUS_FINISHED = 'FINISHED' + APPOINTMENT_STATUS_CANCELLED = 'CANCELLED' + APPOINTMENT_STATUS_NO_SHOW = 'NO_SHOW' + APPOINTMENT_STATUS_CHOICES = { + APPOINTMENT_STATUS_SCHEDULED: 'Scheduled', + APPOINTMENT_STATUS_FINISHED: 'Finished', + APPOINTMENT_STATUS_CANCELLED: 'Cancelled', + APPOINTMENT_STATUS_NO_SHOW: 'No Show', + } + + flying_team = models.ForeignKey(FlyingTeam, + verbose_name='Flying team (if applicable)', + null=True, blank=True + ) + worker_assigned = models.ForeignKey(Worker, + verbose_name='Worker conducting the assessment (if applicable)', + null=True, blank=True + ) + appointment_types = models.ManyToManyField("web.AppointmentType", + verbose_name='Appointment types', + blank=True + ) + room = models.ForeignKey(Room, + verbose_name='Room ID', + null=True, + blank=True + ) + location = models.ForeignKey(Location, + verbose_name='Location', + ) + visit = models.ForeignKey(Visit, + verbose_name='Visit ID' + ) + comment = models.TextField(max_length=1024, + verbose_name='Comment', + null=True, + blank=True + ) + datetime_when = models.DateTimeField( + verbose_name='Appointment on', + null=True, blank=True + ) + length = models.IntegerField( + verbose_name='Appointment length (in minutes)' + ) # Potentially redundant; but can be used to manually adjust appointment's length + + status = models.CharField(max_length=20, choices=APPOINTMENT_STATUS_CHOICES.items(), + verbose_name='Status', + editable=False, + default=APPOINTMENT_STATUS_SCHEDULED + ) + + def mark_as_finished(self): + self.status = Appointment.APPOINTMENT_STATUS_FINISHED + self.save() + + def mark_as_cancelled(self): + self.status = Appointment.APPOINTMENT_STATUS_CANCELLED + self.save() + + def mark_as_no_show(self): + self.status = Appointment.APPOINTMENT_STATUS_NO_SHOW + self.save() + + def datetime_until(self): + if self.datetime_when is None: + return None + else: + return self.datetime_when + datetime.timedelta(minutes=max(self.length, 15)) + + def color(self): + result = APPOINTMENT_TYPE_DEFAULT_COLOR + priority = 1000000 + for type in self.appointment_types.all(): + if type.calendar_color_priority < priority: + priority = type.calendar_color_priority + result = type.calendar_color + return result + + def font_color(self): + result = APPOINTMENT_TYPE_DEFAULT_FONT_COLOR + priority = 1000000 + for type in self.appointment_types.all(): + if type.calendar_color_priority < priority: + priority = type.calendar_color_priority + result = type.calendar_font_color + return result + + def title(self): + if self.visit.subject.screening_number == "---": + return self.comment.replace("\n", ";").replace("\r", ";") + else: + title = self.visit.subject.first_name + " " + self.visit.subject.last_name + " type: " + for appointment_type in self.appointment_types.all(): + title += appointment_type.code + ", " + return title diff --git a/smash/web/models/appointment_type.py b/smash/web/models/appointment_type.py new file mode 100644 index 00000000..9b3e48dc --- /dev/null +++ b/smash/web/models/appointment_type.py @@ -0,0 +1,64 @@ +# coding=utf-8 +from django.db import models + +from constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_FONT_COLOR + +__author__ = 'Valentin Grouès' + + +class AppointmentType(models.Model): + class Meta: + app_label = 'web' + + required_equipment = models.ManyToManyField("web.Item", + verbose_name='Required equipment', + blank=True + ) + code = models.CharField(max_length=20, + verbose_name='Appointment code' + ) + description = models.CharField(max_length=2000, + verbose_name='Appointment description' + ) + default_duration = models.IntegerField( + verbose_name='Default duration (in minutes)' + ) + calendar_color_priority = models.IntegerField( + verbose_name='Calendar color priority', + default=1 + ) + calendar_color = models.CharField(max_length=2000, + verbose_name='Calendar color', + default=APPOINTMENT_TYPE_DEFAULT_COLOR + ) + calendar_font_color = models.CharField(max_length=2000, + verbose_name='Calendar color', + default=APPOINTMENT_TYPE_DEFAULT_FONT_COLOR + ) + rest_time = models.IntegerField( + verbose_name='Suggested rest time', + default=0 + ) + can_be_parallelized = models.BooleanField( + verbose_name='Can be parallelized', + default=False + ) + REQ_ROLE_CHOICES = ( + ('DOCTOR', 'Doctor'), + ('NURSE', 'Nurse'), + ('PSYCHOLOGIST', 'Psychologist'), + ('ANY', 'Any') + ) + required_worker = models.CharField(max_length=20, choices=REQ_ROLE_CHOICES, + verbose_name='Type of worker required for appointment', + default='ANY' + ) + + class Meta: + ordering = ['description'] + + def __str__(self): + return self.description + + def __unicode__(self): + return self.description diff --git a/smash/web/models/avaibility.py b/smash/web/models/avaibility.py new file mode 100644 index 00000000..bbc465ad --- /dev/null +++ b/smash/web/models/avaibility.py @@ -0,0 +1,32 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Avaibility(models.Model): + class Meta: + app_label = 'web' + + person = models.ForeignKey("web.Worker", on_delete=models.CASCADE, + verbose_name='Worker' + ) + day_number = models.IntegerField( + verbose_name='Day of the week' + ) + available_from = models.TimeField( + verbose_name='Avaible since' + ) + available_till = models.TimeField( + verbose_name='Avaible until' + ) + is_current = models.BooleanField( + verbose_name='Is current?', + default=True + ) + + def __str__(self): + return "%d %s %s" % (self.day_number, self.person.last_name, self.person.first_name) + + def __unicode__(self): + return "%d %s %s" % (self.day_number, self.person.last_name, self.person.first_name) diff --git a/smash/web/models/constants.py b/smash/web/models/constants.py new file mode 100644 index 00000000..1d8ad089 --- /dev/null +++ b/smash/web/models/constants.py @@ -0,0 +1,16 @@ +# coding=utf-8 +__author__ = 'Valentin Grouès' +BOOL_CHOICES = ((True, 'Yes'), (False, 'No')) +SEX_CHOICES_MALE = 'M' +SEX_CHOICES_FEMALE = 'F' +SEX_CHOICES = ( + (SEX_CHOICES_MALE, 'Male'), + (SEX_CHOICES_FEMALE, 'Female'), +) +SUBJECT_TYPE_CHOICES_CONTROL = 'C' +SUBJECT_TYPE_CHOICES = { + SUBJECT_TYPE_CHOICES_CONTROL: 'CONTROL', + 'P': 'PATIENT', +} +APPOINTMENT_TYPE_DEFAULT_COLOR = '#cfc600' +APPOINTMENT_TYPE_DEFAULT_FONT_COLOR = '#00000' diff --git a/smash/web/models/flying_team.py b/smash/web/models/flying_team.py new file mode 100644 index 00000000..04d6f670 --- /dev/null +++ b/smash/web/models/flying_team.py @@ -0,0 +1,38 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class FlyingTeam(models.Model): + class Meta: + app_label = 'web' + + # doctor = models.ForeignKey(Worker, related_name='FlyingTeamDoctor', + # verbose_name='Doctor' + # ) + # nurse = models.ForeignKey(Worker, related_name='FlyingTeamNurse', + # verbose_name='Nurse' + # ) + # psychologist = models.ForeignKey(Worker, related_name='FlyingTeamPsychologist', + # verbose_name='Psychologist' + # ) + # datetime_called = models.DateTimeField( + # verbose_name='Created on' + # ) + # datetime_until = models.DateTimeField( + # verbose_name='Disbanded on' + # ) + # + # def __str__(self): + # return "%s %s %s" % (self.doctor.last_name, self.nurse.last_name, self.psychologist.last_name) + # + # def __unicode__(self): + # return "%s %s %s" % (self.doctor.last_name, self.nurse.last_name, self.psychologist.last_name) + place = models.CharField(max_length=255, verbose_name='Place') + + def __str__(self): + return "%s" % self.place + + def __unicode__(self): + return "%s" % self.place diff --git a/smash/web/models/holiday.py b/smash/web/models/holiday.py new file mode 100644 index 00000000..6e3f37b4 --- /dev/null +++ b/smash/web/models/holiday.py @@ -0,0 +1,25 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Holiday(models.Model): + class Meta: + app_label = 'web' + + person = models.ForeignKey("web.Worker", on_delete=models.CASCADE, + verbose_name='Worker' + ) + datetime_start = models.DateTimeField( + verbose_name='On leave since' + ) + datetime_end = models.DateTimeField( + verbose_name='On leave until' + ) + + def __str__(self): + return "%s %s" % (self.person.first_name, self.person.last_name) + + def __unicode__(self): + return "%s %s" % (self.person.first_name, self.person.last_name) diff --git a/smash/web/models/item.py b/smash/web/models/item.py new file mode 100644 index 00000000..89467831 --- /dev/null +++ b/smash/web/models/item.py @@ -0,0 +1,29 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Item(models.Model): + class Meta: + app_label = 'web' + + is_fixed = models.BooleanField( + default=False, + verbose_name='Is the item fixed?' + ) + + disposable = models.BooleanField( + default=False, + verbose_name='Disposable set' + ) + + name = models.CharField(max_length=255, + verbose_name='Name' + ) + + def __str__(self): + return self.name + + def __unicode__(self): + return self.name diff --git a/smash/web/models/language.py b/smash/web/models/language.py new file mode 100644 index 00000000..58219183 --- /dev/null +++ b/smash/web/models/language.py @@ -0,0 +1,24 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Language(models.Model): + class Meta: + app_label = 'web' + + name = models.CharField(max_length=20) + image = models.ImageField() + + def __str__(self): + return self.name + + def image_img(self): + if self.image: + return u'<img class="flag-icon" src="%s" />' % self.image.url + else: + return 'No image' + + image_img.short_description = 'Flag icon' + image_img.allow_tags = True diff --git a/smash/web/models/location.py b/smash/web/models/location.py new file mode 100644 index 00000000..a81f2af3 --- /dev/null +++ b/smash/web/models/location.py @@ -0,0 +1,17 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Location(models.Model): + class Meta: + app_label = 'web' + + name = models.CharField(max_length=20) + + def __str__(self): + return "%s" % self.name + + def __unicode__(self): + return "%s" % self.name diff --git a/smash/web/models/room.py b/smash/web/models/room.py new file mode 100644 index 00000000..1a02850b --- /dev/null +++ b/smash/web/models/room.py @@ -0,0 +1,35 @@ +# coding=utf-8 +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Room(models.Model): + equipment = models.ManyToManyField("web.Item", + verbose_name='On-site equipment', + blank=True + ) + owner = models.CharField(max_length=50, + verbose_name='Owner' + ) + address = models.CharField(max_length=255, + verbose_name='Address' + ) + city = models.CharField(max_length=50, + verbose_name='City' + ) + room_number = models.IntegerField( + verbose_name='Room number' + ) + floor = models.IntegerField( + verbose_name='Floor' + ) + is_vehicle = models.BooleanField( + verbose_name='Is a vehicle?' + ) + + def __str__(self): + return "%d %s %s" % (self.room_number, self.address, self.city) + + def __unicode__(self): + return "%d %s %s" % (self.room_number, self.address, self.city) diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py new file mode 100644 index 00000000..2eddd765 --- /dev/null +++ b/smash/web/models/subject.py @@ -0,0 +1,177 @@ +# coding=utf-8 +from django.db import models + +from constants import BOOL_CHOICES, SEX_CHOICES, SUBJECT_TYPE_CHOICES +from . import Appointment, Language, Location, Visit + +__author__ = 'Valentin Grouès' + + +class Subject(models.Model): + class Meta: + app_label = 'web' + + def finish_all_visits(self): + visits = Visit.objects.filter(subject=self, is_finished=False) + for visit in visits: + visit.is_finished = True + visit.save() + + def finish_all_appointments(self): + appointments = Appointment.objects.filter(visit__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED) + for appointment in appointments: + appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED + appointment.save() + + def mark_as_dead(self): + self.dead = True + self.save() + + self.finish_all_visits() + self.finish_all_appointments() + + def mark_as_rejected(self): + self.resigned = True + self.save() + + self.finish_all_visits() + self.finish_all_appointments() + + sex = models.CharField(max_length=1, + choices=SEX_CHOICES, + verbose_name='Sex' + ) + postponed = models.BooleanField(choices=BOOL_CHOICES, + verbose_name='Postponed', + default=False + ) + datetime_contact_reminder = models.DateField( + null=True, + blank=True, + verbose_name='Contact on', + ) + type = models.CharField(max_length=1, + choices=SUBJECT_TYPE_CHOICES.items(), + verbose_name='Type' + ) + + dead = models.BooleanField( + verbose_name='Dead', + default=False, + editable=False + ) + resigned = models.BooleanField( + verbose_name='Resigned', + default=False, + editable=False + ) + default_location = models.ForeignKey(Location, + verbose_name='Default appointment location', + ) + first_name = models.CharField(max_length=50, + verbose_name='First name' + ) + last_name = models.CharField(max_length=50, + verbose_name='Last name' + ) + languages = models.ManyToManyField(Language, + blank=True, + verbose_name='Known languages' + ) + default_written_communication_language = models.ForeignKey(Language, + null=True, + blank=True, + related_name="%(class)s_written_comunication", + verbose_name='Default language for document generation' + ) + phone_number = models.CharField(max_length=20, + null=True, + blank=True, + verbose_name='Phone number' + ) + phone_number_2 = models.CharField(max_length=20, + null=True, + blank=True, + verbose_name='Phone number 2' + ) + phone_number_3 = models.CharField(max_length=20, + null=True, + blank=True, + verbose_name='Phone number 3' + ) + email = models.EmailField( + null=True, + blank=True, + verbose_name='E-mail' + ) + date_born = models.DateField( + null=True, + blank=True, + verbose_name='Date of birth (YYYY-MM-DD)' + ) + address = models.CharField(max_length=255, + blank=True, + verbose_name='Address' + ) + postal_code = models.CharField(max_length=7, + blank=True, + verbose_name='Postal code' + ) + city = models.CharField(max_length=50, + blank=True, + verbose_name='City' + ) + country = models.CharField(max_length=50, + verbose_name='Country' + ) + screening_number = models.CharField(max_length=50, + unique=True, + verbose_name='Screening number' + ) + nd_number = models.CharField(max_length=6, + blank=True, + verbose_name='ND number' + ) + mpower_id = models.CharField(max_length=20, + blank=True, + verbose_name='MPower ID' + ) + comments = models.TextField(max_length=2000, + blank=True, + verbose_name='Comments' + ) + date_added = models.DateField(verbose_name='Added on', + auto_now=True + ) + referral = models.CharField(max_length=128, + null=True, + blank=True, + verbose_name='Referred by' + ) + diagnosis = models.CharField(max_length=128, + null=True, + blank=True, + verbose_name='Diagnosis' + ) + year_of_diagnosis = models.IntegerField( + default=0, + null=True, + blank=True, + verbose_name='Year of diagnosis (YYYY)' + ) + + def latest_visit(self): + visits = self.visit_set.all() + if len(visits) == 0: + return None + result = visits[0] + for visit in visits: + if visit.datetime_begin > result.datetime_begin: + result = visit + return result + + def __str__(self): + return "%s %s" % (self.first_name, self.last_name) + + def __unicode__(self): + return "%s %s" % (self.first_name, self.last_name) diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py new file mode 100644 index 00000000..313b960d --- /dev/null +++ b/smash/web/models/visit.py @@ -0,0 +1,63 @@ +# coding=utf-8 +import datetime + +from django.db import models + +from constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES_CONTROL + +__author__ = 'Valentin Grouès' + + +class Visit(models.Model): + class Meta: + app_label = 'web' + + subject = models.ForeignKey("web.Subject", 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, + ) + + def __unicode__(self): + return "%s %s" % (self.subject.first_name, self.subject.last_name) + + def __str__(self): + return "%s %s" % (self.subject.first_name, self.subject.last_name) + + def follow_up_title(self): + count = Visit.objects.filter(subject=self.subject, datetime_begin__lt=self.datetime_begin).count() + return "Visit " + str(count + 1) + + def mark_as_finished(self): + self.is_finished = True + self.save() + + if (not self.subject.dead) and (not self.subject.resigned): + visit_started = self.datetime_begin + + time_to_next_visit = datetime.timedelta(days=365) + if self.subject.type == SUBJECT_TYPE_CHOICES_CONTROL: + time_to_next_visit = datetime.timedelta(days=365 * 3 + 366) + + Visit.objects.create( + subject=self.subject, + datetime_begin=visit_started + time_to_next_visit, + datetime_end=visit_started + time_to_next_visit + datetime.timedelta(days=93) + ) diff --git a/smash/web/models/worker.py b/smash/web/models/worker.py new file mode 100644 index 00000000..d6bf3c1e --- /dev/null +++ b/smash/web/models/worker.py @@ -0,0 +1,95 @@ +# coding=utf-8 +import datetime + +from django.contrib.auth.models import User +from django.db import models + +__author__ = 'Valentin Grouès' + + +class Worker(models.Model): + class Meta: + app_label = 'web' + + languages = models.ManyToManyField("web.Language", + verbose_name='Known languages' + ) + locations = models.ManyToManyField("web.Location", + verbose_name='Locations' + ) + appointments = models.ManyToManyField('web.Appointment', blank=True, + verbose_name='Appointments' + ) + user = models.OneToOneField(User, blank=True, null=True, + verbose_name='Username' + ) + first_name = models.CharField(max_length=50, + verbose_name='First name' + ) + last_name = models.CharField(max_length=50, + verbose_name='Last name' + ) + phone_number = models.CharField(max_length=20, + verbose_name='Phone number' + ) + unit = models.CharField(max_length=50, + verbose_name='Unit' + ) + email = models.EmailField( + verbose_name='E-mail' + ) + ROLE_CHOICES_SECRETARY = "SECRETARY" + ROLE_CHOICES = ( + ('DOCTOR', 'Doctor'), + ('NURSE', 'Nurse'), + ('PSYCHOLOGIST', 'Psychologist'), + ('TECHNICIAN', 'Technician'), + (ROLE_CHOICES_SECRETARY, 'Secretary') + ) + role = models.CharField(max_length=20, choices=ROLE_CHOICES, + verbose_name='Role' + ) + specialization = models.CharField(max_length=20, + verbose_name='Specialization' + ) + + def is_on_leave(self): + if len(self.holiday_set.filter(datetime_end__gt=datetime.datetime.now(), + datetime_start__lt=datetime.datetime.now())): + return True + return False + + @staticmethod + def get_by_user(the_user): + if isinstance(the_user, User): + workers = Worker.objects.filter(user=the_user) + if len(workers) > 0: + return workers[0] + else: + return None + elif isinstance(the_user, Worker): + return the_user + elif the_user is not None: + raise TypeError("Unknown class type: " + the_user.__class__.__name__) + else: + return None + + @staticmethod + def get_details(the_user): + if not the_user.is_authenticated: + return 'Guest', 'Test account' + + person = Worker.objects.filter(user=the_user) + + if len(person) == 0: + return the_user.get_full_name(), '<No worker information>' + else: + # For get_*_display, see: + # https://docs.djangoproject.com/en/1.10/topics/db/models/#field-options + return unicode(person[0]), person[0].get_role_display() + + def __str__(self): + return "%s %s" % (self.first_name, self.last_name) + + def __unicode__(self): + return "%s %s" % (self.first_name, self.last_name) diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py index 3b5f1a3e..98c169a9 100644 --- a/smash/web/tests/functions.py +++ b/smash/web/tests/functions.py @@ -3,6 +3,7 @@ import datetime from django.contrib.auth.models import User from web.models import Location, AppointmentType, Subject, Worker, Visit, Appointment +from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL from web.views import get_today_midnight_date @@ -31,8 +32,8 @@ def create_subject(): first_name="Piotr", last_name="Gawron", default_location=get_test_location(), - sex=Subject.SEX_CHOICES_MALE, - type=Subject.SUBJECT_TYPE_CHOICES_CONTROL, + sex=SEX_CHOICES_MALE, + type=SUBJECT_TYPE_CHOICES_CONTROL, screening_number="piotr's number", country="france") diff --git a/smash/web/tests/test_SubjectAddForm.py b/smash/web/tests/test_SubjectAddForm.py index c0af1e31..8cac955f 100644 --- a/smash/web/tests/test_SubjectAddForm.py +++ b/smash/web/tests/test_SubjectAddForm.py @@ -2,7 +2,7 @@ from django.test import TestCase from functions import get_test_location from web.forms import SubjectAddForm -from web.models import Subject +from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL class SubjectAddFormTests(TestCase): @@ -10,8 +10,8 @@ class SubjectAddFormTests(TestCase): location = get_test_location() self.sample_data = {'first_name': 'name', 'last_name': 'name', - 'sex': Subject.SEX_CHOICES_MALE, - 'type': Subject.SUBJECT_TYPE_CHOICES_CONTROL, + 'sex': SEX_CHOICES_MALE, + 'type': SUBJECT_TYPE_CHOICES_CONTROL, 'default_location': location.id, 'screening_number': "123", 'country': 'Luxembourg' diff --git a/smash/web/tests/test_SubjectEditForm.py b/smash/web/tests/test_SubjectEditForm.py index 7baf1732..a5de02f9 100644 --- a/smash/web/tests/test_SubjectEditForm.py +++ b/smash/web/tests/test_SubjectEditForm.py @@ -4,6 +4,7 @@ from functions import get_test_location from web.forms import SubjectAddForm from web.forms import SubjectEditForm from web.models import Subject +from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL class SubjectEditFormTests(TestCase): @@ -11,8 +12,8 @@ class SubjectEditFormTests(TestCase): location = get_test_location() self.sample_data = {'first_name': 'name', 'last_name': 'name', - 'sex': Subject.SEX_CHOICES_MALE, - 'type': Subject.SUBJECT_TYPE_CHOICES_CONTROL, + 'sex': SEX_CHOICES_MALE, + 'type': SUBJECT_TYPE_CHOICES_CONTROL, 'default_location': location.id, 'country': 'Luxembourg', 'screening_number': '123', -- GitLab