From b3f5721f3cf4225902000f2f76e5138135383792 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Thu, 8 Feb 2018 14:29:38 +0530 Subject: Add weightage for Quiz and Create Grading System App App Name: grades Grading System provides with the grade for a given value. It contains different grade ranges. Has its own default grading system. Allows you to modify and add grading system wth grade ranges. To be done: - Need to add README - Good UI - There are fields like can_be_used and order in models for future use. - More tests App name: Yaksh Now every quiz has a default weightage of 100%, can be changed. An aggregate is calculated for a given course. Using grades app a grade is provide to the aggregate value. --- .travis.yml | 1 + grades/__init__.py | 0 grades/admin.py | 9 +++ grades/apps.py | 5 ++ grades/forms.py | 13 ++++ grades/migrations/0001_initial.py | 45 ++++++++++++ grades/migrations/__init__.py | 0 grades/migrations/default_grading_system.py | 36 ++++++++++ grades/models.py | 49 +++++++++++++ grades/templates/add_grades.html | 28 ++++++++ grades/templates/grading_systems.html | 17 +++++ grades/tests/test_models.py | 27 +++++++ grades/tests/test_views.py | 106 ++++++++++++++++++++++++++++ grades/urls.py | 10 +++ grades/views.py | 44 ++++++++++++ online_test/settings.py | 1 + online_test/urls.py | 1 + yaksh/forms.py | 2 +- yaksh/models.py | 34 ++++++++- yaksh/templates/manage.html | 3 +- yaksh/templates/yaksh/course_modules.html | 1 + yaksh/views.py | 6 ++ 22 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 grades/__init__.py create mode 100644 grades/admin.py create mode 100644 grades/apps.py create mode 100644 grades/forms.py create mode 100644 grades/migrations/0001_initial.py create mode 100644 grades/migrations/__init__.py create mode 100644 grades/migrations/default_grading_system.py create mode 100644 grades/models.py create mode 100644 grades/templates/add_grades.html create mode 100644 grades/templates/grading_systems.html create mode 100644 grades/tests/test_models.py create mode 100644 grades/tests/test_views.py create mode 100644 grades/urls.py create mode 100644 grades/views.py diff --git a/.travis.yml b/.travis.yml index b1a8402..86fcd0c 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.tests - 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 diff --git a/grades/admin.py b/grades/admin.py new file mode 100644 index 0000000..ab38f6b --- /dev/null +++ b/grades/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from grades.models import GradingSystem, GradeRange + +# Register your models here. +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..f8c800a --- /dev/null +++ b/grades/forms.py @@ -0,0 +1,13 @@ +from grades.models import GradingSystem +from django import forms + +class GradingSystemForm(forms.ModelForm): + def __init__(self, *args, ** kwargs): + super(GradingSystemForm, self).__init__(*args, **kwargs) + system = getattr(self, 'instance', None) + if system.name == 'default': + self.fields['name'].widget.attrs['readonly'] = True + + class Meta: + model = GradingSystem + fields = ['name', 'description', 'can_be_used'] diff --git a/grades/migrations/0001_initial.py b/grades/migrations/0001_initial.py new file mode 100644 index 0000000..65d711e --- /dev/null +++ b/grades/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2018-02-02 06:20 +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')), + ('order', models.IntegerField(default=0)), + ('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!')), + ('can_be_used', models.BooleanField(default=False)), + ('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 diff --git a/grades/migrations/default_grading_system.py b/grades/migrations/default_grading_system.py new file mode 100644 index 0000000..1629d29 --- /dev/null +++ b/grades/migrations/default_grading_system.py @@ -0,0 +1,36 @@ +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', + can_be_used=True) + GradeRange.objects.using(db).create(system=default_system, order=1, lower_limit=0, + upper_limit=40, grade='F', description='Fail') + GradeRange.objects.using(db).create(system=default_system, order=2, lower_limit=40, + upper_limit=55, grade='P', description='Pass') + GradeRange.objects.using(db).create(system=default_system, order=3, lower_limit=55, + upper_limit=60, grade='C', description='Average') + GradeRange.objects.using(db).create(system=default_system, order=4, lower_limit=60, + upper_limit=75, grade='B', description='Satisfactory') + GradeRange.objects.using(db).create(system=default_system, order=5, lower_limit=75, + upper_limit=90, grade='A', description='Good') + GradeRange.objects.using(db).create(system=default_system, order=6, lower_limit=90, + upper_limit=101, grade='A+', description='Excellent') + + +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..33895bb --- /dev/null +++ b/grades/models.py @@ -0,0 +1,49 @@ +from django.db import models +from django.contrib.auth.models import User + +# Create your models here. + +class GradingSystem(models.Model): + name = models.CharField(max_length=255, unique=True) + description = models.TextField(default='About the grading system!') + can_be_used = models.BooleanField(default=False) + 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) + order = models.IntegerField(default=0) + 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..f2f0051 --- /dev/null +++ b/grades/templates/add_grades.html @@ -0,0 +1,28 @@ +<html> +<a href="{% url 'grades:grading_systems'%}"> Back to Grading Systems </a> +<p><b>Note: For grade range lower limit is inclusive and upper limit is exclusive</b></p> +{% 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> + {{ grade_form }} + </table> + {{ formset.management_form }} + <br> + <b><u>Grade Ranges</u></b> + <hr> + {% for form in formset %} + <div> + {{ form }} + </div> + <hr> + {% endfor %} + + <input type="submit" id="add" name="add" value="Add"> + <input type="submit" id="save" name="save" value="Save"> + +</form> +</html> diff --git a/grades/templates/grading_systems.html b/grades/templates/grading_systems.html new file mode 100644 index 0000000..143b037 --- /dev/null +++ b/grades/templates/grading_systems.html @@ -0,0 +1,17 @@ +<html> + <b>Default Grading System:</b> + <ul> + <li><a href="{% url 'grades:edit_grade' default_grading_system.id %}">{{ default_grading_system.name }}</a></li> + </ul> + <b> My grading System: </b> + {% if grading_systems %} + <ul> + {% for system in grading_systems %} + <li><a href="{% url 'grades:edit_grade' system.id %}">{{ system.name }}</a></li> + {% endfor %} + </ul> + {% else %} + <p> None. You can add one.</p> + {% endif %} + <a href="{% url 'grades:add_grade' %}"> Add a Grading System </a> +</html> diff --git a/grades/tests/test_models.py b/grades/tests/test_models.py new file mode 100644 index 0000000..89708e2 --- /dev/null +++ b/grades/tests/test_models.py @@ -0,0 +1,27 @@ +from django.test import TestCase +from grades.models import GradingSystem, GradeRange + +# Create your tests here. +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 + 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..2c29ae5 --- /dev/null +++ b/grades/tests/test_views.py @@ -0,0 +1,106 @@ +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 the grading system!'], + 'graderange_set-MIN_NUM_FORMS': ['0'], + 'graderange_set-TOTAL_FORMS': ['0'], 'can_be_used': ['on'], + '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-order': ['0'], + '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'], 'can_be_used': ['on'], + '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..86803c9 --- /dev/null +++ b/grades/views.py @@ -0,0 +1,44 @@ +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 + +# Create your views here. +@login_required +def grading_systems(request): + user = request.user + default_grading_system = GradingSystem.objects.get(name='default') + grading_systems = GradingSystem.objects.filter(creator=user).exclude( + name='default') + 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) + + 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}) 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 258a1ee..6e70d46 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -279,7 +279,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 f065190..f76feed 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"), @@ -311,7 +311,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) @@ -551,6 +552,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): @@ -737,6 +740,33 @@ class CourseStatus(models.Model): grade = models.CharField(max_length=255, null=True, blank=True) total_marks = models.FloatField(default=0.0) + def set_grade(self): + grade = self.course.grading_system.get_grade(self.total_marks) + self.grade = grade + + def calculate_total_marks(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.total_marks = (sum/total_weightage)*100 + self.set_grade() + + + 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 ############################################################################### class ConcurrentUser(models.Model): diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html index 17ce23e..2590655 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -18,7 +18,8 @@ <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"> Grader </a></li> + <li><a href="{% url 'grades:grading_systems'%}"> Grading Systems </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_modules.html b/yaksh/templates/yaksh/course_modules.html index fad1be0..eba7409 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 %} {% for module in learning_modules %} <div class="row well"> diff --git a/yaksh/views.py b/yaksh/views.py index 011b417..30b454b 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -2710,6 +2710,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 course_status.is_course_complete() and not course_status.grade: + course_status.calculate_total_marks() + context['grade'] = course_status.grade return my_render_to_response('yaksh/course_modules.html', context) -- cgit From 24bce890f07b6696ef1e910443a41a54840a6e90 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Thu, 8 Feb 2018 15:16:22 +0530 Subject: Treat grades/tests as python package directory --- .travis.yml | 2 +- grades/tests/__init__.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 grades/tests/__init__.py diff --git a/.travis.yml b/.travis.yml index 86fcd0c..59eaa66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +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.tests + - 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/tests/__init__.py b/grades/tests/__init__.py new file mode 100644 index 0000000..e69de29 -- cgit From f06a5d2ffbb1a06320935841a4ba24720e651985 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Tue, 13 Feb 2018 12:48:19 +0530 Subject: Change default grading system behaviour Cannot edit default system. Code as per PEP8 standards. Updates grade after regrade or manual grading Field change from total marks to percentage Removed unused fields from grades app --- grades/admin.py | 2 +- grades/forms.py | 9 ++----- grades/migrations/0001_initial.py | 4 +--- grades/migrations/default_grading_system.py | 37 ++++++++++++++++------------- grades/models.py | 3 --- grades/templates/add_grades.html | 10 ++++---- grades/tests/test_models.py | 9 +++---- grades/tests/test_views.py | 11 ++++----- grades/views.py | 9 +++---- yaksh/models.py | 20 +++++++++++----- yaksh/views.py | 23 +++++++++++++++--- 11 files changed, 80 insertions(+), 57 deletions(-) diff --git a/grades/admin.py b/grades/admin.py index ab38f6b..548791e 100644 --- a/grades/admin.py +++ b/grades/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from grades.models import GradingSystem, GradeRange -# Register your models here. + class GradingSystemAdmin(admin.ModelAdmin): readonly_fields = ('creator',) diff --git a/grades/forms.py b/grades/forms.py index f8c800a..130659d 100644 --- a/grades/forms.py +++ b/grades/forms.py @@ -1,13 +1,8 @@ from grades.models import GradingSystem from django import forms -class GradingSystemForm(forms.ModelForm): - def __init__(self, *args, ** kwargs): - super(GradingSystemForm, self).__init__(*args, **kwargs) - system = getattr(self, 'instance', None) - if system.name == 'default': - self.fields['name'].widget.attrs['readonly'] = True +class GradingSystemForm(forms.ModelForm): class Meta: model = GradingSystem - fields = ['name', 'description', 'can_be_used'] + fields = ['name', 'description'] diff --git a/grades/migrations/0001_initial.py b/grades/migrations/0001_initial.py index 65d711e..04a3006 100644 --- a/grades/migrations/0001_initial.py +++ b/grades/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.5 on 2018-02-02 06:20 +# Generated by Django 1.9.5 on 2018-02-12 11:12 from __future__ import unicode_literals from django.conf import settings @@ -20,7 +20,6 @@ class Migration(migrations.Migration): name='GradeRange', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('order', models.IntegerField(default=0)), ('lower_limit', models.FloatField()), ('upper_limit', models.FloatField()), ('grade', models.CharField(max_length=10)), @@ -33,7 +32,6 @@ class Migration(migrations.Migration): ('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!')), - ('can_be_used', models.BooleanField(default=False)), ('creator', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), diff --git a/grades/migrations/default_grading_system.py b/grades/migrations/default_grading_system.py index 1629d29..85390d6 100644 --- a/grades/migrations/default_grading_system.py +++ b/grades/migrations/default_grading_system.py @@ -1,24 +1,28 @@ 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', - can_be_used=True) - GradeRange.objects.using(db).create(system=default_system, order=1, lower_limit=0, - upper_limit=40, grade='F', description='Fail') - GradeRange.objects.using(db).create(system=default_system, order=2, lower_limit=40, - upper_limit=55, grade='P', description='Pass') - GradeRange.objects.using(db).create(system=default_system, order=3, lower_limit=55, - upper_limit=60, grade='C', description='Average') - GradeRange.objects.using(db).create(system=default_system, order=4, lower_limit=60, - upper_limit=75, grade='B', description='Satisfactory') - GradeRange.objects.using(db).create(system=default_system, order=5, lower_limit=75, - upper_limit=90, grade='A', description='Good') - GradeRange.objects.using(db).create(system=default_system, order=6, lower_limit=90, - upper_limit=101, grade='A+', description='Excellent') + 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): @@ -32,5 +36,6 @@ def delete_default_system(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [('grades', '0001_initial'),] - operations = [migrations.RunPython(create_default_system, delete_default_system),] + dependencies = [('grades', '0001_initial'), ] + operations = [migrations.RunPython(create_default_system, + delete_default_system), ] diff --git a/grades/models.py b/grades/models.py index 33895bb..fcea510 100644 --- a/grades/models.py +++ b/grades/models.py @@ -1,12 +1,10 @@ from django.db import models from django.contrib.auth.models import User -# Create your models here. class GradingSystem(models.Model): name = models.CharField(max_length=255, unique=True) description = models.TextField(default='About the grading system!') - can_be_used = models.BooleanField(default=False) creator = models.ForeignKey(User, null=True, blank=True) def get_grade(self, marks): @@ -42,7 +40,6 @@ class GradingSystem(models.Model): class GradeRange(models.Model): system = models.ForeignKey(GradingSystem) - order = models.IntegerField(default=0) lower_limit = models.FloatField() upper_limit = models.FloatField() grade = models.CharField(max_length=10) diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html index f2f0051..1e4d29e 100644 --- a/grades/templates/add_grades.html +++ b/grades/templates/add_grades.html @@ -20,9 +20,11 @@ </div> <hr> {% endfor %} - - <input type="submit" id="add" name="add" value="Add"> - <input type="submit" id="save" name="save" value="Save"> - + {% if not is_default %} + <input type="submit" id="add" name="add" value="Add"> + <input type="submit" id="save" name="save" value="Save"> + {% else %} + <p><b>Note: This is a default grading system. You cannot change this.</b></p> + {% endif %} </form> </html> diff --git a/grades/tests/test_models.py b/grades/tests/test_models.py index 89708e2..f8d5c5c 100644 --- a/grades/tests/test_models.py +++ b/grades/tests/test_models.py @@ -1,7 +1,7 @@ from django.test import TestCase from grades.models import GradingSystem, GradeRange -# Create your tests here. + class GradingSystemTestCase(TestCase): def setUp(self): GradingSystem.objects.create(name='unusable') @@ -9,8 +9,9 @@ class GradingSystemTestCase(TestCase): 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} + 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) @@ -19,9 +20,9 @@ class GradingSystemTestCase(TestCase): 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 index 2c29ae5..c944f03 100644 --- a/grades/tests/test_views.py +++ b/grades/tests/test_views.py @@ -6,7 +6,8 @@ from grades.models import GradingSystem def setUpModule(): user = User.objects.create_user(username='grades_user', - password='grades_user') + password='grades_user') + def tearDownModule(): User.objects.all().delete() @@ -66,9 +67,9 @@ class AddGradingSystemTest(TestCase): def test_add_grades_post(self): # Given self.client.login(username='grades_user', password='grades_user') - data = {'name': ['new_sys'], 'description': ['About the grading system!'], + data = {'name': ['new_sys'], 'description': ['About grading system!'], 'graderange_set-MIN_NUM_FORMS': ['0'], - 'graderange_set-TOTAL_FORMS': ['0'], 'can_be_used': ['on'], + 'graderange_set-TOTAL_FORMS': ['0'], 'graderange_set-MAX_NUM_FORMS': ['1000'], 'add': ['Add'], 'graderange_set-INITIAL_FORMS': ['0']} # When @@ -86,12 +87,11 @@ class AddGradingSystemTest(TestCase): # Given data = {'graderange_set-0-upper_limit': ['40'], - 'graderange_set-0-order': ['0'], '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'], 'can_be_used': ['on'], + 'graderange_set-TOTAL_FORMS': ['1'], 'graderange_set-MAX_NUM_FORMS': ['1000'], 'graderange_set-0-id': [''], 'description': ['About the grading system!'], @@ -103,4 +103,3 @@ class AddGradingSystemTest(TestCase): # Then ranges = grading_system.graderange_set.all() self.assertEqual(len(ranges), 1) - diff --git a/grades/views.py b/grades/views.py index 86803c9..10f9999 100644 --- a/grades/views.py +++ b/grades/views.py @@ -4,13 +4,12 @@ from django.forms import inlineformset_factory from grades.forms import GradingSystemForm from grades.models import GradingSystem, GradeRange -# Create your views here. + @login_required def grading_systems(request): user = request.user default_grading_system = GradingSystem.objects.get(name='default') - grading_systems = GradingSystem.objects.filter(creator=user).exclude( - 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}) @@ -24,6 +23,7 @@ def add_grading_system(request, system_id=None): 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) @@ -41,4 +41,5 @@ def add_grading_system(request, system_id=None): formset = GradeRangeFormSet(instance=grading_system) return render(request, 'add_grades.html', {'formset': formset, - 'grade_form': grade_form, "system_id": system_id}) + 'grade_form': grade_form, "system_id": system_id, + 'is_default': is_default}) diff --git a/yaksh/models.py b/yaksh/models.py index f76feed..1ecb1f8 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -738,13 +738,22 @@ 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): - grade = self.course.grading_system.get_grade(self.total_marks) - self.grade = grade + 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 - def calculate_total_marks(self): + def calculate_percentage(self): if self.is_course_complete(): quizzes = self.course.get_quizzes() total_weightage = 0 @@ -755,8 +764,7 @@ class CourseStatus(models.Model): quiz, self.user.id, self.course.id) out_of = quiz.questionpaper_set.first().total_marks sum += (marks/out_of)*quiz.weightage - self.total_marks = (sum/total_weightage)*100 - self.set_grade() + self.percentage = (sum/total_weightage)*100 def is_course_complete(self): diff --git a/yaksh/views.py b/yaksh/views.py index 30b454b..356c66e 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1664,6 +1664,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 ) @@ -1919,14 +1923,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}) @@ -2713,9 +2730,9 @@ def course_modules(request, course_id, msg=None): course_status = CourseStatus.objects.filter(course=course, user=user) if course_status.exists(): course_status = course_status.first() - if course_status.is_course_complete() and not course_status.grade: - course_status.calculate_total_marks() - context['grade'] = course_status.grade + 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) -- cgit From e4d8c21c1d035fac72dd0475bcd804013bd311e3 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Wed, 14 Feb 2018 17:11:29 +0530 Subject: Add Tests for CourseStatus methods --- yaksh/test_models.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/yaksh/test_models.py b/yaksh/test_models.py index cd4279b..50d1843 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1801,3 +1801,98 @@ 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') -- cgit From 95f5f27c18380e2fb2f33526c467e822dd10c4d8 Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 1 Mar 2018 18:56:13 +0530 Subject: Add basic UI for grading system --- grades/templates/add_grades.html | 12 ++++-- grades/templates/grading_systems.html | 80 +++++++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html index 1e4d29e..e4ea915 100644 --- a/grades/templates/add_grades.html +++ b/grades/templates/add_grades.html @@ -1,13 +1,16 @@ +{% extends "manage.html" %} +{% block main %} <html> -<a href="{% url 'grades:grading_systems'%}"> Back to Grading Systems </a> +<a href="{% url 'grades:grading_systems'%}" class="btn btn-primary"> Back to Grading Systems </a> <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> + <table class="table"> {{ grade_form }} </table> {{ formset.management_form }} @@ -21,10 +24,11 @@ <hr> {% endfor %} {% if not is_default %} - <input type="submit" id="add" name="add" value="Add"> - <input type="submit" id="save" name="save" value="Save"> + <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 index 143b037..baa5610 100644 --- a/grades/templates/grading_systems.html +++ b/grades/templates/grading_systems.html @@ -1,17 +1,73 @@ +{% extends "manage.html" %} +{% block main %} <html> - <b>Default Grading System:</b> - <ul> - <li><a href="{% url 'grades:edit_grade' default_grading_system.id %}">{{ default_grading_system.name }}</a></li> - </ul> - <b> My grading System: </b> - {% if grading_systems %} - <ul> + <a href="{% url 'grades:add_grade' %}" class="btn btn-primary"> Add a Grading System </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 %} - <li><a href="{% url 'grades:edit_grade' system.id %}">{{ system.name }}</a></li> + <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 %} - </ul> - {% else %} - <p> None. You can add one.</p> + </table> {% endif %} - <a href="{% url 'grades:add_grade' %}"> Add a Grading System </a> </html> +{% endblock %} -- cgit From 65c368a1360a83c2b10458ec61a4b74d9ac8e9f5 Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 1 Mar 2018 19:00:55 +0530 Subject: Show student grade in course status page --- yaksh/models.py | 12 +++++++++++- yaksh/templates/yaksh/course_detail.html | 6 ++++++ yaksh/templatetags/custom_filters.py | 5 +++++ yaksh/test_models.py | 3 +++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/yaksh/models.py b/yaksh/models.py index 1ecb1f8..32d7b3d 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -725,6 +725,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 __str__(self): return self.name @@ -752,6 +760,7 @@ class CourseStatus(models.Model): 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(): @@ -765,7 +774,7 @@ class CourseStatus(models.Model): 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() @@ -776,6 +785,7 @@ class CourseStatus(models.Model): break return complete + ############################################################################### class ConcurrentUser(models.Model): concurrent_user = models.OneToOneField(User) 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> @@ -170,6 +172,10 @@ <td> {% 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 %} diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 3c2c6fd..717c9bb 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -62,3 +62,8 @@ def module_completion_percent(course, module, user): @register.simple_tag def course_completion_percent(course, user): return course.percent_completed(user) + + +@register.simple_tag +def course_grade(course, user): + return course.get_grade(user) diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 50d1843..4e2047e 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1896,3 +1896,6 @@ class CourseStatusTestCases(unittest.TestCase): 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') -- cgit From e1c1d0d0d6ae170d3ce9966b98ec6d03ff35c062 Mon Sep 17 00:00:00 2001 From: adityacp Date: Wed, 21 Mar 2018 17:43:26 +0530 Subject: Move Grading systems from navbar to Courses sidebar --- grades/templates/add_grades.html | 3 ++- grades/templates/grading_systems.html | 1 + yaksh/templates/manage.html | 3 +-- yaksh/templates/yaksh/courses.html | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html index e4ea915..a3f52da 100644 --- a/grades/templates/add_grades.html +++ b/grades/templates/add_grades.html @@ -1,7 +1,8 @@ {% extends "manage.html" %} {% block main %} <html> -<a href="{% url 'grades:grading_systems'%}" class="btn btn-primary"> Back to Grading Systems </a> +<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 %} diff --git a/grades/templates/grading_systems.html b/grades/templates/grading_systems.html index baa5610..3a71ebf 100644 --- a/grades/templates/grading_systems.html +++ b/grades/templates/grading_systems.html @@ -2,6 +2,7 @@ {% 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"> diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html index 2590655..c1f9da3 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -18,8 +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 'grades:grading_systems'%}"> Grading Systems </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/courses.html b/yaksh/templates/yaksh/courses.html index bc96bf5..811aa0f 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -47,6 +47,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> -- cgit