From c2ecca692038c8173d5f992c2b05019114e874d9 Mon Sep 17 00:00:00 2001 From: Joseph Bryan Date: Mon, 20 May 2024 22:34:17 -0400 Subject: [PATCH 1/3] changes made from lab env --- existing_file.html | 0 onlinecourse/migrations/0001_initial.py | 78 +++++++++++++++++++++++++ sample.html | 0 3 files changed, 78 insertions(+) create mode 100644 existing_file.html create mode 100644 onlinecourse/migrations/0001_initial.py create mode 100644 sample.html diff --git a/existing_file.html b/existing_file.html new file mode 100644 index 00000000..e69de29b diff --git a/onlinecourse/migrations/0001_initial.py b/onlinecourse/migrations/0001_initial.py new file mode 100644 index 00000000..f4ad5869 --- /dev/null +++ b/onlinecourse/migrations/0001_initial.py @@ -0,0 +1,78 @@ +# Generated by Django 4.2.3 on 2024-05-21 02:27 + +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), + ), + ] diff --git a/sample.html b/sample.html new file mode 100644 index 00000000..e69de29b From f7d5cfbad9f011e4911367ebdd17722e34984c38 Mon Sep 17 00:00:00 2001 From: Joseph Bryan Date: Mon, 20 May 2024 23:02:41 -0400 Subject: [PATCH 2/3] updated models.py and admin.py --- onlinecourse/admin.py | 16 ++++++- ...ice_submission_question_choice_question.py | 44 +++++++++++++++++++ onlinecourse/models.py | 27 ++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 onlinecourse/migrations/0002_choice_submission_question_choice_question.py diff --git a/onlinecourse/admin.py b/onlinecourse/admin.py index ffd8a631..08b1f479 100644 --- a/onlinecourse/admin.py +++ b/onlinecourse/admin.py @@ -1,14 +1,20 @@ from django.contrib import admin # Import any new Models here -from .models import Course, Lesson, Instructor, Learner +from .models import Course, Lesson, Instructor, Learner, Question, Choice, Submission # Register QuestionInline and ChoiceInline classes here - class LessonInline(admin.StackedInline): model = Lesson extra = 5 +class QuestionInline(admin.StackedInline): + model = Question + extra = 2 + +class ChoiceInline(admin.StackedInline): + model = Choice + extra = 2 # Register your models here. class CourseAdmin(admin.ModelAdmin): @@ -21,6 +27,9 @@ class CourseAdmin(admin.ModelAdmin): class LessonAdmin(admin.ModelAdmin): list_display = ['title'] +class QuestionAdmin(admin.ModelAdmin): + inlines = [ChoiceInline] + list_display = ['content'] # Register Question and Choice models here @@ -28,3 +37,6 @@ class LessonAdmin(admin.ModelAdmin): 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) diff --git a/onlinecourse/migrations/0002_choice_submission_question_choice_question.py b/onlinecourse/migrations/0002_choice_submission_question_choice_question.py new file mode 100644 index 00000000..63029d15 --- /dev/null +++ b/onlinecourse/migrations/0002_choice_submission_question_choice_question.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.3 on 2024-05-21 02:53 + +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')), + ('content', models.CharField(max_length=200)), + ('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')), + ('content', models.CharField(max_length=200)), + ('grade', models.IntegerField(default=50)), + ('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'), + ), + ] diff --git a/onlinecourse/models.py b/onlinecourse/models.py index f6fbcbb5..4aa2a9ea 100644 --- a/onlinecourse/models.py +++ b/onlinecourse/models.py @@ -101,3 +101,30 @@ class Enrollment(models.Model): #class Submission(models.Model): # enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE) # choices = models.ManyToManyField(Choice) + +class Question(models.Model): + course = models.ForeignKey(Course, on_delete=models.CASCADE) + content = models.CharField(max_length=200) + grade = models.IntegerField(default=50) + + # display question + def __str__(self): + return "Question: " + self.content + + # method to calculate if the learner gets it correct + 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 + +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE) + content = models.CharField(max_length=200) + is_correct = models.BooleanField(default=False) + +class Submission(models.Model): + enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE) + choices = models.ManyToManyField(Choice) \ No newline at end of file From d1be9cd3736d13125612cec30faf66fa28f7c36b Mon Sep 17 00:00:00 2001 From: Joseph Bryan Date: Tue, 21 May 2024 16:22:06 -0400 Subject: [PATCH 3/3] finished files --- .../onlinecourse/course_detail_bootstrap.html | 30 +++++++++++++- .../onlinecourse/exam_result_bootstrap.html | 22 ++++++++++ onlinecourse/tests.py | 2 +- onlinecourse/urls.py | 5 ++- onlinecourse/views.py | 38 +++++++++++------- .../course_images/image-ibm-question.png | Bin 0 -> 18350 bytes 6 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 static/media/course_images/image-ibm-question.png diff --git a/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html b/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html index fb48e939..9169fd9f 100644 --- a/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html @@ -41,7 +41,7 @@
-

