from django.urls import reverse
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse, StreamingHttpResponse
from django.contrib import messages
from django.db.models import Q
from django.utils.html import conditional_escape as escape
from ra.models import RAAppointment, Project, Account, SemesterConfig, Program
from ra.forms import RAForm, RASearchForm, AccountForm, ProjectForm, RALetterForm, RABrowseForm, SemesterConfigForm, \
    LetterSelectForm, RAAppointmentAttachmentForm, ProgramForm
from grad.forms import possible_supervisors
from coredata.models import Person, Role, Semester, Unit
from coredata.queries import more_personal_info, SIMSProblem
from courselib.auth import requires_role, has_role, ForbiddenResponse, user_passes_test
from courselib.search import find_userid_or_emplid, get_query
from grad.models import GradStudent, Scholarship
from visas.models import Visa
from log.models import LogEntry
from dashboard.letters import ra_form, OfficialLetter, LetterContents
from django import forms
from django.db import transaction
import csv


from django_datatables_view.base_datatable_view import BaseDatatableView
from haystack.query import SearchQuerySet

import json, datetime, urllib.request, urllib.parse, urllib.error

def _can_view_ras():
    """
    Allows access to funding admins, and supervisors of (any) RA.

    Request object gets .units and .is_supervisor set along the way.
    """
    def auth_test(request, **kwargs):
        supervisor = RAAppointment.objects.filter(hiring_faculty__userid=request.user.username).exists()
        request.is_supervisor = supervisor
        return has_role('FUND', request, **kwargs) or supervisor

    actual_decorator = user_passes_test(auth_test)
    return actual_decorator


#This is the search function that that returns a list of RA Appointments related to the query.
@requires_role("FUND")
def search(request, student_id=None):
    if student_id:
        student = get_object_or_404(Person, id=student_id)
    else:
        student = None
    if request.method == 'POST':
        form = RASearchForm(request.POST)
        if not form.is_valid():
            return HttpResponseRedirect(reverse('ra:found') + "?search=" + urllib.parse.quote_plus(form.data['search']))
        search = form.cleaned_data['search']
        # deal with people without active computing accounts
        if search.userid:
            userid = search.userid
        else:
            userid = search.emplid
        return HttpResponseRedirect(reverse('ra:student_appointments', kwargs={'userid': userid}))
    if student_id:
        form = RASearchForm(instance=student, initial={'student': student.userid})
    else:
        form = RASearchForm()
    context = {'form': form}
    return render(request, 'ra/search.html', context)


@requires_role("FUND")
def found(request):
    """
    View to handle the enter-search/press-enter behaviour in the autocomplete box
    """
    if 'search' not in request.GET:
        return ForbiddenResponse(request, 'must give search in query')
    search = request.GET['search']
    studentQuery = get_query(search, ['userid', 'emplid', 'first_name', 'last_name'])
    people = Person.objects.filter(studentQuery)[:200]
    for p in people:
        # decorate with RAAppointment count
        p.ras = RAAppointment.objects.filter(unit__in=request.units, person=p, deleted=False).count()

    context = {'people': people}
    return render(request, 'ra/found.html', context)


#This is an index of all RA Appointments belonging to a given person.
@requires_role("FUND")
def student_appointments(request, userid):
    student = get_object_or_404(Person, find_userid_or_emplid(userid))
    appointments = RAAppointment.objects.filter(person=student, unit__in=request.units, deleted=False).order_by("-created_at")
    grads = GradStudent.objects.filter(person=student, program__unit__in=request.units)
    context = {'appointments': appointments, 'student': student,
               'grads': grads}
    return render(request, 'ra/student_appointments.html', context)

