Skip to content

Commit

Permalink
Merge pull request #78 from mnqrt/feat/adrian/gpa-calculator
Browse files Browse the repository at this point in the history
feat: add gpa calculator
  • Loading branch information
Veivel authored Jul 11, 2024
2 parents b4d6a1d + cd1f511 commit efd8432
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 4 deletions.
34 changes: 34 additions & 0 deletions main/migrations/0008_usercumulativegpa_usergpa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 3.1.2 on 2024-07-10 04:21

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


class Migration(migrations.Migration):

dependencies = [
('main', '0007_alter_review_rating_beneficial_and_more'),
]

operations = [
migrations.CreateModel(
name='UserCumulativeGPA',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cumulative_gpa', models.FloatField(default=0)),
('total_gpa', models.FloatField(default=0)),
('total_sks', models.PositiveIntegerField(default=0)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.profile')),
],
),
migrations.CreateModel(
name='UserGPA',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('given_semester', models.PositiveSmallIntegerField(editable=False)),
('total_sks', models.PositiveIntegerField()),
('semester_gpa', models.FloatField()),
('userCumulativeGPA', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.usercumulativegpa')),
],
),
]
28 changes: 28 additions & 0 deletions main/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,31 @@ class ScoreComponent(models.Model):
weight = models.FloatField()
score = models.FloatField()

class UserCumulativeGPA(models.Model):
"""
User's Cumulative GPA/IPK (Indeks Prestasi Kumulatif)
"""
user = models.ForeignKey(Profile, on_delete=CASCADE)
cumulative_gpa = models.FloatField(default=0)
total_gpa = models.FloatField(default=0)
total_sks = models.PositiveIntegerField(default=0)

class UserGPA(models.Model):
"""
User's GPA/IP (Indeks Prestasi) for the given semester
"""
userCumulativeGPA = models.ForeignKey(UserCumulativeGPA, on_delete=CASCADE)
given_semester = models.PositiveSmallIntegerField(editable=False)
total_sks = models.PositiveIntegerField()
semester_gpa = models.FloatField()

def save(self, *args, **kwargs):
# On save, autoincrement given_semester, starting with 1
if not self.pk :
# If there's no UserGPA for the current user, initialize it as 1, otherwise increment it by 1
last_user_gpa = UserGPA.objects.filter(userCumulativeGPA__user=self.userCumulativeGPA.user).order_by('given_semester').last()
if last_user_gpa :
self.given_semester = last_user_gpa.given_semester + 1
else :
self.given_semester = 1
super(UserGPA, self).save(*args, **kwargs)
20 changes: 18 additions & 2 deletions main/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework import serializers
from django.db.models import Avg

from .models import Calculator, Course, Profile, Review, ScoreComponent, Tag, Bookmark
from .models import Calculator, Course, Profile, Review, ScoreComponent, Tag, Bookmark, UserCumulativeGPA, UserGPA

# class CurriculumSerializer(serializers.ModelSerializer):
# class Meta:
Expand Down Expand Up @@ -277,4 +277,20 @@ class Meta:
fields = ('id', 'calculator_id', 'name', 'weight', 'score')

def get_calculator_id(self, obj):
return obj.calculator.id
return obj.calculator.id

class UserCumulativeGPASerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField('get_user')
class Meta:
model = UserCumulativeGPA
fields = ('user', 'cumulative_gpa', 'total_gpa', 'total_sks')

def get_user(self, obj):
return obj.user.username

class UserGPASerializer(serializers.ModelSerializer):

class Meta:
model = UserGPA
fields = ('given_semester', 'total_sks', 'semester_gpa')
read_only_fields = ('given_semester',)
4 changes: 3 additions & 1 deletion main/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django import conf
from rest_framework import routers
from django.urls import path, include
from .views_gpa_calculator import gpa_calculator
from .views_calculator import calculator, score_component
from .views import like, tag, bookmark, account, leaderboard
from .views_review import ds_review, review
Expand All @@ -19,6 +20,7 @@
path("account", account, name="account"),
path("leaderboard", leaderboard, name="leaderboard"),
path("calculator", calculator, name="calculator"),
path("score-component", score_component, name="score-component")
path("score-component", score_component, name="score-component"),
path("calculator-gpa", gpa_calculator, name="gpa-calculator")
] + router.urls

35 changes: 34 additions & 1 deletion main/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from rest_framework import status
from .models import Profile
from .models import Profile, UserCumulativeGPA


def process_sso_profile(sso_profile):
Expand Down Expand Up @@ -73,3 +73,36 @@ def get_paged_obj(objs, page):
paginator = Paginator(objs, 10)
objs = paginator.get_page(page)
return objs, paginator.num_pages


def check_notexist_and_create_user_cumulative_gpa(user):
user_cumulative_gpa = UserCumulativeGPA.objects.filter(user = user).first()
if not user_cumulative_gpa :
user_cumulative_gpa = UserCumulativeGPA.objects.create(user = user)
user_cumulative_gpa.save()
return user_cumulative_gpa

