def add_topt(request, next_page=None): next_page, okay_auth, okay_2fa = _setup_view(request, next_page) if not okay_auth: return ForbiddenResponse(request) # This enforces that users have exactly one TOTP. That *seems* like the best practice. devices = TOTPDevice.objects.devices_for_user(request.maybe_stale_user, confirmed=True) if devices: return ForbiddenResponse(request, "You have already configured an authenticator with this account, and cannot add another. Contact %s if you are unable to authenticate with CourSys", (help_email(request),)) device = TOTPDevice(user=request.maybe_stale_user, name='Authenticator, enabled %s' % (datetime.date.today())) device.save() l = LogEntry(userid=request.user.username, description=("Added TOPT from %s") % (get_client_ip(request),), related_object=device) l.save() # build QR code uri = totpauth_url(device) qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage) qrdata = BytesIO() qr.save(qrdata) # This is the OTP secret (bits) encoded as base32, wrapped in an otpauth URL, encoded as a QR code, encoded as an # SVG, encoded as base64, wrapped in a data URL. I'm strangely proud. dataurl = (b'data:image/svg+xml;base64,' + base64.b64encode(qrdata.getvalue())).decode("utf-8") context = { 'device': device, 'dataurl': dataurl, 'next_page': next_page, } return render(request, 'otp/add_topt.html', context)
def _delete_pagefile(request, course_slug, page_label, kind): """ Delete page/file """ with django.db.transaction.atomic(): offering = get_object_or_404(CourseOffering, slug=course_slug) page = get_object_or_404(Page, offering=offering, label=page_label) version = page.current_version() member = _check_allowed(request, offering, page.can_write, page.editdate()) if not member: return ForbiddenResponse(request, 'Not allowed to edit this '+kind+'.') can_create = member.role in MEMBER_ROLES[offering.page_creators()] if not can_create: return ForbiddenResponse(request, 'Not allowed to delete pages in for this offering (must have page-creator permission).') from django.core.validators import URLValidator from django.core.exceptions import ValidationError val = URLValidator() redirect = request.POST.get('redirect', 'Index') url = request.build_absolute_uri(urljoin(page.get_absolute_url(), redirect)) try: val(url) except ValidationError: messages.error(request, "Bad redirect URL entered. Not deleted.") return HttpResponseRedirect(reverse('offering:pages:edit_page', kwargs={'course_slug': course_slug, 'page_label': page.label})) redir_version = PageVersion(page=page, title=version.title, redirect=redirect, editor=member, comment='automatically generated on deletion') redir_version.set_redirect_reason('delete') redir_version.save() messages.success(request, "Page deleted and will redirect to this location.") return HttpResponseRedirect(urljoin(page.get_absolute_url(), redirect))
def _delete_pagefile(request, course_slug, page_label, kind): """ Delete page/file """ with django.db.transaction.atomic(): offering = get_object_or_404(CourseOffering, slug=course_slug) page = get_object_or_404(Page, offering=offering, label=page_label) #version = page.current_version() member = _check_allowed(request, offering, page.can_write, page.editdate()) if not member: return ForbiddenResponse(request, 'Not allowed to edit this ' + kind + '.') can_create = member.role in MEMBER_ROLES[offering.page_creators()] if not can_create: return ForbiddenResponse( request, 'Not allowed to delete pages in for this offering (must have page-creator permission).' ) page.safely_delete() messages.success( request, "Page deleted (but can be recovered by an administrator in an emergency)." ) return HttpResponseRedirect( reverse(index_page, kwargs={'course_slug': course_slug}))
def offering_by_id(request): if 'id' not in request.GET: return ForbiddenResponse(request, "Must provide 'id' query.") id_ = request.GET['id'] try: int(id_) except ValueError: return ForbiddenResponse(request, "'id' must be an integer.") offering = get_object_or_404(CourseOffering, pk=id_) return HttpResponse(offering.search_label_value())
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 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 edit_contract(request, contract_slug): contract = get_object_or_404(SessionalContract, slug=contract_slug) if not _has_unit_role(request.user, contract): return ForbiddenResponse(request) if request.method == 'POST': form = SessionalContractForm(request, request.POST, instance=contract) if form.is_valid(): contract = form.save(commit=False) # Let's convert the person in there to an AnyPerson person = form.cleaned_data['person'] contract.sessional = AnyPerson.get_or_create_for(person=person) contract.save() messages.add_message(request, messages.SUCCESS, u'Contract was edited.' ) l = LogEntry(userid=request.user.username, description="edited sessional contract for: %s" % contract.sessional, related_object=contract ) l.save() return HttpResponseRedirect(reverse('sessionals:sessionals_index')) else: form = SessionalContractForm(request, instance=contract) form.fields['person'].initial = contract.sessional.get_person().emplid return render(request, 'sessionals/edit_contract.html', {'form': form, 'contract': contract})
def get_letter(request, grad_slug, letter_slug): grad, authtype, units = _can_view_student(request, grad_slug) if grad is None or authtype == 'student': return ForbiddenResponse(request) letter = get_object_or_404(Letter, slug=letter_slug, student=grad, student__program__unit__in=units) response = HttpResponse(content_type="application/pdf") response['Content-Disposition'] = 'inline; filename="%s.pdf"' % ( letter_slug) doc = OfficialLetter(response, unit=letter.student.program.unit) l = LetterContents(to_addr_lines=letter.to_lines.split("\n"), from_name_lines=letter.from_lines.split("\n"), date=letter.date, closing=letter.closing, signer=letter.from_person, use_sig=letter.use_sig(), body_font_size=letter.template.body_font_size()) content_lines = letter.content.split("\n\n") l.add_paragraphs(content_lines) doc.add_letter(l) doc.write() return response
def artifact_search(request): if 'text-search' not in request.GET: return ForbiddenResponse(request, "must send search query") search = request.GET['text-search'] query = get_query(search, ('text', )) artifact_notes = ArtifactNote.objects.filter( query, unit__in=request.units, hidden=False).order_by("-created_at")[:100] for a in artifact_notes: if a.course: a.url = reverse('advising:view_course_notes', kwargs={'unit_course_slug': a.course.slug}) a.description = a.course elif a.course_offering: a.url = reverse('advising:view_offering_notes', kwargs={'course_slug': a.course_offering.slug}) a.description = a.course_offering else: a.url = reverse('advising:view_artifact_notes', kwargs={'artifact_slug': a.artifact.slug}) a.description = a.artifact artifact_form = ArtifactSearchForm(prefix="text", initial={'search': search}) context = { 'artifact_notes': artifact_notes, 'artifact_form': artifact_form } return render(request, 'advisornotes/artifact_search.html', context)
def import_page(request, course_slug, page_label): with django.db.transaction.atomic(): offering = get_object_or_404(CourseOffering, slug=course_slug) page = get_object_or_404(Page, offering=offering, label=page_label) version = page.current_version() member = _check_allowed(request, offering, page.can_write) if not member: return ForbiddenResponse(request, 'Not allowed to edit/create this page.') if request.method == 'POST': form = PageImportForm(data=request.POST, files=request.FILES) if form.is_valid(): wiki = form.cleaned_data['file'] or form.cleaned_data['url'] # create Page editing form for preview-before-save pageform = EditPageForm(instance=page, offering=offering) pageform.initial['wikitext'] = wiki # URL for submitting that form url = reverse(edit_page, kwargs={'course_slug': offering.slug, 'page_label': page.label}) messages.warning(request, "Page has not yet been saved, but your HTML has been imported below.") context = {'offering': offering, 'page': page, 'form': pageform, 'kind': 'Page', 'import': True, 'url': url} return render(request, 'pages/edit_page.html', context) else: form = PageImportForm() context = {'offering': offering, 'page': page, 'version': version, 'form': form} return render(request, 'pages/import_page.html', context)
def import_site(request, course_slug): with django.db.transaction.atomic(): offering = get_object_or_404(CourseOffering, slug=course_slug) member = _check_allowed(request, offering, 'STAF') # only staff can import if not member: return ForbiddenResponse(request, 'Not allowed to edit/create pages.') if request.method == 'POST': form = SiteImportForm(offering=offering, editor=member, data=request.POST, files=request.FILES) if form.is_valid(): pages, errors = form.cleaned_data['url'] for label in pages: page, pv = pages[label] page.save() pv.page_id = page.id pv.save() else: form = SiteImportForm(offering=offering, editor=member) context = {'offering': offering, 'form': form} return render(request, 'pages/import_site.html', context)
def show_components_submission_history(request, course_slug, activity_slug, userid=None): if userid is None: userid = request.GET.get('userid') 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 userid is None: # can always see your own submissions userid = request.user.username else: # specifying a userid: must be course staff if is_course_staff_by_slug(request, course.slug): staff = True else: return ForbiddenResponse(request) member = get_object_or_404(Member, find_member(userid), offering=course) if activity.group: messages.add_message(request, messages.INFO, "This is a group submission. This history is based on submissions from all your group members.") gms = GroupMember.objects.filter(student=member, confirmed=True, activity=activity) submissions = GroupSubmission.objects.filter(activity=activity, group__groupmember__in=gms) else: submissions = StudentSubmission.objects.filter(activity=activity, member=member) # get all submission components component_list = select_all_components(activity, include_deleted=staff) all_submitted_components = [] for submission in submissions: c = get_submission_components(submission, activity, component_list) all_submitted_components.append({'sub':submission, 'comp':c}) return render(request, "submission/submission_history_view.html", {"course":course, "activity":activity,'userid':userid,'submitted_components': all_submitted_components})
def groupmanage(request, course_slug, activity_slug=None): if is_course_staff_by_slug(request, course_slug): return _groupmanage_staff(request, course_slug, activity_slug) elif is_course_student_by_slug(request, course_slug): return _groupmanage_student(request, course_slug) else: return ForbiddenResponse(request)
def view_registration(request, registration_id): registration = get_object_or_404(OutreachEventRegistration, pk=registration_id) if not _has_unit_role(request.user, registration.event): return ForbiddenResponse(request) print registration.extra_questions return render(request, 'outreach/view_registration.html', {'registration': registration})
def new_file(request, course_slug, case_slug): course = get_object_or_404(CourseOffering, slug=course_slug) case = get_object_or_404(DisciplineCaseBase, slug=case_slug, offering__slug=course_slug) case = case.subclass() if not case.can_edit('attach'): # once case is closed, don't allow editing return ForbiddenResponse(request)
def student_search(request): # check permissions roles = Role.all_roles(request.user.username) allowed = set(['ADVS', 'ADMN', 'GRAD', 'FUND', 'SYSA']) if not (roles & allowed) and not has_formgroup(request): # doesn't have any allowed roles return ForbiddenResponse(request, "Not permitted to do student search.") if 'term' not in request.GET: return ForbiddenResponse(request, "Must provide 'term' query.") term = request.GET['term'] response = HttpResponse(content_type='application/json') # do the query with Haystack # experimentally, score >= 1 seems to correspond to useful things student_qs = SearchQuerySet().models(Person).filter(text=term)[:20] data = [{ 'value': r.emplid, 'label': r.search_display } for r in student_qs if r and r.score >= 1 and unicode(r.emplid) not in EXCLUDE_EMPLIDS] # non-haystack version of the above query if len(student_qs) == 0: studentQuery = get_query( term, ['userid', 'emplid', 'first_name', 'last_name']) students = Person.objects.filter(studentQuery)[:20] data = [{ 'value': s.emplid, 'label': s.search_label_value() } for s in students if unicode(s.emplid) not in EXCLUDE_EMPLIDS] if 'nonstudent' in request.GET and 'ADVS' in roles: nonStudentQuery = get_query( term, ['first_name', 'last_name', 'pref_first_name']) nonStudents = NonStudent.objects.filter(nonStudentQuery)[:10] data.extend([{ 'value': n.slug, 'label': n.search_label_value() } for n in nonStudents]) #data.sort(key = lambda x: x['label']) json.dump(data, response, indent=1) return response
def XXX_sims_person_search(request): # check permissions roles = Role.all_roles(request.user.username) allowed = set(['ADVS', 'ADMN', 'GRAD', 'FUND']) if not (roles & allowed): # doesn't have any allowed roles return ForbiddenResponse(request, "Not permitted to do person search.") if 'emplid' not in request.GET: return ForbiddenResponse(request, "Must provide 'emplid' query.") emplid = request.GET['emplid'] response = HttpResponse(content_type='application/json') data = find_person(emplid) json.dump(data, response, indent=1) return response
def can_access_report(request, report, **kwargs): rep = get_object_or_404(Report, slug=report) if not _has_access(request, rep): return ForbiddenResponse(request) if needs_privacy_signature(request): return privacy_redirect(request) request.report = rep # no need to re-fetch it later return view_func(request=request, report=report, **kwargs)
def show_components(request, course_slug, activity_slug): #if course staff if is_course_staff_by_slug(request, course_slug): return _show_components_staff(request, course_slug, activity_slug) #else course member elif is_course_member_by_slug(request, course_slug): return _show_components_student(request, course_slug, activity_slug) #else not found, return 403 else: return ForbiddenResponse(request);
def view_event_registrations(request, event_slug): event = get_object_or_404(OutreachEvent, slug=event_slug) if not _has_unit_role(request.user, event): return ForbiddenResponse(request) registrations = OutreachEventRegistration.objects.filter(event=event, hidden=False) return render(request, 'outreach/event_registrations.html', { 'event': event, 'registrations': registrations })
def view_event(request, event_slug): event = get_object_or_404(OutreachEvent, slug=event_slug) if not _has_unit_role(request.user, event): return ForbiddenResponse(request) register_url = request.build_absolute_uri( reverse('outreach:register', kwargs={'event_slug': event.slug})) return render(request, 'outreach/view_event.html', { 'event': event, 'register_url': register_url })
def note_search(request): if 'text-search' not in request.GET: return ForbiddenResponse(request, "must send search query") search = request.GET['text-search'] query = get_query(search, ('text',)) notes = AdvisorNote.objects.filter(query, unit__in=request.units) \ .select_related('student', 'advisor').order_by("-created_at")[:100] note_form = NoteSearchForm(prefix="text", initial={'search': search}) context = {'notes': notes, 'note_form': note_form} return render(request, 'advisornotes/note_search.html', context)
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 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
def edit_related(request, course_slug, case_slug): """ View function to edit related items: more difficult than the generic function above. """ course = get_object_or_404(CourseOffering, slug=course_slug) case = get_object_or_404(DisciplineCaseBase, slug=case_slug, offering__slug=course_slug) case = case.subclass() if not case.can_edit('related'): # once case is closed, don't allow editing return ForbiddenResponse(request)
def convert_content(request, course_slug, page_label=None): """ Convert between wikicreole and HTML (AJAX called in editor when switching editing modes) """ if request.method != 'POST': return ForbiddenResponse(request, 'POST only') if 'to' not in request.POST: return ForbiddenResponse(request, 'must send "to" language') if 'data' not in request.POST: return ForbiddenResponse(request, 'must sent source "data"') offering = get_object_or_404(CourseOffering, slug=course_slug) to = request.POST['to'] data = request.POST['data'] if to == 'html': # convert wikitext to HTML # temporarily change the current version to get the result (but don't save) if page_label: page = get_object_or_404(Page, offering=offering, label=page_label) pv = page.current_version() else: # create temporary Page for conversion during creation p = Page(offering=offering) pv = PageVersion(page=p) pv.wikitext = data pv.diff_from = None result = {'data': pv.html_contents()} return HttpResponse(json.dumps(result), content_type="application/json") else: # convert HTML to wikitext converter = HTMLWiki([]) try: wiki = converter.from_html(data) except converter.ParseError: wiki = '' result = {'data': wiki} return HttpResponse(json.dumps(result), content_type="application/json")
def delete_asset(request, asset_id): asset = get_object_or_404(Asset, pk=asset_id) if not _has_unit_role(request.user, asset): return ForbiddenResponse(request) if request.method == 'POST': asset.delete() messages.success(request, 'Hid asset %s' % asset) l = LogEntry(userid=request.user.username, description="Deleted asset: %s" % asset, related_object=asset) l.save() return HttpResponseRedirect(reverse('inventory:inventory_index'))
def delete_event(request, event_id): event = get_object_or_404(OutreachEvent, pk=event_id) if not _has_unit_role(request.user, event): return ForbiddenResponse(request) if request.method == 'POST': event.delete() messages.success(request, 'Hid event %s' % event) l = LogEntry(userid=request.user.username, description="Deleted event: %s" % event, related_object=event) l.save() return HttpResponseRedirect(reverse('outreach:outreach_index'))
def edit_case_info(request, course_slug, case_slug, field): """ View function for all of the "edit this aspect of the case" steps. Uses the STEP_* dictionaries to get relevant strings/classes. """ course = get_object_or_404(CourseOffering, slug=course_slug) case = get_object_or_404(DisciplineCaseBase, slug=case_slug, offering__slug=course_slug) case = case.subclass() # permisson checks roles = request.session['discipline-'+course_slug] if not case.can_edit(field): # once instructor finished, don't allow editing those fields return ForbiddenResponse(request, "letter has been sent: cannot edit this field")
def edit_attach(request, course_slug, case_slug): """ Front page of the file attachments interface """ course = get_object_or_404(CourseOffering, slug=course_slug) case = get_object_or_404(DisciplineCaseBase, slug=case_slug, offering__slug=course_slug) case = case.subclass() attach_pub = CaseAttachment.objects.filter(case=case, public=True) attach_pri = CaseAttachment.objects.filter(case=case, public=False) if not case.can_edit('attach'): # once case is closed, don't allow editing return ForbiddenResponse(request)