def _appointment_defaults(units, emplid=None):
    hiring_faculty_choices = possible_supervisors(units)
    unit_choices = [(u.id, u.name) for u in units]
    project_choices = [(p.id, str(p)) for p in Project.objects.filter(unit__in=units, hidden=False)]
    account_choices = [(a.id, str(a)) for a in Account.objects.filter(unit__in=units, hidden=False)]
    scholarship_choices = [("", '\u2014')]
    if emplid:
        for s in Scholarship.objects.filter(student__person__emplid=emplid):
            scholarship_choices.append((s.pk, s.scholarship_type.unit.label + ": " + s.scholarship_type.name + " (" + s.start_semester.name + " to " + s.end_semester.name + ")"))
    program_choices = [('', "00000, None")] + [(p.id, str(p)) for p in Program.objects.visible_by_unit(units).order_by('program_number')]
    return (scholarship_choices, hiring_faculty_choices, unit_choices, project_choices, account_choices,
            program_choices)
    

#New RA Appointment
@requires_role("FUND")
def new(request):
    scholarship_choices, hiring_faculty_choices, unit_choices, project_choices, account_choices, program_choices = \
        _appointment_defaults(request.units)
    if request.method == 'POST':
        data = request.POST.copy()
        if data['pay_frequency'] == 'L':
            # force legal values into the non-submitted (and don't-care) fields for lump sum pay
            data['biweekly_pay'] = 1
            data['hourly_pay'] = 1
            data['hours'] = 1
            data['pay_periods'] = 1

        raform = RAForm(data)
        raform.fields['hiring_faculty'].choices = hiring_faculty_choices
        raform.fields['unit'].choices = unit_choices
        raform.fields['project'].choices = project_choices
        raform.fields['account'].choices = account_choices
        raform.fields['program'].choices = program_choices

        if raform.is_valid():
            userid = raform.cleaned_data['person'].userid_or_emplid()
            appointment = raform.save(commit=False)
            appointment.set_use_hourly(raform.cleaned_data['use_hourly'])
            appointment.save()
            l = LogEntry(userid=request.user.username,
                         description="Added RA appointment %s." % appointment,
                         related_object=appointment)
            l.save()
            messages.success(request, 'Created RA Appointment for ' + appointment.person.name())
            return HttpResponseRedirect(reverse('ra:student_appointments', kwargs=({'userid': userid})))
    else:
        semester = Semester.next_starting()
        semesterconfig = SemesterConfig.get_config(request.units, semester)
        raform = RAForm(initial={'start_date': semesterconfig.start_date(), 'end_date': semesterconfig.end_date(), 'hours': 80 })
        raform.fields['scholarship'].choices = scholarship_choices
        raform.fields['hiring_faculty'].choices = hiring_faculty_choices
        raform.fields['unit'].choices = unit_choices
        raform.fields['project'].choices = project_choices
        raform.fields['account'].choices = account_choices
        raform.fields['program'].choices = program_choices
    return render(request, 'ra/new.html', { 'raform': raform })

#New RA Appointment with student pre-filled.
@requires_role("FUND")
def new_student(request, userid):
    person = get_object_or_404(Person, find_userid_or_emplid(userid))
    semester = Semester.next_starting()
    semesterconfig = SemesterConfig.get_config(request.units, semester)
    student = get_object_or_404(Person, find_userid_or_emplid(userid))
    initial = {'person': student.emplid, 'start_date': semesterconfig.start_date(), 'end_date': semesterconfig.end_date(), 'hours': 80 }
    scholarship_choices, hiring_faculty_choices, unit_choices, project_choices, account_choices, program_choices = \
        _appointment_defaults(request.units, emplid=student.emplid)
    gss = GradStudent.objects.filter(person=student)
    if gss:
        gradstudent = gss[0]
        initial['sin'] = gradstudent.person.sin()
    
    raform = RAForm(initial=initial)
    raform.fields['person'] = forms.CharField(widget=forms.HiddenInput())
    raform.fields['scholarship'].choices = scholarship_choices
    raform.fields['hiring_faculty'].choices = hiring_faculty_choices
    raform.fields['unit'].choices = unit_choices
    raform.fields['project'].choices = project_choices
    raform.fields['account'].choices = account_choices
    raform.fields['program'].choices = program_choices
    return render(request, 'ra/new.html', { 'raform': raform, 'person': person })