def validate_body_minimum(request, attrs):
# At least one element in attrs must be present in request.data
for attr in attrs:
res = request.data.get(attr)
if res is not None:
return None
return response(error="None of the following attributes are present in the request data: {}.".format(attrs), status=status.HTTP_404_NOT_FOUND)

def add_semester_gpa(user_cumulative_gpa, total_sks, semester_gpa):
user_cumulative_gpa.total_sks += total_sks
user_cumulative_gpa.total_gpa += semester_gpa * total_sks
user_cumulative_gpa.cumulative_gpa = user_cumulative_gpa.total_gpa / user_cumulative_gpa.total_sks
user_cumulative_gpa.save()

def delete_semester_gpa(user_cumulative_gpa, total_sks, semester_gpa):
user_cumulative_gpa.total_sks -= total_sks
user_cumulative_gpa.total_gpa -= semester_gpa * total_sks

if user_cumulative_gpa.total_sks == 0:
user_cumulative_gpa.cumulative_gpa = 0
else:
user_cumulative_gpa.cumulative_gpa = user_cumulative_gpa.total_gpa / user_cumulative_gpa.total_sks

user_cumulative_gpa.save()
95 changes: 95 additions & 0 deletions main/views_gpa_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import logging

from rest_framework.decorators import api_view
from rest_framework import status
from .serializers import UserCumulativeGPASerializer, UserGPASerializer

from .utils import response, validate_body, check_notexist_and_create_user_cumulative_gpa, validate_body_minimum, add_semester_gpa, delete_semester_gpa
from .models import Profile, UserGPA, UserCumulativeGPA
from django.db.models import F

logger = logging.getLogger(__name__)


@api_view(['GET', 'POST', 'DELETE', 'PUT'])
def gpa_calculator(request):

user = Profile.objects.get(username=str(request.user))

if request.method == 'GET':
user_cumulative_gpa = check_notexist_and_create_user_cumulative_gpa(user)

user_gpas = UserGPA.objects.filter(userCumulativeGPA = user_cumulative_gpa)
return response(data={'all_semester_gpa':UserGPASerializer(user_gpas, many=True).data, 'cumulative_gpa': UserCumulativeGPASerializer(user_cumulative_gpa).data})

if request.method == 'POST':
is_valid = validate_body(request, ['semester_gpa', 'total_sks'])

if is_valid != None :
return is_valid

semester_gpa = request.data.get('semester_gpa')
total_sks = request.data.get('total_sks')

user_cumulative_gpa = check_notexist_and_create_user_cumulative_gpa(user)

user_gpa = UserGPA.objects.create(userCumulativeGPA=user_cumulative_gpa, \
total_sks=total_sks, \
semester_gpa=semester_gpa)

add_semester_gpa(user_cumulative_gpa,
total_sks=total_sks,
semester_gpa=semester_gpa)

return response(data=UserGPASerializer(user_gpa).data, status=status.HTTP_201_CREATED)

if request.method == 'DELETE':
user_cumulative_gpa = check_notexist_and_create_user_cumulative_gpa(user)

user_gpa = UserGPA.objects.filter(userCumulativeGPA=user_cumulative_gpa).order_by('given_semester').last()

if user_gpa is None:
return response(error="There is no gpa to delete.", status=status.HTTP_404_NOT_FOUND)

delete_semester_gpa(user_cumulative_gpa,
total_sks=user_gpa.total_sks,
semester_gpa=user_gpa.semester_gpa)

user_gpa.delete()

return response(status=status.HTTP_200_OK)

if request.method == 'PUT':
is_valid = validate_body(request, ['given_semester'])
if is_valid != None:
return is_valid

is_valid = validate_body_minimum(request, ['total_sks', 'semester_gpa'])
if is_valid != None:
return is_valid

given_semester = request.data.get('given_semester')

user_cumulative_gpa = check_notexist_and_create_user_cumulative_gpa(user)
user_gpa = UserGPA.objects.filter(userCumulativeGPA=user_cumulative_gpa,
given_semester=given_semester).first()

if user_gpa is None:
return response(error="There is no object with given_semester={}.".format(given_semester), status=status.HTTP_404_NOT_FOUND)

total_sks = request.data.get('total_sks') or user_gpa.total_sks
semester_gpa = request.data.get('semester_gpa') or user_gpa.semester_gpa

#Updates Cumulative GPA / Indeks Prestasi Kumulatif
delete_semester_gpa(user_cumulative_gpa,
total_sks=user_gpa.total_sks,
semester_gpa=user_gpa.semester_gpa)
add_semester_gpa(user_cumulative_gpa,
total_sks=total_sks,
semester_gpa=semester_gpa)

user_gpa.total_sks = total_sks
user_gpa.semester_gpa = semester_gpa
user_gpa.save()

return response(data=UserGPASerializer(user_gpa).data, status=status.HTTP_200_OK)

0 comments on commit efd8432

Please sign in to comment.