Skip to content

Commit

Permalink
homework stats
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeygrigorev committed Oct 11, 2024
1 parent 1fb5439 commit f401745
Show file tree
Hide file tree
Showing 7 changed files with 369 additions and 6 deletions.
2 changes: 2 additions & 0 deletions add_more_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ def create_answers_for_student(submission):
homework=homework,
student=user,
defaults={"enrollment": enrollment},
time_spent_lectures=random.randint(0, 10),
time_spent_homework=random.randint(0, 10),
)

if created:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generated by Django 4.2.14 on 2024-10-10 12:48

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('courses', '0016_enrollment_about_me_enrollment_github_url_and_more'),
]

operations = [
migrations.AlterField(
model_name='projectsubmission',
name='learning_in_public_links',
field=models.JSONField(blank=True, null=True),
),
migrations.AlterField(
model_name='submission',
name='learning_in_public_links',
field=models.JSONField(blank=True, help_text='Links where students talk about the course', null=True),
),
migrations.CreateModel(
name='HomeworkStatistics',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('total_submissions', models.IntegerField(default=0)),
('min_questions_score', models.IntegerField(blank=True, null=True)),
('max_questions_score', models.IntegerField(blank=True, null=True)),
('avg_questions_score', models.FloatField(blank=True, null=True)),
('median_questions_score', models.FloatField(blank=True, null=True)),
('q1_questions_score', models.FloatField(blank=True, null=True)),
('q3_questions_score', models.FloatField(blank=True, null=True)),
('min_total_score', models.IntegerField(blank=True, null=True)),
('max_total_score', models.IntegerField(blank=True, null=True)),
('avg_total_score', models.FloatField(blank=True, null=True)),
('median_total_score', models.FloatField(blank=True, null=True)),
('q1_total_score', models.FloatField(blank=True, null=True)),
('q3_total_score', models.FloatField(blank=True, null=True)),
('min_learning_in_public_score', models.IntegerField(blank=True, null=True)),
('max_learning_in_public_score', models.IntegerField(blank=True, null=True)),
('avg_learning_in_public_score', models.FloatField(blank=True, null=True)),
('median_learning_in_public_score', models.FloatField(blank=True, null=True)),
('q1_learning_in_public_score', models.FloatField(blank=True, null=True)),
('q3_learning_in_public_score', models.FloatField(blank=True, null=True)),
('min_time_spent_lectures', models.FloatField(blank=True, null=True)),
('max_time_spent_lectures', models.FloatField(blank=True, null=True)),
('avg_time_spent_lectures', models.FloatField(blank=True, null=True)),
('median_time_spent_lectures', models.FloatField(blank=True, null=True)),
('q1_time_spent_lectures', models.FloatField(blank=True, null=True)),
('q3_time_spent_lectures', models.FloatField(blank=True, null=True)),
('min_time_spent_homework', models.FloatField(blank=True, null=True)),
('max_time_spent_homework', models.FloatField(blank=True, null=True)),
('avg_time_spent_homework', models.FloatField(blank=True, null=True)),
('median_time_spent_homework', models.FloatField(blank=True, null=True)),
('q1_time_spent_homework', models.FloatField(blank=True, null=True)),
('q3_time_spent_homework', models.FloatField(blank=True, null=True)),
('last_calculated', models.DateTimeField(auto_now=True)),
('homework', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='statistics', to='courses.homework')),
],
),
]
122 changes: 121 additions & 1 deletion courses/models/homework.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ class Submission(models.Model):
homework_link = models.URLField(
blank=True,
null=True,
validators=[URLValidator(schemes=["http", "https", "git"]), validate_url_200],
validators=[
URLValidator(schemes=["http", "https", "git"]),
validate_url_200,
],
)
learning_in_public_links = models.JSONField(
blank=True,
Expand Down Expand Up @@ -199,3 +202,120 @@ class Answer(models.Model):

def __str__(self):
return f"Answer id={self.id} for {self.question}"


class HomeworkStatistics(models.Model):
homework = models.OneToOneField(
Homework, on_delete=models.CASCADE, related_name="statistics"
)

total_submissions = models.IntegerField(default=0)

# Fields for questions_score
min_questions_score = models.IntegerField(null=True, blank=True)
max_questions_score = models.IntegerField(null=True, blank=True)
avg_questions_score = models.FloatField(null=True, blank=True)
median_questions_score = models.FloatField(null=True, blank=True)
q1_questions_score = models.FloatField(null=True, blank=True)
q3_questions_score = models.FloatField(null=True, blank=True)

# Fields for total_score
min_total_score = models.IntegerField(null=True, blank=True)
max_total_score = models.IntegerField(null=True, blank=True)
avg_total_score = models.FloatField(null=True, blank=True)
median_total_score = models.FloatField(null=True, blank=True)
q1_total_score = models.FloatField(null=True, blank=True)
q3_total_score = models.FloatField(null=True, blank=True)

# Fields for learning_in_public_score
min_learning_in_public_score = models.IntegerField(null=True, blank=True)
max_learning_in_public_score = models.IntegerField(null=True, blank=True)
avg_learning_in_public_score = models.FloatField(null=True, blank=True)
median_learning_in_public_score = models.FloatField(null=True, blank=True)
q1_learning_in_public_score = models.FloatField(null=True, blank=True)
q3_learning_in_public_score = models.FloatField(null=True, blank=True)

# Fields for time_spent_lectures
min_time_spent_lectures = models.FloatField(null=True, blank=True)
max_time_spent_lectures = models.FloatField(null=True, blank=True)
avg_time_spent_lectures = models.FloatField(null=True, blank=True)
median_time_spent_lectures = models.FloatField(null=True, blank=True)
q1_time_spent_lectures = models.FloatField(null=True, blank=True)
q3_time_spent_lectures = models.FloatField(null=True, blank=True)

# Fields for time_spent_homework
min_time_spent_homework = models.FloatField(null=True, blank=True)
max_time_spent_homework = models.FloatField(null=True, blank=True)
avg_time_spent_homework = models.FloatField(null=True, blank=True)
median_time_spent_homework = models.FloatField(null=True, blank=True)
q1_time_spent_homework = models.FloatField(null=True, blank=True)
q3_time_spent_homework = models.FloatField(null=True, blank=True)

last_calculated = models.DateTimeField(auto_now=True)

def get_value(self, field_name, stats_type):
attribute_name = f"{stats_type}_{field_name}"
return getattr(self, attribute_name)

def get_stat_fields(self):
results = []

results.append(
("Questions score", [
(self.min_questions_score, "Minimum", "fas fa-arrow-down"),
(self.max_questions_score, "Maximum", "fas fa-arrow-up"),
(self.avg_questions_score, "Average", "fas fa-equals"),
(self.q1_questions_score, "25th Percentile", "fas fa-percentage"),
(self.median_questions_score, "Median", "fas fa-percentage"),
(self.q3_questions_score, "75th Percentile", "fas fa-percentage"),
], 'fas fa-question-circle')
)

results.append(
("Total score", [
(self.min_total_score, "Minimum", "fas fa-arrow-down"),
(self.max_total_score, "Maximum", "fas fa-arrow-up"),
(self.avg_total_score, "Average", "fas fa-equals"),
(self.q1_total_score, "25th Percentile", "fas fa-percentage"),
(self.median_total_score, "Median", "fas fa-percentage"),
(self.q3_total_score, "75th Percentile", "fas fa-percentage"),
], 'fas fa-star')
)

results.append(
("Time spent on lectures", [
(self.min_time_spent_lectures, "Minimum", "fas fa-arrow-down"),
(self.max_time_spent_lectures, "Maximum", "fas fa-arrow-up"),
(self.avg_time_spent_lectures, "Average", "fas fa-equals"),
(self.q1_time_spent_lectures, "25th Percentile", "fas fa-percentage"),
(self.median_time_spent_lectures, "Median", "fas fa-percentage"),
(self.q3_time_spent_lectures, "75th Percentile", "fas fa-percentage"),
], 'fas fa-book-reader')
)

results.append(
("Time spent on homework", [
(self.min_time_spent_homework, "Minimum", "fas fa-arrow-down"),
(self.max_time_spent_homework, "Maximum", "fas fa-arrow-up"),
(self.avg_time_spent_homework, "Average", "fas fa-equals"),
(self.q1_time_spent_homework, "25th Percentile", "fas fa-percentage"),
(self.median_time_spent_homework, "Median", "fas fa-percentage"),
(self.q3_time_spent_homework, "75th Percentile", "fas fa-percentage"),
], 'fas fa-clock')
)

results.append(
("Learning in public score", [
(self.min_learning_in_public_score, "Minimum", "fas fa-arrow-down"),
(self.max_learning_in_public_score, "Maximum", "fas fa-arrow-up"),
(self.avg_learning_in_public_score, "Average", "fas fa-equals"),
(self.q1_learning_in_public_score, "25th Percentile", "fas fa-percentage"),
(self.median_learning_in_public_score, "Median", "fas fa-percentage"),
(self.q3_learning_in_public_score, "75th Percentile", "fas fa-percentage"),
], 'fas fa-globe')
)

return results

def __str__(self):
return f"Statistics for {self.homework.slug}"
4 changes: 4 additions & 0 deletions courses/templates/homework/homework.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ <h2 class="mb-3 text-center">
<p>This homework is already scored. You didn't submit your answers.</p>
</div>
{% endif %}

<div class="pb-2">
<a href="{% url 'homework_statistics' course.slug homework.slug %}">Homework statistics</a>
</div>
{% endif %}

<div>
Expand Down
53 changes: 53 additions & 0 deletions courses/templates/homework/stats.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% extends 'base.html' %}

