Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bunch of fixes for code submission backend #108

Merged
merged 4 commits into from
Mar 28, 2021
Merged
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
19 changes: 19 additions & 0 deletions src/problems/migrations/0009_user_timestamped_filepaths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-03-28 18:26

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


class Migration(migrations.Migration):

dependencies = [
('problems', '0008_problem_subfields'),
]

operations = [
migrations.AlterField(
model_name='submission',
name='file',
field=models.FileField(upload_to=problems.models.user_timestamped_filepath, verbose_name='source code file'),
),
]
18 changes: 17 additions & 1 deletion src/problems/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import os
import datetime

from datetime import date

from django.db import models
Expand Down Expand Up @@ -58,6 +61,19 @@ def __str__(self):

visible = models.BooleanField(default=False)

# This is used to determine media filepaths for code files submitted via the "submit file" button.
def user_timestamped_filepath(instance, filename):
datetime_now = datetime.datetime.now()
year = datetime_now.strftime("%Y")
month = datetime_now.strftime("%m")
day = datetime_now.strftime("%d")

base_filename, extension = os.path.splitext(filename)
username = instance.user.user.username
timestamp = datetime_now.strftime("%Y%m%d%H%M%S")
user_timestamped_filename = f"{base_filename}_{username}_{timestamp}{extension}"

return f"uploads/{year}/{month}/{day}/{user_timestamped_filename}"

class Submission(models.Model):
id = models.AutoField(primary_key=True)
Expand All @@ -68,7 +84,7 @@ class Submission(models.Model):
passed = models.BooleanField()
date = models.DateTimeField(auto_now_add=True, verbose_name='submission date')
language = models.CharField(max_length=256, verbose_name='programming language')
file = models.FileField(upload_to='uploads/%Y/%m/%d', verbose_name='source code file')
file = models.FileField(upload_to=user_timestamped_filepath, verbose_name='source code file')
runtime_sum = models.FloatField(default=0.0)
max_mem_usage = models.FloatField(default=0.0)

Expand Down
66 changes: 45 additions & 21 deletions src/problems/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,45 @@ def post(request, problem_name):
logger.info("Submission from user: {}".format(user))
logger.info("Submission from user_profile: {}".format(user_profile))

language = request.POST['language']
extension = {'python': 'py', 'javascript': 'js', 'julia': 'jl'}.get(language)
logger.info("Language selected: {:s}".format(language))

if button_clicked == 'submit-code-button':
language = request.POST['language']
logger.info("Language selected: {:s}".format(language))

extension = {'python': 'py', 'javascript': 'js', 'julia': 'jl', 'c': 'c'}.get(language)

editor_code = request.POST['raw-code']
logger.info("User code:\n{:}".format(editor_code))
raw_code = str(base64.b64encode(bytes(editor_code, 'utf-8')), 'utf-8')

t = datetime.datetime.now()
user_code_filename = "{:}_{:}_{:}.{}".format(problem_name, username, t.strftime("%Y%m%d%H%M%S"), extension)
user_code_filepath = os.path.join(settings.MEDIA_ROOT, "uploads", str(t.year), str(t.month), str(t.day), user_code_filename)
datetime_now = datetime.datetime.now()
year = datetime_now.strftime("%Y")
month = datetime_now.strftime("%m")
day = datetime_now.strftime("%d")
timestamp = datetime_now.strftime("%Y%m%d%H%M%S")

user_code_filename = f"{problem_name}_{username}_{timestamp}.{extension}"
user_code_filepath = os.path.join(settings.MEDIA_ROOT, "uploads", year, month, day, user_code_filename)

user_code_dir = os.path.dirname(user_code_filepath)
if not os.path.exists(user_code_dir):
logger.info("Creating directory: {:}".format(user_code_dir))
logger.info(f"Creating directory: {user_code_dir}")
os.makedirs(user_code_dir)

logger.info("Writing user code to file: {:s}".format(user_code_filepath))
pfile = open(user_code_filepath, 'w+') # Python file
file = File(pfile) # Creating Django file from Python file
file.name = os.path.join("uploads", str(t.year), str(t.month), str(t.day), user_code_filename)
logger.info(f"Writing user code to file: {user_code_filepath}")

# Not sure if there's a simpler way to do this but we first create a Python File
# and write the code into it. We then create a Django File from the Python File
# so we can pass it to Django when creating the `Submission` below.
# We then delete the Python File at the end lol.

pfile = open(user_code_filepath, 'w+') # Python File
pfile.write(editor_code)
# We close pfile at the end, after the submission has been saved.

file = File(pfile) # Creating Django File from Python file

# Apparently this will use the user_timestamped_filepath function to determine the filepath
# so we give it a generic filename like "chaos.py" and Django figures out the rest.
file.name = problem_name + "." + extension

logger.debug("user_code_filepath = {}".format(user_code_filepath))
logger.debug("user_code_filename = {}".format(user_code_filename))
Expand All @@ -179,10 +194,18 @@ def post(request, problem_name):
try:
file = form.files['file']
except KeyError:
return JsonResponse({'error': 'Please attach your file before submitting.'})
return JsonResponse({'error': 'Please choose and attach your file before submitting.'})

if file.size > MAX_FILE_SIZE_BYTES:
return JsonResponse({'error': 'File must be smaller than {} bytes.'.format(MAX_FILE_SIZE_BYTES)})
return JsonResponse({'error': f'File must be smaller than {MAX_FILE_SIZE_BYTES} bytes.'})

base_filename, extension = os.path.splitext(file.name)
ext2language = {'.py': 'python', '.js': 'javascript', '.jl': 'julia', '.c': 'c'}
language = ext2language.get(extension)

if language is None:
supported_extensions = "".join([lang + " -> " + ext + "\n" for ext, lang in ext2language.items()])
return JsonResponse({'error': f'Invalid file extension: {extension}. Supported languages and extensions are\n' + supported_extensions})

raw_code = str(base64.b64encode(file.read()), 'utf-8')

Expand Down Expand Up @@ -236,19 +259,20 @@ def post(request, problem_name):
)
submission.save()

if button_clicked == "submit-code-button":
pfile.close()
os.remove(pfile.name)

# Increment user's submission count by 1.
# user_profile.update(submissions_made=F('submissions_made') + 1)
UserProfile.objects.filter(user=user).update(submissions_made=F('submissions_made') + 1) # this can be simplified
# If successful and is user's first successful submission, increment problem's solved_by and user's problems_solved.

# If successful and is user's first successful submission, increment problem's solved_by by 1 and user's problems_solved by 1.
if results['success']:
num_previous_successful_submissions = Submission.objects.filter(user=user_profile, passed=True, problem__name=problem_name).count()
logger.info("# previous successful submissions: {:}".format(num_previous_successful_submissions))
if num_previous_successful_submissions == 1:
logger.info("User {:} successfully solved {:}. Incrementing problem's solved_by 1 to TODO.".format(user_profile, problem_name))
logger.info("User {:} successfully solved {:}. Incrementing problem's solved_by 1.".format(user_profile, problem_name))
Problem.objects.filter(name=problem_name).update(solved_by=F('solved_by') + 1)
UserProfile.objects.filter(user=user).update(problems_solved=F('problems_solved') + 1) # this can be simplified

if button_clicked == "submit-code-button":
pfile.close()

return JsonResponse({'results': results})