import datetime import logging from collections import OrderedDict from django import forms from django.forms import ModelForm, Form from django.utils.dates import MONTHS from web.models import StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \ Availability, Holiday from web.models.constants import SUBJECT_TYPE_CHOICES 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 logger = logging.getLogger(__name__) def get_worker_from_args(kwargs): user = kwargs.pop('user', None) if user is None: raise TypeError("User not defined") result = Worker.get_by_user(user) if result is None: raise TypeError("Worker not defined for: " + user.username) return result 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('subject__last_name', 'subject__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)