From 5e670ce48206507829d56921a99065677455f159 Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Mon, 4 Sep 2023 01:34:09 +0800 Subject: [PATCH 1/6] Project is done --- db.sqlite3 | Bin 0 -> 258048 bytes manage.py | 0 myproject/settings.py | 3 + onlinecourse/admin.py | 30 ++- onlinecourse/migrations/0001_initial.py | 108 ++++++++ .../migrations/0002_auto_20230901_0926.py | 23 ++ onlinecourse/models.py | 36 +-- .../onlinecourse/course_detail_bootstrap.html | 179 +++++++++----- .../onlinecourse/course_list_bootstrap.html | 150 +++++++----- .../onlinecourse/exam_result_bootstrap.html | 157 ++++++++---- .../onlinecourse/user_login_bootstrap.html | 8 +- .../user_registration_bootstrap.html | 4 +- onlinecourse/urls.py | 31 ++- onlinecourse/views.py | 231 ++++++++++++++---- requirements.txt | 20 +- runtime.txt | 2 +- static/media/course_images/django_6KDa7yI.png | Bin 0 -> 10591 bytes static/media/course_images/python_DzFSaZ6.png | Bin 0 -> 9955 bytes static/onlinecourse/course.css | 47 ++-- 19 files changed, 750 insertions(+), 279 deletions(-) create mode 100644 db.sqlite3 mode change 100755 => 100644 manage.py create mode 100644 onlinecourse/migrations/0001_initial.py create mode 100644 onlinecourse/migrations/0002_auto_20230901_0926.py create mode 100644 static/media/course_images/django_6KDa7yI.png create mode 100644 static/media/course_images/python_DzFSaZ6.png diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..de26f37ba3aee493f5f0f78ba3fef18993f4d19a GIT binary patch literal 258048 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 literal 0 HcmV?d00001 diff --git a/manage.py b/manage.py old mode 100755 new mode 100644 diff --git a/myproject/settings.py b/myproject/settings.py index f9d48c9bb..0c5a1c139 100644 --- a/myproject/settings.py +++ b/myproject/settings.py @@ -12,6 +12,9 @@ import os +# Defauly AUTO Field +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/onlinecourse/admin.py b/onlinecourse/admin.py index ffd8a631d..4036ad32f 100644 --- a/onlinecourse/admin.py +++ b/onlinecourse/admin.py @@ -1,10 +1,16 @@ 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 # Register QuestionInline and ChoiceInline classes here +class ChoiceInline(admin.TabularInline): + model = Choice + extra = 3 + + class LessonInline(admin.StackedInline): model = Lesson extra = 5 @@ -13,18 +19,32 @@ class LessonInline(admin.StackedInline): # Register your models here. class CourseAdmin(admin.ModelAdmin): inlines = [LessonInline] - list_display = ('name', 'pub_date') - list_filter = ['pub_date'] - search_fields = ['name', 'description'] + list_display = ("name", "pub_date") + list_filter = ["pub_date"] + search_fields = ["name", "description"] class LessonAdmin(admin.ModelAdmin): - list_display = ['title'] + list_display = ["title"] + + +class QuestionAdmin(admin.ModelAdmin): + list_display = ("question_text", "grade", "course") + list_filter = ("course",) + search_fields = ("question_text",) + inlines = [ChoiceInline] + + +class ChoiceAdmin(admin.ModelAdmin): + list_display = ("choice_text", "is_correct") + list_filter = ("question",) # Register Question and Choice models here admin.site.register(Course, CourseAdmin) admin.site.register(Lesson, LessonAdmin) +admin.site.register(Question, QuestionAdmin) +admin.site.register(Choice, ChoiceAdmin) admin.site.register(Instructor) admin.site.register(Learner) diff --git a/onlinecourse/migrations/0001_initial.py b/onlinecourse/migrations/0001_initial.py new file mode 100644 index 000000000..2291effd3 --- /dev/null +++ b/onlinecourse/migrations/0001_initial.py @@ -0,0 +1,108 @@ +# Generated by Django 3.1.3 on 2023-09-01 08:13 + +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='Choice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('choice_text', models.CharField(default='choice', max_length=200)), + ('is_correct', models.BooleanField(default=False)), + ], + ), + 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='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.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.CharField(default='question', max_length=200)), + ('grade', models.IntegerField(default=0)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.course')), + ], + ), + 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.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), + ), + migrations.AddField( + model_name='choice', + name='question', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='onlinecourse.question'), + ), + ] diff --git a/onlinecourse/migrations/0002_auto_20230901_0926.py b/onlinecourse/migrations/0002_auto_20230901_0926.py new file mode 100644 index 000000000..4594c5789 --- /dev/null +++ b/onlinecourse/migrations/0002_auto_20230901_0926.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.3 on 2023-09-01 09:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('onlinecourse', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='choice', + name='choice_text', + field=models.CharField(max_length=200), + ), + migrations.AlterField( + model_name='question', + name='question_text', + field=models.CharField(max_length=200), + ), + ] diff --git a/onlinecourse/models.py b/onlinecourse/models.py index e5b540b16..7a8b4a433 100644 --- a/onlinecourse/models.py +++ b/onlinecourse/models.py @@ -101,19 +101,19 @@ class Enrollment(models.Model): # Has a grade point for each question # Has question content # Other fields and methods you would like to design -#class Question(models.Model): - # Foreign key to lesson - # question text - # question grade/mark - +class Question(models.Model): + course = models.ForeignKey(Course, on_delete=models.CASCADE) + question_text = models.CharField(max_length=200) + grade = models.IntegerField(default=0) + # 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 + 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 # Create a Choice Model with: @@ -122,13 +122,17 @@ class Enrollment(models.Model): # Choice content # Indicate if this choice of the question is a correct one or not # Other fields and methods you would like to design -# class Choice(models.Model): +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE) + choice_text = models.CharField(max_length=200) + is_correct = models.BooleanField(default=False) # The submission model # 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) + # Other fields and methods you would like to design \ No newline at end of file diff --git a/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html b/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html index 7a22e1694..555c08439 100644 --- a/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/course_detail_bootstrap.html @@ -1,80 +1,142 @@ - - {% load static %} - - - - - - + + {% load static %} + + + + + + + + + + + + +