#Edit RA Appointment
@requires_role("FUND")
def edit(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)
    scholarship_choices, hiring_faculty_choices, unit_choices, project_choices, account_choices, program_choices = \
        _appointment_defaults(request.units, emplid=appointment.person.emplid)
    if request.method == 'POST':
        data = request.POST.copy()
        if data['pay_frequency'] == 'L':
            # force legal values into the non-submitted (and don't-care) fields for lump sum pay
            data['biweekly_pay'] = 1
            data['hourly_pay'] = 1
            data['hours'] = 1
            data['pay_periods'] = 1
        
        raform = RAForm(data, instance=appointment)
        if raform.is_valid():
            userid = raform.cleaned_data['person'].userid
            appointment = raform.save(commit=False)
            appointment.set_use_hourly(raform.cleaned_data['use_hourly'])
            appointment.save()
            l = LogEntry(userid=request.user.username,
                         description="Edited RA appointment %s." % appointment,
                         related_object=appointment)
            l.save()
            messages.success(request, 'Updated RA Appointment for ' + appointment.person.first_name + " " + appointment.person.last_name)
            return HttpResponseRedirect(reverse('ra:student_appointments', kwargs=({'userid': userid})))
    else:
        #The initial value needs to be the person's emplid in the form. Django defaults to the pk, which is not human readable.
        raform = RAForm(instance=appointment, initial={'person': appointment.person.emplid, 'use_hourly': appointment.use_hourly()})
        #As in the new method, choices are restricted to relevant options.
        raform.fields['person'] = forms.CharField(widget=forms.HiddenInput())
        raform.fields['hiring_faculty'].choices = hiring_faculty_choices
        raform.fields['scholarship'].choices = scholarship_choices
        raform.fields['unit'].choices = unit_choices
        raform.fields['project'].choices = project_choices
        raform.fields['account'].choices = account_choices
        raform.fields['program'].choices = program_choices
    return render(request, 'ra/edit.html', { 'raform': raform, 'appointment': appointment, 'person': appointment.person })


#Quick Reappoint, The difference between this and edit is that the reappointment box is automatically checked, and date information is filled out as if a new appointment is being created.
#Since all reappointments will be new appointments, no post method is present, rather the new appointment template is rendered with the existing data which will call the new method above when posting.
@requires_role("FUND")
def reappoint(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)
    semester = Semester.next_starting()
    semesterconfig = SemesterConfig.get_config(request.units, semester)
    raform = RAForm(instance=appointment, initial={'person': appointment.person.emplid, 'reappointment': True,
                    'start_date': semesterconfig.start_date(), 'end_date': semesterconfig.end_date(), 'hours': 80,
                    'use_hourly': appointment.use_hourly() })
    scholarship_choices, hiring_faculty_choices, unit_choices, project_choices, account_choices, program_choices = \
        _appointment_defaults(request.units, emplid=appointment.person.emplid)
    raform.fields['hiring_faculty'].choices = hiring_faculty_choices
    raform.fields['scholarship'].choices = scholarship_choices
    raform.fields['unit'].choices = unit_choices
    raform.fields['project'].choices = project_choices
    raform.fields['account'].choices = account_choices
    raform.fields['program'].choices = program_choices
    return render(request, 'ra/new.html', { 'raform': raform, 'appointment': appointment })


@requires_role("FUND")
def edit_letter(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)

    if request.method == 'POST':
        form = RALetterForm(request.POST, instance=appointment)
        if form.is_valid():
            form.save()
            messages.success(request, 'Updated RA Letter Text for ' + appointment.person.first_name + " " + appointment.person.last_name)
            return HttpResponseRedirect(reverse('ra:student_appointments', kwargs=({'userid': appointment.person.userid})))
    else:
        if not appointment.offer_letter_text:
            letter_choices = RAAppointment.letter_choices(request.units)
            if len(letter_choices) == 1: # why make them select from one?
                appointment.build_letter_text(letter_choices[0][0])
            else:
                return HttpResponseRedirect(reverse('ra:select_letter', kwargs=({'ra_slug': ra_slug})))
        form = RALetterForm(instance=appointment)
    
    context = {'appointment': appointment, 'form': form}
    return render(request, 'ra/edit_letter.html', context)

