Skip to content
Open

done #23

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.1.3 on 2023-05-03 11:34

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
('core', '0012_teams_and_teamapplications'),
]

operations = [
migrations.AddField(
model_name='round',
name='round_target',
field=models.FileField(null=True, upload_to='round_target'),
),
migrations.AddField(
model_name='submit',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='submit',
name='score',
field=models.FloatField(null=True),
),
]
1 change: 1 addition & 0 deletions bb_back/bb_back/core/models/round.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ class Round(models.Model):
is_active = models.BooleanField(default=True)

data_of_round = models.FileField(upload_to="round_data", null=True)
round_target = models.FileField(upload_to="round_target", null=True)
7 changes: 4 additions & 3 deletions bb_back/bb_back/core/models/submit.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from django.db import models
from datetime import datetime
from django.utils import timezone


class Submit(models.Model):
file = models.FileField(upload_to="submits", null=True)
id_command = models.IntegerField(null=True)
round_num = models.IntegerField(null=True)
score = models.FloatField(null=True)
final = models.BooleanField(null=False, default=False)
score = models.FloatField(null=False, default=0)
create_at = models.DateTimeField(default=datetime.min)

created_at = models.DateTimeField(default=timezone.now)
3 changes: 2 additions & 1 deletion bb_back/bb_back/core/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
RoundView, GetRoundDataView, CreateRoundView,
UploudRoundData, TeamApplicationView,
ReviewTeamApplicationView, TeamListView, CurrentTeamView,
UploadRoundTargetView
MemberApplicationView, ReviewMemberApplicationView)

__all__ = [
Expand All @@ -14,5 +15,5 @@
"GetGameLogoView", "UploadGameLogoView", "RoundView", "GetRoundDataView",
"CreateRoundView", "UploudRoundData", "TeamApplicationView",
"ReviewTeamApplicationView", "TeamListView", "CurrentTeamView",
"MemberApplicationView", "ReviewMemberApplicationView"
"UploadRoundTargetView", "MemberApplicationView", "ReviewMemberApplicationView"
]
4 changes: 3 additions & 1 deletion bb_back/bb_back/core/views/shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from .game import CreateGameView, GameView, GetGameLogoView, UploadGameLogoView
from .submit_upload import SubmitView
from .view_404 import view_404
from .round import RoundView, UploadRoundTargetView, GetRoundDataView, CreateRoundView, UploudRoundData
from .teams import TeamApplicationView, ReviewTeamApplicationView, TeamListView, CurrentTeamView
from .round import RoundView
from .round import GetRoundDataView
from .round import CreateRoundView
Expand All @@ -24,5 +26,5 @@
"UploadGameLogoView", "SubmitView", "view_404", "RoundView",
"GetRoundDataView", "CreateRoundView", "UploudRoundData",
"TeamApplicationView", "ReviewTeamApplicationView", "TeamListView",
"CurrentTeamView", "MemberApplicationView", "ReviewMemberApplicationView"
"CurrentTeamView", "MemberApplicationView", "ReviewMemberApplicationView", "UploadRoundTargetView"
]
79 changes: 79 additions & 0 deletions bb_back/bb_back/core/views/shared/round.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
NotFoundResponseSerializer,
UserRolePermissionDeniedSerializer,
)
import codecs
import csv
from bb_back.core.views.utils.decorators import is_staff_user


Expand Down Expand Up @@ -204,6 +206,7 @@ class CreateRoundView(APIView):
request_body=CreateRoundRequestSerializer,
responses={status.HTTP_201_CREATED: CreateRoundResponseSerializer},
)
@is_staff_user
def post(self, request):
request_data = CreateRoundRequestSerializer(data=request.data)

Expand Down Expand Up @@ -293,3 +296,79 @@ def put(self, request, round_id):
data={"response_data": {}})
response_data.is_valid()
return Response(data=response_data.data, status=status.HTTP_200_OK)


class UploadRoundTargetView(APIView):
parser_classes = [MultiPartParser, FormParser, FileUploadParser]

@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
"file",
in_=openapi.IN_FORM,
description="file",
type=openapi.TYPE_FILE,
required=True,
)
],
responses={status.HTTP_200_OK: UploadRoundDataResponseSerializer},
)
@is_staff_user
def post(self, request, round_id):
round = Round.objects.filter(id=round_id).first()
if not round:
return response(
status_code=status.HTTP_404_NOT_FOUND,
data={},
message=f"Round with id = {round_id} does not exist.")

file = request.FILES.get("file")
try:
csv_file = csv.DictReader(codecs.iterdecode(file, 'utf-8'))
for line in csv_file:
line: dict
if list(line.keys()) != ['id', 'fact', 'amount']:
raise ValueError(
'CSV file columns must be: "id", "fact", "amount"')
if not all([value.isnumeric() for value in line.values()]):
raise ValueError(
'All provided values must be valid integers')
except ValueError as ex:
return response(status_code=status.HTTP_400_BAD_REQUEST,
data={},
message=f"{str(ex)}")
if not file.name.endswith(".csv"):
return response(status_code=status.HTTP_400_BAD_REQUEST,
data={},
message="Target file must have .csv extension")
round.round_target = file
round.save()
round.round_rates = file
round.save()
response_data = UploadRoundDataResponseSerializer(
data={"response_data": {}})
response_data.is_valid()
return Response(data=response_data.data, status=status.HTTP_200_OK)

