Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions onlinecourse/admin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from django.contrib import admin
# <HINT> Import any new Models here
from .models import Course, Lesson, Instructor, Learner
from .models import Course, Lesson, Instructor, Learner, Question, Choice, Submission

# <HINT> Register QuestionInline and ChoiceInline classes here
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 2

class QuestionInline(admin.StackedInline):
model = Question
extra = 2

class LessonInline(admin.StackedInline):
model = Lesson
extra = 5


# Register your models here.
class CourseAdmin(admin.ModelAdmin):
inlines = [LessonInline]
Expand All @@ -21,10 +26,16 @@ class CourseAdmin(admin.ModelAdmin):
class LessonAdmin(admin.ModelAdmin):
list_display = ['title']

class QuestionAdmin(admin.ModelAdmin):
inlines = [ChoiceInline]
list_display = ['question_text']

# <HINT> Register Question and Choice models here

# <HINT> Register Question and Choice models here
admin.site.register(Course, CourseAdmin)
admin.site.register(Lesson, LessonAdmin)
admin.site.register(Instructor)
admin.site.register(Learner)
admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice)
admin.site.register(Submission)
78 changes: 78 additions & 0 deletions onlinecourse/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Generated by Django 4.2.4 on 2025-05-12 23:33

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='Course',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='online course', max_length=30)),
('image', models.ImageField(upload_to='course_images/')),
('description', models.CharField(max_length=1000)),
('pub_date', models.DateField(null=True)),
('total_enrollment', models.IntegerField(default=0)),
],
),
migrations.CreateModel(
name='Lesson',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(default='title', max_length=200)),
('order', models.IntegerField(default=0)),
('content', models.TextField()),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.course')),
],
),
migrations.CreateModel(
name='Learner',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('occupation', models.CharField(choices=[('student', 'Student'), ('developer', 'Developer'), ('data_scientist', 'Data Scientist'), ('dba', 'Database Admin')], default='student', max_length=20)),
('social_link', models.URLField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Instructor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('full_time', models.BooleanField(default=True)),
('total_learners', models.IntegerField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Enrollment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_enrolled', models.DateField(default=django.utils.timezone.now)),
('mode', models.CharField(choices=[('audit', 'Audit'), ('honor', 'Honor'), ('BETA', 'BETA')], default='audit', max_length=5)),
('rating', models.FloatField(default=5.0)),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.course')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='course',
name='instructors',
field=models.ManyToManyField(to='onlinecourse.instructor'),
),
migrations.AddField(
model_name='course',
name='users',
field=models.ManyToManyField(through='onlinecourse.Enrollment', to=settings.AUTH_USER_MODEL),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.2.4 on 2025-05-13 00:54

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


class Migration(migrations.Migration):

dependencies = [
('onlinecourse', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Choice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choice_text', models.TextField()),
('is_correct', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='Submission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choices', models.ManyToManyField(to='onlinecourse.choice')),
('enrollment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.enrollment')),
],
),
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_text', models.TextField()),
('grade_point', models.IntegerField()),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.course')),
],
),
migrations.AddField(
model_name='choice',
name='question',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.question'),
),
]
41 changes: 38 additions & 3 deletions onlinecourse/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,45 @@ class Enrollment(models.Model):
mode = models.CharField(max_length=5, choices=COURSE_MODES, default=AUDIT)
rating = models.FloatField(default=5.0)

# <HINT> Create a Question Model with:
# Used to persist questions for a course
# Has a Many-To-One relationship with the course
# Has question text
# Has a grade point for each question
class Question(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE)
question_text = models.TextField()
grade_point = models.IntegerField()

def __str__(self):
return "Question: " + self.question_text

# <HINT> A sample model method to calculate if learner get the score of the question
def is_get_score(self, selected_ids):
all_answers = self.choice_set.filter(is_correct=True).count()
selected_correct = self.choice_set.filter(is_correct=True, id__in=selected_ids).count()
if all_answers == selected_correct:
return True
else:
return False

# <HINT> Create a Choice Model with:
# Used to persist choice content for a question
# Has a Many-To-One relationship with the Question model
# Has choice text
# Has a boolean field to indicate if the choice is correct
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.TextField()
is_correct = models.BooleanField(default=False)

def __str__(self):
return self.choice_text

# One enrollment could have multiple submission
# One submission could have multiple choices
# One choice could belong to multiple submissions
#class Submission(models.Model):
# enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE)
# choices = models.ManyToManyField(Choice)
class Submission(models.Model):
enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE)
choices = models.ManyToManyField(Choice)

38 changes: 24 additions & 14 deletions onlinecourse/templates/onlinecourse/course_detail_bootstrap.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,31 @@
</div>
<ul class="nav navbar-nav navbar-right">
{% if user.is_authenticated %}
<li>
<a class="btn btn-link" href="#">{{ user.first_name }}({{ user.username }})</a>
<a class="btn btn-link" href="{% url 'onlinecourse:logout' %}">Logout</a>
</li>
{% else %}
<li>
<form class="form-inline" action="{% url 'onlinecourse:login' %}" method="post">
{% csrf_token %}
<div class="input-group">
<input type="text" class="form-control" placeholder="Username" name="username" >
<input type="password" class="form-control" placeholder="Username" name="psw" >
<button class="btn btn-primary" type="submit">Login</button>
<a class="btn btn-link" href="{% url 'onlinecourse:registration' %}">Sign Up</a>
</br>
<button class="btn btn-primary btn-block" data-toggle="collapse" data-target="#exam">Start Exam</button>
<div id="exam" class="collapse">
<form id="questionform" action="{% url 'onlinecourse:submit' course.id %}" method="POST">
{% for question in course.question_set.all %}
<div class="card mt-1">
<div class="card-header">
<h5>{{ question.question_text }}</h5>
</div>
{% csrf_token %}
<div class="form-group">
{% for choice in question.choice_set.all %}
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" name="choice_{{choice.id}}" class="form-check-input"
id="{{choice.id}}" value="{{choice.id}}">{{ choice.choice_text }}
</label>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<input class="btn btn-success btn-block" type="submit" value="Submit">
</form>
</li>
</div>
{% endif %}
</ul>
</div>
Expand All @@ -54,3 +63,4 @@ <h2>{{ course.name }}</h2>
</div>
</body>
</html>