# If we don't have an appointment letter yet, pick one.
@requires_role("FUND")
def select_letter(request, ra_slug, print_only=None):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)
    # Forcing sorting of the letter choices so the Standard template is first.
    letter_choices = sorted(RAAppointment.letter_choices(request.units))
    if request.method == 'POST':
        filled_form = LetterSelectForm(data=request.POST, choices=letter_choices)
        if filled_form.is_valid():
            appointment.build_letter_text(filled_form.cleaned_data['letter_choice'])
        if print_only == 'print':
            return HttpResponseRedirect(reverse('ra:letter', kwargs=({'ra_slug': ra_slug})))
        else:
            return HttpResponseRedirect(reverse('ra:edit_letter', kwargs=({'ra_slug': ra_slug})))

    else:
        new_form = LetterSelectForm(choices=letter_choices)
        context = {'form': new_form, 'ra_slug': ra_slug, 'print_only': print_only}
        return render(request, 'ra/select_letter.html', context)


#View RA Appointment
@_can_view_ras()
def view(request, ra_slug):
    appointment = get_object_or_404(RAAppointment,
        Q(unit__in=request.units) | Q(hiring_faculty__userid=request.user.username),
        slug=ra_slug, deleted=False)
    student = appointment.person
    return render(request, 'ra/view.html',
        {'appointment': appointment, 'student': student, 'supervisor_only': not request.units})

#View RA Appointment Form (PDF)
@requires_role("FUND")
def form(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)
    response = HttpResponse(content_type="application/pdf")
    response['Content-Disposition'] = 'inline; filename="%s.pdf"' % (appointment.slug)
    ra_form(appointment, response)
    return response

@requires_role("FUND")
def letter(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, deleted=False, unit__in=request.units)
    if not appointment.offer_letter_text:
        letter_choices = RAAppointment.letter_choices(request.units)
        if len(letter_choices) == 1:  # why make them select from one?
            appointment.build_letter_text(letter_choices[0][0])
        else:
            return HttpResponseRedirect(reverse('ra:select_letter', kwargs=({'ra_slug': ra_slug, 'print_only': 'print'})))
    response = HttpResponse(content_type="application/pdf")
    response['Content-Disposition'] = 'inline; filename="%s-letter.pdf"' % (appointment.slug)
    letter = OfficialLetter(response, unit=appointment.unit)
    contents = LetterContents(
        to_addr_lines=[appointment.person.name(), 'c/o '+appointment.unit.name], 
        from_name_lines=[appointment.hiring_faculty.letter_name(), appointment.unit.name],
        closing="Yours Truly", 
        signer=appointment.hiring_faculty,
        cosigner_lines=['I agree to the conditions of employment', appointment.person.first_name + " " + appointment.person.last_name])
    contents.add_paragraphs(["Dear " + appointment.person.get_title() + ' ' + appointment.person.last_name])
    contents.add_paragraphs(appointment.letter_paragraphs())
    letter.add_letter(contents)
    letter.write()
    return response

@requires_role("FUND")
def delete_ra(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, unit__in=request.units)
    if request.method == 'POST':
        appointment.deleted = True
        appointment.save()
        messages.success(request, "Deleted RA appointment." )
        l = LogEntry(userid=request.user.username,
              description="Deleted RA appointment %s." % (str(appointment),),
              related_object=appointment)
        l.save()              
    
    return HttpResponseRedirect(reverse('ra:student_appointments', kwargs={'userid': appointment.person.emplid}))




# Methods relating to Account creation. These are all straight forward.
@requires_role(["FUND", "TAAD", "GRAD"])
def new_account(request):
    accountform = AccountForm(request.POST or None)
    #This restricts a user to only creating account for a unit to which they belong.
    accountform.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    if request.method == 'POST':
        if accountform.is_valid():
            account = accountform.save()
            messages.success(request, 'Created account ' + str(account.account_number))
            return HttpResponseRedirect(reverse('ra:accounts_index'))
    return render(request, 'ra/new_account.html', {'accountform': accountform})