{{ course.name }}

{% for lesson in course.lesson_set.all %}
-
Lesson {{lesson.order|add:1}}: {{lesson.title}}
-
{{lesson.content}}
+
+
Lesson {{ forloop.counter }}: {{ lesson.title }}
+
+
{{ lesson.content }}
{% endfor %}
+
+ +
+ {% if user.is_authenticated %} +
+
+
+ {% for questions in question %} +
+
Question {{ forloop.counter }}: {{ questions.question_text }}
+
+ {% csrf_token %} + {% for choices in choice %} + {% if questions.id == choices.question_id %} +
+
+ +
+
+ {% endif %} + {% endfor %} + {% endfor %} + +
+
+
+ {% endif %} +
+
- +https://www.w3schools.com/bootstrap4/bootstrap_collapse.asp--> - +--> - - - - - +--> - + --> -
- - \ No newline at end of file + + + diff --git a/onlinecourse/templates/onlinecourse/course_list_bootstrap.html b/onlinecourse/templates/onlinecourse/course_list_bootstrap.html index 34793b319..983cd91d5 100644 --- a/onlinecourse/templates/onlinecourse/course_list_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/course_list_bootstrap.html @@ -1,65 +1,97 @@ - - {% load static %} - - - Online Courses - - - - + + {% load static %} + + + + Online Courses + + + + + + + {% if course_list %} +
+
+ {% for course in course_list %} +
+ Course image +
+
+ {{ course.name }}, {{ course.total_enrollment }} enrolled +
+

{{ course.description }}

+
+ {% csrf_token %} + +
+
- {% endfor %} + {% endfor %} +
- - {% else %} -

No courses are available.

- {% endif %} - - \ No newline at end of file + {% else %} +

No courses are available.

+ {% endif %} + + diff --git a/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html b/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html index 264728f5c..e561a134d 100644 --- a/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/exam_result_bootstrap.html @@ -1,55 +1,112 @@ - - - {% load static %} - - - - - - -
- {% if grade > 80 %} -
- -
- {% else %} -
- -
- Re-test - {% endif %} -
-
Exam results
- +
+
Exam results
+ + {% for question in course.question_set.all %} +
+
+
{{ question.question_text }}
+
+
+ {% for choice in question.choice_set.all %} + {% if choice.is_correct and choice in choices %} +

Correct answer: {{ choice.choice_text }}

+ {% elif choice.is_correct and choice not in choices %} +

Not selected: {{ choice.choice_text }}

+ {% elif choice.is_correct == False and choice in choices %} +

Wrong answer: {{ choice.choice_text }}

+ {% else %} +

{{ choice.choice_text }}

+ {% endif %} + {% endfor %} +
+
+ {% endfor %} + {% comment %}
+ {% for questions in questioners %} +
+
+
Question {{ forloop.counter }}: {{ questions.question_text }}
+
+ {% for choices in choices %} + {% if questions.id == choices.question_id %} +
+
+ {% for item in question_results %} + {% if item.selected_choice == choices.choice_text and item.is_correct %} + + {% elif item.selected_choice != choices.choice_text and item.is_correct %} + + {% elif item.selected_choice == choices.choice_text and not item.is_correct %} + + {% else %} + + {% endif %} + {% endfor %} +
+
+ {% endif %} + {% endfor %} + {% endfor %} +
+
{% endcomment %} +
-
- - \ No newline at end of file + + diff --git a/onlinecourse/templates/onlinecourse/user_login_bootstrap.html b/onlinecourse/templates/onlinecourse/user_login_bootstrap.html index c58611c57..0d4c20710 100644 --- a/onlinecourse/templates/onlinecourse/user_login_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/user_login_bootstrap.html @@ -4,6 +4,7 @@ {% load static %} + @@ -43,9 +44,9 @@

Login

- + - +
{% if message %}
@@ -56,4 +57,5 @@

Login

