Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
forms.py 19.18 KiB
import datetime
import logging
from collections import OrderedDict

from django import forms
from django.forms import ModelForm, Form
from django.utils import timezone
from django.utils.dates import MONTHS

from web.algorithm import VerhoeffAlgorithm
from web.models import StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \
    Availability, Holiday, VoucherType, VoucherTypePrice, Voucher
from web.models.constants import SUBJECT_TYPE_CHOICES, VOUCHER_STATUS_NEW, VOUCHER_STATUS_USED
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)


class VoucherTypeForm(ModelForm):
    class Meta:
        model = VoucherType
        exclude = ['study']


class VoucherTypePriceForm(ModelForm):
    start_date = forms.DateField(label="Start date",
                                 widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d")
                                 )
    end_date = forms.DateField(label="End date",
                               widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d")
                               )

    class Meta:
        model = VoucherTypePrice
        exclude = ['voucher_type']


class VoucherForm(ModelForm):
    class Meta:
        model = Voucher
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(VoucherForm, self).__init__(*args, **kwargs)
        self.fields['number'].widget.attrs['readonly'] = True
        self.fields['number'].required = False

        self.fields['issue_date'].widget.attrs['readonly'] = True
        self.fields['issue_date'].required = False
        self.fields['expiry_date'].widget.attrs['readonly'] = True
        self.fields['expiry_date'].required = False
        self.fields['use_date'].widget.attrs['readonly'] = True
        instance = getattr(self, 'instance', None)
        if instance and instance.pk:
            self.fields['voucher_type'].widget.attrs['readonly'] = True
            if instance.status != VOUCHER_STATUS_NEW:
                self.fields['status'].widget.attrs['readonly'] = True
                self.fields['feedback'].widget.attrs['readonly'] = True
                self.fields['usage_partner'].widget.attrs['readonly'] = True

    def clean(self):
        if self.cleaned_data["status"] == VOUCHER_STATUS_USED and not self.cleaned_data["usage_partner"]:
            self.add_error('usage_partner', "Partner must be defined for used voucher")
        if self.cleaned_data["status"] != VOUCHER_STATUS_USED and self.cleaned_data["usage_partner"]:
            self.add_error('status', "Status must be used for voucher with defined partner")

    def save(self, commit=True):
        instance = super(VoucherForm, self).save(commit=False)
        if not instance.id:
            instance.issue_date = timezone.now()
            instance.expiry_date = instance.issue_date + datetime.timedelta(days=92)
            max_id = str(0).zfill(5)
            if Voucher.objects.all().count() > 0:
                max_id = str(Voucher.objects.latest('id').id).zfill(5)
            instance.number = max_id + VerhoeffAlgorithm.calculate_verhoeff_check_sum(max_id)
        if instance.status == VOUCHER_STATUS_USED and not instance.use_date:
            instance.use_date = timezone.now()

        if commit:
            instance.save()