@swagger_auto_schema(
responses={status.HTTP_200_OK: RoundDataResponseSerializer}, )
def get(self, request, round_id):
round = Round.objects.filter(id=round_id).first()
if not round:
return response(
status_code=status.HTTP_404_NOT_FOUND,
data={},
message=f"Round with id = {round_id} does not exist.")
if not round.round_target:
return response(
success=False,
status_code=status.HTTP_404_NOT_FOUND,
data={},
message=f"No target found for round {round.name}",
)

response_data = HttpResponse(round.round_target,
content_type="application/vnd.ms-excel")
response_data[
"Content-Disposition"] = "inline; filename=" + round.round_target.name
return response_data
72 changes: 54 additions & 18 deletions bb_back/bb_back/core/views/shared/submit_upload.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import codecs
import csv
from math import exp

from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers, status
Expand All @@ -6,15 +10,14 @@
from rest_framework.views import APIView
from datetime import datetime

from bb_back.core.models import Submit
from bb_back.core.utils.view_utils import failed_validation_response
from bb_back.core.models import Submit, Round
from bb_back.core.utils.view_utils import failed_validation_response, response
from bb_back.core.views.utils.base_serializers import BaseResponseSerializer
from bb_back.settings import SUBMIT_MAX_SIZE


class SubmitRequestSerializer(serializers.Serializer):
file = serializers.FileField()
id_command = serializers.IntegerField()
round_num = serializers.IntegerField()


Expand All @@ -39,6 +42,7 @@ class SubmitView(APIView):
responses={status.HTTP_200_OK: SubmitResponseSerializer},
)
def post(self, request):
team = request.user.team
request_data = SubmitRequestSerializer(data=request.data)

if not request_data.is_valid() or request.FILES.get("file") is None:
Expand All @@ -50,24 +54,56 @@ def post(self, request):
return failed_validation_response(serializer=request_data)

submit_schema = request_data.data
submit = Submit.objects.filter(
id_command=submit_schema.get("id_command"),
round_num=submit_schema.get("round_num")).first()

if submit:
submit.file = submit_file
submit.create_at = datetime.datetime.now()
submit.save()
else:
Submit.objects.create(
file=submit_file,
id_command=submit_schema.get("id_command"),
round_num=submit_schema.get("round_num"),
create_at=datetime.now(),
)
round = Round.objects.filter(id=submit_schema.get("round_num")).first()
if not round:
return response(
status_code=status.HTTP_400_BAD_REQUEST,
data={},
message=
f"Round with id={submit_schema.get('round_num')} not found")
try:
csv_file = csv.DictReader(codecs.iterdecode(submit_file, 'utf-8'))
for line in csv_file:
line: dict
if list(line.keys()) != ['id', 'rate']:
raise ValueError(
'CSV file columns must be: "id", "fact", "amount"')
if not all([value.isnumeric() for value in line.values()]):
raise ValueError(
'All provided values must be valid integers')
except ValueError as ex:
return response(status_code=status.HTTP_400_BAD_REQUEST,
data={},
message=f"{str(ex)}")
score = score_submit(submit_file=submit_file, round_id=round.id)

Submit.objects.create(file=submit_file,
id_command=team.id,
round_num=submit_schema.get("round_num"),
score=score)

response_data = SubmitResponseSerializer(
data={"response_data": submit_schema})

response_data.is_valid()
return Response(data=response_data.data, status=status.HTTP_200_OK)


def score_submit(submit_file, round_id: int):
submit = list(csv.DictReader(codecs.iterdecode(submit_file, 'utf-8')))
total_score = 0.0
round = Round.objects.get(id=round_id)
target = list(
csv.DictReader(codecs.iterdecode(round.round_target, 'utf-8')))
for result in submit:
target_line = [
line for line in target if line.get('id') == result.get("id")
][0]
rate: float = float(result.get("rate")) # type: ignore
fact = int(target_line.get("fact")) # type: ignore
amount = float(target_line.get("amount")) # type: ignore
p_tup = 1 / (1 + exp(-(0.1 - rate / 100) * 20))
result_score = p_tup * ((1 - fact) * amount * rate - fact * amount)
total_score += result_score

return total_score
11 changes: 10 additions & 1 deletion bb_back/bb_back/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,16 @@
"DEFAULT_AUTHENTICATION_CLASSES":
("rest_framework_simplejwt.authentication.JWTAuthentication", )
}
SWAGGER_SETTINGS = {"USE_SESSION_AUTH": False}
SWAGGER_SETTINGS = {
'SECURITY_DEFINITIONS': {
'Bearer': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
},
"USE_SESSION_AUTH": False
}
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
Expand Down
4 changes: 4 additions & 0 deletions bb_back/bb_back/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@
f"{API_PREFIX}/{API_VERSION}/round/uploud_data/<int:round_id>/",
views.UploudRoundData.as_view(),
),
path(
f"{API_PREFIX}/{API_VERSION}/round/target/<int:round_id>/",
views.UploadRoundTargetView.as_view(),
),
path(
f"{API_PREFIX}/{API_VERSION}/team/application/",
views.TeamApplicationView.as_view(),
Expand Down