Skip to content

Commit

Permalink
Merge branch 'ifrh-feature_boxplot-taskstatistic'
Browse files Browse the repository at this point in the history
  • Loading branch information
ratefuchs committed Jul 27, 2022
2 parents d25f71a + 9611156 commit 92d1cf5
Show file tree
Hide file tree
Showing 12 changed files with 1,034 additions and 2 deletions.
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ python-ldap

#needed for praktomat.wsgi not only in unit tests for it
mod_wsgi # make sure, that you have called : sudo apt-get install apache2-dev

#needed for task statistic as boxplot diagrams
pandas
matplotlib
3 changes: 2 additions & 1 deletion src/settings/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def __setattr__(self, k, v):
'utilities',
'settings',
#'sessionprofile', #phpBB integration
'taskstatistics',
)

d.MIDDLEWARE = [
Expand Down Expand Up @@ -375,7 +376,7 @@ def __setattr__(self, k, v):
'accounts.ldap_auth.LDAPBackend',
d.AUTH_BACKEND,
)
d.LDAP_URI="ldap://ldap.DOMAINNAME.TOPLEVEL"
d.LDAP_URI="ldap://ldap.DOMAINNAME.TOPLEVEL"
d.LDAP_BASE="dc=DOMAINNAME,dc=TOPLEVEL"


Expand Down
Empty file added src/taskstatistics/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions src/taskstatistics/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.contrib import admin

# I think, it is not usefull to have the taskstatistic raw data "Table" inside the admin-interface.
# So I do not write this admin view. (R.H. @H-BRS)
513 changes: 513 additions & 0 deletions src/taskstatistics/migrations/0001_initial_TaskStatisticsDBview.py

Large diffs are not rendered by default.

Empty file.
41 changes: 41 additions & 0 deletions src/taskstatistics/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# We create a model for an unmanaged database view: The database view gets constructed by RAW-SQL commands inside Praktomat/src/statistics/migrations/0001_initial.py
class TasksStatistic(models.Model):
id = models.AutoField(primary_key=True)
task = models.ForeignKey('tasks.Task',on_delete=models.DO_NOTHING)
title = models.CharField(help_text='The name of the Task', max_length=100)
submitters_all = models.BigIntegerField() # is the number of users, who had done at least one upload for that task
submitters_passed_finals = models.BigIntegerField() # is the number of users, who passed sucessfully all pre and post tests after submission date has been over for that task
submitters_failed_finals = models.BigIntegerField() # is the number of users, who failed in the post tests after submission date has been over for that task
submitters_latest_not_accepted = models.BigIntegerField() # is the number of users, who failed in the pre tests at uploadtime and don't have any final solution after submission date has been over for that task
uploads_all = models.BigIntegerField() # is the number of all failed or passed submissions
uploads_accepted = models.BigIntegerField() # is the number of all passed submissions
uploads_rejected = models.BigIntegerField() # is the number of all failed submissions
# the following datas are information about how many submissions did users done until they finally passed
avg_upl_until_final_pass = models.BigIntegerField(verbose_name='avg uploads until final passed')
lo_whisker_upl_til_final_pass = models.BigIntegerField(verbose_name='lower whisker uploads until final passed')
lo_quart_upl_til_final_pass = models.BigIntegerField(verbose_name='lower quartiel uploads until final passed')
med_upl_til_final_pass = models.BigIntegerField(verbose_name='median uploads until final passed')
up_quart_upl_til_final_pass = models.BigIntegerField(verbose_name='upper quartiel uploads until final passed')
up_whisker_upl_until_final_pass = models.BigIntegerField(verbose_name='upper whisker uploads until final passed')
# the following data are information about how many uploads did users to pass pretests but failing in posttest.
avg_uploads_final_failed= models.BigIntegerField()
lo_whisker_upl_final_fail = models.BigIntegerField(verbose_name='lower whisker uploads final failed')
lo_quart_upl_final_fail = models.BigIntegerField(verbose_name='lower quartiel uploads final failed')
median_uploads_final_failed = models.BigIntegerField()
up_quart_upl_final_failed = models.BigIntegerField(verbose_name='upper quartiel uploads final failed')
up_whisker_upl_final_failed = models.BigIntegerField(verbose_name='upper whisker uploads final failed')
# the following data are information about how many failed uploads did users done before they gave up.
avg_uploads_only_failed= models.BigIntegerField()
lo_whisker_upl_only_fail = models.BigIntegerField(verbose_name='lower whisker uploads only failed')
lo_quart_upl_only_fail = models.BigIntegerField(verbose_name='lower quartiel uploads only failed')
median_uploads_only_failed = models.BigIntegerField()
up_quart_upl_only_failed = models.BigIntegerField(verbose_name='upper quartiel uploads only failed')
up_whisker_upl_only_failed = models.BigIntegerField(verbose_name='upper whisker uploads only failed')
class Meta:
managed = False
db_table = 'dbview_tasksstatistic'
177 changes: 177 additions & 0 deletions src/taskstatistics/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.test import TestCase

# Create your tests here.

try:
from django.urls import resolve
except ImportError:
from django.core.urlresolvers import resolve


from django.http import HttpRequest
from django.template.loader import render_to_string

from django.db import migrations
from django.db import connection

from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.sessions.middleware import SessionMiddleware

from taskstatistics.models import TasksStatistic
from taskstatistics.views import tasks_statistic
from taskstatistics.views import prepare_statistic_data
from taskstatistics.views import prepare_graphic_list
from taskstatistics.views import tasks_statistic_download

from solutions.models import Solution
from tasks.models import Task

from collections import OrderedDict

class TasksStatisticTest(TestCase):

def get_table_description(self, table):
with connection.cursor() as cursor:
return connection.introspection.get_table_description(cursor, table)

