summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrabhu Ramachandran2018-03-22 18:36:05 +0530
committerGitHub2018-03-22 18:36:05 +0530
commitd4f49e8a93ed2ce2e63bd67fa77aa75741c2d6c5 (patch)
tree8a767d224ac6a1688cd3eb301b2ccac40413fc7e
parent734b221a98745ccb6218f43923731323a265966a (diff)
parentc49550b6793c0481fae08f04c794218513894cc4 (diff)
downloadonline_test-d4f49e8a93ed2ce2e63bd67fa77aa75741c2d6c5.tar.gz
online_test-d4f49e8a93ed2ce2e63bd67fa77aa75741c2d6c5.tar.bz2
online_test-d4f49e8a93ed2ce2e63bd67fa77aa75741c2d6c5.zip
Merge pull request #438 from prathamesh920/Grades
Grades
-rw-r--r--.travis.yml1
-rw-r--r--grades/__init__.py0
-rw-r--r--grades/admin.py9
-rw-r--r--grades/apps.py5
-rw-r--r--grades/forms.py8
-rw-r--r--grades/migrations/0001_initial.py43
-rw-r--r--grades/migrations/__init__.py0
-rw-r--r--grades/migrations/default_grading_system.py41
-rw-r--r--grades/models.py46
-rw-r--r--grades/templates/add_grades.html35
-rw-r--r--grades/templates/grading_systems.html74
-rw-r--r--grades/tests/__init__.py0
-rw-r--r--grades/tests/test_models.py28
-rw-r--r--grades/tests/test_views.py105
-rw-r--r--grades/urls.py10
-rw-r--r--grades/views.py45
-rw-r--r--online_test/settings.py1
-rw-r--r--online_test/urls.py1
-rw-r--r--yaksh/forms.py2
-rw-r--r--yaksh/models.py54
-rw-r--r--yaksh/templates/manage.html2
-rw-r--r--yaksh/templates/yaksh/course_detail.html6
-rw-r--r--yaksh/templates/yaksh/course_modules.html1
-rw-r--r--yaksh/templates/yaksh/courses.html4
-rw-r--r--yaksh/templatetags/custom_filters.py7
-rw-r--r--yaksh/test_models.py98
-rw-r--r--yaksh/views.py23
27 files changed, 643 insertions, 6 deletions
diff --git a/.travis.yml b/.travis.yml
index b1a8402..59eaa66 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,6 +21,7 @@ install:
script:
- coverage erase
- coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh
+ - coverage run -p manage.py test -v 2 --settings online_test.test_settings grades
- coverage run -p manage.py test -v 2 --settings online_test.test_settings yaksh.live_server_tests.load_test
after_success:
diff --git a/grades/__init__.py b/grades/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/grades/__init__.py
diff --git a/grades/admin.py b/grades/admin.py
new file mode 100644
index 0000000..548791e
--- /dev/null
+++ b/grades/admin.py
@@ -0,0 +1,9 @@
+from django.contrib import admin
+from grades.models import GradingSystem, GradeRange
+
+
+class GradingSystemAdmin(admin.ModelAdmin):
+ readonly_fields = ('creator',)
+
+admin.site.register(GradingSystem, GradingSystemAdmin)
+admin.site.register(GradeRange)
diff --git a/grades/apps.py b/grades/apps.py
new file mode 100644
index 0000000..6d0985e
--- /dev/null
+++ b/grades/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class GradesConfig(AppConfig):
+ name = 'grades'
diff --git a/grades/forms.py b/grades/forms.py
new file mode 100644
index 0000000..130659d
--- /dev/null
+++ b/grades/forms.py
@@ -0,0 +1,8 @@
+from grades.models import GradingSystem
+from django import forms
+
+
+class GradingSystemForm(forms.ModelForm):
+ class Meta:
+ model = GradingSystem
+ fields = ['name', 'description']
diff --git a/grades/migrations/0001_initial.py b/grades/migrations/0001_initial.py
new file mode 100644
index 0000000..04a3006
--- /dev/null
+++ b/grades/migrations/0001_initial.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.5 on 2018-02-12 11:12
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='GradeRange',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('lower_limit', models.FloatField()),
+ ('upper_limit', models.FloatField()),
+ ('grade', models.CharField(max_length=10)),
+ ('description', models.CharField(blank=True, max_length=127, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='GradingSystem',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=255, unique=True)),
+ ('description', models.TextField(default='About the grading system!')),
+ ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='graderange',
+ name='system',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='grades.GradingSystem'),
+ ),
+ ]
diff --git a/grades/migrations/__init__.py b/grades/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/grades/migrations/__init__.py
diff --git a/grades/migrations/default_grading_system.py b/grades/migrations/default_grading_system.py
new file mode 100644
index 0000000..85390d6
--- /dev/null
+++ b/grades/migrations/default_grading_system.py
@@ -0,0 +1,41 @@
+from django.db import migrations
+
+
+def create_default_system(apps, schema_editor):
+ GradingSystem = apps.get_model('grades', 'GradingSystem')
+ GradeRange = apps.get_model('grades', 'GradeRange')
+ db = schema_editor.connection.alias
+
+ default_system = GradingSystem.objects.using(db).create(name='default')
+
+ graderanges_objects = [
+ GradeRange(system=default_system, lower_limit=0, upper_limit=40,
+ grade='F', description='Fail'),
+ GradeRange(system=default_system, lower_limit=40, upper_limit=55,
+ grade='P', description='Pass'),
+ GradeRange(system=default_system, lower_limit=55, upper_limit=60,
+ grade='C', description='Average'),
+ GradeRange(system=default_system, lower_limit=60, upper_limit=75,
+ grade='B', description='Satisfactory'),
+ GradeRange(system=default_system, lower_limit=75, upper_limit=90,
+ grade='A', description='Good'),
+ GradeRange(system=default_system, lower_limit=90, upper_limit=101,
+ grade='A+', description='Excellent')
+ ]
+ GradeRange.objects.using(db).bulk_create(graderanges_objects)
+
+
+def delete_default_system(apps, schema_editor):
+ GradingSystem = apps.get_model('grades', 'GradingSystem')
+ GradeRange = apps.get_model('grades', 'GradeRange')
+ db = schema_editor.connection.alias
+
+ default_system = GradingSystem.objects.using(db).get(creator=None)
+ GradeRange.object.using(db).filter(system=default_system).delete()
+ default_system.delete()
+
+
+class Migration(migrations.Migration):
+ dependencies = [('grades', '0001_initial'), ]
+ operations = [migrations.RunPython(create_default_system,
+ delete_default_system), ]
diff --git a/grades/models.py b/grades/models.py
new file mode 100644
index 0000000..fcea510
--- /dev/null
+++ b/grades/models.py
@@ -0,0 +1,46 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+
+class GradingSystem(models.Model):
+ name = models.CharField(max_length=255, unique=True)
+ description = models.TextField(default='About the grading system!')
+ creator = models.ForeignKey(User, null=True, blank=True)
+
+ def get_grade(self, marks):
+ ranges = self.graderange_set.all()
+ lower_limits = ranges.values_list('lower_limit', flat=True)
+ upper_limits = ranges.values_list('upper_limit', flat=True)
+ lower_limit = self._get_lower_limit(marks, lower_limits)
+ upper_limit = self._get_upper_limit(marks, upper_limits)
+ grade_range = ranges.filter(lower_limit=lower_limit,
+ upper_limit=upper_limit).first()
+ if grade_range:
+ return grade_range.grade
+
+ def _get_upper_limit(self, marks, upper_limits):
+ greater_than = [upper_limit for upper_limit in upper_limits
+ if upper_limit > marks]
+ if greater_than:
+ return min(greater_than, key=lambda x: x-marks)
+
+ def _get_lower_limit(self, marks, lower_limits):
+ less_than = []
+ for lower_limit in lower_limits:
+ if lower_limit == marks:
+ return lower_limit
+ if lower_limit < marks:
+ less_than.append(lower_limit)
+ if less_than:
+ return max(less_than, key=lambda x: x-marks)
+
+ def __str__(self):
+ return self.name.title()
+
+
+class GradeRange(models.Model):
+ system = models.ForeignKey(GradingSystem)
+ lower_limit = models.FloatField()
+ upper_limit = models.FloatField()
+ grade = models.CharField(max_length=10)
+ description = models.CharField(max_length=127, null=True, blank=True)
diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html
new file mode 100644
index 0000000..a3f52da
--- /dev/null
+++ b/grades/templates/add_grades.html
@@ -0,0 +1,35 @@
+{% extends "manage.html" %}
+{% block main %}
+<html>
+<a href="{% url 'grades:grading_systems'%}" class="btn btn-danger"> Back to Grading Systems </a>
+<br><br>
+<p><b>Note: For grade range lower limit is inclusive and upper limit is exclusive</b></p>
+<br>
+{% if not system_id %}
+ <form action="{% url 'grades:add_grade' %}" method="POST">
+{% else %}
+ <form action="{% url 'grades:edit_grade' system_id %}" method="POST">
+{% endif %}
+ {% csrf_token %}
+ <table class="table">
+ {{ grade_form }}
+ </table>
+ {{ formset.management_form }}
+ <br>
+ <b><u>Grade Ranges</u></b>
+ <hr>
+ {% for form in formset %}
+ <div>
+ {{ form }}
+ </div>
+ <hr>
+ {% endfor %}
+ {% if not is_default %}
+ <input type="submit" id="add" name="add" value="Add" class="btn btn-info">
+ <input type="submit" id="save" name="save" value="Save" class="btn btn-success">
+ {% else %}
+ <p><b>Note: This is a default grading system. You cannot change this.</b></p>
+ {% endif %}
+</form>
+</html>
+{% endblock %}
diff --git a/grades/templates/grading_systems.html b/grades/templates/grading_systems.html
new file mode 100644
index 0000000..3a71ebf
--- /dev/null
+++ b/grades/templates/grading_systems.html
@@ -0,0 +1,74 @@
+{% extends "manage.html" %}
+{% block main %}
+<html>
+ <a href="{% url 'grades:add_grade' %}" class="btn btn-primary"> Add a Grading System </a>
+ <a href="{% url 'yaksh:courses' %}" class="btn btn-danger"> Back to Courses </a>
+ <br><br>
+ <b> Available Grading Systems: </b>
+ <table class="table">
+ <tr>
+ <th>Grading System</th>
+ <th>Grading Ranges</th>
+ </tr>
+ <tr>
+ <td>
+ <a href="{% url 'grades:edit_grade' default_grading_system.id %}">
+ {{ default_grading_system.name }}</a> (<b>Default Grading System</b>)
+ </td>
+ <td>
+ <table class="table">
+ <tr>
+ <th>Lower Limit</th>
+ <th>Upper Limit</th>
+ <th>Grade</th>
+ <th>Description</th>
+ </tr>
+ {% for range in default_grading_system.graderange_set.all %}
+ <tr>
+ <td>{{range.lower_limit}}</td>
+ <td>{{range.upper_limit}}</td>
+ <td>{{range.grade}}</td>
+ {% if range.description %}
+ <td>{{range.description}}</td>
+ {% else %}
+ <td>------</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ </td>
+ </tr>
+ {% if grading_systems %}
+ {% for system in grading_systems %}
+ <tr>
+ <td>
+ <a href="{% url 'grades:edit_grade' system.id %}">{{ system.name }}</a>
+ </td>
+ <td>
+ <table class="table">
+ <tr>
+ <th>Lower Limit</th>
+ <th>Upper Limit</th>
+ <th>Grade</th>
+ <th>Description</th>
+ </tr>
+ {% for range in system.graderange_set.all %}
+ <tr>
+ <td>{{range.lower_limit}}</td>
+ <td>{{range.upper_limit}}</td>
+ <td>{{range.grade}}</td>
+ {% if range.description %}
+ <td>{{range.description}}</td>
+ {% else %}
+ <td>------</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% endif %}
+</html>
+{% endblock %}
diff --git a/grades/tests/__init__.py b/grades/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/grades/tests/__init__.py
diff --git a/grades/tests/test_models.py b/grades/tests/test_models.py
new file mode 100644
index 0000000..f8d5c5c
--- /dev/null
+++ b/grades/tests/test_models.py
@@ -0,0 +1,28 @@
+from django.test import TestCase
+from grades.models import GradingSystem, GradeRange
+
+
+class GradingSystemTestCase(TestCase):
+ def setUp(self):
+ GradingSystem.objects.create(name='unusable')
+
+ def test_get_grade(self):
+ # Given
+ grading_system = GradingSystem.objects.get(name='default')
+ expected_grades = {0: 'F', 31: 'F', 49: 'P', 55: 'C', 60: 'B', 80: 'A',
+ 95: 'A+', 100: 'A+', 100.5: 'A+', 101: None,
+ 109: None}
+ for marks in expected_grades.keys():
+ # When
+ grade = grading_system.get_grade(marks)
+ # Then
+ self.assertEqual(expected_grades.get(marks), grade)
+
+ def test_grade_system_unusable(self):
+ # Given
+ # System with out ranges
+ grading_system = GradingSystem.objects.get(name='unusable')
+ # When
+ grade = grading_system.get_grade(29)
+ # Then
+ self.assertIsNone(grade)
diff --git a/grades/tests/test_views.py b/grades/tests/test_views.py
new file mode 100644
index 0000000..c944f03
--- /dev/null
+++ b/grades/tests/test_views.py
@@ -0,0 +1,105 @@
+from django.test import TestCase, Client
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from grades.models import GradingSystem
+
+
+def setUpModule():
+ user = User.objects.create_user(username='grades_user',
+ password='grades_user')
+
+
+def tearDownModule():
+ User.objects.all().delete()
+
+
+class GradeViewTest(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_grade_view(self):
+ # Given
+ # URL redirection due to no login credentials
+ status_code = 302
+ # When
+ response = self.client.get(reverse('grades:grading_systems'))
+ # Then
+ self.assertEqual(response.status_code, status_code)
+
+ # Given
+ # successful login and grading systems views
+ self.client.login(username='grades_user', password='grades_user')
+ status_code = 200
+ # When
+ response = self.client.get(reverse('grades:grading_systems'))
+ # Then
+ self.assertEqual(response.status_code, status_code)
+ self.assertTemplateUsed(response, 'grading_systems.html')
+
+
+class AddGradingSystemTest(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ def tearDown(self):
+ self.client.logout()
+
+ def test_add_grades_view(self):
+ # Given
+ status_code = 302
+ # When
+ response = self.client.get(reverse('grades:add_grade'))
+ # Then
+ self.assertEqual(response.status_code, status_code)
+
+ # Given
+ status_code = 200
+ self.client.login(username='grades_user', password='grades_user')
+ # When
+ response = self.client.get(reverse('grades:add_grade'))
+ # Then
+ self.assertEqual(response.status_code, status_code)
+ self.assertTemplateUsed(response, 'add_grades.html')
+
+ def test_add_grades_post(self):
+ # Given
+ self.client.login(username='grades_user', password='grades_user')
+ data = {'name': ['new_sys'], 'description': ['About grading system!'],
+ 'graderange_set-MIN_NUM_FORMS': ['0'],
+ 'graderange_set-TOTAL_FORMS': ['0'],
+ 'graderange_set-MAX_NUM_FORMS': ['1000'], 'add': ['Add'],
+ 'graderange_set-INITIAL_FORMS': ['0']}
+ # When
+ response = self.client.post(reverse('grades:add_grade'), data)
+ # Then
+ grading_systems = GradingSystem.objects.filter(name='new_sys')
+ self.assertEqual(len(grading_systems), 1)
+
+ # Given
+ grading_system = grading_systems.first()
+ # When
+ ranges = grading_system.graderange_set.all()
+ # Then
+ self.assertEqual(len(ranges), 0)
+
+ # Given
+ data = {'graderange_set-0-upper_limit': ['40'],
+ 'graderange_set-0-description': ['Fail'],
+ 'graderange_set-0-lower_limit': ['0'],
+ 'graderange_set-0-system': [''], 'name': ['new_sys'],
+ 'graderange_set-MIN_NUM_FORMS': ['0'],
+ 'graderange_set-TOTAL_FORMS': ['1'],
+ 'graderange_set-MAX_NUM_FORMS': ['1000'],
+ 'graderange_set-0-id': [''],
+ 'description': ['About the grading system!'],
+ 'graderange_set-0-grade': ['F'],
+ 'graderange_set-INITIAL_FORMS': ['0'], 'save': ['Save']}
+ # When
+ response = self.client.post(reverse('grades:edit_grade',
+ kwargs={'system_id': 2}), data)
+ # Then
+ ranges = grading_system.graderange_set.all()
+ self.assertEqual(len(ranges), 1)
diff --git a/grades/urls.py b/grades/urls.py
new file mode 100644
index 0000000..49276ba9
--- /dev/null
+++ b/grades/urls.py
@@ -0,0 +1,10 @@
+from django.conf.urls import url, patterns
+from grades import views
+
+urlpatterns = [
+ url(r'^$', views.grading_systems, name="grading_systems_home"),
+ url(r'^grading_systems/$', views.grading_systems, name="grading_systems"),
+ url(r'^add_grade/$', views.add_grading_system, name="add_grade"),
+ url(r'^add_grade/(?P<system_id>\d+)/$', views.add_grading_system,
+ name="edit_grade"),
+]
diff --git a/grades/views.py b/grades/views.py
new file mode 100644
index 0000000..10f9999
--- /dev/null
+++ b/grades/views.py
@@ -0,0 +1,45 @@
+from django.shortcuts import render
+from django.contrib.auth.decorators import login_required
+from django.forms import inlineformset_factory
+from grades.forms import GradingSystemForm
+from grades.models import GradingSystem, GradeRange
+
+
+@login_required
+def grading_systems(request):
+ user = request.user
+ default_grading_system = GradingSystem.objects.get(name='default')
+ grading_systems = GradingSystem.objects.filter(creator=user)
+ return render(request, 'grading_systems.html', {'default_grading_system':
+ default_grading_system, 'grading_systems': grading_systems})
+
+
+@login_required
+def add_grading_system(request, system_id=None):
+ user = request.user
+ grading_system = None
+ if system_id is not None:
+ grading_system = GradingSystem.objects.get(id=system_id)
+ GradeRangeFormSet = inlineformset_factory(GradingSystem, GradeRange,
+ fields='__all__', extra=0)
+ grade_form = GradingSystemForm(instance=grading_system)
+ is_default = grading_system is not None and grading_system.name == 'default'
+
+ if request.method == 'POST':
+ formset = GradeRangeFormSet(request.POST, instance=grading_system)
+ grade_form = GradingSystemForm(request.POST, instance=grading_system)
+ if grade_form.is_valid():
+ system = grade_form.save(commit=False)
+ system.creator = user
+ system.save()
+ system_id = system.id
+ if formset.is_valid():
+ formset.save()
+ if 'add' in request.POST:
+ GradeRangeFormSet = inlineformset_factory(GradingSystem, GradeRange,
+ fields='__all__', extra=1)
+ formset = GradeRangeFormSet(instance=grading_system)
+
+ return render(request, 'add_grades.html', {'formset': formset,
+ 'grade_form': grade_form, "system_id": system_id,
+ 'is_default': is_default})
diff --git a/online_test/settings.py b/online_test/settings.py
index c55a056..797d982 100644
--- a/online_test/settings.py
+++ b/online_test/settings.py
@@ -44,6 +44,7 @@ INSTALLED_APPS = (
'yaksh',
'taggit',
'social.apps.django_app.default',
+ 'grades',
)
MIDDLEWARE_CLASSES = (
diff --git a/online_test/urls.py b/online_test/urls.py
index ce0de41..3e62fd6 100644
--- a/online_test/urls.py
+++ b/online_test/urls.py
@@ -13,5 +13,6 @@ urlpatterns = [
url(r'^exam/', include('yaksh.urls', namespace='yaksh', app_name='yaksh')),
url(r'^exam/reset/', include('yaksh.urls_password_reset')),
url(r'^', include('social.apps.django_app.urls', namespace='social')),
+ url(r'^grades/', include('grades.urls', namespace='grades', app_name='grades')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/yaksh/forms.py b/yaksh/forms.py
index 9a44cfa..41c9176 100644
--- a/yaksh/forms.py
+++ b/yaksh/forms.py
@@ -282,7 +282,7 @@ class CourseForm(forms.ModelForm):
class Meta:
model = Course
fields = ['name', 'enrollment', 'active', 'code', 'instructions',
- 'start_enroll_time', 'end_enroll_time']
+ 'start_enroll_time', 'end_enroll_time', 'grading_system']
class ProfileForm(forms.ModelForm):
diff --git a/yaksh/models.py b/yaksh/models.py
index 826c0e8..d011bb0 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -35,7 +35,7 @@ from yaksh.code_server import (
from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME
from django.conf import settings
from django.forms.models import model_to_dict
-
+from grades.models import GradingSystem
languages = (
("python", "Python"),
@@ -331,7 +331,8 @@ class Quiz(models.Model):
allow_skip = models.BooleanField("Allow students to skip questions",
default=True)
- weightage = models.FloatField(default=1.0)
+ weightage = models.FloatField(help_text='Will be considered as percentage',
+ default=100)
is_exercise = models.BooleanField(default=False)
@@ -605,6 +606,8 @@ class Course(models.Model):
null=True
)
+ grading_system = models.ForeignKey(GradingSystem, null=True, blank=True)
+
objects = CourseManager()
def _create_duplicate_instance(self, creator, course_name=None):
@@ -775,6 +778,14 @@ class Course(models.Model):
percent = round((count / len(modules)))
return percent
+ def get_grade(self, user):
+ course_status = CourseStatus.objects.filter(course=self, user=user)
+ if course_status.exists():
+ grade = course_status.first().get_grade()
+ else:
+ grade = "NA"
+ return grade
+
def days_before_start(self):
""" Get the days remaining for the start of the course """
if timezone.now() < self.start_enroll_time:
@@ -796,7 +807,44 @@ class CourseStatus(models.Model):
course = models.ForeignKey(Course)
user = models.ForeignKey(User)
grade = models.CharField(max_length=255, null=True, blank=True)
- total_marks = models.FloatField(default=0.0)
+ percentage = models.FloatField(default=0.0)
+
+ def get_grade(self):
+ return self.grade
+
+ def set_grade(self):
+ if self.is_course_complete():
+ self.calculate_percentage()
+ if self.course.grading_system is None:
+ grading_system = GradingSystem.objects.get(name='default')
+ else:
+ grading_system = self.course.grading_system
+ grade = grading_system.get_grade(self.percentage)
+ self.grade = grade
+ self.save()
+
+ def calculate_percentage(self):
+ if self.is_course_complete():
+ quizzes = self.course.get_quizzes()
+ total_weightage = 0
+ sum = 0
+ for quiz in quizzes:
+ total_weightage += quiz.weightage
+ marks = AnswerPaper.objects.get_user_best_of_attempts_marks(
+ quiz, self.user.id, self.course.id)
+ out_of = quiz.questionpaper_set.first().total_marks
+ sum += (marks/out_of)*quiz.weightage
+ self.percentage = (sum/total_weightage)*100
+ self.save()
+
+ def is_course_complete(self):
+ modules = self.course.get_learning_modules()
+ complete = False
+ for module in modules:
+ complete = module.get_status(self.user, self.course) == 'completed'
+ if not complete:
+ break
+ return complete
###############################################################################
diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html
index 17ce23e..c1f9da3 100644
--- a/yaksh/templates/manage.html
+++ b/yaksh/templates/manage.html
@@ -18,7 +18,7 @@
<li><a href="{{ URL_ROOT }}/exam/manage/courses">Courses</a></li>
<li><a href="{{ URL_ROOT }}/exam/manage/monitor">Monitor</a></li>
<li><a href="{{ URL_ROOT }}/exam/manage/gradeuser">Grade User</a></li>
- <li><a href="{{ URL_ROOT }}/exam/manage/grader"> Grader </a></li>
+ <li><a href="{{ url_root }}/exam/manage/grader"> Regrade </a></li>
<li><a href="{{ URL_ROOT }}/exam/reset/changepassword">Change Password</a></li>
<li><a href="{{ URL_ROOT }}/exam/viewprofile"> {{ user.get_full_name.title }} </a></li>
<li><a href="{{URL_ROOT}}/exam/logout/" id="logout">Logout</a></li>
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index a5d10a7..9fcae68 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -136,12 +136,14 @@
<th>Sr No.</th>
<th>Students</th>
<th>Total</th>
+ <th>Grade</th>
<th colspan="{{modules|length}}">Modules</th>
</tr>
<tr>
<th scope="row"></th>
<th></th>
<th></th>
+ <th></th>
{% if modules %}
{% for module in modules %}
<th>
@@ -171,6 +173,10 @@
{% course_completion_percent course student as c_percent %}
{{c_percent}} %
</td>
+ <td>
+ {% course_grade course student as grade %}
+ {{grade}}
+ </td>
{% if modules %}
{% for module in modules %}
<td>
diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html
index afbae75..6c93e97 100644
--- a/yaksh/templates/yaksh/course_modules.html
+++ b/yaksh/templates/yaksh/course_modules.html
@@ -17,6 +17,7 @@
<center>{{ msg }}</center>
</div>
{% endif %}
+<b>Grade: {% if grade %} {{ grade }} {% else %} Will be available once the course is complete {% endif %}</b>
{% if learning_modules %}
<table class="table">
{% for module in learning_modules %}
diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html
index dabf8eb..ba09c6d 100644
--- a/yaksh/templates/yaksh/courses.html
+++ b/yaksh/templates/yaksh/courses.html
@@ -56,6 +56,10 @@
<a href="{{URL_ROOT}}/exam/manage/courses/all_learning_module">
Add/View Modules</a>
</li>
+ <li>
+ <a href="{% url 'grades:grading_systems'%}">
+ Add/View Grading Systems </a>
+ </li>
</ul>
</div>
</div>
diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py
index 26646b1..0c5eb5a 100644
--- a/yaksh/templatetags/custom_filters.py
+++ b/yaksh/templatetags/custom_filters.py
@@ -66,8 +66,13 @@ def course_completion_percent(course, user):
@register.simple_tag
+def course_grade(course, user):
+ return course.get_grade(user)
+
+
+@register.simple_tag
def get_ordered_testcases(question, answerpaper):
- return question.get_ordered_test_cases(answerpaper)
+ return question.get_ordered_test_cases(answerpaper)
@register.simple_tag
def get_answer_for_arrange_options(ans, question):
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index e7a6fe5..41730c3 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -1838,3 +1838,101 @@ class AssignmentUploadTestCases(unittest.TestCase):
actual_file_name = self.quiz.description.replace(" ", "_")
file_name = file_name.replace(" ", "_")
self.assertIn(actual_file_name, file_name)
+
+
+class CourseStatusTestCases(unittest.TestCase):
+ def setUp(self):
+ user = User.objects.get(username='creator')
+ self.course = Course.objects.create(name="Demo Course", creator=user,
+ enrollment="Enroll Request")
+ self.module = LearningModule.objects.create(name='M1', creator=user,
+ description='module one')
+ self.quiz1 = Quiz.objects.create(time_between_attempts=0, weightage=50,
+ description='qz1')
+ self.quiz2 = Quiz.objects.create(time_between_attempts=0, weightage=100,
+ description='qz2')
+ question = Question.objects.first()
+ self.qpaper1 = QuestionPaper.objects.create(quiz=self.quiz1)
+ self.qpaper2 = QuestionPaper.objects.create(quiz=self.quiz2)
+ self.qpaper1.fixed_questions.add(question)
+ self.qpaper2.fixed_questions.add(question)
+ self.qpaper1.update_total_marks()
+ self.qpaper2.update_total_marks()
+ self.qpaper1.save()
+ self.qpaper2.save()
+ self.unit_1_quiz = LearningUnit.objects.create(order=1, type='quiz',
+ quiz=self.quiz1)
+ self.unit_2_quiz = LearningUnit.objects.create(order=2, type='quiz',
+ quiz=self.quiz2)
+ self.module.learning_unit.add(self.unit_1_quiz)
+ self.module.learning_unit.add(self.unit_2_quiz)
+ self.module.save()
+ self.course.learning_module.add(self.module)
+ student = User.objects.get(username='course_user')
+ self.course.students.add(student)
+ self.course.save()
+
+ attempt = 1
+ ip = '127.0.0.1'
+ self.answerpaper1 = self.qpaper1.make_answerpaper(student, ip, attempt,
+ self.course.id)
+ self.answerpaper2 = self.qpaper2.make_answerpaper(student, ip, attempt,
+ self.course.id)
+
+ self.course_status = CourseStatus.objects.create(course=self.course,
+ user=student)
+
+ def tearDown(self):
+ self.course_status.delete()
+ self.answerpaper1.delete()
+ self.answerpaper2.delete()
+ self.qpaper1.delete()
+ self.qpaper2.delete()
+ self.quiz1.delete()
+ self.quiz2.delete()
+ self.unit_1_quiz.delete()
+ self.unit_2_quiz.delete()
+ self.module.delete()
+ self.course.delete()
+
+ def test_course_is_complete(self):
+ # When
+ self.course_status.completed_units.add(self.unit_1_quiz)
+ # Then
+ self.assertFalse(self.course_status.is_course_complete())
+
+ # When
+ self.course_status.completed_units.add(self.unit_2_quiz)
+ # Then
+ self.assertTrue(self.course_status.is_course_complete())
+
+ # Given
+ self.answerpaper1.marks_obtained = 1
+ self.answerpaper1.save()
+ self.answerpaper2.marks_obtained = 0
+ self.answerpaper2.save()
+ # When
+ self.course_status.calculate_percentage()
+ # Then
+ self.assertEqual(round(self.course_status.percentage, 2), 33.33)
+ # When
+ self.course_status.set_grade()
+ # Then
+ self.assertEqual(self.course_status.get_grade(), 'F')
+
+ # Given
+ self.answerpaper1.marks_obtained = 0
+ self.answerpaper1.save()
+ self.answerpaper2.marks_obtained = 1
+ self.answerpaper2.save()
+ # When
+ self.course_status.calculate_percentage()
+ # Then
+ self.assertEqual(round(self.course_status.percentage, 2), 66.67)
+ # When
+ self.course_status.set_grade()
+ # Then
+ self.assertEqual(self.course_status.get_grade(), 'B')
+
+ # Test get course grade after completion
+ self.assertEqual(self.course.get_grade(self.answerpaper1.user), 'B')
diff --git a/yaksh/views.py b/yaksh/views.py
index 083fd67..1cb77fc 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -1672,6 +1672,10 @@ def grade_user(request, quiz_id=None, user_id=None, attempt_number=None,
'comments_%d' % paper.question_paper.id, 'No comments')
paper.save()
+ course_status = CourseStatus.objects.filter(course=course, user=user)
+ if course_status.exists():
+ course_status.first().set_grade()
+
return my_render_to_response(
'yaksh/grade_user.html', context, context_instance=ci
)
@@ -1927,14 +1931,27 @@ def regrade(request, course_id, question_id=None, answerpaper_id=None,
answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id)
for question in answerpaper.questions.all():
details.append(answerpaper.regrade(question.id))
+ course_status = CourseStatus.objects.filter(user=answerpaper.user,
+ course=answerpaper.course)
+ if course_status.exists():
+ course_status.first().set_grade()
if questionpaper_id is not None and question_id is not None:
answerpapers = AnswerPaper.objects.filter(questions=question_id,
question_paper_id=questionpaper_id, course_id=course_id)
for answerpaper in answerpapers:
details.append(answerpaper.regrade(question_id))
+ course_status = CourseStatus.objects.filter(user=answerpaper.user,
+ course=answerpaper.course)
+ if course_status.exists():
+ course_status.first().set_grade()
if answerpaper_id is not None and question_id is not None:
answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id)
details.append(answerpaper.regrade(question_id))
+ course_status = CourseStatus.objects.filter(user=answerpaper.user,
+ course=answerpaper.course)
+ if course_status.exists():
+ course_status.first().set_grade()
+
return grader(request, extra_context={'details': details})
@@ -2723,6 +2740,12 @@ def course_modules(request, course_id, msg=None):
learning_modules = course.get_learning_modules()
context = {"course": course, "learning_modules": learning_modules,
"user": user, "msg": msg}
+ course_status = CourseStatus.objects.filter(course=course, user=user)
+ if course_status.exists():
+ course_status = course_status.first()
+ if not course_status.grade:
+ course_status.set_grade()
+ context['grade'] = course_status.get_grade()
return my_render_to_response('yaksh/course_modules.html', context)