def download_file(request, course_slug, activity_slug, component_slug=None, submission_id=None, userid=None): course = get_object_or_404(CourseOffering, slug=course_slug) activity = get_object_or_404(course.activity_set, slug=activity_slug, deleted=False) staff = False if is_course_staff_by_slug(request, course_slug): staff = True # find the appropriate submission object if submission_id: # explicit request: get that one. try: submission_info = SubmissionInfo.from_submission_id(submission_id) except ValueError: return NotFoundResponse(request) elif userid: # userid specified: get their most recent submission student = get_object_or_404(Person, find_userid_or_emplid(userid)) submission_info = SubmissionInfo(student=student, activity=activity, include_deleted=staff) else: return NotFoundResponse(request) if not submission_info.have_submitted( ) or submission_info.activity != activity: return NotFoundResponse(request) if not submission_info.accessible_by(request): return ForbiddenResponse(request) # create the result if component_slug: # download single component if specified submission_info.get_most_recent_components() submitted_components = [ subcomp for comp, subcomp in submission_info.components_and_submitted() if subcomp and comp.slug == component_slug ] if not submitted_components: return NotFoundResponse(request) submitted_component = submitted_components[0] return submitted_component.download_response( slug=submission_info.submissions[0].file_slug()) else: # no component specified: give back the full ZIP file. return submission_info.generate_student_zip()
def download_file(request, course_slug, activity_slug, component_slug=None, submission_id=None, userid=None): course = get_object_or_404(CourseOffering, slug=course_slug) activity = get_object_or_404(course.activity_set, slug = activity_slug, deleted=False) staff = False if is_course_staff_by_slug(request, course_slug): staff = True # find the appropriate submission object if submission_id: # explicit request: get that one. submission = get_submission(submission_id) if not submission or submission.activity!=activity: return NotFoundResponse(request) submitted_components = get_submission_components(submission, activity, include_deleted=staff) elif userid: # userid specified: get their most recent submission student = get_object_or_404(Person, find_userid_or_emplid(userid)) submission, submitted_components = get_current_submission(student, activity, include_deleted=staff) if not submission: return NotFoundResponse(request) else: return NotFoundResponse(request) # make sure this user is allowed to see the file if staff: pass elif isinstance(submission, GroupSubmission): membership = submission.group.groupmember_set.filter(student__person__userid=request.user.username, activity=activity, confirmed=True) if not membership: return ForbiddenResponse(request) elif isinstance(submission, StudentSubmission): if submission.member.person.userid != request.user.username: return ForbiddenResponse(request) # create the result if component_slug: # download single component if specified # get the actual component: already did the searching above, so just look in that list components = [sub for comp,sub in submitted_components if sub and sub.component.slug==component_slug] if not components: return NotFoundResponse(request) return components[0].download_response() else: # no component specified: give back the full ZIP file. return generate_zip_file(submission, submitted_components)
def news_config(request): users = Person.objects.filter(userid=request.user.username) if users.count() == 1: user = users[0] else: return NotFoundResponse( request, errormsg= "Your account is not known to this system. There is nothing to configure." ) # get appropriate UserConfig object configs = UserConfig.objects.filter(user=user, key='newsitems') if configs: config = configs[0] else: config = UserConfig(user=user, key='newsitems', value={}) if request.method == 'POST': form = NewsConfigForm(request.POST) if form.is_valid(): config.value['email'] = form.cleaned_data['want_email'] config.save() messages.add_message(request, messages.SUCCESS, 'News settings updated.') return HttpResponseRedirect(reverse('config:config')) else: initial = { 'want_email': 'email' not in config.value or config.value['email'] } form = NewsConfigForm(initial) context = {'form': form} return render(request, "dashboard/news_config.html", context)
def calendar_data(request): """ AJAX JSON results for the calendar (rendered by dashboard.views.calendar) """ try: st = iso8601.parse_date(request.GET['start']) en = iso8601.parse_date(request.GET['end']) except (KeyError, ValueError, iso8601.ParseError): return NotFoundResponse(request, errormsg="Bad request") user = get_object_or_404(Person, userid=request.user.username) local_tz = pytz.timezone(settings.TIME_ZONE) start = st - datetime.timedelta(days=1) end = en + datetime.timedelta(days=1) resp = HttpResponse(content_type="application/json") events = _calendar_event_data(user, start, end, local_tz, dt_string=True, colour=True, due_before=datetime.timedelta(minutes=1), due_after=datetime.timedelta(minutes=30)) json.dump(list(events), resp, indent=1) return resp
def _offering_meeting_time_data(request, offering): """ fullcalendar.js data for this offering's events """ try: int(request.GET['start']) int(request.GET['end']) except (KeyError, ValueError): return NotFoundResponse(request, errormsg="Bad request") local_tz = pytz.timezone(settings.TIME_ZONE) start = local_tz.localize( datetime.datetime.fromtimestamp(int( request.GET['start']))) - datetime.timedelta(days=1) end = local_tz.localize( datetime.datetime.fromtimestamp(int( request.GET['end']))) + datetime.timedelta(days=1) response = HttpResponse(content_type='application/json') data = list( _offerings_calendar_data([offering], None, start, end, local_tz, dt_string=True, colour=True, browse_titles=True)) json.dump(data, response, indent=1) return response
def atom_feed(request, token, userid, course_slug=None): """ Return an Atom feed for this user, authenticated by the token in the URL """ user = get_object_or_404(Person, userid=userid) # make sure the token in the URL (32 hex characters) matches the token stored in the DB configs = UserConfig.objects.filter(user=user, key="feed-token") if not configs or 'token' not in configs[0].value or configs[0].value['token'] != token: # no token configured or wrong token provided return NotFoundResponse(request) #else: # authenticated news_list = NewsItem.objects.filter(user=user).order_by('-updated') course = None if course_slug: course = get_object_or_404(CourseOffering, slug=course_slug) news_list = news_list.filter(course=course) news_list = news_list[:20] if news_list: updated = news_list[0].rfc_updated() else: # no news items -> no recent updates. updated = '2000-01-01T00:00:00Z' context = {"news_list": news_list, 'person': user, 'updated': updated, 'course': course, 'server_url': settings.BASE_ABS_URL} return render(request, "dashboard/atom_feed.xml", context, content_type="application/atom+xml")
def config(request): users = Person.objects.filter(userid=request.user.username) if users.count() == 1: user = users[0] else: return NotFoundResponse(request, errormsg="Your account is not known to this system. There is nothing to configure.") # calendar config config = _get_calendar_config(user) if 'token' not in config: caltoken = None else: caltoken = config['token'] # feed config configs = UserConfig.objects.filter(user=user, key="feed-token") if not configs: newstoken = None else: newstoken = configs[0].value['token'] # news config configs = UserConfig.objects.filter(user=user, key="newsitems") # By default, users get emails for news items unless they specifically opted-out. The value here doesn't # change any data, it just displays the same thing as if someone had a UserConfig where they specifically set # email to True. if not configs: newsconfig = {'email': True} else: newsconfig = configs[0].value # advisor note API config advisortoken = None advisor = False if has_role('ADVS', request): advisor = True configs = UserConfig.objects.filter(user=user, key='advisor-token') if len(configs) > 0: advisortoken = configs[0].value['token'] # ID photo agreement instructor = False photo_agreement = False if Member.objects.filter(person=user, role__in=['INST', 'TA']).count() > 0: instructor = True configs = UserConfig.objects.filter(user=user, key='photo-agreement') if len(configs) > 0: photo_agreement = configs[0].value['agree'] # privacy config roles = Role.all_roles(user.userid) roles_with_privacy = [r for r in roles if r in PRIVACY_ROLES] privacy_visible = len(roles_with_privacy) > 0 context={'caltoken': caltoken, 'newstoken': newstoken, 'newsconfig': newsconfig, 'advisor': advisor, 'advisortoken': advisortoken, 'instructor': instructor, 'photo_agreement': photo_agreement, 'userid': user.userid, 'server_url': settings.BASE_ABS_URL, 'privacy_visible': privacy_visible} return render(request, "dashboard/config.html", context)
def calendar_ical(request, token, userid): """ Return an iCalendar for this user, authenticated by the token in the URL """ local_tz = pytz.timezone(settings.TIME_ZONE) utc = pytz.utc user = get_object_or_404(Person, userid=userid) # make sure the token in the URL (32 hex characters) matches the token stored in the DB config = _get_calendar_config(user) if 'token' not in config or config['token'] != token: # no token set or wrong token provided return NotFoundResponse(request) #else: # authenticated now = datetime.datetime.now() start = local_tz.localize(now - datetime.timedelta(days=180)) end = local_tz.localize(now + datetime.timedelta(days=365)) cal = Calendar() cal.add('version', '2.0') cal.add('prodid', '-//SFU CourSys//courses.cs.sfu.ca//') cal.add('X-PUBLISHED-TTL', 'PT1D') for data in _calendar_event_data(user, start, end, local_tz, dt_string=False): e = Event() e['uid'] = str(data['id']) e.add('summary', data['title']) e.add('dtstart', _ical_datetime(utc, data['start'])) e.add('dtend', _ical_datetime(utc, data['end'])) if data['category'] in ('DUE', 'HOLIDAY'): # these shouldn't be "busy" on calendars e.add('transp', 'TRANSPARENT') else: e.add('transp', 'OPAQUE') # spec says no TZID on UTC times if 'TZID' in e['dtstart'].params: del e['dtstart'].params['TZID'] if 'TZID' in e['dtend'].params: del e['dtend'].params['TZID'] e.add('categories', data['category']) if 'url' in data: e.add('url', data['url']) if 'location' in data: e.add('location', data['location']) cal.add_component(e) resp = HttpResponse(cal.to_ical(), content_type="text/calendar") print resp.reason_phrase return resp
def edit_single(request, course_slug, activity_slug): course = get_object_or_404(CourseOffering, slug=course_slug) activity = get_object_or_404(course.activity_set, slug=activity_slug, deleted=False) component_list = select_all_components(activity) #get component edit_id = request.GET.get('id') component = get_component(activity=activity, id=edit_id) if component is None: return NotFoundResponse(request) form = component.Type.ComponentForm(instance=component) #if form submitted if request.method == 'POST': new_form = component.Type.ComponentForm(request.POST) if new_form.is_valid(): new_component = new_form.save(commit=False) new_component.activity = activity new_component.id = component.id if new_component.position == None: count = len(select_all_components(activity)) new_component.position = count + 1 new_component.save() #LOG EVENT# l = LogEntry(userid=request.user.username, description=("edited component %s of %s") % (component.title, activity), related_object=new_component) l.save() messages.add_message( request, messages.SUCCESS, 'Component "' + new_component.title + '" successfully updated.') return HttpResponseRedirect( reverse('offering:submission:show_components', args=[course_slug, activity_slug])) else: form = new_form messages.add_message(request, messages.ERROR, 'Please correct the errors in the form.') return render( request, "submission/component_edit_single.html", { "course": course, "activity": activity, "component": component, "edit_id": edit_id, "form": form })
def delete_savedsearch(request): current_user = Person.objects.get(userid=request.user.username) if request.method != 'POST': return ForbiddenResponse(request) savedsearches = SavedSearch.objects.filter( person=request.POST['person'], query=request.POST['query']) if not savedsearches: return NotFoundResponse(request, "This Saved Search doesn't exist.") savedsearch = savedsearches[0] if current_user != savedsearch.person: return ForbiddenResponse(request, "You cannot delete this Saved Search.") savedsearch.delete() messages.add_message(request, messages.SUCCESS, "Saved Search '%s' was successfully deleted." % savedsearch.name()) return HttpResponseRedirect(reverse('grad:index'))
def get_form(request, grad_slug): grad = get_object_or_404(GradStudent, slug=grad_slug, program__unit__in=request.units) if 'type' in request.GET and request.GET['type'] == 'cardreq': response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'inline; filename="card_access.pdf"' card_req_forms([grad], response) elif 'type' in request.GET and request.GET['type'] == 'fasnet': response = HttpResponse(content_type='application/pdf') response[ 'Content-Disposition'] = 'inline; filename="fasnet_access.pdf"' fasnet_forms([grad], response) else: response = NotFoundResponse(request) return response
def _get_file(request, course_slug, page_label, disposition): """ view for either inlinte viewing or downloading file contents """ offering = get_object_or_404(CourseOffering, slug=course_slug) page = get_object_or_404(Page, offering=offering, label=page_label) version = page.current_version() if not version.is_filepage(): return NotFoundResponse(request) member = _check_allowed(request, offering, page.can_read, page.releasedate()) # check that we have an allowed member of the course (and can continue) if not member: return _forbidden_response(request, page.get_can_read_display()) resp = HttpResponse(version.file_attachment.chunks(), content_type=version.file_mediatype) resp['Content-Disposition'] = disposition+'; filename="' + version.file_name + '"' resp['Content-Length'] = version.file_attachment.size return resp
def browse_courses_info(request, course_slug): """ Browsing info about a single course offering. """ offering = get_object_or_404(CourseOffering, slug=course_slug) if offering.flags.combined: return NotFoundResponse(request) if 'data' in request.GET: # more_course_info data requested response = HttpResponse(content_type='application/json') try: data = more_offering_info(offering, browse_data=True, offering_effdt=True) except SIMSProblem as e: data = {'error': e.message} json.dump(data, response, indent=1) return response elif 'caldata' in request.GET: # calendar data requested return _offering_meeting_time_data(request, offering) elif 'outline' in request.GET: # course outline data requested response = HttpResponse(content_type='application/json') data = outlines_data_json(offering) response.write(data) return response # the page itself (with most data assembled by AJAX requests to the above) context = { 'offering': offering, } return render(request, 'coredata/browse_courses_info.html', context)
def view(request, grad_slug, section=None): grad, authtype, units = _can_view_student(request, grad_slug) if grad is None or authtype == 'student': return ForbiddenResponse(request) # uses of the cortez link routed through here to see if they're actually being used if 'cortez-bounce' in request.GET and 'cortezid' in grad.config: from log.models import LogEntry from django.shortcuts import redirect l = LogEntry(userid=request.user.username, description="used cortez link for %s" % (grad.slug, ), related_object=grad) l.save() return redirect( "https://cortez.cs.sfu.ca/grad/scripts/grabcurrent.asp?Identifier=" + grad.config['cortezid']) # Only an authtype ruled as "admin" by _can_view_student should be allowed to edit anything here. can_edit = authtype == 'admin' context = { 'grad': grad, 'can_edit': can_edit, 'authtype': authtype, } for s in all_sections: context[s + '_content'] = '' if 'section' in request.GET: # page sections fetched by AJAX calls section = request.GET['section'] if section: if section not in all_sections: return NotFoundResponse(request) elif section == 'general': programhistory = GradProgramHistory.objects.filter( student=grad, program__unit__in=units).order_by('starting') context['programhistory'] = programhistory flag_values = grad.flags_and_values() context['extras'] = [(title, grad.config[field]) for field, title in grad.tacked_on_fields if field in grad.config] context['flag_values'] = flag_values return render(request, 'grad/view__general.html', context) elif section == 'supervisors': supervisors = Supervisor.objects.filter( student=grad, removed=False).select_related('supervisor') context['supervisors'] = supervisors return render(request, 'grad/view__supervisors.html', context) elif section == 'status': statuses = GradStatus.objects.filter( student=grad, hidden=False).order_by('start__name') context['statuses'] = statuses return render(request, 'grad/view__status.html', context) elif section == 'requirements': completed_req = CompletedRequirement.objects.filter(student=grad, removed=False) completed_gradreq_id = [ cr.requirement_id for cr in completed_req if cr.removed == False ] req = GradRequirement.objects.filter(program=grad.program, hidden=False) missing_req = req.exclude(id__in=completed_gradreq_id) context['completed_req'] = completed_req context['missing_req'] = missing_req return render(request, 'grad/view__requirements.html', context) elif section == 'scholarships': scholarships = Scholarship.objects.filter( student=grad, removed=False).select_related( 'scholarship_type').order_by('start_semester__name') comments = FinancialComment.objects.filter( student=grad, comment_type='SCO', removed=False).order_by('created_at') context['scholarships'] = scholarships context['scholarship_comments'] = comments return render(request, 'grad/view__scholarships.html', context) elif section == 'otherfunding': otherfunding = OtherFunding.objects.filter( student=grad, removed=False).order_by('semester__name') context['otherfunding'] = otherfunding return render(request, 'grad/view__otherfunding.html', context) elif section == 'promises': promises = Promise.objects.filter( student=grad, removed=False).order_by('start_semester__name') context['promises'] = promises return render(request, 'grad/view__promises.html', context) elif section == 'tacontracts': tacontracts = TAContract.objects.filter(person=grad.person, status__in=['NEW', 'SGN']) oldcontracts = OldTAContract.objects.filter( application__person=grad.person) context['tacontracts'] = tacontracts context['oldcontracts'] = oldcontracts return render(request, 'grad/view__tacontracts.html', context) elif section == 'financialcomments': comments = FinancialComment.objects.filter( student=grad, removed=False).order_by('created_at') context['financial_comments'] = comments return render(request, 'grad/view__financialcomments.html', context) elif section == 'letters': letters = Letter.objects.filter( student=grad, removed=False).select_related('template').order_by('date') context['letters'] = letters return render(request, 'grad/view__letters.html', context) elif section == 'progressreports': progressreports = ProgressReport.objects.filter(student=grad, removed=False)\ .order_by('date') context['progress_reports'] = progressreports return render(request, 'grad/view__progress.html', context) elif section == 'documents': documents = ExternalDocument.objects.filter(student=grad, removed=False)\ .order_by('date') context['documents'] = documents return render(request, 'grad/view__documents.html', context) elif section == 'visas': visas = Visa.get_visas([grad.person]) context['visas'] = visas return render(request, 'grad/view__visas.html', context) else: raise ValueError("Not all sections handled by view code: " + repr(section)) elif '_escaped_fragment_' in request.GET: # Implement google-suggested hash-bang workaround. Not terribly efficient, but probably uncommon. # https://developers.google.com/webmasters/ajax-crawling/docs/getting-started sections = request.GET['_escaped_fragment_'].split(',') for s in sections: resp = view(request, grad_slug, section=s) context[s + '_content'] = mark_safe(resp.content.decode('utf8')) other_grad = GradStudent.objects \ .filter(program__unit__in=units, person=grad.person) \ .exclude(id=grad.id) other_applicant = [x for x in other_grad if x.is_applicant()] other_grad = [x for x in other_grad if not x.is_applicant()] context['other_grad'] = other_grad context['other_applicant'] = other_applicant return render(request, 'grad/view.html', context)
def _show_components_student(request, course_slug, activity_slug, userid=None, template="dashboard_student.html", staff=False): """ Show all the component submission history of this activity """ if userid == None: userid = request.user.username course = get_object_or_404(CourseOffering, slug=course_slug) activity = get_object_or_404(course.activity_set,slug=activity_slug, deleted=False) student = get_object_or_404(Person, find_userid_or_emplid(userid)) cansubmit = True submission, submitted_components = get_current_submission(student, activity, include_deleted=staff) if len(submitted_components) == 0: return NotFoundResponse(request) if submission and activity.due_date and activity.due_date < submission.created_at: late = submission.created_at - activity.due_date else: late = 0 if activity.group: gm = GroupMember.objects.filter(student__person=student, activity=activity, confirmed=True) if gm: group = gm[0].group member = gm[0].student else: group = None #cansubmit = False #messages.add_message(request, messages.INFO, "This is a group submission. You cannot submit since you aren't in a group.") else: group = None # activity should be submitable cansubmit = cansubmit and activity.submitable() if not cansubmit: messages.add_message(request, messages.ERROR, "This activity is not submittable.") return render(request, "submission/" + template, {"course":course, "activity":activity, "submission": submission, "submitted_components":submitted_components, "userid":userid, "late":late, "student":student, "group":group, "cansubmit":cansubmit}) # get all components of activity component_list = select_all_components(activity) component_list.sort() component_form_list=[] if request.method == 'POST': component_form_list = make_form_from_list(component_list, request=request) submitted_comp = [] # list all components which has content submitted in the POST not_submitted_comp = [] #list allcomponents which has no content submitted in the POST if not activity.group: new_sub = StudentSubmission() # the submission foreign key for newly submitted components new_sub.member = get_object_or_404(Member, offering__slug=course_slug, person__userid=request.user.username) elif gm: new_sub = GroupSubmission() new_sub.group = group new_sub.creator = member else: messages.add_message(request, messages.ERROR, "This is a group submission. You cannot submit since you aren't in a group.") return ForbiddenResponse(request) new_sub.activity = activity # begin validating uploaded data submitted_comp = [] not_submitted_comp = [] # validate forms one by one for data in component_form_list: component = data['comp'] form = data['form'] if form.is_valid(): sub = form.save(commit=False) sub.component = component submitted_comp.append(sub) else: # hack to replace the "required" message to something more appropriate for k,v in form.errors.items(): for i,e in enumerate(v): if e == "This field is required.": v[i] = "Nothing submitted." not_submitted_comp.append(component) # check duplicate filenames here all_ok = False while not all_ok: all_ok = True d = {} for c,s in submitted_components: d[c] = s and s.get_filename() for s in submitted_comp: d[s.component] = s.get_filename() # a list holding all file names file_name_list = [a[1] for a in d.items() if a[1] is not None] to_be_removed = [] for (i, s) in enumerate(submitted_comp): if file_name_list.count(s.get_filename()) > 1: all_ok = False to_be_removed.append(i) not_submitted_comp.append(s.component) #HACK: modify the 'errors' field in the form for data in component_form_list: if s.component == data['comp']: # assume we have only one field for submission form field_name = data['form'].fields.keys()[0] data['form']._errors[field_name] = ErrorList([u"This file has the same name as another file in your submission."]) # remove those has errors in submitted_comp to_be_removed.reverse() for t in to_be_removed: submitted_comp.pop(t) # all okay now # end validating, begin saving if len(submitted_comp) > 0: new_sub.save() for sub in submitted_comp: sub.submission = new_sub sub.save() #LOG EVENT# if activity.group: group_str = " as a member of group %s" % new_sub.group.name else: group_str = "" l = LogEntry(userid=request.user.username, description=u"submitted for %s %s%s" % (activity, sub.component.title, group_str), related_object=sub) l.save() if len(not_submitted_comp) == 0: messages.add_message(request, messages.SUCCESS, "Your submission was successful.") return HttpResponseRedirect(reverse(show_components, args=[course_slug, activity_slug])) return render(request, "submission/submission_error.html", {"course":course, "activity":activity, "component_list":component_form_list, "submitted_comp":submitted_comp, "not_submitted_comp":not_submitted_comp}) else: #not POST if activity.group and gm: messages.add_message(request, messages.INFO, "This is a group submission. You will submit on behalf of the group %s." % group.name) component_form_list = make_form_from_list(component_list) return render(request, "submission/" + template, {'component_form_list': component_form_list, "course": course, "activity": activity, "submission": submission, "submitted_components":submitted_components, "userid":userid, "late":late, "student":student, "group":group, "cansubmit":cansubmit, "is_staff":staff})
def financials(request, grad_slug, style='complete'): if style not in STYLES: return NotFoundResponse(request) grad, _, units = _can_view_student(request, grad_slug, funding=True) if grad is None: return ForbiddenResponse(request) current_status = GradStatus.objects.filter( student=grad, hidden=False).order_by('-start')[0] grad_status_qs = GradStatus.objects.filter( student=grad, hidden=False, status__in=STATUS_ACTIVE).select_related('start', 'end') scholarships_qs = Scholarship.objects.filter(student=grad, removed=False).select_related( 'start_semester', 'end_semester') promises_qs = Promise.objects.filter(student=grad, removed=False).select_related( 'start_semester', 'end_semester') other_fundings = OtherFunding.objects.filter( student=grad, removed=False).select_related('semester') contracts = TAContract.objects.filter( application__person=grad.person).exclude( status__in=STATUSES_NOT_TAING).select_related('posting__semester') other_contracts = NewTAContract.objects.filter(person=grad.person, status__in=['NEW', 'SGN'])\ .select_related('category')\ .select_related('email_receipt')\ .prefetch_related('course') appointments = RAAppointment.objects.filter(person=grad.person, deleted=False) program_history = GradProgramHistory.objects.filter( student=grad).select_related('start_semester', 'program') financial_comments = FinancialComment.objects.filter( student=grad, removed=False).select_related('semester') # initialize earliest starting and latest ending semesters for display. # Falls back on current semester if none all_semesters = itertools.chain( # every semester we have info for (s.start for s in grad_status_qs), (s.end for s in grad_status_qs), (p.start_semester for p in promises_qs), (p.end_semester for p in promises_qs), (s.start_semester for s in scholarships_qs), (s.end_semester for s in scholarships_qs), (o.semester for o in other_fundings), (c.posting.semester for c in contracts), (c.semester for c in financial_comments), (get_semester(a.start_date) for a in appointments), (get_semester(a.end_date) for a in appointments), (ph.start_semester for ph in program_history), ) all_semesters = itertools.ifilter(lambda x: isinstance(x, Semester), all_semesters) all_semesters = set(all_semesters) if len(all_semesters) == 0: all_semesters = [get_semester()] earliest_semester = min(all_semesters) latest_semester = max(all_semesters) semesters = [] semesters_qs = Semester.objects.filter( start__gte=earliest_semester.start, end__lte=latest_semester.end).order_by('-start') current_acad_year = None # build data structure with funding for each semester for semester in semesters_qs: semester_total = decimal.Decimal(0) yearpos = ( semester - grad.start_semester ) % 3 # position in academic year: 0 is start of a new academic year for this student if not current_acad_year or yearpos == 2: # keep this (mutable) structure that we can alias in each semester and keep running totals current_acad_year = {'total': 0, 'semcount': 0, 'endsem': semester} # other funding other_funding = other_fundings.filter(semester=semester) other_total = 0 for other in other_funding: if other.eligible: other_total += other.amount semester_total += other.amount # scholarships semester_scholarships = scholarships_qs.filter( start_semester__name__lte=semester.name, end_semester__name__gte=semester.name) semester_eligible_scholarships = semester_scholarships.filter( scholarship_type__eligible=True) scholarships = [] scholarship_total = 0 for ss in semester_scholarships: amt = ss.amount / (ss.end_semester - ss.start_semester + 1) scholarship_total += amt scholarships.append({'scholarship': ss, 'semester_amount': amt}) for semester_eligible_scholarship in semester_eligible_scholarships: if (semester_eligible_scholarship.start_semester != semester_eligible_scholarship.end_semester): semester_span = semester_eligible_scholarship.end_semester - semester_eligible_scholarship.start_semester + 1 semester_total += semester_eligible_scholarship.amount / semester_span else: semester_total += semester_eligible_scholarship.amount # grad status status = None status_short = None for s in GradStatus.objects.filter(student=grad): if s.start <= semester and (s.end == None or semester <= s.end): status = s.get_status_display() status_short = s.get_short_status_display() # grad program program = None for ph in program_history: if ph.start_semester == semester: program = ph # financial comments comments = [] for c in financial_comments: if c.semester == semester: comments.append(c) # TAs ta_total = 0 courses = [] for contract in contracts: if contract.posting.semester == semester: for course in TACourse.objects.filter( contract=contract).exclude( bu=0).select_related('course'): ta_total += course.pay() if contract.status == 'SGN': text = "%s (%s BU)" % (course.course.name(), course.total_bu) else: text = "%s (%s BU, current status: %s)" \ % (course.course.name(), course.total_bu, contract.get_status_display().lower()) courses.append({'course': text, 'amount': course.pay()}) for contract in other_contracts: if contract.category.hiring_semester.semester == semester: if contract.status == 'SGN': for course in contract.course.all(): ta_total += course.total courses.append({ 'course': "%s (%s BU)" % (course.course.name(), course.total_bu), 'amount': course.total }) else: for course in contract.course.all(): courses.append({ 'course': "%s (%s BU - $%.02f) - Draft" % (course.course.name(), course.total_bu, course.total), 'amount': 0 }) ta = {'courses': courses, 'amount': ta_total} semester_total += ta_total # RAs ra_total = 0 appt = [] for appointment in appointments: app_start_sem = appointment.start_semester() app_end_sem = appointment.end_semester() length = appointment.semester_length() if app_start_sem <= semester and app_end_sem >= semester: sem_pay = appointment.lump_sum_pay / length ra_total += sem_pay appt.append({ 'desc': "RA for %s - %s" % (appointment.hiring_faculty.name(), appointment.project), 'amount': sem_pay, 'semesters': appointment.semester_length() }) ra = {'appt': appt, 'amount': ra_total} semester_total += ra_total # promises (ending in this semester, so we display them in the right spot) try: promise = promises_qs.get(end_semester=semester) except Promise.DoesNotExist: promise = None current_acad_year['total'] += semester_total current_acad_year['semcount'] += 1 semester_data = { 'semester': semester, 'status': status, 'status_short': status_short, 'scholarships': scholarships, 'promise': promise, 'semester_total': semester_total, 'comments': comments, 'ta': ta, 'ra': ra, 'other_funding': other_funding, 'program': program, 'other_total': other_total, 'scholarship_total': scholarship_total, 'ta_total': ta_total, 'ra_total': ra_total, 'acad_year': current_acad_year } semesters.append(semester_data) promises = [] for promise in promises_qs: received = decimal.Decimal(0) for semester in semesters: if semester['semester'] < promise.start_semester or semester[ 'semester'] > promise.end_semester: continue received += semester['semester_total'] owing = received - promise.amount # minor logic for display. if owing < 0: owing = abs(owing) else: owing = -1 # annotate the semester where we're displaying the promise with relevant info for semester in semesters: if semester['semester'] == promise.end_semester: semester['promisereceived'] = received semester['promiseowing'] = owing totals = {'ta': 0, 'ra': 0, 'scholarship': 0, 'other': 0, 'total': 0} for s in semesters: totals['ta'] += s['ta_total'] totals['ra'] += s['ra_total'] totals['scholarship'] += s['scholarship_total'] totals['other'] += s['other_total'] totals['total'] += s['semester_total'] context = { 'semesters': semesters, 'promises': promises, 'grad': grad, 'status': current_status, 'unit': units, 'totals': totals, } return render(request, 'grad/view_financials-%s.html' % (style), context)