- \ No newline at end of file + + diff --git a/onlinecourse/templates/onlinecourse/user_registration_bootstrap.html b/onlinecourse/templates/onlinecourse/user_registration_bootstrap.html index 14dc39584..e8b66b6e2 100644 --- a/onlinecourse/templates/onlinecourse/user_registration_bootstrap.html +++ b/onlinecourse/templates/onlinecourse/user_registration_bootstrap.html @@ -4,6 +4,7 @@ {% load static %} + @@ -61,4 +62,5 @@

Sign Up

- \ No newline at end of file + + diff --git a/onlinecourse/urls.py b/onlinecourse/urls.py index 2960e1045..bf120c5f5 100644 --- a/onlinecourse/urls.py +++ b/onlinecourse/urls.py @@ -3,22 +3,31 @@ from django.conf.urls.static import static from . import views -app_name = 'onlinecourse' +app_name = "onlinecourse" urlpatterns = [ # route is a string contains a URL pattern # view refers to the view function # name the URL - path(route='', view=views.CourseListView.as_view(), name='index'), - path('registration/', views.registration_request, name='registration'), - path('login/', views.login_request, name='login'), - path('logout/', views.logout_request, name='logout'), + path(route="", view=views.CourseListView.as_view(), name="index"), + path("registration/", views.registration_request, name="registration"), + path("login/", views.login_request, name="login"), + path("logout/", views.logout_request, name="logout"), + # question path + path( + "/", + views.combined_course_detail, + name="course_and_question_detail", + ), # ex: /onlinecourse/5/ - path('/', views.CourseDetailView.as_view(), name='course_details'), + path("/", views.CourseDetailView.as_view(), name="course_details"), # ex: /enroll/5/ - path('/enroll/', views.enroll, name='enroll'), - + path("/enroll/", views.enroll, name="enroll"), # Create a route for submit view - + path("/submit/", views.submit_view, name="submit"), # Create a route for show_exam_result view - - ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + path( + "course//submission//result/", + views.show_exam_result, + name="show_exam_result", + ), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/onlinecourse/views.py b/onlinecourse/views.py index 6567363ea..a59827553 100644 --- a/onlinecourse/views.py +++ b/onlinecourse/views.py @@ -1,28 +1,51 @@ +from typing import Any +from django.db import models from django.shortcuts import render from django.http import HttpResponseRedirect + # 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 from django.views import generic from django.contrib.auth import login, logout, authenticate + import logging + # Get an instance of a logger logger = logging.getLogger(__name__) # Create your views here. +# def question_detail(request, question_id): +# question = Question.objects.get(pk=question_id) +# context = { +# 'question': question, +# 'question_id': question_id, +# } +# return render(request, 'onlinecourse/course_detail_bootstrap.html', context) + + +# def question_detail(request, course_id): +# question = Question.objects.get(pk=course_id) +# context = { +# 'question': question, +# 'course_id': course_id, +# } +# return render(request, 'onlinecourse/course_detail_bootstrap.html', context) + + def registration_request(request): context = {} - if request.method == 'GET': - return render(request, 'onlinecourse/user_registration_bootstrap.html', context) - elif request.method == 'POST': + if request.method == "GET": + return render(request, "onlinecourse/user_registration_bootstrap.html", context) + elif request.method == "POST": # Check if user exists - username = request.POST['username'] - password = request.POST['psw'] - first_name = request.POST['firstname'] - last_name = request.POST['lastname'] + username = request.POST["username"] + password = request.POST["psw"] + first_name = request.POST["firstname"] + last_name = request.POST["lastname"] user_exist = False try: User.objects.get(username=username) @@ -30,34 +53,40 @@ def registration_request(request): except: logger.error("New user") if not user_exist: - user = User.objects.create_user(username=username, first_name=first_name, last_name=last_name, - password=password) + user = User.objects.create_user( + username=username, + first_name=first_name, + last_name=last_name, + password=password, + ) login(request, user) return redirect("onlinecourse:index") else: - context['message'] = "User already exists." - return render(request, 'onlinecourse/user_registration_bootstrap.html', context) + context["message"] = "User already exists." + return render( + request, "onlinecourse/user_registration_bootstrap.html", context + ) def login_request(request): context = {} if request.method == "POST": - username = request.POST['username'] - password = request.POST['psw'] + username = request.POST["username"] + password = request.POST["psw"] user = authenticate(username=username, password=password) if user is not None: login(request, user) - return redirect('onlinecourse:index') + return redirect("onlinecourse:index") else: - context['message'] = "Invalid username or password." - return render(request, 'onlinecourse/user_login_bootstrap.html', context) + context["message"] = "Invalid username or password." + return render(request, "onlinecourse/user_login_bootstrap.html", context) else: - return render(request, 'onlinecourse/user_login_bootstrap.html', context) + return render(request, "onlinecourse/user_login_bootstrap.html", context) def logout_request(request): logout(request) - return redirect('onlinecourse:index') + return redirect("onlinecourse:index") def check_if_enrolled(user, course): @@ -72,12 +101,12 @@ def check_if_enrolled(user, course): # CourseListView class CourseListView(generic.ListView): - template_name = 'onlinecourse/course_list_bootstrap.html' - context_object_name = 'course_list' + template_name = "onlinecourse/course_list_bootstrap.html" + context_object_name = "course_list" def get_queryset(self): user = self.request.user - courses = Course.objects.order_by('-total_enrollment')[:10] + courses = Course.objects.order_by("-total_enrollment")[:10] for course in courses: if user.is_authenticated: course.is_enrolled = check_if_enrolled(user, course) @@ -86,7 +115,30 @@ def get_queryset(self): class CourseDetailView(generic.DetailView): model = Course - template_name = 'onlinecourse/course_detail_bootstrap.html' + template_name = "onlinecourse/course_detail_bootstrap.html" + + +def combined_course_detail(request, course_id): + course = get_object_or_404(Course, pk=course_id) + question = Question.objects.filter(course=course_id) + choice = Choice.objects.order_by("-question_id")[:20] + + context = { + "course": course, + "question": question, + "choice": choice, + } + + return render(request, "onlinecourse/course_detail_bootstrap.html", context) + + +# class QuestionListView(generic.ListView): +# template_name = 'onlinecourse/course_detail_bootstrap.html' +# context_object_name = 'question_list' + +# def get_queryset(self): +# question = Question.objects.order_by('-grade')[:10] +# return question def enroll(request, course_id): @@ -96,41 +148,132 @@ def enroll(request, course_id): is_enrolled = check_if_enrolled(user, course) if not is_enrolled and user.is_authenticated: # Create an enrollment - Enrollment.objects.create(user=user, course=course, mode='honor') + Enrollment.objects.create(user=user, course=course, mode="honor") course.total_enrollment += 1 course.save() - return HttpResponseRedirect(reverse(viewname='onlinecourse:course_details', args=(course.id,))) + return HttpResponseRedirect( + reverse(viewname="onlinecourse:course_details", args=(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): +# 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_view(request, course_id): + # Get the current user and the course object, then get the associated the enrollment object + user = request.user + course = get_object_or_404(Course, pk=course_id) + enrollment = Enrollment.objects.get(user=user, course=course) + + if request.method == "POST": + # Create a submission object referring to the enrollment + submission = Submission.objects.create(enrollment=enrollment) + submission.save() + + # Collect the selected choices from HTTP request object + selected_choices = extract_answers(request) + + # Add each selected choice object to the submission object + for choice_id in selected_choices: + submission.choices.add(choice_id) + + # Redirect to a show_exam_result view with the submission id to show the exam result + # return redirect("show_exam_result/" + f"{submission.id}") + + return HttpResponseRedirect( + reverse( + viewname="onlinecourse:show_exam_result", + args=( + course.id, + submission.id, + ), + ) + ) + + # # Render the exam submission form + # choices = Choice.objects.filter(question__course=course) + # return render( + # request, "onlinecourse/exam_result_bootstrap.html", {"choices": choices} + # ) # A example method to collect the selected choices from the exam form from the request object -#def extract_answers(request): -# submitted_anwsers = [] -# for key in request.POST: -# if key.startswith('choice'): -# value = request.POST[key] -# choice_id = int(value) -# submitted_anwsers.append(choice_id) -# return submitted_anwsers +def extract_answers(request): + submitted_anwsers = [] + for key in request.POST: + if key.startswith("choice"): + value = request.POST[key] + choice_id = int(value) + submitted_anwsers.append(choice_id) + return submitted_anwsers # 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): +# 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): + # Get the course object and submission object based on their ids in view arguments + course = get_object_or_404(Course, pk=course_id) + submission = get_object_or_404(Submission, pk=submission_id) + + questioners = Question.objects.filter(course=course_id) + choices = submission.choices.all() + + # Get the selected choice ids from the submission record + # selected_choice_ids = submission.choices.filter(question__course=course).values_list("id", flat=True) + + selected_choice_ids = ( + submission.choices.all() + .filter(question__course=course) + .values_list("id", flat=True) + ) + + total_score = 0 + question_results = [] + + # For each selected choice, check if it is a correct answer or not + for choice_id in selected_choice_ids: + choice = get_object_or_404(Choice, pk=choice_id) + question = Question.objects.get(pk=choice.question_id) + + # Check if the selected choice is correct + is_correct = choice.is_correct + + # Calculate the total score by adding up the grades for all questions in the course + if is_correct: + total_score += question.grade + print(total_score) + + # Append the result for each question + question_results.append( + { + "question_text": question.question_text, + "selected_choice": choice.choice_text, + "is_correct": is_correct, + } + ) + print(question_results) + # Add the course, selected_ids, and grade to context for rendering HTML page + context = { + "course": course, + "submission": submission, + "total_score": total_score, + "selected_choice_ids": selected_choice_ids, + "question_results": question_results, + "questioners": questioners, + "choices": choices, + } + return render(request, "onlinecourse/exam_result_bootstrap.html", context) diff --git a/requirements.txt b/requirements.txt index 39b095d51..bf8774b4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -Django==3.1.3 -Pillow==8.0.1 -jinja2==3.0 -gunicorn==20.1.0 -contextvars -typing-extensions==4.1.1 -aiohttp==3.8.1 -click==8.0.4 -wheel==0.37.1 -multidict==4.5 +Django==3.1.3 +Pillow==10.0.0 +jinja2==3.0 +gunicorn==20.1.0 +contextvars +typing-extensions==4.2.0 +aiohttp==3.8.3 +click==8.0.4 +wheel==0.41.1 +multidict==4.5 \ No newline at end of file diff --git a/runtime.txt b/runtime.txt index 5b3694c1c..e1958b91b 100644 --- a/runtime.txt +++ b/runtime.txt @@ -1 +1 @@ -python-3.8.13 +python-3.8.13 \ No newline at end of file diff --git a/static/media/course_images/django_6KDa7yI.png b/static/media/course_images/django_6KDa7yI.png new file mode 100644 index 0000000000000000000000000000000000000000..d425493c0fc7b8a2027a74149e27c6ce10c9be67 GIT binary patch literal 10591 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 P#sMr$Y>g`n>2d!b!VCd- literal 0 HcmV?d00001 diff --git a/static/onlinecourse/course.css b/static/onlinecourse/course.css index 3df1544d8..dcc3201b5 100644 --- a/static/onlinecourse/course.css +++ b/static/onlinecourse/course.css @@ -1,6 +1,10 @@ -body {font-family: Arial, Helvetica, sans-serif;} -* {box-sizing: border-box} +body { + font-family: Arial, Helvetica, sans-serif; +} +* { + box-sizing: border-box; +} /* Add a gray background color with some padding */ body { @@ -17,7 +21,6 @@ body { background: white; } - /* Create two unequal columns that floats next to each other */ /* Left column */ .leftcolumn { @@ -41,9 +44,9 @@ body { /* Add a card effect for articles */ .card { - background-color: white; - padding: 20px; - margin-top: 20px; + background-color: white; + padding: 20px; + margin-top: 20px; } /* Clear floats after the columns */ @@ -63,13 +66,13 @@ body { /* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */ @media screen and (max-width: 800px) { - .leftcolumn, .rightcolumn { + .leftcolumn, + .rightcolumn { width: 100%; padding: 0; } } - .button { border: none; color: white; @@ -88,17 +91,15 @@ body { } .rightalign { - float: right; + float: right; } - .column { float: left; width: 33.33%; padding: 5px; } - /* 2/3 column */ .column-66 { float: left; @@ -123,7 +124,8 @@ body { } /* Full-width input fields */ -input[type=text], input[type=password] { +input[type="text"], +input[type="password"] { width: 100%; padding: 15px; margin: 5px 0 22px 0; @@ -132,13 +134,14 @@ input[type=text], input[type=password] { background: #dadfe1; } -input[type=text]:focus, input[type=password]:focus { +input[type="text"]:focus, +input[type="password"]:focus { background-color: #ddd; outline: none; } -.red{ - color:red +.red { + color: red; } .dropbtn { @@ -159,7 +162,7 @@ input[type=text]:focus, input[type=password]:focus { position: absolute; background-color: #f1f1f1; min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); z-index: 1; } @@ -170,8 +173,14 @@ input[type=text]:focus, input[type=password]:focus { display: block; } -.dropdown-content a:hover {background-color: #ddd;} +.dropdown-content a:hover { + background-color: #ddd; +} -.dropdown:hover .dropdown-content {display: block;} +.dropdown:hover .dropdown-content { + display: block; +} -.dropdown:hover .dropbtn {background-color: darkblue;} \ No newline at end of file +.dropdown:hover .dropbtn { + background-color: darkblue; +} From c47b6f0361ef98496ecc7726ea65ea1ec4c6c6c8 Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Mon, 4 Sep 2023 01:53:48 +0800 Subject: [PATCH 2/6] updating --- onlinecourse/admin.py | 6 --- onlinecourse/models.py | 95 ++++++++++++++++-------------------------- onlinecourse/tests.py | 1 - onlinecourse/urls.py | 19 ++++----- onlinecourse/views.py | 74 ++++---------------------------- 5 files changed, 50 insertions(+), 145 deletions(-) diff --git a/onlinecourse/admin.py b/onlinecourse/admin.py index 4036ad32f..faa0e191d 100644 --- a/onlinecourse/admin.py +++ b/onlinecourse/admin.py @@ -1,10 +1,7 @@ from django.contrib import admin -# Import any new Models here from .models import Course, Lesson, Instructor, Learner, Question, Choice -# Register QuestionInline and ChoiceInline classes here - class ChoiceInline(admin.TabularInline): model = Choice @@ -16,7 +13,6 @@ class LessonInline(admin.StackedInline): extra = 5 -# Register your models here. class CourseAdmin(admin.ModelAdmin): inlines = [LessonInline] list_display = ("name", "pub_date") @@ -40,8 +36,6 @@ class ChoiceAdmin(admin.ModelAdmin): list_filter = ("question",) -# Register Question and Choice models here - admin.site.register(Course, CourseAdmin) admin.site.register(Lesson, LessonAdmin) admin.site.register(Question, QuestionAdmin) diff --git a/onlinecourse/models.py b/onlinecourse/models.py index 7a8b4a433..d4cd6a71d 100644 --- a/onlinecourse/models.py +++ b/onlinecourse/models.py @@ -1,5 +1,6 @@ import sys from django.utils.timezone import now + try: from django.db import models except Exception: @@ -29,43 +30,38 @@ class Learner(models.Model): settings.AUTH_USER_MODEL, on_delete=models.CASCADE, ) - STUDENT = 'student' - DEVELOPER = 'developer' - DATA_SCIENTIST = 'data_scientist' - DATABASE_ADMIN = 'dba' + STUDENT = "student" + DEVELOPER = "developer" + DATA_SCIENTIST = "data_scientist" + DATABASE_ADMIN = "dba" OCCUPATION_CHOICES = [ - (STUDENT, 'Student'), - (DEVELOPER, 'Developer'), - (DATA_SCIENTIST, 'Data Scientist'), - (DATABASE_ADMIN, 'Database Admin') + (STUDENT, "Student"), + (DEVELOPER, "Developer"), + (DATA_SCIENTIST, "Data Scientist"), + (DATABASE_ADMIN, "Database Admin"), ] occupation = models.CharField( - null=False, - max_length=20, - choices=OCCUPATION_CHOICES, - default=STUDENT + null=False, max_length=20, choices=OCCUPATION_CHOICES, default=STUDENT ) social_link = models.URLField(max_length=200) def __str__(self): - return self.user.username + "," + \ - self.occupation + return self.user.username + "," + self.occupation # Course model class Course(models.Model): - name = models.CharField(null=False, max_length=30, default='online course') - image = models.ImageField(upload_to='course_images/') + name = models.CharField(null=False, max_length=30, default="online course") + image = models.ImageField(upload_to="course_images/") description = models.CharField(max_length=1000) pub_date = models.DateField(null=True) instructors = models.ManyToManyField(Instructor) - users = models.ManyToManyField(settings.AUTH_USER_MODEL, through='Enrollment') + users = models.ManyToManyField(settings.AUTH_USER_MODEL, through="Enrollment") total_enrollment = models.IntegerField(default=0) is_enrolled = False def __str__(self): - return "Name: " + self.name + "," + \ - "Description: " + self.description + return "Name: " + self.name + "," + "Description: " + self.description # Lesson model @@ -77,17 +73,11 @@ class Lesson(models.Model): # Enrollment model -# Once a user enrolled a class, an enrollment entry should be created between the user and course -# And we could use the enrollment to track information such as exam submissions class Enrollment(models.Model): - AUDIT = 'audit' - HONOR = 'honor' - BETA = 'BETA' - COURSE_MODES = [ - (AUDIT, 'Audit'), - (HONOR, 'Honor'), - (BETA, 'BETA') - ] + AUDIT = "audit" + HONOR = "honor" + BETA = "BETA" + COURSE_MODES = [(AUDIT, "Audit"), (HONOR, "Honor"), (BETA, "BETA")] user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) course = models.ForeignKey(Course, on_delete=models.CASCADE) date_enrolled = models.DateField(default=now) @@ -95,44 +85,31 @@ class Enrollment(models.Model): rating = models.FloatField(default=5.0) -# Create a Question Model with: - # Used to persist question content for a course - # Has a One-To-Many (or Many-To-Many if you want to reuse questions) relationship with course - # Has a grade point for each question - # Has question content - # Other fields and methods you would like to design +# Question model class Question(models.Model): course = models.ForeignKey(Course, on_delete=models.CASCADE) question_text = models.CharField(max_length=200) grade = models.IntegerField(default=0) - - # 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 - - -# Create a Choice Model with: - # Used to persist choice content for a question - # One-To-Many (or Many-To-Many if you want to reuse choices) relationship with Question - # Choice content - # Indicate if this choice of the question is a correct one or not - # Other fields and methods you would like to design + 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 + + +# Choice model class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) is_correct = models.BooleanField(default=False) -# The submission model -# One enrollment could have multiple submission -# One submission could have multiple choices -# One choice could belong to multiple submissions + +# Submission model class Submission(models.Model): - enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE) - choices = models.ManyToManyField(Choice) - -# Other fields and methods you would like to design \ No newline at end of file + enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE) + choices = models.ManyToManyField(Choice) diff --git a/onlinecourse/tests.py b/onlinecourse/tests.py index 7ce503c2d..ef8d717a1 100644 --- a/onlinecourse/tests.py +++ b/onlinecourse/tests.py @@ -1,3 +1,2 @@ from django.test import TestCase -# Create your tests here. diff --git a/onlinecourse/urls.py b/onlinecourse/urls.py index bf120c5f5..7a03adc15 100644 --- a/onlinecourse/urls.py +++ b/onlinecourse/urls.py @@ -5,26 +5,21 @@ app_name = "onlinecourse" urlpatterns = [ - # route is a string contains a URL pattern - # view refers to the view function - # name the URL + path(route="", view=views.CourseListView.as_view(), name="index"), path("registration/", views.registration_request, name="registration"), path("login/", views.login_request, name="login"), path("logout/", views.logout_request, name="logout"), + # question path - path( - "/", - views.combined_course_detail, - name="course_and_question_detail", - ), - # ex: /onlinecourse/5/ + path("/", views.combined_course_detail, name="course_and_question_detail"), + # course details path path("/", views.CourseDetailView.as_view(), name="course_details"), - # ex: /enroll/5/ + # enroll path path("/enroll/", views.enroll, name="enroll"), - # Create a route for submit view + # submit path path("/submit/", views.submit_view, name="submit"), - # Create a route for show_exam_result view + # exam result path path( "course//submission//result/", views.show_exam_result, diff --git a/onlinecourse/views.py b/onlinecourse/views.py index a59827553..ed07a6388 100644 --- a/onlinecourse/views.py +++ b/onlinecourse/views.py @@ -3,7 +3,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 django.contrib.auth.models import User from django.shortcuts import get_object_or_404, render, redirect @@ -15,25 +14,6 @@ # Get an instance of a logger logger = logging.getLogger(__name__) -# Create your views here. - - -# def question_detail(request, question_id): -# question = Question.objects.get(pk=question_id) -# context = { -# 'question': question, -# 'question_id': question_id, -# } -# return render(request, 'onlinecourse/course_detail_bootstrap.html', context) - - -# def question_detail(request, course_id): -# question = Question.objects.get(pk=course_id) -# context = { -# 'question': question, -# 'course_id': course_id, -# } -# return render(request, 'onlinecourse/course_detail_bootstrap.html', context) def registration_request(request): @@ -113,11 +93,13 @@ def get_queryset(self): return courses +# CourseDetailView class CourseDetailView(generic.DetailView): model = Course template_name = "onlinecourse/course_detail_bootstrap.html" +# Combined course and question detail view def combined_course_detail(request, course_id): course = get_object_or_404(Course, pk=course_id) question = Question.objects.filter(course=course_id) @@ -132,15 +114,7 @@ def combined_course_detail(request, course_id): return render(request, "onlinecourse/course_detail_bootstrap.html", context) -# class QuestionListView(generic.ListView): -# template_name = 'onlinecourse/course_detail_bootstrap.html' -# context_object_name = 'question_list' - -# def get_queryset(self): -# question = Question.objects.order_by('-grade')[:10] -# return question - - +# Enroll view def enroll(request, course_id): course = get_object_or_404(Course, pk=course_id) user = request.user @@ -157,36 +131,21 @@ 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 - - +# Submit view def submit_view(request, course_id): - # Get the current user and the course object, then get the associated the enrollment object user = request.user course = get_object_or_404(Course, pk=course_id) enrollment = Enrollment.objects.get(user=user, course=course) if request.method == "POST": - # Create a submission object referring to the enrollment submission = Submission.objects.create(enrollment=enrollment) submission.save() - # Collect the selected choices from HTTP request object selected_choices = extract_answers(request) - # Add each selected choice object to the submission object for choice_id in selected_choices: submission.choices.add(choice_id) - # Redirect to a show_exam_result view with the submission id to show the exam result - # return redirect("show_exam_result/" + f"{submission.id}") - return HttpResponseRedirect( reverse( viewname="onlinecourse:show_exam_result", @@ -197,14 +156,8 @@ def submit_view(request, course_id): ) ) - # # Render the exam submission form - # choices = Choice.objects.filter(question__course=course) - # return render( - # request, "onlinecourse/exam_result_bootstrap.html", {"choices": choices} - # ) - -# A example method to collect the selected choices from the exam form from the request object +# Extract answers from request def extract_answers(request): submitted_anwsers = [] for key in request.POST: @@ -215,23 +168,15 @@ def extract_answers(request): return submitted_anwsers -# 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 +# Show exam result view def show_exam_result(request, course_id, submission_id): - # Get the course object and submission object based on their ids in view arguments course = get_object_or_404(Course, pk=course_id) submission = get_object_or_404(Submission, pk=submission_id) questioners = Question.objects.filter(course=course_id) choices = submission.choices.all() - # Get the selected choice ids from the submission record - # selected_choice_ids = submission.choices.filter(question__course=course).values_list("id", flat=True) - + selected_choice_ids = ( submission.choices.all() .filter(question__course=course) @@ -241,20 +186,16 @@ def show_exam_result(request, course_id, submission_id): total_score = 0 question_results = [] - # For each selected choice, check if it is a correct answer or not for choice_id in selected_choice_ids: choice = get_object_or_404(Choice, pk=choice_id) question = Question.objects.get(pk=choice.question_id) - # Check if the selected choice is correct is_correct = choice.is_correct - # Calculate the total score by adding up the grades for all questions in the course if is_correct: total_score += question.grade print(total_score) - # Append the result for each question question_results.append( { "question_text": question.question_text, @@ -265,7 +206,6 @@ def show_exam_result(request, course_id, submission_id): print(question_results) - # Add the course, selected_ids, and grade to context for rendering HTML page context = { "course": course, "submission": submission, From 5af343be1ea03f090ad8126195772dd3cc3dc6fe Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:55:19 +0800 Subject: [PATCH 3/6] update docker --- Dockerfile | 8 ++++++++ db.sqlite3 | Bin 258048 -> 258048 bytes myproject/urls.py | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..52e7663fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9-alpine +ENV PYTHONUNBUFFERED 1 + +WORKDIR /app +COPY requirements.txt . +RUN pip install -r requirements.txt +COPY . . +CMD python manage.py runserver 443 diff --git a/db.sqlite3 b/db.sqlite3 index de26f37ba3aee493f5f0f78ba3fef18993f4d19a..52b9f298caadfd5326d35b9aca747bbe51b7cf18 100644 GIT binary patch delta 955 zcmaiyPiWg#9LN2BPm|`>U=IQf?Yg>Cg^4_)z0>s zuJqRy#0onV5G1+SpEXiVX6;fooXFQ?L)dI8YEo(GRrZ2zE*s`KT4B1Ck6D@x1sOWX zuzor+$I)})&nKf9JHw+iVVe_h<8#sxy1cKx_Q0X_>N^OK9=G>}=dS(X_we^I%Xbe*q}mQ% z3-6DG>yC0PbQ6HU(E*Ip2|(jC39fOPK>yh01GsS<2go}%wLf}>+$Vn`?~`|&-Xh_r zFgULOjETl{9nrb|>o4$;8waRI7Igq!pq^Xi0sIcsV=Mg-XrP{1S0BOwP><~NZ*b+U zQF&t0X+>fLPNfl?J|m3E9R3NXKLgK~dyId_GepzUjvA{7Wu|!9Wchw1%xPvjJSc@b z(Ltm*=+v9_VrQl-Ue48$-OQjCQ{v@|+ESyj-Iv2%@dDH8rZ@O_rK~JRb7Hkv;kAO2 zV2uXHM`*Q^?WH%i1#_vsT59B$T6u0mY(?@trOIb{Lv9Y+YRGKLtL2E4X15A#y`SLD z1w$pJT`KkX&6P#57S~hx47a*jjA!*Kr!5wiV@W!f3dY#Ma$Kn`$f?X$TS`caeDYGq zjF*jmIx5XE?Y6Pno4csQ&u7d|jx}>~>3lC1(xk3l*9t+6Q~7>Mola;Axr!JmNJ94Y z0JD)d2AE;kC}Tfp;yi%&uf2DSPrdI?|KE4vDJ&uD=O*^sPllNBf%mp^^Bb@K%zr!Z SdpL%y{T4oHKl>J+k^cpggbAtu delta 402 zcmZp8z~AtIe}Xh)#zYxs#*B>#?{%3N|4k0mlVuFroUiBTP;X`c0xF&+*{PX<#qLoC z>6N)|nFR*fxgHT2DxNus-lbtKi9Y3l!TBy3d66kOks-m}X(>U*#u@tVQTo|VmFYpL zi5?Ni8-0+DL@3Jvw&g-mJZGO z`P=pL8Lv)Yp1>opm7PhEak>E`Q_OS+MkbE!J&a7XToVJ>w+rwvePL!+XQ|rGF2Ll^ zwAoNVg=u@02vb)#8&Deq|IevBOz)@vT)?Ew#{Y?D6`$*Nt%XeYAzIhKwQfLY-NMwx z&8*ILVmtc*h+q-R_7lgLY&ePgxPzPk diff --git a/myproject/urls.py b/myproject/urls.py index c0fb54212..7454aa0bc 100644 --- a/myproject/urls.py +++ b/myproject/urls.py @@ -20,5 +20,5 @@ urlpatterns = [ path('admin/', admin.site.urls), - path('onlinecourse/', include('onlinecourse.urls')), + path('', include('onlinecourse.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From f6fd188d61f44d9966c1fe7a391eec0458d848cf Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Thu, 12 Oct 2023 23:14:49 +0800 Subject: [PATCH 4/6] update docker --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 52e7663fc..3f802e929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . -CMD python manage.py runserver 443 +CMD python manage.py runserver 80 From 1c729686cff7fb4870c243bbc95b1c5efd2a720c Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Thu, 12 Oct 2023 23:25:27 +0800 Subject: [PATCH 5/6] update docker --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3f802e929..3cedbe8ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . -CMD python manage.py runserver 80 +CMD python manage.py runserver 0.0.0.0:80 From 9c2430afd98c23475bab92e2a6570aa430d21429 Mon Sep 17 00:00:00 2001 From: Jeph Mari Daligdig <122143538+greatxrider@users.noreply.github.com> Date: Thu, 12 Oct 2023 23:31:38 +0800 Subject: [PATCH 6/6] update docker --- myproject/settings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/myproject/settings.py b/myproject/settings.py index 0c5a1c139..1e578700a 100644 --- a/myproject/settings.py +++ b/myproject/settings.py @@ -30,8 +30,7 @@ DEBUG = True # add your cloud host here -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = ['final-cloud-app-with-database-imq6imauja-uc.a.run.app'] # Application definition INSTALLED_APPS = [