@requires_role("FUND")
def accounts_index(request):
    accounts = Account.objects.filter(unit__in=request.units, hidden=False).order_by("account_number")
    return render(request, 'ra/accounts_index.html', {'accounts': accounts})

@requires_role("FUND")
def edit_account(request, account_slug):
    account = get_object_or_404(Account, slug=account_slug, unit__in=request.units)
    if request.method == 'POST':
        accountform = AccountForm(request.POST, instance=account)
        if accountform.is_valid():
            accountform.save()
            messages.success(request, 'Updated account ' + str(account.account_number))
            return HttpResponseRedirect(reverse('ra:accounts_index'))
    else:
        accountform = AccountForm(instance=account)
        accountform.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    return render(request, 'ra/edit_account.html', {'accountform': accountform, 'account': account})

@requires_role("FUND")
def remove_account(request, account_slug):
    account = get_object_or_404(Account, slug=account_slug, unit__in=request.units)
    account.delete()
    messages.success(request, "Removed account %s." % str(account.account_number))
    l = LogEntry(userid=request.user.username,
          description="Removed account %s" % (str(account.account_number)),
          related_object=account)
    l.save()              
    
    return HttpResponseRedirect(reverse('ra:accounts_index'))

#Project methods. Also straight forward.
@requires_role("FUND")
def new_project(request):
    projectform = ProjectForm(request.POST or None)
    #Again, the user should only be able to create projects for units that they belong to.
    projectform.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    if request.method == 'POST':
        if projectform.is_valid():
            project = projectform.save()
            messages.success(request, 'Created project ' + str(project.project_number))
            return HttpResponseRedirect(reverse('ra:projects_index'))
    return render(request, 'ra/new_project.html', {'projectform': projectform})

@requires_role("FUND")
def projects_index(request):
    depts = Role.objects_fresh.filter(person__userid=request.user.username, role='FUND').values('unit_id')
    projects = Project.objects.filter(unit__id__in=depts, hidden=False).order_by("project_number")
    return render(request, 'ra/projects_index.html', {'projects': projects})

@requires_role("FUND")
def edit_project(request, project_slug):
    project = get_object_or_404(Project, slug=project_slug, unit__in=request.units)
    if request.method == 'POST':
        projectform = ProjectForm(request.POST, instance=project)
        if projectform.is_valid():
            projectform.save()
            messages.success(request, 'Updated project ' + str(project.project_number))
            return HttpResponseRedirect(reverse('ra:projects_index'))
    else:
        projectform = ProjectForm(instance=project)
        projectform.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    return render(request, 'ra/edit_project.html', {'projectform': projectform, 'project': project})

@requires_role("FUND")
def remove_project(request, project_slug):
    project = get_object_or_404(Project, slug=project_slug, unit__in=request.units)
    project.delete()
    messages.success(request, "Removed project %s." % str(project.project_number))
    l = LogEntry(userid=request.user.username,
          description="Removed project %s" % (str(project.project_number)),
          related_object=project)
    l.save()              
    
    return HttpResponseRedirect(reverse('ra:projects_index'))

@requires_role("FUND")
def semester_config(request, semester_name=None):
    if semester_name:
        semester = get_object_or_404(Semester, name=semester_name)
    else:
        semester = Semester.next_starting()

    unit_choices = [(u.id, u.name) for u in request.units]
    if request.method == 'POST':
        form = SemesterConfigForm(request.POST)
        form.fields['unit'].choices = unit_choices
        if form.is_valid():
            config = SemesterConfig.get_config(units=[form.cleaned_data['unit']], semester=semester)
            config.set_start_date(form.cleaned_data['start_date'])
            config.set_end_date(form.cleaned_data['end_date'])
            config.save()
            messages.success(request, 'Updated semester configuration for %s.' % (semester.name))
            return HttpResponseRedirect(reverse('ra:search'))
    else:
        config = SemesterConfig.get_config(units=request.units, semester=semester)
        form = SemesterConfigForm(initial={'start_date': config.start_date(), 'end_date': config.end_date()})
        form.fields['unit'].choices = unit_choices

    return render(request, 'ra/semester_config.html', {'semester': semester, 'form': form})



