179 lines
8.1 KiB
Python
179 lines
8.1 KiB
Python
from collections import OrderedDict, defaultdict
|
|
from operator import attrgetter
|
|
|
|
from django.conf import settings
|
|
from django.core.cache import cache
|
|
from django.db import models
|
|
from django.db.models import CASCADE
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from django.utils.functional import cached_property
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from judge.judgeapi import disconnect_judge
|
|
|
|
__all__ = ['Language', 'RuntimeVersion', 'Judge']
|
|
|
|
|
|
class Language(models.Model):
|
|
key = models.CharField(max_length=6, verbose_name=_('short identifier'),
|
|
help_text=_('The identifier for this language; the same as its executor id for judges.'),
|
|
unique=True)
|
|
name = models.CharField(max_length=20, verbose_name=_('long name'),
|
|
help_text=_('Longer name for the language, e.g. "Python 2" or "C++11".'))
|
|
short_name = models.CharField(max_length=10, verbose_name=_('short name'),
|
|
help_text=_('More readable, but short, name to display publicly; e.g. "PY2" or '
|
|
'"C++11". If left blank, it will default to the '
|
|
'short identifier.'),
|
|
null=True, blank=True)
|
|
common_name = models.CharField(max_length=10, verbose_name=_('common name'),
|
|
help_text=_('Common name for the language. For example, the common name for C++03, '
|
|
'C++11, and C++14 would be "C++"'))
|
|
ace = models.CharField(max_length=20, verbose_name=_('ace mode name'),
|
|
help_text=_('Language ID for Ace.js editor highlighting, appended to "mode-" to determine '
|
|
'the Ace JavaScript file to use, e.g., "python".'))
|
|
pygments = models.CharField(max_length=20, verbose_name=_('pygments name'),
|
|
help_text=_('Language ID for Pygments highlighting in source windows.'))
|
|
template = models.TextField(verbose_name=_('code template'),
|
|
help_text=_('Code template to display in submission editor.'), blank=True)
|
|
info = models.CharField(max_length=50, verbose_name=_('runtime info override'), blank=True,
|
|
help_text=_("Do not set this unless you know what you're doing! It will override the "
|
|
"usually more specific, judge-provided runtime info!"))
|
|
description = models.TextField(verbose_name=_('language description'),
|
|
help_text=_('Use this field to inform users of quirks with your environment, '
|
|
'additional restrictions, etc.'), blank=True)
|
|
extension = models.CharField(max_length=10, verbose_name=_('extension'),
|
|
help_text=_('The extension of source files, e.g., "py" or "cpp".'))
|
|
|
|
def runtime_versions(self):
|
|
runtimes = OrderedDict()
|
|
# There be dragons here if two judges specify different priorities
|
|
for runtime in self.runtimeversion_set.all():
|
|
id = runtime.name
|
|
if id not in runtimes:
|
|
runtimes[id] = set()
|
|
if not runtime.version: # empty str == error determining version on judge side
|
|
continue
|
|
runtimes[id].add(runtime.version)
|
|
|
|
lang_versions = []
|
|
for id, version_list in runtimes.items():
|
|
lang_versions.append((id, sorted(version_list, key=lambda a: tuple(map(int, a.split('.'))))))
|
|
return lang_versions
|
|
|
|
@classmethod
|
|
def get_common_name_map(cls):
|
|
result = cache.get('lang:cn_map')
|
|
if result is not None:
|
|
return result
|
|
result = defaultdict(set)
|
|
for id, cn in Language.objects.values_list('id', 'common_name'):
|
|
result[cn].add(id)
|
|
result = {id: cns for id, cns in result.items() if len(cns) > 1}
|
|
cache.set('lang:cn_map', result, 86400)
|
|
return result
|
|
|
|
@cached_property
|
|
def short_display_name(self):
|
|
return self.short_name or self.key
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
@cached_property
|
|
def display_name(self):
|
|
if self.info:
|
|
return '%s (%s)' % (self.name, self.info)
|
|
else:
|
|
return self.name
|
|
|
|
@classmethod
|
|
def get_python3(cls):
|
|
# We really need a default language, and this app is in Python 3
|
|
return Language.objects.get_or_create(key='PY3', defaults={'name': 'Python 3'})[0]
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('runtime_list') + '#' + self.key
|
|
|
|
@classmethod
|
|
def get_default_language(cls):
|
|
try:
|
|
return Language.objects.get(key=settings.DEFAULT_USER_LANGUAGE)
|
|
except Language.DoesNotExist:
|
|
return cls.get_python3()
|
|
|
|
@classmethod
|
|
def get_default_language_pk(cls):
|
|
return cls.get_default_language().pk
|
|
|
|
class Meta:
|
|
ordering = ['key']
|
|
verbose_name = _('language')
|
|
verbose_name_plural = _('languages')
|
|
|
|
|
|
class RuntimeVersion(models.Model):
|
|
language = models.ForeignKey(Language, verbose_name=_('language to which this runtime belongs'), on_delete=CASCADE)
|
|
judge = models.ForeignKey('Judge', verbose_name=_('judge on which this runtime exists'), on_delete=CASCADE)
|
|
name = models.CharField(max_length=64, verbose_name=_('runtime name'))
|
|
version = models.CharField(max_length=64, verbose_name=_('runtime version'), blank=True)
|
|
priority = models.IntegerField(verbose_name=_('order in which to display this runtime'), default=0)
|
|
|
|
|
|
class Judge(models.Model):
|
|
name = models.CharField(max_length=50, help_text=_('Server name, hostname-style'), unique=True)
|
|
created = models.DateTimeField(auto_now_add=True, verbose_name=_('time of creation'))
|
|
auth_key = models.CharField(max_length=100, help_text=_('A key to authenticate this judge'),
|
|
verbose_name=_('authentication key'))
|
|
is_blocked = models.BooleanField(verbose_name=_('block judge'), default=False,
|
|
help_text=_('Whether this judge should be blocked from connecting, '
|
|
'even if its key is correct.'))
|
|
online = models.BooleanField(verbose_name=_('judge online status'), default=False)
|
|
start_time = models.DateTimeField(verbose_name=_('judge start time'), null=True)
|
|
ping = models.FloatField(verbose_name=_('response time'), null=True)
|
|
load = models.FloatField(verbose_name=_('system load'), null=True,
|
|
help_text=_('Load for the last minute, divided by processors to be fair.'))
|
|
description = models.TextField(blank=True, verbose_name=_('description'))
|
|
last_ip = models.GenericIPAddressField(verbose_name='Last connected IP', blank=True, null=True)
|
|
problems = models.ManyToManyField('Problem', verbose_name=_('problems'), related_name='judges')
|
|
runtimes = models.ManyToManyField(Language, verbose_name=_('judges'), related_name='judges')
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def disconnect(self, force=False):
|
|
disconnect_judge(self, force=force)
|
|
|
|
disconnect.alters_data = True
|
|
|
|
@cached_property
|
|
def runtime_versions(self):
|
|
qs = (self.runtimeversion_set.values('language__key', 'language__name', 'version', 'name')
|
|
.order_by('language__key', 'priority'))
|
|
|
|
ret = OrderedDict()
|
|
|
|
for data in qs:
|
|
key = data['language__key']
|
|
if key not in ret:
|
|
ret[key] = {'name': data['language__name'], 'runtime': []}
|
|
ret[key]['runtime'].append((data['name'], (data['version'],)))
|
|
|
|
return list(ret.items())
|
|
|
|
@cached_property
|
|
def uptime(self):
|
|
return timezone.now() - self.start_time if self.online else 'N/A'
|
|
|
|
@cached_property
|
|
def ping_ms(self):
|
|
return self.ping * 1000 if self.ping is not None else None
|
|
|
|
@cached_property
|
|
def runtime_list(self):
|
|
return map(attrgetter('name'), self.runtimes.all())
|
|
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name = _('judge')
|
|
verbose_name_plural = _('judges')
|