{% load static %}
{% load stats_filters %}

{% block breadcrumbs %}
<li><a href="{% url 'course' course.slug %}">{{ course.title }}</a></li>
<li><a href="{% url 'homework' course.slug homework.slug %}">{{ homework.title }}</a></li>

{% endblock %}

{% block content %}

<h1 class="mb-4">
{{ homework.title }} statistics
</h1>

<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-users"></i> Total submissions
</h5>
<p class="card-text display-4">{{ stats.total_submissions }}</p>
</div>
</div>


{% for field_name, field_stats, field_icon in stats.get_stat_fields %}
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">
<i class="{{ field_icon }}"></i> {{ field_name }}
</h5>
</div>
<div class="card-body pt-4 pb-3">
<div class="row g-2 mb-0">
{% for value, label, icon in field_stats %}
<div class="col-md-4 col-6">
<h6 class="mb-1"><i class="{{ icon }}"></i> {{ label }}</h6>
<p class="lead mb-2">{{ value|floatformat:0 }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
{% endfor %}

<p class="text-muted">
<i class="fas fa-calendar-alt"></i> Calculated: {{ stats.last_calculated }}
</p>


{% endblock %}
11 changes: 11 additions & 0 deletions courses/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
course.enrollment_view,
name="enrollment",
),

# project
path(
"<slug:course_slug>/project/<slug:project_slug>",
project.project_view,
Expand Down Expand Up @@ -63,11 +65,20 @@
project.projects_eval_delete,
name="projects_eval_delete",
),

# homework
path(
"<slug:course_slug>/homework/<slug:homework_slug>",
homework.homework_view,
name="homework",
),
path(
"<slug:course_slug>/homework/<slug:homework_slug>/stats",
homework.homework_statistics,
name="homework_statistics",
),

# API
path(
"data/<slug:course_slug>/homework/<slug:homework_slug>",
data.homework_data_view,
Expand Down
Loading

0 comments on commit f401745

Please sign in to comment.