@requires_role("FUND")
def search_scholarships_by_student(request, student_id):
    #check permissions
    roles = Role.all_roles(request.user.username)
    allowed = set(['FUND'])
    if not (roles & allowed):
        return ForbiddenResponse(request, "Not permitted to search scholarships by student.")
    scholarships = Scholarship.objects.filter(student__person__emplid=student_id)
    response = HttpResponse(content_type="application/json")
    data = [{'value': s.pk, 'display': s.scholarship_type.unit.label + ": " + s.scholarship_type.name + " (" + s.start_semester.name + " to " + s.end_semester.name + ")"}  for s in scholarships]
    json.dump(data, response, indent=1)
    return response

@_can_view_ras()
def browse(request):
    if 'tabledata' in request.GET:
        return RADataJson.as_view()(request)

    form = RABrowseForm()
    context = {'form': form, 'supervisor_only': not request.units}
    return render(request, 'ra/browse.html', context)


class RADataJson(BaseDatatableView):
    model = RAAppointment
    columns = ['person', 'hiring_faculty', 'unit', 'project', 'account', 'start_date', 'end_date', 'lump_sum_pay']
    order_columns = [
        ['person__last_name', 'person__first_name'],
        ['hiring_faculty__last_name', 'hiring_faculty__first_name'],
        'unit__label',
        'project__project_number',
        'account__account_number',
        'start_date',
        'end_date',
        'lump_sum_pay',
    ]
    max_display_length = 500

    def get_initial_queryset(self):
        qs = super(RADataJson, self).get_initial_queryset()
        # do some select related because we'll need them for display later
        qs = qs.select_related('person', 'hiring_faculty', 'unit')
        return qs

    def filter_queryset(self, qs):
        GET = self.request.GET

        # limit to those visible to this user
        qs = qs.filter(
            Q(unit__in=self.request.units)
            | Q(hiring_faculty__userid=self.request.user.username)
        )
        qs = qs.exclude(deleted=True)

        # "current" contracts filter
        if 'current' in GET and GET['current'] == 'yes':
            today = datetime.date.today()
            slack = 14 # number of days to fudge the start/end
            qs = qs.filter(start_date__lte=today + datetime.timedelta(days=slack),
                           end_date__gte=today - datetime.timedelta(days=slack))

        # search box
        srch = GET.get('sSearch', None)
        if srch:
            # get RA set from haystack, and use it to limit our query.
            ra_qs = SearchQuerySet().models(RAAppointment).filter(text__fuzzy=srch)[:500]
            ra_qs = [r for r in ra_qs if r is not None]
            if ra_qs:
                # ignore very low scores: elasticsearch grabs too much sometimes
                max_score = max(r.score for r in ra_qs)
                ra_pks = (r.pk for r in ra_qs if r.score > max_score/5)
                qs = qs.filter(pk__in=ra_pks)
            else:
                qs = qs.none()

        return qs

    def render_column(self, ra, column):
        if column == 'lump_sum_pay':
            return "${:,}".format(ra.lump_sum_pay)
        elif column == 'person':
            url = ra.get_absolute_url()
            name = ra.person.sortname()
            if ra.has_attachments():
                extra_string = '&nbsp; <i class="fa fa-paperclip" title="Attachment(s)"></i>'
            else:
                extra_string = ''
            return '<a href="%s">%s%s</a>' % (escape(url), escape(name), extra_string)
        elif column == 'unit':
            return ra.unit.label

        return str(getattr(ra, column))


@_can_view_ras()
def download_ras(request, current=True):
    ras = RAAppointment.objects.filter(Q(unit__in=request.units)
                                       | Q(hiring_faculty__userid=request.user.username))\
        .select_related('person', 'hiring_faculty', 'unit', 'project', 'account').exclude(deleted=True)
    if current:
        today = datetime.date.today()
        slack = 14  # number of days to fudge the start/end
        ras = ras.filter(start_date__lte=today + datetime.timedelta(days=slack),
                         end_date__gte=today - datetime.timedelta(days=slack))
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'inline; filename="ras-%s-%s.csv"' % (datetime.datetime.now().strftime('%Y%m%d'),
                                                                            'current' if current else 'all')
    writer = csv.writer(response)
    writer.writerow(['Name', 'ID', 'Hiring Faculty', 'Unit', 'Project', 'Account', 'Start Date', 'End Date', 'Amount'])
    for ra in ras:
        writer.writerow([ra.person.sortname(), ra.person.emplid, ra.hiring_faculty.sortname(), ra.unit.label, ra.project, ra.account, ra.start_date, ra.end_date, ra.lump_sum_pay])
    return response


