Skip to content

Commit

Permalink
Merge pull request #573 from bounswe/BACKEND-519
Browse files Browse the repository at this point in the history
BACKEND-519: Preliminary quiz endpoints
  • Loading branch information
arastasci authored Nov 23, 2024
2 parents aa1b430 + 2387413 commit 18be31f
Show file tree
Hide file tree
Showing 7 changed files with 486 additions and 4 deletions.
1 change: 0 additions & 1 deletion backend/app/apps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.apps import AppConfig
import requests
from django.db.models.signals import post_migrate, post_init

import json


Expand Down
84 changes: 83 additions & 1 deletion backend/app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.16 on 2024-11-22 11:51
# Generated by Django 4.2.16 on 2024-11-23 14:38

from django.conf import settings
import django.contrib.postgres.fields
Expand All @@ -16,6 +16,29 @@ class Migration(migrations.Migration):
]

operations = [
migrations.CreateModel(
name='Quiz',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('description', models.TextField()),
('level', models.CharField(choices=[('A1', 'A1'), ('A2', 'A2'), ('B1', 'B1'), ('B2', 'B2'), ('C1', 'C1'), ('C2', 'C2')], max_length=2)),
('question_count', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('times_taken', models.IntegerField(default=0)),
('total_score', models.FloatField(default=0)),
('time_limit', models.IntegerField(default=0)),
('like_count', models.IntegerField(default=0)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quizzes', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Tags',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
],
),
migrations.CreateModel(
name='Word',
fields=[
Expand Down Expand Up @@ -45,6 +68,51 @@ class Migration(migrations.Migration):
('word', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='relationships', to='app.word')),
],
),
migrations.CreateModel(
name='QuizResults',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('score', models.FloatField()),
('time_taken', models.IntegerField()),
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to='app.quiz')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='results', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='QuizProgress',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('score', models.FloatField(default=0)),
('quiz_attempt', models.IntegerField(default=0)),
('date_started', models.DateTimeField(default=django.utils.timezone.now)),
('completed', models.BooleanField(default=False)),
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress', to='app.quiz')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='quiz_progress', to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('quiz', 'user', 'quiz_attempt')},
},
),
migrations.AddField(
model_name='quiz',
name='tags',
field=models.ManyToManyField(related_name='quizzes', to='app.tags'),
),
migrations.CreateModel(
name='Question',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_number', models.IntegerField()),
('question_text', models.TextField()),
('level', models.CharField(choices=[('A1', 'A1'), ('A2', 'A2'), ('B1', 'B1'), ('B2', 'B2'), ('C1', 'C1'), ('C2', 'C2')], max_length=2)),
('choice1', models.CharField(max_length=100)),
('choice2', models.CharField(max_length=100)),
('choice3', models.CharField(max_length=100)),
('choice4', models.CharField(max_length=100)),
('correct_choice', models.IntegerField()),
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='app.quiz')),
],
),
migrations.CreateModel(
name='Profile',
fields=[
Expand Down Expand Up @@ -90,6 +158,20 @@ class Migration(migrations.Migration):
('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activities', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='QuestionProgress',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('answer', models.IntegerField(default=0)),
('time_taken', models.IntegerField(default=0)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress', to='app.question')),
('quiz_progress', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='question_progress', to='app.quizprogress')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='question_progress', to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('question', 'quiz_progress')},
},
),
migrations.CreateModel(
name='Bookmark',
fields=[
Expand Down
17 changes: 17 additions & 0 deletions backend/app/migrations/0002_remove_questionprogress_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-11-23 14:42

from django.db import migrations


class Migration(migrations.Migration):

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

operations = [
migrations.RemoveField(
model_name='questionprogress',
name='user',
),
]
88 changes: 88 additions & 0 deletions backend/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
('NA', 'NA')
]

class Tags(models.Model):
name = models.CharField(max_length=50)

def __str__(self):
return self.name


class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
Expand All @@ -25,6 +31,88 @@ def __str__(self):
return self.name


class Quiz(models.Model):
LEVEL_CHOICES = [
('A1', 'A1'),
('A2', 'A2'),
('B1', 'B1'),
('B2', 'B2'),
('C1', 'C1'),
('C2', 'C2'),
]

title = models.CharField(max_length=100)
description = models.TextField()
author = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='quizzes')
tags = models.ManyToManyField('Tags', related_name='quizzes')
level = models.CharField(max_length=2, choices=LEVEL_CHOICES)
question_count = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
times_taken = models.IntegerField(default=0)
total_score = models.FloatField(default=0)
time_limit = models.IntegerField(default=0)
like_count = models.IntegerField(default=0)

def __str__(self):
return self.title


class Question(models.Model):
LEVEL_CHOICES = Quiz.LEVEL_CHOICES
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='questions')
question_number = models.IntegerField()
question_text = models.TextField()
level = models.CharField(max_length=2, choices=LEVEL_CHOICES)
choice1 = models.CharField(max_length=100)
choice2 = models.CharField(max_length=100)
choice3 = models.CharField(max_length=100)
choice4 = models.CharField(max_length=100)
correct_choice = models.IntegerField()

def __str__(self):
return self.question_text


class QuizProgress(models.Model):
id = models.AutoField(primary_key=True)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='progress')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz_progress')
score = models.FloatField(default=0)
quiz_attempt = models.IntegerField(default=0)
date_started = models.DateTimeField(default=timezone.now)
completed = models.BooleanField(default=False)

