class Test(models.Model): problem = models.ForeignKey(Problem) name = models.CharField(max_length=30, verbose_name=_("name")) input_file = FileField(upload_to=make_problem_filename, verbose_name=_("input"), null=True, blank=True) output_file = FileField(upload_to=make_problem_filename, verbose_name=_("output/hint"), null=True, blank=True) kind = EnumField(test_kinds, verbose_name=_("kind")) group = models.CharField(max_length=30, verbose_name=_("group")) time_limit = models.IntegerField(verbose_name=_("time limit (ms)"), null=True, blank=True) memory_limit = models.IntegerField(verbose_name=_("memory limit (KiB)"), null=True, blank=True) max_score = models.IntegerField(verbose_name=_("score"), default=10) order = models.IntegerField(default=0) class Meta: ordering = ['order'] verbose_name = _("test") verbose_name_plural = _("tests")
class ContestAttachment(models.Model): """Represents an additional file visible to the contestant, linked to the contest or to the round. This may be used for additional materials, like rules, documentation etc. """ contest = models.ForeignKey(Contest, related_name='c_attachments', verbose_name=_("contest")) description = models.CharField(max_length=255, verbose_name=_("description")) content = FileField(upload_to=make_contest_filename, verbose_name=_("content")) round = models.ForeignKey('Round', related_name='r_attachments', blank=True, null=True, verbose_name=_("round")) @property def filename(self): return os.path.split(self.content.name)[1] class Meta(object): verbose_name = _("attachment") verbose_name_plural = _("attachments")
class ProblemAttachment(models.Model): """Represents an additional file visible to the contestant, linked to a problem. This may be used for things like input data for open data tasks, or for giving users additional libraries etc. """ problem = models.ForeignKey(Problem, related_name='attachments') description = models.CharField(max_length=255, verbose_name=_("description")) content = FileField(upload_to=make_problem_filename, verbose_name=_("content")) @property def filename(self): return os.path.split(self.content.name)[1] @property def download_name(self): return strip_num_or_hash(self.filename) class Meta(object): verbose_name = _("attachment") verbose_name_plural = _("attachments") def __unicode__(self): return '%s / %s' % (self.problem.name, self.filename)
class MailSubmission(models.Model): problem_instance = models.ForeignKey(ProblemInstance, verbose_name=_("problem"), on_delete=models.CASCADE) user = models.ForeignKey(User, blank=True, null=True, verbose_name=_("user"), on_delete=models.CASCADE) date = models.DateTimeField(default=timezone.now, blank=True, verbose_name=_("date"), db_index=True) source_file = FileField(upload_to=make_submission_filename) submission = models.ForeignKey( Submission, blank=True, null=True, verbose_name=_("related submission"), on_delete=models.CASCADE, ) accepted_by = models.ForeignKey( User, blank=True, null=True, verbose_name=_("accepted by"), related_name='+', on_delete=models.SET_NULL, )
class TestsPackage(models.Model): __test__ = False problem = models.ForeignKey(Problem) name = models.CharField(max_length=30, verbose_name=_("file name"), help_text=_("File name can only contain letters, digits," " - and _. It should not contain file extension such as" " .zip, .tgz, etc."), validators=[RegexValidator(r'^[0-9a-zA-Z\-_]+$', _("Name can only contain letters, digits, - and _."))]) description = models.TextField(null=False, blank=True) tests = models.ManyToManyField(Test) package = FileField(upload_to=make_problem_filename, null=True, blank=True, verbose_name=_("package")) publish_date = models.DateTimeField(null=True, blank=True, verbose_name=_("publish date"), help_text=_("If the date is left blank, the package will never " "be visible for participants of the contest.")) _old_tests = None def is_visible(self, current_datetime): return self.publish_date is not None and \ self.publish_date < current_datetime class Meta(object): verbose_name = _("tests package") verbose_name_plural = _("tests packages")
class TestRunReport(models.Model): submission_report = models.ForeignKey(SubmissionReport) status = EnumField(submission_statuses) comment = models.CharField(max_length=255, blank=True) time_used = models.IntegerField(blank=True) test_time_limit = models.IntegerField(null=True, blank=True) output_file = FileField(upload_to=make_custom_output_filename)
class ContestAttachment(models.Model): """Represents an additional file visible to the contestant, linked to the contest or to the round. This may be used for additional materials, like rules, documentation etc. """ contest = models.ForeignKey(Contest, related_name='c_attachments', verbose_name=_("contest"), on_delete=models.CASCADE) description = models.CharField(max_length=255, verbose_name=_("description")) content = FileField(upload_to=make_contest_filename, verbose_name=_("content")) round = models.ForeignKey('Round', related_name='r_attachments', blank=True, null=True, verbose_name=_("round"), on_delete=models.CASCADE) pub_date = models.DateTimeField(default=None, blank=True, null=True, verbose_name=_("publication date")) @property def filename(self): return os.path.split(self.content.name)[1] @property def download_name(self): return strip_num_or_hash(self.filename) def __unicode__(self): return self.filename class Meta(object): verbose_name = _("attachment") verbose_name_plural = _("attachments")
class ProblemStatement(models.Model): """Represents a file containing problem statement. Problem may have multiple statements, for example in various languages or formats. Formats should be detected according to filename extension of :attr:`content`. """ problem = models.ForeignKey(Problem, related_name='statements') language = models.CharField(max_length=6, blank=True, null=True, verbose_name=_("language code")) content = FileField(upload_to=make_problem_filename, verbose_name=_("content")) @property def filename(self): return os.path.split(self.content.name)[1] @property def download_name(self): return self.problem.short_name + self.extension @property def extension(self): return os.path.splitext(self.content.name)[1].lower() class Meta(object): verbose_name = _("problem statement") verbose_name_plural = _("problem statements") def __unicode__(self): return '%s / %s' % (self.problem.name, self.filename)
class OutputChecker(models.Model): problem = models.OneToOneField(Problem) exe_file = FileField(upload_to=make_problem_filename, null=True, blank=True, verbose_name=_("checker executable file")) class Meta(object): verbose_name = _("output checker") verbose_name_plural = _("output checkers")
class OriginalPackage(models.Model): problem = models.ForeignKey(Problem) package_file = FileField(upload_to=make_problem_filename, verbose_name=_("package")) class Meta(object): verbose_name = _("original sinolpack package") verbose_name_plural = _("original sinolpack packages")
class Test(models.Model): __test__ = False problem_instance = models.ForeignKey(ProblemInstance, on_delete=models.CASCADE) name = models.CharField(max_length=30, verbose_name=_("name")) input_file = FileField(upload_to=make_problem_filename, verbose_name=_("input"), null=True, blank=True) output_file = FileField( upload_to=make_problem_filename, verbose_name=_("output/hint"), null=True, blank=True, ) kind = EnumField(test_kinds, verbose_name=_("kind")) group = models.CharField(max_length=30, verbose_name=_("group")) time_limit = models.IntegerField( verbose_name=_("time limit (ms)"), null=True, blank=False, validators=[validate_time_limit], ) memory_limit = models.IntegerField( verbose_name=_("memory limit (KiB)"), null=True, blank=True, validators=[validate_memory_limit], ) max_score = models.IntegerField(verbose_name=_("score"), default=10) order = models.IntegerField(default=0) is_active = models.BooleanField(default=True) @property def problem(self): return self.problem_instance.problem def __str__(self): return six.text_type(self.name) class Meta(object): ordering = ['order'] verbose_name = _("test") verbose_name_plural = _("tests") unique_together = ('problem_instance', 'name')
class ProgramSubmission(Submission): source_file = FileField(upload_to=make_submission_filename) source_length = models.IntegerField(verbose_name=_("Source code length"), blank=True, null=True) def save(self, *args, **kwargs): if self.source_file: self.source_length = self.source_file.size super(ProgramSubmission, self).save(*args, **kwargs)
class ExtraFile(models.Model): """Model to store extra files (for example ``extra.zip``) present in some Sinol packages.""" problem = models.ForeignKey(Problem) name = models.CharField(max_length=255, verbose_name=_("name")) file = FileField(upload_to=make_problem_filename) class Meta(object): verbose_name = _("sinolpack's extra file") verbose_name_plural = _("sinolpack's extra files")
class ModelSolution(models.Model): objects = ModelSolutionsManager() problem = models.ForeignKey(Problem) name = models.CharField(max_length=30, verbose_name=_("name")) source_file = FileField(upload_to=make_problem_filename, verbose_name=_("source")) kind = EnumField(model_solution_kinds, verbose_name=_("kind")) @property def short_name(self): return self.name.rsplit('.', 1)[0]
class ProgramSubmission(Submission): source_file = FileField(upload_to=make_submission_filename) source_length = models.IntegerField(verbose_name=_("Source code length"), blank=True, null=True) def save(self, *args, **kwargs): if self.source_file: self.source_length = self.source_file.size super(ProgramSubmission, self).save(*args, **kwargs) @property def extension(self): return os.path.splitext(self.source_file.name)[1][1:] def get_language_display(self): return get_language_by_extension(self.problem_instance, self.extension)
class ContestIcon(models.Model): contest = models.ForeignKey(Contest, verbose_name=_("contest")) image = FileField(upload_to=make_icon_filename, verbose_name=_('icon image')) updated_at = models.DateTimeField(default=timezone.now) def save(self, *args, **kwargs): self.updated_at = timezone.now() return super(ContestIcon, self).save(*args, **kwargs) @property def filename(self): return os.path.split(self.image.name)[1] class Meta(object): verbose_name = _("contest icon") verbose_name_plural = _("contest icons")
class TestReport(models.Model): submission_report = models.ForeignKey(SubmissionReport) status = EnumField(submission_statuses) comment = models.CharField(max_length=255, blank=True) score = ScoreField(null=True, blank=True) time_used = models.IntegerField(blank=True) output_file = FileField(upload_to=make_output_filename, null=True, blank=True) test = models.ForeignKey(Test, blank=True, null=True, on_delete=models.SET_NULL) test_name = models.CharField(max_length=30) test_group = models.CharField(max_length=30) test_time_limit = models.IntegerField(null=True, blank=True) test_max_score = models.IntegerField(null=True, blank=True)
class ContestLogo(models.Model): contest = models.OneToOneField(Contest, verbose_name=_("contest"), primary_key=True) image = FileField(upload_to=make_logo_filename, verbose_name=_("logo image")) updated_at = models.DateTimeField(default=timezone.now) def save(self, *args, **kwargs): self.updated_at = timezone.now() return super(ContestLogo, self).save(*args, **kwargs) @property def filename(self): return os.path.split(self.image.name)[1] class Meta(object): verbose_name = _("contest logo") verbose_name_plural = _("contest logo")
class OutputChecker(models.Model): problem = models.OneToOneField(Problem) exe_file = FileField(upload_to=make_problem_filename, null=True, blank=True)
class TestRunProgramSubmission(ProgramSubmission): input_file = FileField(upload_to=make_custom_input_filename)
class ZeusTestRunProgramSubmission(TestRunProgramSubmission): library_file = FileField(upload_to=make_custom_library_filename, null=True)
class ExtraFile(models.Model): """Model to store extra files (for example ``extra.zip``) present in some Sinol packages.""" problem = models.ForeignKey(Problem) name = models.CharField(max_length=255) file = FileField(upload_to=make_problem_filename)
class ProgramSubmission(Submission): source_file = FileField(upload_to=make_submission_filename)
class OriginalPackage(models.Model): problem = models.ForeignKey(Problem) package_file = FileField(upload_to=make_problem_filename)
class PrizeGiving(models.Model): """Represents an event of distributing prizes to users. Such an event needs some proper preparation. First of all, we decide on a date when it's going to happen. Secondly, we decide on a list of Prizes to be distributed. Finally, we specify a distributor, which will effectively assign Prizes to users creating PrizeForUser objects. To make it all work together, we set up a celery worker to which we send scheduled distribution tasks. Each time a PrizeGiving object is changed in a way that makes recently sent task irrelevant the task is invalidated and a new task is sent if needed. """ # Usage: # pg = PrizeGiving.objects.create(contest=c, date=..., name=..., key=...) # Prize.objects.create(contest=c, prize_giving=pg, ...) # pg.schedule() -- you don't have to call it immediately # # You can change parameters if ``pg`` hasn't taken place yet: # pg.update(contest=..., date=..., name=..., key=...) # pg.schedule() # # Later, when ``pg`` has already succeeded or failed, # in order to undo everything and start the logic again, # you will need to specify 'force_reset=True' with ``update``: # pg.update(force_reset=True, ...) # pg.schedule() # # At any time you can delete ``pg`` as if it has never existed. # pg.delete() contest = models.ForeignKey(Contest, on_delete=models.CASCADE) date = models.DateTimeField(_("Distribution date"), blank=True, null=True, help_text=_("Leave blank for 'later'.")) name = models.CharField(_("name"), max_length=100, help_text=_("Prize-givings with the same name " "are listed together.")) # key in dictionary returned by ContestController.get_prizes_distributors() key = models.CharField(_("awarding rules"), max_length=100, choices=[('', '')]) # read-only fields state = EnumField(prizegiving_states, default='NOT_SCHEDULED', editable=False) report = FileField(upload_to=_make_report_filename, null=True, editable=False) # private fields version = models.DateTimeField(default=timezone.now, editable=False) class Meta(object): verbose_name = _("prize-giving") verbose_name_plural = _("prize-givings") ordering = ['-date', 'id'] def _distribute(self, on_success=lambda: None, on_failure=lambda: None): try: _name, distributor = \ self.contest.controller.get_prizes_distributors()[self.key] distributor(self) self.state = 'SUCCESS' report = generate_success_report(self) on_success() except AssignmentNotFound as e: e.send_email() self.date = None self.state = 'FAILURE' report = e.report on_failure() # The related PrizeForUsers are deleted in call to `save`. self._set_report(report) self._change_version() self.save() def _set_report(self, report): if report is not None: filename = 'id%s__v%s__%s.csv' % \ (self.id, self.version, self.state) report = ContentFile(report, filename) self.report = report def _send_task_to_worker(self): prizesmgr_job.apply_async(args=[self.pk, self.version], eta=self.date) def _delegate_distribution(self): self.state = 'SCHEDULED' self.save() self._send_task_to_worker() def _distribute_in_place(self): self.date = timezone.now() self._distribute() def schedule(self): """Schedule to happen. You need to call it both after creating a new PrizeGiving or updating an old one. Note, that here we refer to an event rather than a particular PrizeGiving instance representing it. For example, you can save information about PrizeGiving to the database using a PrizeGiving instance, retrieve a new instance representing it two days later, and call `schedule` on it. If PrizeGiving is past its date, the distribution will be triggered from within this funtion. It calls `save` behind the scenes. """ if self.state == 'NOT_SCHEDULED' and self.date: self._change_version() # Take into account that user chooses "now" with 1 minute accuracy if self.date >= timezone.now() + timedelta(minutes=1): self._delegate_distribution() else: self._distribute_in_place() def _celery_task_invalid(self): """Check if there is a valid celery task that now must be invalidated. """ try: old_self = PrizeGiving.objects.select_for_update().get(pk=self.pk) except PrizeGiving.DoesNotExist: old_self = PrizeGiving() if old_self.state != 'SCHEDULED': return False def essential_params(pg): return model_to_dict(pg, fields=['state', 'date']) return essential_params(old_self) != essential_params(self) def _change_version(self): self.version = timezone.now() def update(self, commit=True, force_reset=False, **kwargs): """Always call this method after changing public attributes. For convenience, you can set attributes and update in one step using keyword arguments. """ for key, value in kwargs.items(): setattr(self, key, value) if force_reset: self._set_report(None) if force_reset or self._celery_task_invalid(): self.state = 'NOT_SCHEDULED' self._change_version() # In case the previous state was 'SUCCESS' and it changed, the # related PrizeForUsers are deleted in call to `save`. if commit: self.save() def save(self, *args, **kwargs): # PrizeGiving can have related PrizeForUsers in 'SUCCESS' state only. if self.state != 'SUCCESS': PrizeForUser.objects.filter(prize__prize_giving=self).delete() return super(PrizeGiving, self).save(*args, **kwargs) def clean_fields(self, *args, **kwargs): if 'key' not in kwargs['exclude']: kwargs['exclude'].append('key') super(PrizeGiving, self).clean_fields(*args, **kwargs) def clean(self): if self.key not in self.contest.controller.get_prizes_distributors(): raise ValidationError( _("Invalid value for position in distribution order.")) def __unicode__(self): suffix = timezone.localtime(self.date).strftime(' (%m-%d %H:%M)') \ if self.date else '' return self.name + suffix
class ProblemPackage(models.Model): """Represents a file with data necessary for creating a :class:`~oioioi.problems.models.Problem` instance. """ package_file = FileField(upload_to=_make_package_filename, verbose_name=_("package")) contest = models.ForeignKey('contests.Contest', null=True, blank=True, verbose_name=_("contest")) problem = models.ForeignKey(Problem, verbose_name=_("problem"), null=True, blank=True) created_by = models.ForeignKey(User, verbose_name=_("created by"), null=True, blank=True) problem_name = models.CharField(max_length=30, validators=[validate_slug], verbose_name=_("problem name"), null=True, blank=True) celery_task_id = models.CharField(max_length=50, unique=True, null=True, blank=True) info = models.CharField(max_length=1000, null=True, blank=True, verbose_name=_("Package information")) traceback = FileField(upload_to=_make_package_filename, verbose_name=_("traceback"), null=True, blank=True) status = EnumField(package_statuses, default='?', verbose_name=_("status")) creation_date = models.DateTimeField(default=timezone.now) @property def download_name(self): ext = split_extension(self.package_file.name)[1] if self.problem: return self.problem.short_name + ext else: filename = os.path.split(self.package_file.name)[1] return strip_num_or_hash(filename) class Meta(object): verbose_name = _("problem package") verbose_name_plural = _("problem packages") ordering = ['-creation_date'] class StatusSaver(object): def __init__(self, package): self.package_id = package.id def __enter__(self): pass def __exit__(self, type, value, traceback): package = ProblemPackage.objects.get(id=self.package_id) if type: package.status = 'ERR' # Truncate error so it doesn't take up whole page in list # view. Full info is available anyway in package.traceback. package.info = Truncator(value).chars(400) package.traceback = ContentFile( ''.join( format_exception(type, value, traceback, TRACEBACK_STACK_LIMIT)), 'traceback.txt') logger.exception("Error processing package %s", package.package_file.name, extra={'omit_sentry': True}) else: package.status = 'OK' # Truncate message to fit in db. package.info = Truncator(package.info).chars(1000) package.celery_task_id = None package.save() return True def save_operation_status(self): """Returns a context manager to be used during the unpacking process. The code inside the ``with`` statment is executed in a transaction. If the code inside the ``with`` statement executes successfully, the package ``status`` field is set to ``OK``. If an exception is thrown, it gets logged together with the traceback. Additionally, its value is saved in the package ``info`` field. Lastly, if the package gets deleted from the database before the ``with`` statement ends, a :class:`oioioi.problems.models.ProblemPackage.DoesNotExist` exception is thrown. """ @contextmanager def manager(): with self.StatusSaver(self), transaction.atomic(): yield None return manager()
class TestFileModel(models.Model): file_field = FileField(upload_to='tests')