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
Jobs: Added API endpoint for creating submission to a Challenge Phase #396
Conversation
@@ -36,8 +40,10 @@ class Submission(TimeStampedModel): | |||
(FINISHED, FINISHED), | |||
) | |||
|
|||
participant_team = models.ForeignKey(ParticipantTeam, related_name='submissions') | |||
challenge_phase = models.ForeignKey(ChallengePhase, related_name='submissions') | |||
participant_team = models.ForeignKey( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make this user? That will store the participant team information as well, right?
It will require one extra join though to query submission per team. Might be good to add both then?
Saying this because then we will loose out information regarding which user submitted. Not super relevant info but still.
|
||
urlpatterns = [ | ||
url(r'challenge/(?P<challange_id>[0-9]+)/' | ||
r'challenge_phase/(?P<challange_phase_id>[0-9]+)/', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be appended with a submission end_point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you update / delete / get the submission given submission id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we will add that functionality soon. This PR mainly focuses on creating the Submission only since this is a major feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, but it still feels odd to not have a submission end_point appended after challenge_phase_id. The post request should be sent to /challenge/<challenge_id>/challenge_phase<phase_id>/submission right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, since it is in the jobs
app, so the url would be /api/jobs/challenge/<challenge_id>/challenge_phase<phase_id>/submission
. Does that sound good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sg
|
||
from challenges.models import ( | ||
ChallengePhase, | ||
Challenge,) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comma at the end of a tuple seems inconsistent in the whole file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the convention that we should ideally follow and we have been maintaining this throughout the project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in this file though. There are a couple of places where there is no comma. Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will look into it.
def get_participant_team_id_of_a_user_for_a_challenge(user, challenge_id): | ||
"""Returns the participant team object for a particular user for a particular challenge""" | ||
participant_teams = Participant.object.filter(user=user).values_list('teams', flat=True) | ||
for participant_team in participant_teams: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a better way to do this using joins.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I am figuring that out.
def check_user_participated_in_challenge(user, challenge_id): | ||
"""Returns boolean if the user has participated in a particular challenge""" | ||
participant_teams = Participant.object.filter(user=user).values_list('teams', flat=True) | ||
return Challenge.objects.filter(pk=challenge_id, participant_teams__in=participant_teams).exists() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should essentially use the information from get_participant_team_id_of_a_user_for_a_challenge function.
If participant_team is provided, use the info otherwise call the function to get that info based on user.
Should also rename these functions to make them concise.
I am assuming this is gonna handle only the file upload part! actual update of the submission status will be done through channels? |
if ((submissions_done_today_count + 1 - failed_count > self.challenge_phase.max_submissions_per_day) or | ||
(self.challenge_phase.max_submissions_per_day == 0)): | ||
print 'PERMISSION DENIED' | ||
raise PermissionDenied( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are not handling this exception in the views.py L78 I guess. We should handle this exception gracefully and send a proper error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what kind of exception we can get there? Can you please elaborate more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If permission is denied then on save, it will throw an error, right? This I guess will call 500 Internal error. We should catch this exception and throw a 401 error saying that max number of submissions have been made.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it will give a 403 response so we are good to go with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Ok for now but minor point is the stack trace will miss the actual endpoint that is called. Can you plz add a simple /TODO just so that we remember to check.
participant_team=self.participant_team, | ||
status=Submission.FAILED).count() | ||
|
||
offset_submission_count = self.submission_number - failed_count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we rename this to successful_count?
Also we are going to use success_count more, so might be better to explicitly save that and not save failed_count?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we are not storing the successful or failure counts since we can calculate that by seeing the status of the submissions made by the user.
|
||
|
||
@admin.register(Submission) | ||
class SubmissioneAdmin(TimeStampedAdmin): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo, SubmissioneAdmin --> SubmissionAdmin
'submission_number', 'submitted_at', 'input_file', 'stdout_file', 'stderr_file') | ||
list_filter = ('participant_team', 'challenge_phase', | ||
'created_by', 'status', 'is_public') | ||
search_fields = ('participant_team', 'challenge_phase', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally search field should contains fields which are text or integer(if it is very necessary). Can you please update here ?
from django.db import models | ||
from django.db.models import Max |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please order the import alphabetically ?
offset_submission_count = self.submission_number - failed_count | ||
|
||
if (offset_submission_count > self.challenge_phase.max_submissions): | ||
print "Checking to see if the offset_submission_count (%d) ", \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this print statement can be more meaningful if we log additional details like submission id, participant team id or phase id. can you please add accordingly here and for the else block also
|
||
self.status = Submission.SUBMITTED | ||
|
||
saved_model = super(Submission, self).save(*args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this variable name should be renamed to saved_model_instance. It actually makes more sense.
|
||
from accounts.permissions import HasVerifiedEmail | ||
|
||
from participants.utils import ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please reorder imports alphabetically ?
# check if the challenge exists or not | ||
try: | ||
challenge = Challenge.objects.get(pk=challenge_id) | ||
if not challenge.is_active(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if take is_active
check out of try-catch
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED) | ||
|
||
# check if challenge is public and accepting solutions | ||
if not challenge.is_public: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why querying for phase, when challenge is not active, should be after challenge.is_public
check
|
||
if ((submissions_done_today_count + 1 - failed_count > self.challenge_phase.max_submissions_per_day) or | ||
(self.challenge_phase.max_submissions_per_day == 0)): | ||
print 'PERMISSION DENIED' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also this print statement can be made more meaningful, so that it becomes easier for us to debug any issue(in case if it happens).
One more thing which I am curious about is why are we not using python logging module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah we will replace all the debugging statements with logging once the pr is ready.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is still pending.
(self.challenge_phase.max_submissions_per_day == 0)): | ||
print 'PERMISSION DENIED' | ||
raise PermissionDenied( | ||
"The maximum number of submissions this day have been reached.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be The maximum number of submissions that can be made today has reached
or The maximum number of submission for today has been reached
.
print 'PERMISSION DENIED' | ||
raise PermissionDenied( | ||
"The maximum number of submissions this day have been reached.") | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really didn't get this else here. What is it for? Can you explain me more clearly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that it's not required here according to our use case. I will remove it.
if ((submissions_done_today_count + 1 - failed_count > self.challenge_phase.max_submissions_per_day) or | ||
(self.challenge_phase.max_submissions_per_day == 0)): | ||
print 'PERMISSION DENIED' | ||
raise PermissionDenied( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Ok for now but minor point is the stack trace will miss the actual endpoint that is called. Can you plz add a simple /TODO just so that we remember to check.
@trojan can you please review the PR again? I have added the tests. |
successful_count = self.submission_number - failed_count | ||
|
||
if (successful_count > self.challenge_phase.max_submissions): | ||
print "Checking to see if the successful_count (%d) ", \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the print message is fine, but what if we write it like this(suggestion)
print 'Submission request is submitted by user {user_id} from participant team {} with successful count {} for challenge phase {} with max submission {}'.format(user_id=, ....)
This way it will be easier for us to grep from logs in case of error.
raise PermissionDenied( | ||
"The maximum number of submissions has been reached.") | ||
else: | ||
print "Submission is below for user (%d) form participant_team (%d) for challenge_phase (%d)" % ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess if we print the above message, then this print is absolutely not required.
try: | ||
challenge = Challenge.objects.get(pk=challenge_id) | ||
except Challenge.DoesNotExist: | ||
response_data = {'error': 'Challenge does not exist!'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should enforce a standard here, some messages have an exclamation at the end, where as others dont.
just a suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets remove the exclamation mark. We have been following that convention so far.
successful_count = self.submission_number - failed_count | ||
|
||
if successful_count > self.challenge_phase.max_submissions: | ||
print "Checking to see if the successful_count {0} ", \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you combine both the print
statements. Also I will prefer that log a print statement containing every required detail just above the if statement and then you can remove print in else statement.
|
||
if ((submissions_done_today_count + 1 - failed_count > self.challenge_phase.max_submissions_per_day) or | ||
(self.challenge_phase.max_submissions_per_day == 0)): | ||
print 'PERMISSION DENIED' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is still pending.
if serializer.is_valid(): | ||
serializer.save() | ||
response_data = serializer.data | ||
print response_data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think this print statement should be not there.
try: | ||
participant_team = ParticipantTeam.objects.get(pk=participant_team_id) | ||
except ParticipantTeam.DoesNotExist: | ||
response_data = {'error': 'You haven\'t participated in the challenge'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can write this string in double quotes, then we wont have to escape t
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have used single quotes since we have been following this convention so far.
from django.contrib.auth.models import User | ||
from django.utils import timezone | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a extra newline.
return Challenge.objects.filter(pk=challenge_id, participant_teams__in=participant_teams).exists() | ||
|
||
|
||
def get_participant_team_id_of_user_for_a_challenge(user, challenge_id): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see if this helps here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am pretty sure that there is a better way of doing this. Will look into it. For now, I think we are good with this.
@trojan can you please check and merge it so that we can start integrating the worker? |
@deshraj : looks good, can be merged now |
…#396) * Challenges: property methods added to Challenge and ChallengePhase Model * Docstring added to each method * Tests added for Challenge model * More test cases added * Jobs: Added API endpoint for making submission to a Challenge Phase * Minor fixes incorporating the changes suggested * API endpoint for submission ready * Unused imports removed * Minor additions in the business logic * Tests added for Challenge Submission API * Unused imports removed from jobs app * Removed unused modules from tests * Removed unused modules from views * Logging statments added for jobs models * Tests modified for Jobs app
No description provided.