def assertTableOrViewExists(self, tableOrView):
with connection.cursor() as cursor:
self.assertIn(tableOrView, connection.introspection.table_names(cursor, include_views=True))

def test_DBview_tasksstatistic_isavailable(self):
#check if view with correct name as been created in database
self.assertTableOrViewExists("dbview_tasksstatistic")
#heuristic check about correct created view, that is checking if database view has a attribute with needed field name
self.assertIn("up_whisker_upl_until_final_pass", str(self.get_table_description("dbview_tasksstatistic")))

def test_Model_TasksStatistic_isavailable(self):
v=TasksStatistic.objects.values()
self.assertIn("QuerySet", str(v))

def test_Foreignkey_Connection_works(self):
ts = TasksStatistic.objects.get(pk=1)
self.assertIsInstance(ts,TasksStatistic)
t = ts.task
self.assertIsInstance(t,Task)


def test_Model_TasksStatistic_isUnmanaged(self):
self.assertFalse(TasksStatistic._meta.managed, "TasksStatistic should be a not managed class since it use a database view.")

def test_tasksstatistic_url_resolves_to_tasksstatistic_view(self):
found = resolve('/tasks/statistic')
self.assertEqual(found.func, tasks_statistic, "Update urls.py to make url resolveable.")

def test_taskstatistic_returns_correct_html_title(self):
request = HttpRequest()

"""Annotate a request object with a session"""
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()

"""Annotate a request object with a messages"""
middleware = MessageMiddleware()
middleware.process_request(request)
request.session.save()


response = tasks_statistic(request)
self.assertContains(text="<!DOCTYPE html",response=response,html=False)
self.assertContains(text="<html",response=response,html=False)
self.assertTrue(response.content.strip().startswith(b'<!DOCTYPE html'), "No HTML Doctype has been defined at beginning.")
self.assertIn(b'<h1>Task statistics</h1>', response.content, "Website has wrong h1 element, maybe its content is wrong, too.")
self.assertTrue(response.content.strip().endswith(b'</html>'), "No valid HTML has been returned.")

def remove_csrf_tag(self, text):
"""Remove csrf tag from TEXT"""
import re
return re.sub(r'<[^>]*csrfmiddlewaretoken[^>]*>', '', text)


def test_taskstatistic_returns_correct_html(self):
request = HttpRequest()

"""Annotate a request object with a session"""
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()

"""Annotate a request object with a messages"""
middleware = MessageMiddleware()
middleware.process_request(request)
request.session.save()

response = tasks_statistic(request)
data_ , tasks_ = prepare_statistic_data()
graphic_list_ = prepare_graphic_list(data_)
context = {'data':data_ , 'graphics':graphic_list_ }
expected_html = render_to_string('taskstatistics/overview.html',context,request=request)
self.assertEqual(self.remove_csrf_tag(response.content.strip().decode('utf-8')),self.remove_csrf_tag(expected_html.strip()))

def test_taskstatistic_uses_Template(self):
self.client.login(username='trainer', password='demo')
response = self.client.get('/tasks/statistic')
self.assertTemplateUsed(response,'taskstatistics/overview.html')
self.assertEqual(response.status_code, 200) # should work
self.client.logout()

def test_taskstatistic_returns_html_table_for_trainers(self):
data_ , tasks_ = prepare_statistic_data()
context = {'data':data_ }
#print(context)
html_output = render_to_string('taskstatistics/overview.html', context)
#print(html_output)
self.assertNotIn("<table" , html_output , "There shouln't be a html table on statistic overview page for non trainers.")
self.assertNotIn("median" , html_output )
self.client.login(username='trainer', password='demo')
responseC = self.client.get('/tasks/statistic')
self.assertEqual(responseC.status_code, 200) # should work
self.assertIn("<table".encode("utf-8"), responseC.content, "There should be a html table on statistic overview page for trainers.")
self.client.logout()

def test_column_order_as_basis_for_html_table(self):
data_ , tasks_ = prepare_statistic_data()
self.assertIs(type(data_), list)
for f in data_:
self.assertIs(type(f), OrderedDict)

def test_tasksstatistic_url_resolves_to_tasksstatistic_download(self):
found = resolve('/tasks/statistic/download')
self.assertEqual(found.func, tasks_statistic_download, "Update urls.py to make url resolveable.")

def test_taskstatistic_csv_download(self):
data_ , tasks_ = prepare_statistic_data()
context = {'data':data_ }
html_output = render_to_string('taskstatistics/overview.html', context)
self.assertNotIn("<a href=/tasks/statistic/download", html_output, "There should not be a download link on statistic overview page, without login as tutor, trainer or superuser")
self.client.login(username='trainer', password='demo')
responseC = self.client.get('/tasks/statistic')
self.assertEqual(responseC.status_code, 200) # should work
self.assertIn("<a href=/tasks/statistic/download".encode("utf-8"), responseC.content, "There should be a download link on statistic overview page")
self.client.logout() # without login a statistic view should possible, if there are data.
responseC = self.client.get('/tasks/statistic/download')
self.assertEqual(responseC.status_code, 200) # should work
request = HttpRequest()

"""Annotate a request object with a session"""
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()

"""Annotate a request object with a messages"""
middleware = MessageMiddleware()
middleware.process_request(request)
request.session.save()

response = tasks_statistic_download(request)

self.assertEqual(self.remove_csrf_tag(responseC.content.strip().decode("utf-8")), self.remove_csrf_tag(response.content.strip().decode("utf-8")))
self.client.logout()

# ToDo: Think of integrating Whisker-Boxplot-Data for given Task on statistics View of attestation.
# URL is like tasks/1363/attestation/statistics
# self.fail('Finish the test')
Loading

0 comments on commit 92d1cf5

Please sign in to comment.