import datetime from collections import OrderedDict from django import forms from django.forms import ModelForm, Form from django.utils.dates import MONTHS from web.models import Subject, StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \ Availability, Holiday from web.models.constants import SUBJECT_TYPE_CHOICES, SCREENING_NUMBER_PREFIXES_FOR_TYPE, COUNTRY_OTHER_ID from web.views.notifications import get_filter_locations """ Possible redundancy, but if need arises, contents of forms can be easily customized """ DATE_FORMAT_TIME = "%H:%M" CURRENT_YEAR = datetime.datetime.now().year YEAR_CHOICES = tuple(range(CURRENT_YEAR, CURRENT_YEAR - 120, -1)) FUTURE_YEAR_CHOICES = tuple(range(CURRENT_YEAR, CURRENT_YEAR + 5, 1)) DATEPICKER_DATE_ATTRS = { 'class': 'datepicker', 'data-date-format': 'yyyy-mm-dd', 'data-date-orientation': 'bottom' } DATETIMEPICKER_DATE_ATTRS = { 'class': 'datetimepicker', 'data-date-format': 'Y-MM-DD HH:mm', } TIMEPICKER_DATE_ATTRS = { 'class': 'datetimepicker', 'data-date-format': 'HH:mm', 'data-date-stepping': 15, } START_YEAR_STATISTICS = 2015 APPOINTMENT_TYPES_FIELD_POSITION = 1 def validate_subject_nd_number(self, cleaned_data): if cleaned_data['nd_number'] != "": subjects_from_db = StudySubject.objects.filter(nd_number=cleaned_data['nd_number']) if subjects_from_db: if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): self.add_error('nd_number', "ND number already in use") def validate_subject_country(self, cleaned_data): if cleaned_data['country'].id == COUNTRY_OTHER_ID: self.add_error('country', "Select valid country") def validate_subject_resign_reason(self, cleaned_data): if cleaned_data['resigned'] and cleaned_data['resign_reason'] == '': self.add_error('resign_reason', "Resign reason cannot be empty") def validate_subject_mpower_number(self, cleaned_data): if cleaned_data['mpower_id'] != "": subjects_from_db = StudySubject.objects.filter(mpower_id=cleaned_data['mpower_id']) if subjects_from_db: if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''): self.add_error('mpower_id', "mPower number already in use") class StudySubjectAddForm(ModelForm): date_born = forms.DateField(label="Date of birth", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False ) datetime_contact_reminder = forms.DateTimeField(label="Contact on", widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), required=False ) class Meta: model = StudySubject fields = '__all__' exclude = ['dead', 'resigned', 'resign_reason'] def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") self.user = Worker.get_by_user(user) if self.user is None: raise TypeError("Worker not defined for: " + user.username) super(ModelForm, self).__init__(*args, **kwargs) self.fields['screening_number'].required = False def build_screening_number(self, cleaned_data): screening_number = cleaned_data.get('screening_number', None) if not screening_number: prefix_screening_number = self.get_prefix_screening_number() if prefix_screening_number is not None: screening_number = get_new_screening_number(prefix_screening_number) return screening_number def clean(self): cleaned_data = super(StudySubjectAddForm, self).clean() screening_number = self.build_screening_number(cleaned_data) if screening_number is not None: cleaned_data['screening_number'] = screening_number subjects_from_db = StudySubject.objects.filter(screening_number=screening_number) if len(subjects_from_db) > 0: self.add_error('screening_number', "Screening number already in use") validate_subject_country(self, cleaned_data) validate_subject_nd_number(self, cleaned_data) validate_subject_mpower_number(self, cleaned_data) return cleaned_data def get_prefix_screening_number(self): default_location = self.cleaned_data.get('default_location', None) screening_number_prefix = None if default_location is not None and default_location.prefix: screening_number_prefix = default_location.prefix else: subject_type = self.cleaned_data.get('type', None) if subject_type is not None: screening_number_prefix = SCREENING_NUMBER_PREFIXES_FOR_TYPE[subject_type] if screening_number_prefix is None: return None prefix_screening_number = screening_number_prefix + "-" return prefix_screening_number def get_new_screening_number(screening_number_prefix): result_number = 0 subjects = StudySubject.objects.filter(screening_number__contains=screening_number_prefix) for subject in subjects: screening_numbers = subject.screening_number.split(";") for screening_number in screening_numbers: screening_number = screening_number.strip() if screening_number.startswith(screening_number_prefix): number = screening_number[len(screening_number_prefix):] try: result_number = max(result_number, int(number)) except ValueError: pass return screening_number_prefix + str(result_number + 1).zfill(3) class SubjectDetailForm(ModelForm): class Meta: model = StudySubject fields = '__all__' class StudySubjectEditForm(ModelForm): datetime_contact_reminder = forms.DateTimeField(label="Contact on", widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), required=False ) date_born = forms.DateField(label="Date of birth", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False ) def __init__(self, *args, **kwargs): was_dead = kwargs.get('was_dead', False) was_resigned = kwargs.get('was_resigned', False) if 'was_resigned' in kwargs: kwargs.pop('was_resigned') if 'was_dead' in kwargs: kwargs.pop('was_dead') super(StudySubjectEditForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance and instance.id: self.fields['screening_number'].widget.attrs['readonly'] = True if was_dead: self.fields['dead'].disabled = True if was_resigned: self.fields['resigned'].disabled = True def clean(self): validate_subject_nd_number(self, self.cleaned_data) validate_subject_mpower_number(self, self.cleaned_data) validate_subject_country(self, self.cleaned_data) validate_subject_resign_reason(self, self.cleaned_data) class Meta: model = StudySubject fields = '__all__' exclude = ['subject'] class WorkerAddForm(ModelForm): class Meta: model = Worker exclude = ['appointments'] class WorkerEditForm(ModelForm): class Meta: model = Worker fields = '__all__' class AppointmentDetailForm(ModelForm): class Meta: model = Appointment fields = '__all__' datetime_when = forms.DateTimeField(label='Appointment on (YYYY-MM-DD HH:MM)', widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS) ) class AppointmentEditForm(ModelForm): class Meta: model = Appointment fields = '__all__' exclude = ['appointment_types'] datetime_when = forms.DateTimeField(label='Appointment on (YYYY-MM-DD HH:MM)', widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS) ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") self.user = Worker.get_by_user(user) if self.user is None: raise TypeError("Worker not defined for: " + user.username) super(ModelForm, self).__init__(*args, **kwargs) if 'instance' in kwargs: initial_appointment_types = AppointmentTypeLink.objects.filter(appointment=kwargs['instance']).values_list( 'appointment_type', flat=True) else: initial_appointment_types = [] fields = OrderedDict() for i, tuple in enumerate(self.fields.items()): key, value = tuple fields[key] = value if i == APPOINTMENT_TYPES_FIELD_POSITION: fields['appointment_types'] = forms.ModelMultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, queryset=AppointmentType.objects.all(), initial=initial_appointment_types) self.fields = fields self.fields['worker_assigned'].queryset = Worker.objects.filter( locations__in=get_filter_locations(self.user)).distinct().order_by('first_name', 'last_name') self.fields['location'].queryset = get_filter_locations(self.user) def clean_location(self): location = self.cleaned_data['location'] if self.user.locations.filter(id=location.id).count() == 0: self.add_error('location', "You cannot create appointment for this location") else: return location def save(self, commit=True): appointment = super(AppointmentEditForm, self).save(commit) # if appointment date change, remove appointment_type links if 'datetime_when' in self.changed_data: AppointmentTypeLink.objects.filter(appointment=appointment).delete() appointment_type_links = [] else: appointment_type_links = AppointmentTypeLink.objects.filter(appointment=appointment).all() appointment_types_from_links = [] appointment_types = self.cleaned_data['appointment_types'] for appointment_type_link in appointment_type_links: if appointment_type_link.appointment_type not in appointment_types: # we delete appointment links for appointments types that have been removed appointment_type_link.delete() else: appointment_types_from_links.append(appointment_type_link.appointment_type) for appointment_type in appointment_types: if appointment_type not in appointment_types_from_links: # we create new appointment links for appointments types that have been added appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type=appointment_type) appointment_type_link.save() return appointment class AppointmentAddForm(ModelForm): class Meta: model = Appointment exclude = ['status', 'appointment_types'] datetime_when = forms.DateTimeField(label='Appointment on (YYYY-MM-DD HH:MM)', widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS) ) def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") self.user = Worker.get_by_user(user) if self.user is None: raise TypeError("Worker not defined for: " + user.username) super(ModelForm, self).__init__(*args, **kwargs) fields = OrderedDict() for i, tuple in enumerate(self.fields.items()): key, value = tuple fields[key] = value if i == APPOINTMENT_TYPES_FIELD_POSITION: fields['appointment_types'] = forms.ModelMultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, queryset=AppointmentType.objects.all(), ) self.fields = fields self.fields['worker_assigned'].queryset = Worker.objects.filter( locations__in=get_filter_locations(self.user)).distinct().order_by('first_name', 'last_name') self.fields['location'].queryset = get_filter_locations(self.user) def clean_location(self): location = self.cleaned_data['location'] if self.user.locations.filter(id=location.id).count() == 0: self.add_error('location', "You cannot create appointment for this location") else: return location def save(self, commit=True): appointment = super(AppointmentAddForm, self).save(commit) appointment_types = self.cleaned_data['appointment_types'] for appointment_type in appointment_types: appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type=appointment_type) appointment_type_link.save() return appointment class VisitDetailForm(ModelForm): datetime_begin = forms.DateField(label="Visit begins on", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d") ) datetime_end = forms.DateField(label="Visit ends on", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d") ) post_mail_sent = forms.RadioSelect() appointment_types = forms.ModelMultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, queryset=AppointmentType.objects.all()) class Meta: model = Visit exclude = ['is_finished', 'visit_number'] class VisitAddForm(ModelForm): subject = forms.ModelChoiceField(queryset=StudySubject.objects.order_by('last_name', 'first_name')) datetime_begin = forms.DateField(label="Visit begins on", widget=forms.TextInput(attrs=DATEPICKER_DATE_ATTRS) ) datetime_end = forms.DateField(label="Visit ends on", widget=forms.TextInput(attrs=DATEPICKER_DATE_ATTRS) ) appointment_types = forms.ModelMultipleChoiceField(required=False, widget=forms.CheckboxSelectMultiple, queryset=AppointmentType.objects.all()) class Meta: model = Visit exclude = ['is_finished', 'visit_number'] def clean(self): super(VisitAddForm, self).clean() if 'datetime_begin' not in self.cleaned_data or 'datetime_end' not in self.cleaned_data: return if self.cleaned_data['datetime_begin'] >= self.cleaned_data['datetime_end']: self.add_error('datetime_begin', "Start date must be before end date") self.add_error('datetime_end', "End date must be after start date") class ContactAttemptForm(ModelForm): datetime_when = forms.DateTimeField(label='When? (YYYY-MM-DD HH:MM)', widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS) ) class Meta: model = ContactAttempt fields = '__all__' def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") self.user = Worker.get_by_user(user) if self.user is None: raise TypeError("Worker not defined for: " + user.username) subject = kwargs.pop('subject', None) super(ContactAttemptForm, self).__init__(*args, **kwargs) self.fields['subject'].initial = subject.id self.fields['subject'].disabled = True self.fields['worker'].initial = self.user class ContactAttemptEditForm(ModelForm): datetime_when = forms.DateTimeField(label='When? (YYYY-MM-DD HH:MM)', widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS) ) class Meta: model = ContactAttempt fields = '__all__' def __init__(self, *args, **kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") self.user = Worker.get_by_user(user) if self.user is None: raise TypeError("Worker not defined for: " + user.username) super(ContactAttemptEditForm, self).__init__(*args, **kwargs) self.fields['subject'].disabled = True class KitRequestForm(Form): start_date = forms.DateField(label="From date", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False ) end_date = forms.DateField(label="End date", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False ) class StatisticsForm(Form): def __init__(self, *args, **kwargs): super(StatisticsForm, self).__init__(*args) visit_choices = kwargs['visit_choices'] month = kwargs['month'] year = kwargs['year'] now = datetime.datetime.now() year_now = now.year number_of_years_for_statistics = year_now - START_YEAR_STATISTICS + 2 year_choices = [(START_YEAR_STATISTICS + i, START_YEAR_STATISTICS + i) for i in range(0, number_of_years_for_statistics + 1)] 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_TYPE_CHOICES.items()) self.fields['subject_type'] = forms.ChoiceField(choices=choices, initial="-1") self.fields['visit'] = forms.ChoiceField(choices=visit_choices, initial="-1") class AvailabilityAddForm(ModelForm): available_from = forms.TimeField(label="Available from", widget=forms.TimeInput(TIMEPICKER_DATE_ATTRS), initial="8:00", ) available_till = forms.TimeField(label="Available until", widget=forms.TimeInput(TIMEPICKER_DATE_ATTRS), initial="17:00", ) class Meta: model = Availability fields = '__all__' def clean(self): worker = Worker.objects.get(id=self.cleaned_data["person"].id) availabilities = worker.availability_set.all() for availability in availabilities: validate_availability_conflict(self, self.cleaned_data, availability) class AvailabilityEditForm(ModelForm): available_from = forms.TimeField(label="Available from", widget=forms.TimeInput(TIMEPICKER_DATE_ATTRS), ) available_till = forms.TimeField(label="Available until", widget=forms.TimeInput(TIMEPICKER_DATE_ATTRS), ) class Meta: model = Availability fields = '__all__' def __init__(self, *args, **kwargs): super(ModelForm, self).__init__(*args, **kwargs) instance = getattr(self, 'instance', None) if instance is not None: self.availability_id = instance.id self.fields['person'].disabled = True def clean(self): worker = Worker.objects.get(id=self.cleaned_data["person"].id) availabilities = worker.availability_set.all() for availability in availabilities: if availability.id <> self.availability_id: validate_availability_conflict(self, self.cleaned_data, availability) def validate_availability_conflict(self, cleaned_data, availability): start_hour = self.cleaned_data.get("available_from", None) end_hour = self.cleaned_data.get("available_till", None) if availability.day_number == self.cleaned_data.get("day_number", None) and \ ((start_hour <= availability.available_from < end_hour) or (start_hour < availability.available_till <= end_hour) or (availability.available_from <= start_hour < availability.available_till) or (availability.available_from < end_hour <= availability.available_till)): error = "User has defined availability for this day that overlaps: " + availability.available_from.strftime( DATE_FORMAT_TIME) + ", " + availability.available_till.strftime(DATE_FORMAT_TIME) self.add_error('day_number', error) self.add_error('available_from', error) self.add_error('available_till', error) class HolidayAddForm(ModelForm): datetime_start = forms.DateTimeField(widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), initial=datetime.datetime.now().replace(hour=8, minute=0), ) datetime_end = forms.DateTimeField(widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS), initial=datetime.datetime.now().replace(hour=17, minute=0), ) class Meta: model = Holiday fields = '__all__' def clean(self): worker = Worker.objects.get(id=self.cleaned_data["person"].id) availabilities = worker.availability_set.all() for availability in availabilities: validate_availability_conflict(self, self.cleaned_data, availability) class SubjectAddForm(ModelForm): date_born = forms.DateField(label="Date of birth", widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"), required=False ) class Meta: model = Subject fields = '__all__' exclude = ['dead'] def build_screening_number(self, cleaned_data): screening_number = cleaned_data.get('screening_number', None) if not screening_number: prefix_screening_number = self.get_prefix_screening_number() if prefix_screening_number is not None: screening_number = get_new_screening_number(prefix_screening_number) return screening_number def clean(self): cleaned_data = super(SubjectAddForm, self).clean() validate_subject_country(self, cleaned_data) return cleaned_data