def pay_periods(request):
    """
    Calculate number of pay periods between contract start and end dates.
    i.e. number of work days in period / 10
    
    I swear this was easier that doing it in JS, okay?
    """
    day = datetime.timedelta(days=1)
    week = datetime.timedelta(days=7)
    if 'start' not in request.GET or 'end' not in request.GET:
        result = ''
    else:
        st = request.GET['start']
        en = request.GET['end']
        try:
            st = datetime.datetime.strptime(st, "%Y-%m-%d").date()
            en = datetime.datetime.strptime(en, "%Y-%m-%d").date()
        except ValueError:
            result = ''
        else:
            # move start/end into Mon-Fri work week
            if st.weekday() == 5:
                st += 2*day
            elif st.weekday() == 6:
                st += day
            if en.weekday() == 5:
                en -= day
            elif en.weekday() == 6:
                en -= 2*day

            # number of full weeks (until sameday: last same weekday before end date)
            weeks = ((en-st)/7).days
            sameday = st + weeks*week
            assert sameday <= en < sameday + week
            
            # number of days remaining
            days = (en - sameday).days
            if sameday.weekday() > en.weekday():
                # don't count weekend days in between
                days -= 2
            
            days += 1 # count both start and end days
            result = "%.1f" % ((weeks*5 + days)/10.0)
    
    return HttpResponse(result, content_type='text/plain;charset=utf-8')


@requires_role("FUND")
def person_info(request):
    """
    Get more info about this person, for AJAX updates on new RA form
    """
    result = {'programs': []}
    emplid = request.GET.get('emplid', None)
    if not emplid or not emplid.isdigit() or len(emplid) != 9:
        pass
    else:
        programs = []
        
        # GradPrograms
        emplid = request.GET['emplid']
        grads = GradStudent.objects.filter(person__emplid=emplid, program__unit__in=request.units)
        for gs in grads:
            pdata = {
                     'program': gs.program.label,
                     'unit': gs.program.unit.name,
                     'status': gs.get_current_status_display(),
                     }
            programs.append(pdata)

        result['programs'] = programs
        
        # other SIMS info
        try:
            otherinfo = more_personal_info(emplid, needed=['citizen', 'visa'])
            result.update(otherinfo)
        except SIMSProblem as e:
            result['error'] = str(e)

    return HttpResponse(json.dumps(result), content_type='application/json;charset=utf-8')


@requires_role("FUND")
def person_visas(request):
    """
    Get info on this person's current visas, for info in the new RA appointment form.
    """
    result = {'visas': []}
    emplid = request.GET.get('emplid', None)
    if not emplid or not emplid.isdigit() or len(emplid) != 9:
        pass
    else:
        visas = []
        personvisas = Visa.objects.visible().filter(person__emplid=emplid, unit__in=request.units)
        for v in personvisas:
            if v.is_current():
                data = {
                    'start': v.start_date.isoformat(),
                    'status': v.status,
                }
                visas.append(data)
        result['visas'] = visas
    return HttpResponse(json.dumps(result), content_type='application/json;charset=utf-8')


@requires_role("FUND")
@transaction.atomic
def new_attachment(request, ra_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, unit__in=request.units)
    editor = get_object_or_404(Person, userid=request.user.username)

    form = RAAppointmentAttachmentForm()
    context = {"appointment": appointment,
               "attachment_form": form}

    if request.method == "POST":
        form = RAAppointmentAttachmentForm(request.POST, request.FILES)
        if form.is_valid():
            attachment = form.save(commit=False)
            attachment.appointment = appointment
            attachment.created_by = editor
            upfile = request.FILES['contents']
            filetype = upfile.content_type
            if upfile.charset:
                filetype += "; charset=" + upfile.charset
            attachment.mediatype = filetype
            attachment.save()
            return HttpResponseRedirect(reverse('ra:view', kwargs={'ra_slug': appointment.slug}))
        else:
            context.update({"attachment_form": form})

    return render(request, 'ra/appointment_attachment_form.html', context)