class Meta:
unique_together = ('quiz', 'user', "quiz_attempt") # Enforce unique combination of quiz and user


def __str__(self):
return self.quiz.title + ' - ' + self.user.username

class QuizResults(models.Model):
id = models.AutoField(primary_key=True)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE, related_name='results')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='results')
score = models.FloatField()
time_taken = models.IntegerField()

def __str__(self):
return self.quiz.title + ' - ' + self.user.username


class QuestionProgress(models.Model):
id = models.AutoField(primary_key=True)
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='progress')
answer = models.IntegerField(default=0)
time_taken = models.IntegerField(default=0)
quiz_progress = models.ForeignKey(QuizProgress, on_delete=models.CASCADE, related_name='question_progress')

class Meta:
unique_together = ('question', 'quiz_progress') # Enforce unique combination of question and user

def __str__(self):
return self.question.question_text + ' - ' + self.user.username

class Post(models.Model):
id = models.AutoField(primary_key=True)
tags = ArrayField(
Expand Down
94 changes: 92 additions & 2 deletions backend/app/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Profile, Post, Comment
from .models import Profile, Quiz, Post, QuizResults, QuizProgress, QuestionProgress, Question, Comment, Tags



Expand Down Expand Up @@ -70,7 +70,97 @@ def update(self, instance, validated_data):

return instance

class QuizResultsSerializer(serializers.ModelSerializer):
class Meta:
model = QuizResults
fields = ['quiz', 'user', 'score', 'time_taken']

def to_representation(self, instance):
representation = super().to_representation(instance)
representation['quiz'] = { 'id' : instance.quiz.id, 'title' : instance.quiz.title }
representation['question_count'] = instance.quiz.question_count
representation['user'] = { 'id' : instance.user.id, 'username' : instance.user.username }
representation['author'] = { 'id' : instance.quiz.author.id, 'username' : instance.quiz.author.username }
return representation


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tags
fields = ['id', 'name']

class QuizSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
level = serializers.ChoiceField(choices=Quiz.LEVEL_CHOICES)

class Meta:
model = Quiz
fields = [
'id',
'title',
'description',
'author',
'tags',
'level',
'question_count',
'created_at',
'times_taken',
'total_score',
'time_limit',
'like_count',
]

def create(self, validated_data):
tags_data = validated_data.pop('tags')
quiz = Quiz.objects.create(**validated_data)

for tag_data in tags_data:
tag, _ = Tags.objects.get_or_create(name=tag_data['name'])
quiz.tags.add(tag)

return quiz

def to_representation(self, instance):
representation = super().to_representation(instance)

# Transform tags to a list of names
representation['tags'] = [tag['name'] for tag in representation['tags']]
representation['author'] = { 'id' : instance.author.id, 'username' : instance.author.username }
return representation

class QuizProgressSerializer(serializers.ModelSerializer):
class Meta:
model = QuizProgress
fields = ['quiz', 'user', 'score', 'quiz_attempt', 'date_started', 'completed']


class QuestionSerializer(serializers.ModelSerializer):
level = serializers.ChoiceField(choices=Question.LEVEL_CHOICES)
class Meta:
model = Question
fields = [
'quiz',
'id',
'question_number',
'question_text',
'choice1',
'choice2',
'choice3',
'choice4',
'correct_choice',
'level',
]

def to_representation(self, instance):
representation = super().to_representation(instance)
representation.pop('quiz')
representation['quiz_id'] = instance.quiz.id
return representation

class QuestionProgressSerializer(serializers.ModelSerializer):
class Meta:
model = QuestionProgress
fields = ['question', 'quiz_progress', 'answer', 'time_taken']


class PostSerializer(serializers.ModelSerializer):
Expand All @@ -80,11 +170,11 @@ class Meta:
model = Post
fields = ['id', 'title', 'description', 'author', 'tags', 'created_at', 'like_count', 'comments']


def get_comments(self, obj):
comments = Comment.objects.filter(post=obj) # Fetch all comments for the post
return CommentSerializer(comments, many=True).data


class CommentSerializer(serializers.ModelSerializer):
replies = serializers.SerializerMethodField() # Fetch nested replies

Expand Down
11 changes: 11 additions & 0 deletions backend/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,22 @@
from app.views_directory.bookmark_views import bookmark_post, unbookmark_post, get_bookmarked_posts


import app.views_directory.quiz_views as quiz_views

urlpatterns = [
path('', index , name='index_page'),
path('profile/', view_profile, name='view_profile'),
path('profile/update/', update_profile, name='update_profile'),
path('quiz/', quiz_views.get_quiz, name="get_quiz"),
path('feed/quiz/', quiz_views.view_quizzes, name="feed_quiz"),
path('quiz/create/', quiz_views.create_quiz, name="create_quiz"),
path('quiz/question/', quiz_views.get_question, name="get_question"),
path('quiz/question/solve/', quiz_views.solve_question, name="solve_question"),
path('quiz/submit/', quiz_views.submit_quiz, name="submit_quiz"),
path('quiz/start/', quiz_views.start_quiz, name="start_quiz"),
path('quiz/results/', quiz_views.get_quiz_results, name="get_quiz_results"),

path('create-post/',create_post, name='create_post'),
path('signup/', RegisterView.as_view(), name='auth_register'),
path('login/', LoginView.as_view(), name='auth_login'),
path('logout/', LogoutView.as_view(), name='auth_logout'),
Expand Down
Loading

0 comments on commit 18be31f

Please sign in to comment.