{"action":"created","comment":{"url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/comments/32920408","id":32920408,"diff_hunk":"@@ -0,0 +1,174 @@\n+from django.conf import settings\n+from django.shortcuts import get_object_or_404, render, redirect\n+from django.db.models import Prefetch\n+from django.contrib import messages\n+from django.utils.translation import ugettext as _\n+from django.http import HttpResponseBadRequest, HttpResponseForbidden\n+from django.contrib.auth.decorators import login_required\n+\n+import os\n+from sendfile import sendfile\n+\n+from evap.evaluation.auth import grade_publisher_required, grade_downloader_required\n+from evap.evaluation.models import Semester, Contribution, Course\n+from evap.grades.models import GradeDocument\n+from evap.grades.forms import GradeDocumentForm\n+from evap.evaluation.tools import send_publish_notifications\n+\n+def get_graded_courses_with_prefetched_data(semester):\n+    courses = semester.course_set.filter(is_graded=True).exclude(state='new').prefetch_related(\n+        Prefetch(\"contributions\", queryset=Contribution.objects.filter(responsible=True).select_related(\"contributor\"), to_attr=\"responsible_contribution\"),\n+        \"degrees\")\n+\n+    course_data = []\n+    for course in courses:\n+        course.responsible_contributor = course.responsible_contribution[0].contributor\n+        course_data.append((course, GradeDocument.objects.filter(course=course, type=GradeDocument.FINAL_GRADES).exists()))\n+\n+    return course_data\n+\n+\n+@grade_publisher_required\n+def index(request):\n+    template_data = dict(\n+        semesters=Semester.objects.all()\n+    )\n+    return render(request, \"grades_index.html\", template_data)\n+\n+\n+@grade_publisher_required\n+def semester_view(request, semester_id):\n+    semester = get_object_or_404(Semester, id=semester_id)\n+\n+    courses = get_graded_courses_with_prefetched_data(semester)\n+\n+    template_data = dict(\n+        semester=semester,\n+        courses=courses,\n+        disable_if_archived=\"disabled=disabled\" if semester.is_archived else \"\",\n+        disable_breadcrumb_semester=True,\n+    )\n+    return render(request, \"grades_semester_view.html\", template_data)\n+\n+\n+@grade_publisher_required\n+def course_view(request, semester_id, course_id):\n+    semester = get_object_or_404(Semester, id=semester_id)\n+    course = get_object_or_404(Course, id=course_id)\n+\n+    grade_documents = GradeDocument.objects.filter(course=course)\n+\n+    template_data = dict(\n+        semester=semester,\n+        course=course,\n+        grade_documents=grade_documents,\n+        disable_if_archived=\"disabled=disabled\" if semester.is_archived else \"\",\n+        disable_breadcrumb_course=True,\n+    )\n+    return render(request, \"grades_course_view.html\", template_data)\n+\n+\n+def helper_grade_upload(request, course, final_grades=False, instance=None):\n+    created = True\n+    if request.method == \"POST\":\n+        if instance:\n+            created = False\n+        form = GradeDocumentForm(request.POST, request.FILES, instance=instance)\n+        if form.is_valid():\n+            instance = form.save(commit=False)\n+            instance.last_modified_user = request.user\n+            instance.course = course\n+            if final_grades:\n+                instance.type = GradeDocument.FINAL_GRADES\n+            instance.save()\n+            return True, created, form\n+    else:\n+        form = GradeDocumentForm(instance=instance, final_grades=final_grades)\n+    return False, created, form\n+\n+\n+@grade_publisher_required\n+def upload_grades(request, semester_id, course_id):\n+    semester = get_object_or_404(Semester, id=semester_id)\n+    course = get_object_or_404(Course, id=course_id)\n+\n+    final_grades = request.GET.get('final', 'false') # default: preliminary grades\n+    final_grades = {'true': True, 'false': False}.get(final_grades.lower()) # convert parameter to boolean\n+\n+    success, created, form = helper_grade_upload(request, course, final_grades=final_grades)\n+\n+    if success:\n+        if created:\n+            if not final_grades or course.state in ['evaluated', 'published']:\n+                send_publish_notifications(grade_document_courses=[course])\n+            elif course.state == 'reviewed':\n+                course.publish()\n+                course.save()\n+                send_publish_notifications(grade_document_courses=[course], evaluation_results_courses=[course])\n+\n+        messages.success(request, _(\"Successfully uploaded grades.\"))\n+        return redirect('grades:course_view', semester.id, course.id)\n+    else:\n+        template_data = dict(\n+            semester=semester,\n+            course=course,\n+            form=form,\n+            final_grades=final_grades,\n+        )\n+        return render(request, \"grades_upload_form.html\", template_data)\n+\n+\n+@grade_downloader_required\n+def download_grades(request, grade_document_id):\n+    if not request.method == \"GET\":\n+        return HttpResponseBadRequest()\n+\n+    grade_document = get_object_or_404(GradeDocument, id=grade_document_id)\n+\n+    # final grades can only be downloaded when the evaluation is finished\n+    if grade_document.type == GradeDocument.FINAL_GRADES and grade_document.course.state not in ['evaluated', 'reviewed', 'published']:\n+        return HttpResponseForbidden()\n+\n+    filename = os.path.join(settings.MEDIA_ROOT, grade_document.file.name)","path":"evap/grades/views.py","position":132,"original_position":132,"commit_id":"339bf2dd83501e9606188d616d66b9cebcbfacf6","original_commit_id":"339bf2dd83501e9606188d616d66b9cebcbfacf6","user":{"login":"karyon","id":1891915,"avatar_url":"https://avatars.githubusercontent.com/u/1891915?v=3","gravatar_id":"","url":"https://api.github.com/users/karyon","html_url":"https://github.com/karyon","followers_url":"https://api.github.com/users/karyon/followers","following_url":"https://api.github.com/users/karyon/following{/other_user}","gists_url":"https://api.github.com/users/karyon/gists{/gist_id}","starred_url":"https://api.github.com/users/karyon/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/karyon/subscriptions","organizations_url":"https://api.github.com/users/karyon/orgs","repos_url":"https://api.github.com/users/karyon/repos","events_url":"https://api.github.com/users/karyon/events{/privacy}","received_events_url":"https://api.github.com/users/karyon/received_events","type":"User","site_admin":false},"body":"please see whether grade_document.file.path works instead of the manual joining.","created_at":"2015-06-22T10:17:01Z","updated_at":"2015-06-22T10:17:01Z","html_url":"https://github.com/fsr-itse/EvaP/pull/603#discussion_r32920408","pull_request_url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603","_links":{"self":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/comments/32920408"},"html":{"href":"https://github.com/fsr-itse/EvaP/pull/603#discussion_r32920408"},"pull_request":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603"}}},"pull_request":{"url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603","id":38189724,"html_url":"https://github.com/fsr-itse/EvaP/pull/603","diff_url":"https://github.com/fsr-itse/EvaP/pull/603.diff","patch_url":"https://github.com/fsr-itse/EvaP/pull/603.patch","issue_url":"https://api.github.com/repos/fsr-itse/EvaP/issues/603","number":603,"state":"open","locked":false,"title":"Grade publishing","user":{"login":"janno42","id":1781719,"avatar_url":"https://avatars.githubusercontent.com/u/1781719?v=3","gravatar_id":"","url":"https://api.github.com/users/janno42","html_url":"https://github.com/janno42","followers_url":"https://api.github.com/users/janno42/followers","following_url":"https://api.github.com/users/janno42/following{/other_user}","gists_url":"https://api.github.com/users/janno42/gists{/gist_id}","starred_url":"https://api.github.com/users/janno42/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janno42/subscriptions","organizations_url":"https://api.github.com/users/janno42/orgs","repos_url":"https://api.github.com/users/janno42/repos","events_url":"https://api.github.com/users/janno42/events{/privacy}","received_events_url":"https://api.github.com/users/janno42/received_events","type":"User","site_admin":false},"body":"fix #352\r\n\r\nThis adds functionality for uploading and downloading grade documents for courses, along with changes for the automated publishing of courses and updates to the publish notification emails.\r\n\r\nTests are still missing and will soon be implemented, but this pull request should already be complete regarding the functionality and allows to review the changes.","created_at":"2015-06-21T20:03:12Z","updated_at":"2015-06-22T10:17:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6acb415deafdb86d092c63b5d27065b476f879eb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603/commits","review_comments_url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603/comments","review_comment_url":"https://api.github.com/repos/fsr-itse/EvaP/pulls/comments{/number}","comments_url":"https://api.github.com/repos/fsr-itse/EvaP/issues/603/comments","statuses_url":"https://api.github.com/repos/fsr-itse/EvaP/statuses/339bf2dd83501e9606188d616d66b9cebcbfacf6","head":{"label":"janno42:grade_publishing","ref":"grade_publishing","sha":"339bf2dd83501e9606188d616d66b9cebcbfacf6","user":{"login":"janno42","id":1781719,"avatar_url":"https://avatars.githubusercontent.com/u/1781719?v=3","gravatar_id":"","url":"https://api.github.com/users/janno42","html_url":"https://github.com/janno42","followers_url":"https://api.github.com/users/janno42/followers","following_url":"https://api.github.com/users/janno42/following{/other_user}","gists_url":"https://api.github.com/users/janno42/gists{/gist_id}","starred_url":"https://api.github.com/users/janno42/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janno42/subscriptions","organizations_url":"https://api.github.com/users/janno42/orgs","repos_url":"https://api.github.com/users/janno42/repos","events_url":"https://api.github.com/users/janno42/events{/privacy}","received_events_url":"https://api.github.com/users/janno42/received_events","type":"User","site_admin":false},"repo":{"id":20739629,"name":"EvaP","full_name":"janno42/EvaP","owner":{"login":"janno42","id":1781719,"avatar_url":"https://avatars.githubusercontent.com/u/1781719?v=3","gravatar_id":"","url":"https://api.github.com/users/janno42","html_url":"https://github.com/janno42","followers_url":"https://api.github.com/users/janno42/followers","following_url":"https://api.github.com/users/janno42/following{/other_user}","gists_url":"https://api.github.com/users/janno42/gists{/gist_id}","starred_url":"https://api.github.com/users/janno42/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/janno42/subscriptions","organizations_url":"https://api.github.com/users/janno42/orgs","repos_url":"https://api.github.com/users/janno42/repos","events_url":"https://api.github.com/users/janno42/events{/privacy}","received_events_url":"https://api.github.com/users/janno42/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/janno42/EvaP","description":"a university course evaluation system written in Python using Django","fork":true,"url":"https://api.github.com/repos/janno42/EvaP","forks_url":"https://api.github.com/repos/janno42/EvaP/forks","keys_url":"https://api.github.com/repos/janno42/EvaP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/janno42/EvaP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/janno42/EvaP/teams","hooks_url":"https://api.github.com/repos/janno42/EvaP/hooks","issue_events_url":"https://api.github.com/repos/janno42/EvaP/issues/events{/number}","events_url":"https://api.github.com/repos/janno42/EvaP/events","assignees_url":"https://api.github.com/repos/janno42/EvaP/assignees{/user}","branches_url":"https://api.github.com/repos/janno42/EvaP/branches{/branch}","tags_url":"https://api.github.com/repos/janno42/EvaP/tags","blobs_url":"https://api.github.com/repos/janno42/EvaP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/janno42/EvaP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/janno42/EvaP/git/refs{/sha}","trees_url":"https://api.github.com/repos/janno42/EvaP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/janno42/EvaP/statuses/{sha}","languages_url":"https://api.github.com/repos/janno42/EvaP/languages","stargazers_url":"https://api.github.com/repos/janno42/EvaP/stargazers","contributors_url":"https://api.github.com/repos/janno42/EvaP/contributors","subscribers_url":"https://api.github.com/repos/janno42/EvaP/subscribers","subscription_url":"https://api.github.com/repos/janno42/EvaP/subscription","commits_url":"https://api.github.com/repos/janno42/EvaP/commits{/sha}","git_commits_url":"https://api.github.com/repos/janno42/EvaP/git/commits{/sha}","comments_url":"https://api.github.com/repos/janno42/EvaP/comments{/number}","issue_comment_url":"https://api.github.com/repos/janno42/EvaP/issues/comments{/number}","contents_url":"https://api.github.com/repos/janno42/EvaP/contents/{+path}","compare_url":"https://api.github.com/repos/janno42/EvaP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/janno42/EvaP/merges","archive_url":"https://api.github.com/repos/janno42/EvaP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/janno42/EvaP/downloads","issues_url":"https://api.github.com/repos/janno42/EvaP/issues{/number}","pulls_url":"https://api.github.com/repos/janno42/EvaP/pulls{/number}","milestones_url":"https://api.github.com/repos/janno42/EvaP/milestones{/number}","notifications_url":"https://api.github.com/repos/janno42/EvaP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/janno42/EvaP/labels{/name}","releases_url":"https://api.github.com/repos/janno42/EvaP/releases{/id}","created_at":"2014-06-11T20:15:30Z","updated_at":"2015-01-06T21:08:30Z","pushed_at":"2015-06-21T20:07:33Z","git_url":"git://github.com/janno42/EvaP.git","ssh_url":"git@github.com:janno42/EvaP.git","clone_url":"https://github.com/janno42/EvaP.git","svn_url":"https://github.com/janno42/EvaP","homepage":"","size":10145,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"fsr-itse:master","ref":"master","sha":"9248aac75bc1b3c5a22b2ab4a0f6d298eb70e68b","user":{"login":"fsr-itse","id":1298457,"avatar_url":"https://avatars.githubusercontent.com/u/1298457?v=3","gravatar_id":"","url":"https://api.github.com/users/fsr-itse","html_url":"https://github.com/fsr-itse","followers_url":"https://api.github.com/users/fsr-itse/followers","following_url":"https://api.github.com/users/fsr-itse/following{/other_user}","gists_url":"https://api.github.com/users/fsr-itse/gists{/gist_id}","starred_url":"https://api.github.com/users/fsr-itse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fsr-itse/subscriptions","organizations_url":"https://api.github.com/users/fsr-itse/orgs","repos_url":"https://api.github.com/users/fsr-itse/repos","events_url":"https://api.github.com/users/fsr-itse/events{/privacy}","received_events_url":"https://api.github.com/users/fsr-itse/received_events","type":"Organization","site_admin":false},"repo":{"id":3110378,"name":"EvaP","full_name":"fsr-itse/EvaP","owner":{"login":"fsr-itse","id":1298457,"avatar_url":"https://avatars.githubusercontent.com/u/1298457?v=3","gravatar_id":"","url":"https://api.github.com/users/fsr-itse","html_url":"https://github.com/fsr-itse","followers_url":"https://api.github.com/users/fsr-itse/followers","following_url":"https://api.github.com/users/fsr-itse/following{/other_user}","gists_url":"https://api.github.com/users/fsr-itse/gists{/gist_id}","starred_url":"https://api.github.com/users/fsr-itse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fsr-itse/subscriptions","organizations_url":"https://api.github.com/users/fsr-itse/orgs","repos_url":"https://api.github.com/users/fsr-itse/repos","events_url":"https://api.github.com/users/fsr-itse/events{/privacy}","received_events_url":"https://api.github.com/users/fsr-itse/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/fsr-itse/EvaP","description":"a university course evaluation system written in Python using Django","fork":false,"url":"https://api.github.com/repos/fsr-itse/EvaP","forks_url":"https://api.github.com/repos/fsr-itse/EvaP/forks","keys_url":"https://api.github.com/repos/fsr-itse/EvaP/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fsr-itse/EvaP/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fsr-itse/EvaP/teams","hooks_url":"https://api.github.com/repos/fsr-itse/EvaP/hooks","issue_events_url":"https://api.github.com/repos/fsr-itse/EvaP/issues/events{/number}","events_url":"https://api.github.com/repos/fsr-itse/EvaP/events","assignees_url":"https://api.github.com/repos/fsr-itse/EvaP/assignees{/user}","branches_url":"https://api.github.com/repos/fsr-itse/EvaP/branches{/branch}","tags_url":"https://api.github.com/repos/fsr-itse/EvaP/tags","blobs_url":"https://api.github.com/repos/fsr-itse/EvaP/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fsr-itse/EvaP/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fsr-itse/EvaP/git/refs{/sha}","trees_url":"https://api.github.com/repos/fsr-itse/EvaP/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fsr-itse/EvaP/statuses/{sha}","languages_url":"https://api.github.com/repos/fsr-itse/EvaP/languages","stargazers_url":"https://api.github.com/repos/fsr-itse/EvaP/stargazers","contributors_url":"https://api.github.com/repos/fsr-itse/EvaP/contributors","subscribers_url":"https://api.github.com/repos/fsr-itse/EvaP/subscribers","subscription_url":"https://api.github.com/repos/fsr-itse/EvaP/subscription","commits_url":"https://api.github.com/repos/fsr-itse/EvaP/commits{/sha}","git_commits_url":"https://api.github.com/repos/fsr-itse/EvaP/git/commits{/sha}","comments_url":"https://api.github.com/repos/fsr-itse/EvaP/comments{/number}","issue_comment_url":"https://api.github.com/repos/fsr-itse/EvaP/issues/comments{/number}","contents_url":"https://api.github.com/repos/fsr-itse/EvaP/contents/{+path}","compare_url":"https://api.github.com/repos/fsr-itse/EvaP/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fsr-itse/EvaP/merges","archive_url":"https://api.github.com/repos/fsr-itse/EvaP/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fsr-itse/EvaP/downloads","issues_url":"https://api.github.com/repos/fsr-itse/EvaP/issues{/number}","pulls_url":"https://api.github.com/repos/fsr-itse/EvaP/pulls{/number}","milestones_url":"https://api.github.com/repos/fsr-itse/EvaP/milestones{/number}","notifications_url":"https://api.github.com/repos/fsr-itse/EvaP/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fsr-itse/EvaP/labels{/name}","releases_url":"https://api.github.com/repos/fsr-itse/EvaP/releases{/id}","created_at":"2012-01-05T13:56:45Z","updated_at":"2015-06-10T14:51:45Z","pushed_at":"2015-06-21T20:07:33Z","git_url":"git://github.com/fsr-itse/EvaP.git","ssh_url":"git@github.com:fsr-itse/EvaP.git","clone_url":"https://github.com/fsr-itse/EvaP.git","svn_url":"https://github.com/fsr-itse/EvaP","homepage":"","size":74343,"stargazers_count":22,"watchers_count":22,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":42,"mirror_url":null,"open_issues_count":48,"forks":42,"open_issues":48,"watchers":22,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603"},"html":{"href":"https://github.com/fsr-itse/EvaP/pull/603"},"issue":{"href":"https://api.github.com/repos/fsr-itse/EvaP/issues/603"},"comments":{"href":"https://api.github.com/repos/fsr-itse/EvaP/issues/603/comments"},"review_comments":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603/comments"},"review_comment":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/fsr-itse/EvaP/pulls/603/commits"},"statuses":{"href":"https://api.github.com/repos/fsr-itse/EvaP/statuses/339bf2dd83501e9606188d616d66b9cebcbfacf6"}}}}