26 changes: 21 additions & 5 deletions onlinecourse/templates/onlinecourse/exam_result_bootstrap.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,34 @@
<div class="container-fluid">
{% if grade > 80 %}
<div class="alert alert-success">
<!--HINT Display passed info -->
<b>Congratulations, {{ user.first_name }}!</b> You have passed the exam and completed the course with score {{ grade }}/100
</div>
{% else %}
<div class="alert alert-danger">
<!--HINT Display failed info -->
<b>Failed</b> Sorry, {{ user.first_name }}! You have failed the exam with score {{ grade }}/100
</div>
<a class="btn btn-link text-danger" href="{% url 'onlinecourse:course_details' course.id %}">Re-test</a>
{% endif %}
<div class="card-columns-vertical mt-1">
<h5 class="">Exam results</h5>
<!--HINT Display exam results-->
{% for question in course.question_set.all %}
<div class="card mt-1">
<div class="card-header"><h5>{{ question.question_text }}</h5></div>
<div class="form-group">
{% for choice in question.choice_set.all %}
<div class="form-check">
{% if choice.is_correct and choice in choices %}
<div class="text-success">Correct answer: {{ choice.choice_text }}</div>
{% else %}{% if choice.is_correct and not choice in choices %}
<div class="text-warning">Not selected: {{ choice.choice_text }}</div>
{% else %}{% if not choice.is_correct and choice in choices %}
<div class="text-danger">Wrong answer: {{ choice.choice_text }}</div>
{% else %}
<div>{{ choice.choice_text }}</div>
{% endif %}{% endif %}{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</body>
</html>
4 changes: 2 additions & 2 deletions onlinecourse/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
path('<int:course_id>/enroll/', views.enroll, name='enroll'),

# <HINT> Create a route for submit view

path('<int:course_id>/submit/', views.submit, name="submit"),
# <HINT> Create a route for show_exam_result view

path('course/<int:course_id>/submission/<int:submission_id>/result/', views.show_exam_result, name="exam_result"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
51 changes: 33 additions & 18 deletions onlinecourse/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.shortcuts import render
from django.http import HttpResponseRedirect
# <HINT> Import any new Models here
from .models import Course, Enrollment
from .models import Course, Enrollment, Question, Choice, Submission
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404, render, redirect
from django.urls import reverse
Expand Down Expand Up @@ -103,16 +103,17 @@ def enroll(request, course_id):
return HttpResponseRedirect(reverse(viewname='onlinecourse:course_details', args=(course.id,)))


# <HINT> Create a submit view to create an exam submission record for a course enrollment,
# you may implement it based on following logic:
# Get user and course object, then get the associated enrollment object created when the user enrolled the course
# Create a submission object referring to the enrollment
# Collect the selected choices from exam form
# Add each selected choice object to the submission object
# Redirect to show_exam_result with the submission id
#def submit(request, course_id):


def submit(request, course_id):
course = get_object_or_404(Course, pk=course_id)
user = request.user
enrollment = Enrollment.objects.get(user=user, course=course)
submission = Submission.objects.create(enrollment=enrollment)
choices = extract_answers(request)
submission.choices.set(choices)
submission_id = submission.id
return HttpResponseRedirect(reverse(viewname='onlinecourse:exam_result', args=(course_id, submission_id,)))


# An example method to collect the selected choices from the exam form from the request object
def extract_answers(request):
submitted_anwsers = []
Expand All @@ -124,13 +125,27 @@ def extract_answers(request):
return submitted_anwsers


# <HINT> Create an exam result view to check if learner passed exam and show their question results and result for each question,
# you may implement it based on the following logic:
# Get course and submission based on their ids
# Get the selected choice ids from the submission record
# For each selected choice, check if it is a correct answer or not
# Calculate the total score
#def show_exam_result(request, course_id, submission_id):
# Create an exam result view to check if learner passed exam and show their question results and result for each question,
def show_exam_result(request, course_id, submission_id):
context = {}
course = get_object_or_404(Course, pk=course_id)
submission = Submission.objects.get(id=submission_id)
choices = submission.choices.all()

total_score = 0
questions = course.question_set.all() # Assuming course has related questions

for question in questions:
correct_choices = question.choice_set.filter(is_correct=True) # Get all correct choices for the question
selected_choices = choices.filter(question=question) # Get the user's selected choices for the question

# Check if the selected choices are the same as the correct choices
if set(correct_choices) == set(selected_choices):
total_score += question.grade_point # Add the question's grade only if all correct answers are selected

context['course'] = course
context['grade'] = total_score
context['choices'] = choices

return render(request, 'onlinecourse/exam_result_bootstrap.html', context)

Loading