{{ course.name }}

+

{{ course.name }}

{% for lesson in course.lesson_set.all %}
@@ -50,7 +50,33 @@

{{ course.name }}

{% endfor %}
- + {% if user.is_authenticated %} +
+ +
+
+ {% for question in course.question_set.all %} +
+
+
{{ question.content }}
+
+ {% csrf_token %} +
+ {% for choice in question.choice_set.all %} +
+ +
+ {% endfor %} +
+
+ {% endfor %} + +
+
+ {% endif %}
diff --git a/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html b/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html index 264728f5..d1b23715 100644 --- a/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html @@ -39,16 +39,38 @@ {% if grade > 80 %}
+ Congratulations, {{ user.first_name }}! You have passed the exam and completed the course with score {{ grade }}/100
{% else %}
+ Failed Sorry, {{ user.first_name }}! You have failed the exam with score {{ grade }}/100
Re-test {% endif %}
Exam results
+ {% for question in course.question_set.all %} +
+
{{ question.content }}
+
+ {% for choice in question.choice_set.all %} +
+ {% if choice.is_correct and choice in choices %} +
Correct answer: {{ choice.content }}
+ {% else %}{% if choice.is_correct and not choice in choices %} +
Not selected: {{ choice.content }}
+ {% else %}{% if not choice.is_correct and choice in choices %} +
Wrong answer: {{ choice.content }}
+ {% else %} +
{{ choice.content }}
+ {% endif %}{% endif %}{% endif %} +
+ {% endfor %} +
+
+ {% endfor %}
diff --git a/onlinecourse/tests.py b/onlinecourse/tests.py index 7ce503c2..c2629a3a 100644 --- a/onlinecourse/tests.py +++ b/onlinecourse/tests.py @@ -1,3 +1,3 @@ from django.test import TestCase -# Create your tests here. +# Create your tests here. \ No newline at end of file diff --git a/onlinecourse/urls.py b/onlinecourse/urls.py index 2960e104..7134f030 100644 --- a/onlinecourse/urls.py +++ b/onlinecourse/urls.py @@ -18,7 +18,8 @@ path('/enroll/', views.enroll, name='enroll'), # Create a route for submit view - + path('/submit/', views.submit, name="submit"), # Create a route for show_exam_result view + path('course//submission//result/', views.show_exam_result, name="exam_result"), - ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/onlinecourse/views.py b/onlinecourse/views.py index 53eb02f0..eba77894 100644 --- a/onlinecourse/views.py +++ b/onlinecourse/views.py @@ -1,6 +1,6 @@ from django.shortcuts import render from django.http import HttpResponseRedirect -# Import any new Models here +from .models import Course, Enrollment, Question, Choice, Submission from .models import Course, Enrollment from django.contrib.auth.models import User from django.shortcuts import get_object_or_404, render, redirect @@ -105,12 +105,15 @@ def enroll(request, course_id): # 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 @@ -126,11 +129,16 @@ def extract_answers(request): # 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): - - - +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 + for choice in choices: + if choice.is_correct: + total_score += choice.question.grade + context['course'] = course + context['grade'] = total_score + context['choices'] = choices + return render(request, 'onlinecourse/exam_result_bootstrap.html', context) \ No newline at end of file diff --git a/static/media/course_images/image-ibm-question.png b/static/media/course_images/image-ibm-question.png new file mode 100644 index 0000000000000000000000000000000000000000..7726870a902ab29e9535be94b751ebf6bf0afe0f GIT binary patch literal 18350 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 YdU-E6Rek4q_)j=Wa_X|>&rL)A7uey5Hvj+t literal 0 HcmV?d00001