@requires_role("FUND")
def view_attachment(request, ra_slug, attach_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, unit__in=request.units)
    attachment = get_object_or_404(appointment.attachments.all(), slug=attach_slug)
    filename = attachment.contents.name.rsplit('/')[-1]
    resp = StreamingHttpResponse(attachment.contents.chunks(), content_type=attachment.mediatype)
    resp['Content-Disposition'] = 'inline; filename="' + filename + '"'
    resp['Content-Length'] = attachment.contents.size
    return resp


@requires_role("FUND")
def download_attachment(request, ra_slug, attach_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, unit__in=request.units)
    attachment = get_object_or_404(appointment.attachments.all(), slug=attach_slug)
    filename = attachment.contents.name.rsplit('/')[-1]
    resp = StreamingHttpResponse(attachment.contents.chunks(), content_type=attachment.mediatype)
    resp['Content-Disposition'] = 'attachment; filename="' + filename + '"'
    resp['Content-Length'] = attachment.contents.size
    return resp


@requires_role("FUND")
def delete_attachment(request, ra_slug, attach_slug):
    appointment = get_object_or_404(RAAppointment, slug=ra_slug, unit__in=request.units)
    attachment = get_object_or_404(appointment.attachments.all(), slug=attach_slug)
    attachment.hide()
    messages.add_message(request,
                         messages.SUCCESS,
                         'Attachment deleted.'
                         )
    l = LogEntry(userid=request.user.username, description="Hid attachment %s" % attachment, related_object=attachment)
    l.save()
    return HttpResponseRedirect(reverse('ra:view', kwargs={'ra_slug': appointment.slug}))


@requires_role("FUND")
def programs_index(request):
    unit_ids = [unit.id for unit in request.units]
    units = Unit.objects.filter(id__in=unit_ids)
    programs = Program.objects.visible_by_unit(units)
    return render(request, 'ra/programs_index.html', {'programs': programs})


@requires_role("FUND")
def new_program(request):
    if request.method == 'POST':
        form = ProgramForm(request.POST)
        if form.is_valid():
            program = form.save()
            messages.add_message(request,
                                 messages.SUCCESS,
                                 'Program was created')
            l = LogEntry(userid=request.user.username,
                         description="Added program %s" % program,
                         related_object=program)
            l.save()
            return HttpResponseRedirect(reverse('ra:programs_index'))
    else:
        form = ProgramForm()
        form.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    return render(request, 'ra/new_program.html', {'form': form})


@requires_role("FUND")
def delete_program(request, program_id):
    program = get_object_or_404(Program, pk=program_id, unit__in=request.units)
    program.delete()
    messages.add_message(request,
                         messages.SUCCESS,
                         'Program deleted.'
                         )
    l = LogEntry(userid=request.user.username, description="Hid program %s" % program, related_object=program)
    l.save()
    return HttpResponseRedirect(reverse('ra:programs_index'))


@requires_role("FUND")
def edit_program(request, program_slug):
    program = get_object_or_404(Program, slug=program_slug, unit__in=request.units)
    if request.method == 'POST':
        form = ProgramForm(request.POST, instance=program)
        if form.is_valid():
            program = form.save()
            messages.add_message(request,
                                 messages.SUCCESS,
                                 'Program was created')
            l = LogEntry(userid=request.user.username,
                         description="Added program %s" % program,
                         related_object=program)
            l.save()
            return HttpResponseRedirect(reverse('ra:programs_index'))
    else:
        form = ProgramForm(instance=program)
        form.fields['unit'].choices = [(u.id, u.name) for u in request.units]
    return render(request, 'ra/edit_program.html', {'form': form, 'program': program})