Merge pull request #3 from LQDJudge/cuom1999

Cuom1999
This commit is contained in:
Phuoc Dinh Le 2020-03-26 19:04:23 -06:00 committed by GitHub
commit 536b275502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 4515 additions and 1684 deletions

View file

@ -49,14 +49,14 @@
<br> <br>
<div class="popup"> <div class="popup">
<div> <div>
<img class="logo" src="/logo.png" alt="DMOJ"> <img class="logo" src="logo.png" alt="DMOJ">
</div> </div>
<h1 style="width: 100%;">Oops, the DMOJ is down.</h1> <h1 style="width: 100%;">Oops, LQDOJ is down now.</h1>
</div> </div>
<br> <br>
<hr> <hr>
<br> <br>
<h2 class="msg">But don't worry, we'll be back soon.</h2> <h2 class="msg">But don't worry, we'll be back soon. <br> In the free time, you can read my idol's codes <a href="http://codeforces.com/profile/cuom1999" style="color:red; text-decoration:none">cuom1999</a></h2>
</div> </div>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

View file

@ -20,3 +20,6 @@ python dmojauto-conf
- modify .po file - modify .po file
- python manage.py compilemessages - python manage.py compilemessages
- python manage.py compilejsi18n - python manage.py compilejsi18n
###8. Run chat server:
docker run -p 6379:6379 -d redis:2.8

View file

@ -1,11 +1,17 @@
import json import json
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
from .models import Message
from .views import format_time
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect
from judge.models.profile import Profile
class ChatConsumer(AsyncWebsocketConsumer): class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
self.room_name = 'common' self.room_name = 'room'
self.room_group_name = 'chat_%s' % self.room_name self.room_group_name = 'chat_%s' % self.room_name
# Join room group # Join room group
@ -27,6 +33,8 @@ class ChatConsumer(AsyncWebsocketConsumer):
async def receive(self, text_data): async def receive(self, text_data):
text_data_json = json.loads(text_data) text_data_json = json.loads(text_data)
message = text_data_json['message'] message = text_data_json['message']
time = save_data_and_get_time(message)
message['time'] = format_time(time)
# Send message to room group # Send message to room group
await self.channel_layer.group_send( await self.channel_layer.group_send(
@ -40,8 +48,17 @@ class ChatConsumer(AsyncWebsocketConsumer):
# Receive message from room group # Receive message from room group
async def chat_message(self, event): async def chat_message(self, event):
message = event['message'] message = event['message']
# Send message to WebSocket # Send message to WebSocket
await self.send(text_data=json.dumps({ await self.send(text_data=json.dumps({
'message': message, 'message': message,
})) }))
# return time
def save_data_and_get_time(message):
new_message = Message(body=message['body'],
author=Profile.objects
.get(pk=message['author_id']),
)
new_message.save()
return new_message.time

View file

@ -1,5 +1,3 @@
# based on https://github.com/narrowfail/django-channels-chat
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.db import models from django.db import models
@ -18,25 +16,10 @@ class Message(models.Model):
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True) time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192) body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
def notify_ws_clients(self):
# inform client that there is a new message
notification = {
'type': 'recieve_group_message',
'message': '{}'.format(self.id)
}
channel_layer = get_channel_layer()
# print("user.id {}".format(self.user.id))
# print("user.id {}".format(self.recipient.id))
async_to_sync(channel_layer.group_send)("{}".format(self.user.id), notification)
async_to_sync(channel_layer.group_send)("{}".format(self.recipient.id), notification)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
new_message = self.id new_message = self.id
self.body = self.body.strip() self.body = self.body.strip()
super(Message, self).save(*args, **kwargs) super(Message, self).save(*args, **kwargs)
if new_message is None:
self.notify_ws_clients()
class Meta: class Meta:
app_label = 'chat_box' app_label = 'chat_box'

View file

@ -1,7 +1,50 @@
from django.shortcuts import render
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views import View from django.views.generic import ListView
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render
from django.core.paginator import Paginator
from django.urls import reverse
from judge.jinja2.gravatar import gravatar
from .models import Message
import json
class ChatView(View): def format_time(time):
template_name = 'chat.html' return time.strftime('%H:%M %p %d-%m-%Y')
def format_messages(messages):
msg_list = [{
'time': format_time(msg.time),
'author': str(msg.author),
'body': msg.body,
'image': gravatar(msg.author, 32),
} for msg in messages]
return json.dumps(msg_list)
class ChatView(ListView):
model = Message
context_object_name = 'message'
template_name = 'chat/chat.html'
title = _('Chat Box')
paginate_by = 50
paginator = Paginator(Message.objects.all(), paginate_by)
def get(self, request, *args, **kwargs):
page = request.GET.get('page')
if (page == None):
return super().get(request, *args, **kwargs)
cur_page = self.paginator.get_page(page)
return HttpResponse(format_messages(cur_page.object_list))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = self.title
for msg in context['message']:
msg.time = format_time(msg.time)
return context

View file

@ -1,314 +0,0 @@
#####################################
########## Django settings ##########
#####################################
# See <https://docs.djangoproject.com/en/1.11/ref/settings/>
# for more info and help. If you are stuck, you can try Googling about
# Django - many of these settings below have external documentation about them.
#
# The settings listed here are of special interest in configuring the site.
# SECURITY WARNING: keep the secret key used in production secret!
# You may use <http://www.miniwebtool.com/django-secret-key-generator/>
# to generate this key.
SECRET_KEY = 's(8*xdlaiy4r@09cidu#)h%%iey39099g=hp(#+kzz+7vefs4u'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True # Change to False once you are done with runserver testing.
# Uncomment and set to the domain names this site is intended to serve.
# You must do this once you set DEBUG to False.
ALLOWED_HOSTS = ['0.0.0.0']
# Optional apps that DMOJ can make use of.
INSTALLED_APPS += (
)
#path to problem folder
DMOJ_PROBLEM_DATA_ROOT = '/home/Projects/CP/LQDJudge/problems'
# Caching. You can use memcached or redis instead.
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/cache/>
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
}
}
# Your database credentials. Only MySQL is supported by DMOJ.
# Documentation: <https://docs.djangoproject.com/en/1.11/ref/databases/>
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dmoj',
'USER': 'dmoj',
'PASSWORD': 'admintl97p1',
'HOST': '127.0.0.1',
'OPTIONS': {
'charset': 'utf8mb4',
'sql_mode': 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION',
},
}
}
# Sessions.
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/http/sessions/>
#SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
# Internationalization.
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/i18n/>
LANGUAGE_CODE = 'vi'
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
USE_I18N = True
USE_L10N = True
USE_TZ = True
## django-compressor settings, for speeding up page load times by minifying CSS and JavaScript files.
# Documentation: https://django-compressor.readthedocs.io/en/latest/
COMPRESS_OUTPUT_DIR = 'cache'
COMPRESS_CSS_FILTERS = [
'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.CSSMinFilter',
]
COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']
COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage'
STATICFILES_FINDERS += ('compressor.finders.CompressorFinder',)
#########################################
########## Email configuration ##########
#########################################
# See <https://docs.djangoproject.com/en/1.11/topics/email/#email-backends>
# for more documentation. You should follow the information there to define
# your email settings.
# Use this if you are just testing.
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# The following block is included for your convenience, if you want
# to use Gmail.
#EMAIL_USE_TLS = True
#EMAIL_HOST = 'smtp.gmail.com'
#EMAIL_HOST_USER = '<your account>@gmail.com'
#EMAIL_HOST_PASSWORD = '<your password>'
#EMAIL_PORT = 587
# To use Mailgun, uncomment this block.
# You will need to run `pip install django-mailgun` for to get `MailgunBackend`.
#EMAIL_BACKEND = 'django_mailgun.MailgunBackend'
#MAILGUN_ACCESS_KEY = '<your Mailgun access key>'
#MAILGUN_SERVER_NAME = '<your Mailgun domain>'
# You can also use Sendgrid, with `pip install sendgrid-django`.
#EMAIL_BACKEND = 'sgbackend.SendGridBackend'
#SENDGRID_API_KEY = '<Your SendGrid API Key>'
# The DMOJ site is able to notify administrators of errors via email,
# if configured as shown below.
# A tuple of (name, email) pairs that specifies those who will be mailed
# when the server experiences an error when DEBUG = False.
ADMINS = (
('luongd', 'doannguyenthanhluong@gmail.com'),
)
# The sender for the aforementioned emails.
SERVER_EMAIL = 'LQDOJ: Le Quy Don Online Judge <errors@lqdoj.com>'
##################################################
########### Static files configuration. ##########
##################################################
# See <https://docs.djangoproject.com/en/1.11/howto/static-files/>.
# Change this to somewhere more permanent., especially if you are using a
# webserver to serve the static files. This is the directory where all the
# static files DMOJ uses will be collected to.
# You must configure your webserver to serve this directory as /static/ in production.
STATIC_ROOT = '/home/luongd/Projects/LQDJudge/online-judge/static_root'
# URL to access static files.
#STATIC_URL = '/static/'
# Uncomment to use hashed filenames with the cache framework.
#STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
############################################
########## DMOJ-specific settings ##########
############################################
## DMOJ site display settings.
SITE_NAME = 'LQDOJ'
SITE_LONG_NAME = 'LQDOJ: Le Quy Don Online Judge'
SITE_ADMIN_EMAIL = 'admin@example.com'
TERMS_OF_SERVICE_URL = '//dmoj.ca/tos' # Use a flatpage.
## Bridge controls.
# The judge connection address and port; where the judges will connect to the site.
# You should change this to something your judges can actually connect to
# (e.g., a port that is unused and unblocked by a firewall).
BRIDGED_JUDGE_ADDRESS = [('0.0.0.0', 9999)]
# The bridged daemon bind address and port to communicate with the site.
BRIDGED_DJANGO_ADDRESS = [('localhost', 9998)]
## DMOJ features.
# Set to True to enable full-text searching for problems.
ENABLE_FTS = True
# Set of email providers to ban when a user registers, e.g., {'throwawaymail.com'}.
BAD_MAIL_PROVIDERS = set()
# The number of submissions that a staff user can rejudge at once without
# requiring the permission 'Rejudge a lot of submissions'.
# Uncomment to change the submission limit.
REJUDGE_SUBMISSION_LIMIT = 10
## Event server.
# Uncomment to enable live updating.
# EVENT_DAEMON_USE = True
# Uncomment this section to use websocket/daemon.js included in the site.
#EVENT_DAEMON_POST = '<ws:// URL to post to>'
# If you are using the defaults from the guide, it is this:
EVENT_DAEMON_POST = 'ws://127.0.0.1:15101/'
# These are the publicly accessed interface configurations.
# They should match those used by the script.
#EVENT_DAEMON_GET = '<public ws:// URL for clients>'
#EVENT_DAEMON_GET_SSL = '<public wss:// URL for clients>'
#EVENT_DAEMON_POLL = '<public URL to access the HTTP long polling of event server>'
# i.e. the path to /channels/ exposed by the daemon, through whatever proxy setup you have.
# Using our standard nginx configuration, these should be.
#EVENT_DAEMON_GET = 'ws://<your domain>/event/'
#EVENT_DAEMON_GET_SSL = 'wss://<your domain>/event/' # Optional
#EVENT_DAEMON_POLL = '/channels/'
# If you would like to use the AMQP-based event server from <https://github.com/DMOJ/event-server>,
# uncomment this section instead. This is more involved, and recommended to be done
# only after you have a working event server.
#EVENT_DAEMON_AMQP = '<amqp:// URL to connect to, including username and password>'
#EVENT_DAEMON_AMQP_EXCHANGE = '<AMQP exchange to use>'
## CDN control.
# Base URL for a copy of ace editor.
# Should contain ace.js, along with mode-*.js.
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/'
JQUERY_JS = '//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js'
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
SELECT2_CSS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
# A map of Earth in Equirectangular projection, for timezone selection.
# Please try not to hotlink this poor site.
TIMEZONE_MAP = 'http://naturalearth.springercarto.com/ne3_data/8192/textures/3_no_ice_clouds_8k.jpg'
## Camo (https://github.com/atmos/camo) usage.
#CAMO_URL = "<URL to your camo install>"
#CAMO_KEY = "<The CAMO_KEY environmental variable you used>"
# Domains to exclude from being camo'd.
#CAMO_EXCLUDE = ("https://dmoj.ml", "https://dmoj.ca")
# Set to True to use https when dealing with protocol-relative URLs.
# See <http://www.paulirish.com/2010/the-protocol-relative-url/> for what they are.
#CAMO_HTTPS = False
# HTTPS level. Affects <link rel='canonical'> elements generated.
# Set to 0 to make http URLs canonical.
# Set to 1 to make the currently used protocol canonical.
# Set to 2 to make https URLs canonical.
#DMOJ_HTTPS = 0
## PDF rendering settings.
# Directory to cache the PDF.
# PROBLEM_PDF_CACHE = '/home/dmoj-uwsgi/pdfcache'
# Path to use for nginx's X-Accel-Redirect feature.
# Should be an internal location mapped to the above directory.
# PROBLEM_PDF_INTERNAL = '/pdfcache'
# Path to a PhantomJS executable.
#PHANTOMJS = '/usr/local/bin/phantomjs'
# If you can't use PhantomJS or prefer wkhtmltopdf, set the path to wkhtmltopdf executable instead.
#WKHTMLTOPDF = '/usr/local/bin/wkhtmltopdf'
# Note that PhantomJS is preferred over wkhtmltopdf and would be used when both are defined.
## ======== Logging Settings ========
# Documentation: https://docs.djangoproject.com/en/1.9/ref/settings/#logging
# https://docs.python.org/2/library/logging.config.html#logging-config-dictschema
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'file': {
'format': '%(levelname)s %(asctime)s %(module)s %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s',
},
},
'handlers': {
# You may use this handler as example for logging to other files..
'bridge': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '<desired bridge log path>',
'maxBytes': 10 * 1024 * 1024,
'backupCount': 10,
'formatter': 'file',
},
'mail_admins': {
'level': 'ERROR',
'class': 'dmoj.throttle_mail.ThrottledEmailHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'file',
},
},
'loggers': {
# Site 500 error mails.
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
# Judging logs as received by bridged.
'judge.bridge': {
'handlers': ['bridge', 'mail_admins'],
'level': 'INFO',
'propagate': True,
},
# Catch all log to stderr.
'': {
'handlers': ['console'],
},
# Other loggers of interest. Configure at will.
# - judge.user: logs naughty user behaviours.
# - judge.problem.pdf: PDF generation log.
# - judge.html: HTML parsing errors when processing problem statements etc.
# - judge.mail.activate: logs for the reply to activate feature.
# - event_socket_server
},
}
## ======== Integration Settings ========
## Python Social Auth
# Documentation: https://python-social-auth.readthedocs.io/en/latest/
# You can define these to enable authentication through the following services.
#SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
#SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
#SOCIAL_AUTH_FACEBOOK_KEY = ''
#SOCIAL_AUTH_FACEBOOK_SECRET = ''
#SOCIAL_AUTH_GITHUB_SECURE_KEY = ''
#SOCIAL_AUTH_GITHUB_SECURE_SECRET = ''
#SOCIAL_AUTH_DROPBOX_OAUTH2_KEY = ''
#SOCIAL_AUTH_DROPBOX_OAUTH2_SECRET = ''
## ======== Custom Configuration ========
# You may add whatever django configuration you would like here.
# Do try to keep it separate so you can quickly patch in new settings.

View file

@ -1,13 +1,12 @@
import chat_box.routing
from channels.auth import AuthMiddlewareStack from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter from channels.routing import ProtocolTypeRouter, URLRouter
import chat_box.routing
application = ProtocolTypeRouter({ application = ProtocolTypeRouter({
# (http->django views is added by default) # (http->django views is added by default)
'websocket': AuthMiddlewareStack( 'websocket': AuthMiddlewareStack(
URLRouter( URLRouter(
chat_box.routing.websocket_urlpatterns, chat_box.routing.websocket_urlpatterns
), )
), ),
}) })

View file

@ -109,7 +109,7 @@ TIMEZONE_MAP = None
TIMEZONE_DETECT_BACKEND = None TIMEZONE_DETECT_BACKEND = None
TERMS_OF_SERVICE_URL = None TERMS_OF_SERVICE_URL = None
DEFAULT_USER_LANGUAGE = 'PY3' DEFAULT_USER_LANGUAGE = 'CPP11'
PHANTOMJS = '' PHANTOMJS = ''
PHANTOMJS_PDF_ZOOM = 0.75 PHANTOMJS_PDF_ZOOM = 0.75
@ -431,9 +431,9 @@ EVENT_DAEMON_SUBMISSION_KEY = '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc
# https://docs.djangoproject.com/en/1.11/topics/i18n/ # https://docs.djangoproject.com/en/1.11/topics/i18n/
# Whatever you do, this better be one of the entries in `LANGUAGES`. # Whatever you do, this better be one of the entries in `LANGUAGES`.
LANGUAGE_CODE = 'en' LANGUAGE_CODE = 'vi'
TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Ho_Chi_Minh'
DEFAULT_USER_TIME_ZONE = 'America/Toronto' DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True
USE_TZ = True USE_TZ = True
@ -503,6 +503,8 @@ TESTCASE_VISIBLE_LENGTH = 60
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240 DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440 DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440
MESSAGES_TO_LOAD = 15
ASGI_APPLICATION = 'dmoj.routing.application' ASGI_APPLICATION = 'dmoj.routing.application'
CHANNEL_LAYERS = { CHANNEL_LAYERS = {
'default': { 'default': {

View file

@ -4,12 +4,14 @@ from django.conf.urls import include, url
from django.contrib import admin from django.contrib import admin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
from django.http import Http404, HttpResponsePermanentRedirect from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
from django.templatetags.static import static from django.templatetags.static import static
from django.urls import reverse from django.urls import reverse
from django.utils.functional import lazystr from django.utils.functional import lazystr
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import RedirectView from django.views.generic import RedirectView
from django.contrib.auth.decorators import login_required
from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
from judge.forms import CustomAuthenticationForm from judge.forms import CustomAuthenticationForm
@ -25,7 +27,6 @@ from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, Contest
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \ ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
UserSearchSelect2View, UserSelect2View UserSearchSelect2View, UserSelect2View
admin.autodiscover() admin.autodiscover()
register_patterns = [ register_patterns = [
@ -367,7 +368,12 @@ urlpatterns = [
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'), url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
url(r'chat/', ChatView.as_view(), name='chat'), url(r'^chat/', include([
url(r'^$',
login_required(ChatView.as_view()),
name='chat'),
])),
] ]
favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png', favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
@ -382,7 +388,7 @@ favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png',
for favicon in favicon_paths: for favicon in favicon_paths:
urlpatterns.append(url(r'^%s$' % favicon, RedirectView.as_view( urlpatterns.append(url(r'^%s$' % favicon, RedirectView.as_view(
url=lazystr(lambda: static('icons/' + favicon)), url=static('icons/' + favicon)
))) )))
handler404 = 'judge.views.error.error404' handler404 = 'judge.views.error.error404'

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
# Generated by Django 2.2.9 on 2020-03-17 05:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('judge', '0101_custom_validator'),
]
operations = [
migrations.AlterField(
model_name='problemdata',
name='checker',
field=models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line'), ('custom', 'Custom checker'), ('customval', 'Custom Validator')], max_length=10, verbose_name='checker'),
),
migrations.AlterField(
model_name='problemtestcase',
name='checker',
field=models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line'), ('custom', 'Custom checker'), ('customval', 'Custom Validator')], max_length=10, verbose_name='checker'),
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 2.2.9 on 2020-03-17 05:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('judge', '0102_fix_custom_validator'),
]
operations = [
migrations.RenameField(
model_name='problemdata',
old_name='custom_valid',
new_name='custom_validator',
),
]

View file

@ -29,7 +29,8 @@ CHECKERS = (
('sorted', _('Unordered')), ('sorted', _('Unordered')),
('identical', _('Byte identical')), ('identical', _('Byte identical')),
('linecount', _('Line-by-line')), ('linecount', _('Line-by-line')),
('custom', _('Custom checker')), ('custom', _('Custom checker (PY)')),
('customval', _('Custom validator (CPP)')),
) )
@ -52,6 +53,12 @@ class ProblemData(models.Model):
blank=True, blank=True,
upload_to=problem_directory_file, upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=['py'])]) validators=[FileExtensionValidator(allowed_extensions=['py'])])
custom_validator = models.FileField(verbose_name=_('custom validator file'),
storage=problem_data_storage,
null=True,
blank=True,
upload_to=problem_directory_file,
validators=[FileExtensionValidator(allowed_extensions=['cpp'])])
__original_zipfile = None __original_zipfile = None
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -78,6 +85,10 @@ class ProblemData(models.Model):
self.generator.name = _problem_directory_file(new, self.generator.name) self.generator.name = _problem_directory_file(new, self.generator.name)
if self.custom_checker: if self.custom_checker:
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name) self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
if self.custom_checker:
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
if self.custom_validator:
self.custom_validator.name = _problem_directory_file(new, self.custom_validator.name)
self.save() self.save()
_update_code.alters_data = True _update_code.alters_data = True

View file

@ -1,6 +1,7 @@
import json import json
import os import os
import re import re
import shutil
import yaml import yaml
from django.conf import settings from django.conf import settings
@ -9,6 +10,10 @@ from django.core.files.storage import FileSystemStorage
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
VALIDATOR_TEMPLATE_PATH = 'validator_template/template.py'
if os.altsep: if os.altsep:
def split_path_first(path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))): def split_path_first(path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))):
return repath.split(path, 1) return repath.split(path, 1)
@ -63,12 +68,38 @@ class ProblemDataCompiler(object):
raise ProblemDataError(_('Empty batches not allowed.')) raise ProblemDataError(_('Empty batches not allowed.'))
cases.append(batch) cases.append(batch)
def make_checker_for_validator(case):
checker_name = "cppvalidator.py"
validator_path = split_path_first(case.custom_validator.name)
if len(validator_path) != 2:
raise ProblemDataError(_('How did you corrupt the custom checker path?'))
checker = os.path.join(settings.DMOJ_PROBLEM_DATA_ROOT,
validator_path[0],
checker_name)
validator_name = validator_path[1]
shutil.copy(VALIDATOR_TEMPLATE_PATH, checker)
# replace {{filecpp}} and {{problemid}} in checker file
filedata = open(checker, 'r').read()
filedata = filedata.replace('{{filecpp}}', "\'%s\'" % validator_name)
filedata = filedata.replace('{{problemid}}', "\'%s\'" % validator_path[0])
open(checker, 'w').write(filedata)
return checker_name
def make_checker(case): def make_checker(case):
if (case.checker == 'custom'): if (case.checker == 'custom'):
custom_checker_path = split_path_first(case.custom_checker.name) custom_checker_path = split_path_first(case.custom_checker.name)
if len(custom_checker_path) != 2: if len(custom_checker_path) != 2:
raise ProblemDataError(_('How did you corrupt the custom checker path?')) raise ProblemDataError(_('How did you corrupt the custom checker path?'))
return(custom_checker_path[1]) return(custom_checker_path[1])
if (case.checker == 'customval'):
return make_checker_for_validator(case)
if case.checker_args: if case.checker_args:
return { return {
'name': case.checker, 'name': case.checker,

View file

@ -287,11 +287,11 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
context_object_name = 'problems' context_object_name = 'problems'
template_name = 'problem/list.html' template_name = 'problem/list.html'
paginate_by = 50 paginate_by = 50
sql_sort = frozenset(('points', 'ac_rate', 'user_count', 'code')) sql_sort = frozenset(('date', 'points', 'ac_rate', 'user_count', 'code'))
manual_sort = frozenset(('name', 'group', 'solved', 'type')) manual_sort = frozenset(('name', 'group', 'solved', 'type'))
all_sorts = sql_sort | manual_sort all_sorts = sql_sort | manual_sort
default_desc = frozenset(('points', 'ac_rate', 'user_count')) default_desc = frozenset(('date', 'points', 'ac_rate', 'user_count'))
default_sort = 'code' default_sort = '-date'
def get_paginator(self, queryset, per_page, orphans=0, def get_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs): allow_empty_first_page=True, **kwargs):

View file

@ -8,7 +8,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory, FileInput
from django.http import Http404, HttpResponse, HttpResponseRedirect from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from django.urls import reverse from django.urls import reverse
@ -50,9 +50,12 @@ class ProblemDataForm(ModelForm):
class Meta: class Meta:
model = ProblemData model = ProblemData
fields = ['zipfile', 'generator', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'custom_checker'] fields = ['zipfile', 'checker', 'checker_args', 'custom_checker', 'custom_validator']
widgets = { widgets = {
'checker_args': HiddenInput, 'checker_args': HiddenInput,
'generator': HiddenInput,
'output_limit': HiddenInput,
'output_prefix': HiddenInput,
} }
@ -62,14 +65,14 @@ class ProblemCaseForm(ModelForm):
class Meta: class Meta:
model = ProblemTestCase model = ProblemTestCase
fields = ('order', 'type', 'input_file', 'output_file', 'points', fields = ('order', 'type', 'input_file', 'output_file', 'points',
'is_pretest', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args') 'is_pretest', 'checker', 'checker_args') #, 'output_limit', 'output_prefix', 'generator_args')
widgets = { widgets = {
'generator_args': HiddenInput, # 'generator_args': HiddenInput,
'type': Select(attrs={'style': 'width: 100%'}), 'type': Select(attrs={'style': 'width: 100%'}),
'points': NumberInput(attrs={'style': 'width: 4em'}), 'points': NumberInput(attrs={'style': 'width: 4em'}),
'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}), # 'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}),
'output_limit': NumberInput(attrs={'style': 'width: 6em'}), # 'output_limit': NumberInput(attrs={'style': 'width: 6em'}),
'checker_args': HiddenInput, # 'checker_args': HiddenInput,
} }

View file

@ -17,7 +17,7 @@ fi
FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css sass_processed/ranks.css) FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css sass_processed/ranks.css)
cd `dirname $0` cd `dirname $0`
sass --update resources:sass_processed sass resources:sass_processed
echo echo
postcss "${FILES[@]}" --verbose --use autoprefixer -d resources postcss "${FILES[@]}" --verbose --use autoprefixer -d resources

View file

@ -115,6 +115,7 @@ body {
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif; font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
color: #000; color: #000;
height: 100%; height: 100%;
overflow-x: hidden;
} }
.unselectable { .unselectable {
@ -180,7 +181,7 @@ header {
} }
a { a {
color: #FFF; color: #9c3706;
} }
li { li {
@ -206,7 +207,7 @@ header {
} }
& > span { & > span {
color: #eee; color: darkslateblue;
vertical-align: middle; vertical-align: middle;
display: inline; display: inline;
margin-top: 11px; margin-top: 11px;
@ -218,20 +219,20 @@ header {
} }
#nav-shadow { #nav-shadow {
height: 3px; height: 2px;
background: linear-gradient(rgba(0, 0, 0, 0.5), transparent); background: linear-gradient(#7dc7ff, transparent);
} }
#nav-container { #nav-container {
background: $widget_black; background: #7dc7ff;
// opacity: 0.77 // opacity: 0.77;
// filter: alpha(opacity=77) // filter: alpha(opacity=77)
height: 100%; height: 100%;
} }
#navigation { #navigation {
position: fixed; position: relative;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
@ -276,12 +277,12 @@ nav {
display: inline-block; display: inline-block;
text-decoration: none; text-decoration: none;
vertical-align: middle; vertical-align: middle;
color: #FFF; color: #9c3706;
padding: 13px 7px; padding: 13px 7px;
height: 18px; height: 18px;
&:link { &:link {
color: #FFF; color: #9c3706;
} }
&:hover { &:hover {
@ -291,8 +292,9 @@ nav {
} }
&.active { &.active {
color: #FFF; // color: #FFF;
background: $highlight_blue; background: $highlight_blue;
color: black;
} }
.nav-expand { .nav-expand {
@ -370,7 +372,7 @@ hr {
} }
#content { #content {
margin: 52px auto auto; margin: 1em auto auto;
// Header // Header
width: 90%; width: 90%;

View file

@ -15,10 +15,10 @@
font-size: 1.7em; font-size: 1.7em;
a { a {
color: #5b80b9 !important; color: Maroon !important;
&:hover { &:hover {
color: #0645ad !important; color: #c00000 !important;
} }
} }
} }

71
resources/chatbox.scss Normal file
View file

@ -0,0 +1,71 @@
#chat-box {
border: 1px solid #ccc;
border-radius: 4px;
height: 20em;
width: 100%;
overflow: hidden;
overflow-wrap: break-word;
overflow-y: scroll;
}
#loader {
display: block;
margin-left: auto;
margin-right: auto;
width: 4%;
}
#chat-log {
padding: 0;
padding-top: 2em;
width: 100%;
}
#chat-log li {
list-style-type: none;
margin: 0.5em;
}
#chat-input {
width: 100%;
padding: 0.4em;
color: black;
}
#chat-submit {
margin-top: 1em;
}
.profile-pic {
height: 2.6em;
width: 2.6em;
border-radius: 0.3em;
margin-top: 0.1em;
float: left;
}
.body-message {
padding-left: 3em;
}
.user-time {
margin-bottom: 0.3em;
}
.time {
margin-left: 0.5em;
}
.user {
font-weight: bold;
}
.clear {
clear: both;
}
.content-message {
word-wrap: break-word;
white-space: pre-line;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -5,8 +5,7 @@
<square70x70logo src="/mstile-70x70.png"/> <square70x70logo src="/mstile-70x70.png"/>
<square150x150logo src="/mstile-150x150.png"/> <square150x150logo src="/mstile-150x150.png"/>
<square310x310logo src="/mstile-310x310.png"/> <square310x310logo src="/mstile-310x310.png"/>
<wide310x150logo src="/mstile-310x150.png"/> <TileColor>#da532c</TileColor>
<TileColor>#00aba9</TileColor>
</tile> </tile>
</msapplication> </msapplication>
</browserconfig> </browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="44">
<g transform="matrix(.08265 0 0 -.0827 -18.736 55.375)">
<path fill="#FCDB05" stroke="#FCDB05" stroke-width="10" d="M477.898 612.007c-107.844-.25-215.89-92.284-206.703-227.507 6.614-97.33 93.848-202.26 227.87-192.475 101.47 7.41 194.44 95.456 192.42 213.653-1.96 114.6-97.744 208.65-213.587 206.33m3.533-437.256c-123.62-2.002-228.5 102.77-227.64 227.46.87 125.882 103.18 228.238 229.38 226.488 124.6-1.727 225.85-98.733 224.96-230.748-.82-122.186-101.1-223.51-226.69-223.2"/>
<path fill="#fcdb05" d="M515.264 390.9c14.44-20.215 31.333-37.793 47.34-56.057 18.196-20.76 18.438-20.43-3.845-36.05-7.16-5.014-14.16-10.288-20.88-15.868-6.27-5.203-10.51-6.375-15.06 2.458-12.79 24.824-43.17 77.232-46.028 76.716-2.73-.5-32.438-53.09-46.36-77.37-4.567-7.97-8.044-6.92-14.116-1.95-10.807 8.84-21.825 17.57-33.59 25.03-7.504 4.75-7.585 8.08-2.073 14.2 18.23 20.26 57.116 65.45 59.423 68.69-28.8 5.974-56.39 11.873-84.065 17.355-10.37 2.055-13.786 4.4-9.2 16.78 4.32 11.65 10.353 23.38 11.56 36.02 1.067 11.187 6.41 11.39 14.473 7.76 24.793-11.16 77.644-34.635 80.63-35.575 1.073 21.123-7.233 79.865-8.628 88.975-1.027 6.686 1.18 9.43 8.496 9.157 14.638-.545 29.38-1.074 43.94.132 11.26.932 13.05-4.163 11.42-12.94-5.17-27.87-5.52-56.143-7.755-85.808 25.865 11.322 51.147 21.774 75.85 33.45 14 6.613 16.357 6.306 19.824-7.96 2.892-11.905 7.53-23.366 11.466-34.877 4.11-12.01-1.486-13.28-9.816-15.007-27.35-5.66-54.684-11.38-83.012-17.28"/>
</g>
<g transform="matrix(.53656 0 0 .52273 46.09 -512.12)">
<path fill="#FCDB05" d="M1.8 1045v-46.4h16.4q24.7 0 24.7 22.6 0 10.8-6.8 17.3-6.7 6.5-18 6.5H1.7zm10.4-37.9v29.4h5.2q6.8 0 10.6-4.1 3.9-4.1 3.9-11.1 0-6.6-3.8-10.4-3.8-3.8-10.7-3.8h-5.1z"/>
<path fill="#ffde05" d="M101.5 1045H91.2v-27.8q0-4.5.4-9.9h-.3q-.8 4.3-1.5 6.1l-10.9 31.5h-8.5l-11.1-31.2q-.5-1.3-1.5-6.5h-.3q.4 6.9.4 12v25.7h-9.4v-46.4h15.3l9.5 27.5q1.1 3.3 1.6 6.6h.2q.9-3.8 1.8-6.7l9.5-27.4h14.9v46.4z"/>
<g fill="#ffde05">
<path d="M113.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
<path d="M129.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
</g>
<path fill="#aaa" d="M161.7 1045.8q-10 0-16.2-6.5-6.3-6.5-6.3-16.9 0-11 6.4-17.8 6.4-6.8 16.9-6.8 9.9 0 16 6.5t6.1 17.1q0 10.9-6.4 17.6-6.3 6.7-16.6 6.7zm.5-39q-5.5 0-8.7 4.1-3.2 4.1-3.2 10.9 0 6.9 3.2 10.9t8.5 4q5.4 0 8.6-3.9 3.2-3.9 3.2-10.8 0-7.2-3.1-11.2-3.1-4-8.4-4z"/>
<path fill="#aaa" d="M209.6 1026.6q0 9.3-4.3 14.3-4.3 4.9-12.5 4.9-3.7 0-6.8-1.3v-9.8q2.7 2 6 2 7.1 0 7.1-10.6v-27.6h10.5v28z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,5 +1,5 @@
{ {
"name": "DMOJ", "name": "LQDOJ",
"icons": [ "icons": [
{ {
"src": "\/android-chrome-36x36.png", "src": "\/android-chrome-36x36.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -2,18 +2,57 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="960.000000pt" height="960.000000pt" viewBox="0 0 960.000000 960.000000" width="223.000000pt" height="223.000000pt" viewBox="0 0 223.000000 223.000000"
preserveAspectRatio="xMidYMid meet"> preserveAspectRatio="xMidYMid meet">
<metadata> <metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013 Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata> </metadata>
<g transform="translate(0.000000,960.000000) scale(0.100000,-0.100000)" <g transform="translate(0.000000,223.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none"> fill="#000000" stroke="none">
<path d="M4430 9579 c-1066 -77 -2049 -478 -2810 -1146 -900 -791 -1457 -1877 <path d="M1101 2183 c0 -7 -1 -15 -1 -18 0 -3 -7 -19 -15 -35 -8 -16 -15 -32
-1597 -3115 -25 -218 -25 -828 0 -1048 126 -1122 585 -2089 1362 -2865 773 -15 -35 0 -3 0 -6 -1 -7 0 -2 -2 -10 -4 -18 -2 -8 -18 -67 -35 -130 -17 -63
-774 1763 -1251 2865 -1381 183 -21 681 -30 891 -15 951 68 1836 397 2584 961 -34 -125 -37 -137 -4 -18 -9 -20 -30 -12 -18 7 -22 6 -17 -4 5 -8 2 -8 -9 2
181 137 295 236 474 413 538 531 923 1151 1161 1867 129 386 200 739 231 1150 -13 11 -17 11 -17 1 0 -8 -3 -10 -7 -7 -3 4 -12 2 -20 -4 -7 -6 -13 -8 -13 -4
15 201 6 734 -16 929 -126 1130 -590 2116 -1355 2881 -763 763 -1738 1226 0 4 -7 2 -15 -5 -8 -7 -15 -10 -15 -7 0 3 -15 1 -32 -4 -18 -6 -42 -12 -53
-2855 1355 -166 20 -744 28 -935 14z"/> -14 -11 -3 -32 -7 -47 -11 -16 -3 -28 -10 -28 -15 0 -5 -3 -8 -7 -8 -23 4 -43
-2 -38 -11 3 -5 1 -10 -5 -10 -6 0 -8 -5 -4 -12 5 -7 2 -9 -7 -6 -11 4 -14 -5
-15 -36 -4 -92 -9 -116 -26 -122 -10 -3 -18 -10 -18 -16 0 -6 -3 -8 -6 -5 -4
4 -19 -2 -33 -12 -22 -15 -46 -27 -71 -36 -32 -11 -86 -40 -97 -51 -8 -8 -19
-12 -24 -9 -5 4 -9 -6 -10 -22 -1 -38 -7 -126 -12 -193 -3 -30 -8 -107 -12
-170 -11 -168 -14 -181 -46 -175 -10 2 -19 -1 -19 -7 0 -6 -11 -8 -25 -6 -13
3 -24 2 -23 -1 2 -20 -3 -31 -12 -26 -6 4 -7 -1 -3 -11 4 -12 3 -15 -5 -10 -8
5 -10 2 -6 -9 4 -9 13 -14 20 -11 7 3 16 0 20 -6 4 -7 3 -8 -4 -4 -8 5 -12 0
-12 -13 0 -12 5 -21 12 -21 9 0 9 -3 0 -12 -7 -7 -12 -17 -12 -22 0 -6 5 -4
11 4 8 12 10 9 6 -13 -3 -18 0 -30 8 -34 8 -2 16 -12 19 -20 6 -14 4 -14 -10
-2 -16 13 -16 12 -4 -11 7 -14 18 -28 24 -32 8 -6 6 -11 -4 -18 -9 -6 -10 -10
-3 -10 6 0 13 -10 15 -22 2 -15 13 -24 35 -30 23 -6 37 -19 52 -51 33 -67 199
-247 228 -247 7 0 16 -6 20 -13 4 -7 26 -21 48 -32 22 -11 42 -22 45 -25 7 -7
53 -27 64 -28 5 0 12 -5 15 -11 4 -5 19 -12 35 -15 15 -3 34 -10 41 -16 10 -8
14 -7 18 2 4 10 6 10 6 1 1 -7 6 -13 12 -13 5 0 7 6 3 12 -5 9 -2 9 9 -1 9 -7
17 -10 17 -5 0 5 5 2 10 -6 7 -11 10 -11 10 -2 0 7 5 10 10 7 6 -4 8 -11 5
-16 -4 -5 -1 -9 4 -9 6 0 11 7 11 16 0 8 4 13 10 9 5 -3 7 -13 4 -23 -4 -10
-3 -13 3 -6 8 11 52 12 69 1 5 -3 16 -2 24 3 8 5 22 5 32 -1 12 -6 18 -6 18 1
0 5 5 10 11 10 5 0 7 -6 3 -12 -5 -9 -2 -9 9 1 13 10 17 10 17 1 0 -9 4 -9 17
1 15 12 16 12 8 -1 -7 -13 -6 -13 7 -2 13 10 17 10 21 0 4 -10 6 -10 6 0 1 6
9 12 19 12 10 0 24 6 31 12 11 11 12 10 6 -2 -8 -13 -7 -13 8 -1 9 7 17 11 17
7 0 -3 16 0 35 8 19 8 35 12 35 9 0 -3 7 0 15 7 8 7 15 10 15 6 0 -3 32 10 70
30 39 21 70 41 70 45 0 4 9 11 21 14 24 8 52 29 82 62 11 12 25 19 31 16 6 -3
8 -3 4 2 -4 4 9 22 28 40 18 18 34 37 34 44 0 6 4 11 10 11 5 0 24 25 41 55
17 30 35 55 41 55 5 0 7 4 4 9 -3 5 1 14 9 21 8 7 12 16 9 21 -3 5 -2 9 3 8
24 -4 43 2 37 13 -4 7 -3 8 5 4 12 -8 44 18 34 28 -4 3 -1 6 5 6 6 0 9 7 6 15
-4 8 1 17 10 21 14 5 14 9 4 22 -7 9 -8 13 -2 9 12 -7 38 39 29 53 -3 4 5 10
18 14 18 5 19 8 7 15 -12 9 -12 11 3 17 9 3 14 10 11 15 -3 5 0 9 5 9 6 0 11
6 11 14 0 8 6 17 13 19 7 4 6 6 -4 6 -9 1 -20 8 -23 17 -4 9 -13 14 -21 10 -8
-3 -15 -1 -15 4 0 6 -6 10 -14 10 -17 0 -41 28 -41 49 0 9 -4 22 -8 29 -5 6
-4 12 1 12 5 0 6 17 2 40 -5 25 -4 40 3 40 6 0 6 5 0 13 -5 6 -9 56 -9 109 1
54 -3 98 -7 98 -5 0 -5 5 -1 12 4 7 6 35 5 62 -2 28 -4 64 -5 80 -1 16 -7 31
-14 34 -7 2 -10 8 -7 13 4 5 -1 6 -10 3 -8 -4 -25 0 -38 8 -12 8 -41 20 -64
28 -24 7 -43 17 -43 22 0 4 -3 8 -7 7 -16 -2 -53 14 -53 22 0 5 -3 8 -7 7 -10
-4 -73 23 -74 31 -2 9 -7 42 -14 86 -3 22 -8 51 -11 65 -4 14 -7 44 -8 67 0
22 -4 39 -8 36 -5 -2 -8 0 -8 6 0 10 -93 57 -130 65 -3 1 -15 7 -27 14 -13 7
-23 9 -23 5 0 -4 -4 -3 -8 2 -11 16 -102 56 -113 50 -5 -4 -12 1 -15 10 -5 13
-9 14 -14 4 -7 -10 -11 -9 -22 2 -8 8 -15 17 -16 22 -1 4 -3 9 -3 12 -1 3 -3
10 -4 15 -1 6 -4 12 -5 15 -1 3 -3 8 -4 13 -18 83 -32 132 -37 132 -4 0 -10 8
-12 18 -3 9 -5 11 -6 5z"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -12,3 +12,4 @@
@import "submission"; @import "submission";
@import "contest"; @import "contest";
@import "misc"; @import "misc";
@import "chatbox";

View file

@ -203,9 +203,7 @@ label[for="language"], label[for="status"] {
} }
#test-cases { #test-cases {
.toggle .fa { font-size: 1.1em;
margin-left: -1.28571em;
}
.batch-cases { .batch-cases {
margin: 0; margin: 0;
@ -215,28 +213,13 @@ label[for="language"], label[for="status"] {
display: inline-block; display: inline-block;
} }
.batch-cases .case-row td b { // .batch-cases .case-row td b {
font-weight: 500; // font-weight: 500;
} // }
.case-row td:nth-child(3) {
padding-right: 0;
text-align: right;
}
.case-row td:nth-child(3) span {
float: right;
}
.case-row td:nth-child(4) {
text-align: right;
}
.case-info { .case-info {
margin: 0; margin: 0;
padding-left: 1em; padding: 1em;
padding-bottom: 3px;
padding-top: 3px;
border: 1px solid #2980b9; border: 1px solid #2980b9;
border-left-width: .5em; border-left-width: .5em;
border-radius: 4px; border-radius: 4px;
@ -245,18 +228,50 @@ label[for="language"], label[for="status"] {
.case-output { .case-output {
margin: 0; margin: 0;
margin-right: 1em; margin-top: 1em;
box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);
margin: 1rem 0;
padding: 1em 1em;
border-radius: .28571429rem;
border: 1px solid rgba(34,36,38,.15);
font-family: Consolas;
}
#testcases-table {
width: 100%;
border: 1px solid grey;
border-radius: 0.5em;
border-spacing: 0;
thead td:first-child {
border-top-left-radius: 0.5em;
}
thead td:last-child {
border-top-right-radius: 0.5em;
}
td {
padding: 0.6em 0.8em;
width: 18.75%;
border-bottom: 0.7px grey solid;
}
.case-row td:nth-child(2) {
width: 25%;
}
.case-row:hover {
cursor: pointer;
} }
table td {
margin: 0;
padding: 0 5px 0 0;
} }
.toggle { .toggle {
font-weight: normal; font-weight: normal;
} }
.case-feedback { .case-feedback {
vertical-align: top; vertical-align: top;
} }
@ -266,6 +281,31 @@ label[for="language"], label[for="status"] {
} }
} }
#overall-row:hover {
cursor: default !important;
}
.overall-result-AC {
background: linear-gradient(45deg, #a8ff78, #78ffd6);
}
.overall-result-WA {
background: linear-gradient(45deg, yellow, red);
}
.overall-result-TLE {
background: linear-gradient(45deg, #D7DDE8, #757F99);
}
.overall-result-RTE,
.overall-result-MLE {
background: linear-gradient(45deg, #fceabb, #f8b500)
}
.col-title {
font-weight: bold;
}
.case-AC { .case-AC {
color: green; color: green;
font-weight: bold; font-weight: bold;

View file

@ -42,9 +42,9 @@ $table_header_rounding: 6px;
th { th {
height: 2em; height: 2em;
color: #FFF; color: white;
background-color: $widget_black; background-color: $widget_black;
border-color: #555; border-color: #cccccc;
border-width: 1px 1px 0 0; border-width: 1px 1px 0 0;
border-style: solid; border-style: solid;
padding: 4px 10px; padding: 4px 10px;

View file

@ -1,5 +1,5 @@
$highlight_blue: #2980B9; $highlight_blue: white;
$widget_black: #3b3b3b; $widget_black: #00007d;//68BBE3, add8e6
$border_gray: #ccc; $border_gray: #ccc;
$background_gray: #ededed; $background_gray: #ededed;
$background_light_gray: #fafafa; $background_light_gray: #fafafa;

View file

@ -305,8 +305,8 @@ ul.pagination {
.active-page > { .active-page > {
a { a {
z-index: 2; z-index: 2;
color: #FFF; color: black;
background-color: $highlight_blue; background-color: #7dc7ff;
border-color: transparent; border-color: transparent;
cursor: default; cursor: default;
} }

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block js_media %} {% block js_media %}
<script type="text/javascript"> <script type="text/javascript">
var chatSocket = new WebSocket( var chatSocket = new WebSocket(
@ -8,33 +7,166 @@
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
let currentPage = 1;
$('#loader').hide();
chatSocket.onmessage = function(e) { chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data); let data = JSON.parse(e.data)
var message = data['message']; data = data['message']
$('#chat-log').append(message + '\n'); loadMessage(data['body'],
data['author'],
data['time'],
data['image'],
true)
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
}; };
function encodeHTML(content) {
return content.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
return '&#'+i.charCodeAt(0)+';';
});
}
function loadMessage(content, user, time, image, isNew) {
if (isNew) content = encodeHTML(content)
li = `<li class="message">
<img src="${image}" class="profile-pic">
<div class="body-message">
<div class="user-time">
<a href="{{ url('user_page') }}/${user}" class="user">
${user}
</a>
<span class="time">${time}</span>
</div>
<span class="content-message">${content} </span>
</div>
<div class="clear"></div>
</li>`
ul = $('#chat-log')
if (isNew) {
ul.append(li)
}
else {
ul.prepend(li)
}
}
(function init_chatlog() {
ul = $('#chat-log')
{% for msg in message %}
loadMessage(`{{msg.body}}`, `{{msg.author}}`, `{{msg.time}}`, `{{gravatar(msg.author, 32)}}`)
{% endfor %}
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
})()
function scrollTopOfBottom(container) {
return container[0].scrollHeight - container.innerHeight()
}
function scrollContainer(container, loader) {
container.scroll(function() {
if (container.scrollTop() == 0) {
if (currentPage < {{paginator.num_pages}}) {
currentPage++;
loader.show();
$.ajax({
url: `{{request.path}}?page=${currentPage}`,
success: function(data) {
let lastMsg = $('.message:first')
let lastMsgPos = scrollTopOfBottom(container)
data = JSON.parse(data)
setTimeout( () => {
for (msg of data) {
loadMessage(msg.body, msg.author, msg.time, msg.image)
}
loader.hide()
// scroll to last msg
container.scrollTop(
scrollTopOfBottom(container) - lastMsgPos
)
}, 500)
}
})
}
}
})}
scrollContainer($('#chat-box'), $('#loader'))
$("#chat-submit").click(function() {
if ($("#chat-input").val().trim()) {
let body = $('#chat-input').val().trim();
let img = '{{ gravatar(request.user, 32) }}'
message = {
'body': body,
'image': img,
'author': '{{request.profile}}',
'author_id': {{request.profile.id}},
}
chatSocket.send(JSON.stringify({
'message': message
}));
$('#chat-input').val('').focus();
}
});
chatSocket.onclose = function(e) { chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly'); console.error('Chat socket closed unexpectedly');
}; };
$('#chat-message-input').focus();
$('#chat-message-input').keyup(function(e) { $("#chat-log").change(function() {
if (e.keyCode === 13) { // enter, return $('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
$('#chat-message-submit').click(); });
$('#chat-input').focus();
$('#chat-input').keydown(function(e) {
if (e.keyCode === 13) {
if (e.ctrlKey || e.shiftKey) {
var val = this.value;
if (typeof this.selectionStart == "number" && typeof this.selectionEnd == "number") {
var start = this.selectionStart;
this.value = val.slice(0, start) + "\n" + val.slice(this.selectionEnd);
this.selectionStart = this.selectionEnd = start + 1;
} else if (document.selection && document.selection.createRange) {
this.focus();
var range = document.selection.createRange();
range.text = "\r\n";
range.collapse(false);
range.select();
} }
}
else {
e.preventDefault();
$('#chat-submit').click();
}
return false
}
return true
}); });
$("#chat-message-submit").click(function() {
var message = "{{ request.user }}: " + $('input#chat-message-input').val();
chatSocket.send(JSON.stringify({
'message': message,
}));
$('input#chat-message-input').val('');
});
}); });
</script> </script>
{% endblock js_media %} {% endblock js_media %}
{% block body %} {% block body %}
<textarea id="chat-log" cols="100" rows="20"></textarea><br/> <div id="chat-area">
<input id="chat-message-input" type="text" size="100"/><br/> <div id="chat-box">
<button id="chat-message-submit"> Send </button> <img src="http://opengraphicdesign.com/wp-content/uploads/2009/01/loader64.gif" id="loader">
<ul id="chat-log">
</ul>
</div>
{{_('Your message')}}
<textarea rows="6" id="chat-input"></textarea>
</div>
<button id="chat-submit"> Send </button>
{% endblock body %} {% endblock body %}

View file

@ -95,21 +95,29 @@
}).change(); }).change();
} }
function checker_custom($checker, $custom_checker) { (function toggle_custom() {
$tr = $custom_checker.parent().parent(); let $custom_checker = $('#id_problem-data-custom_checker')
let $checker = $('#id_problem-data-checker')
let $validator = $('#id_problem-data-custom_validator')
$tr_checker = $custom_checker.parent().parent();
$tr_validator = $validator.parent().parent()
$td = $checker.parent(); $td = $checker.parent();
var $sample = $("<a/>",{ var $sample = $("<a/>",{
text: "Sample Checker", text: "{{_('Instruction')}}",
style: "margin-left:3em;", style: "margin-left:3em;",
target: "_blank", target: "_blank",
href: "{{url('custom_checker_sample')}}" href: "{{url('custom_checker_sample')}}"
}).appendTo($td); }).appendTo($td);
$checker.change(function () { $checker.change(function () {
$tr.toggle($checker.val().startsWith('custom')).change(); $tr_checker.toggle($checker.val() == 'custom').change();
$tr_validator.toggle($checker.val() == 'customval').change();
$sample.toggle($checker.val().startsWith('custom')).change(); $sample.toggle($checker.val().startsWith('custom')).change();
}).change(); }).change();
} })();
function swap_row($a, $b) { function swap_row($a, $b) {
var $a_order = $a.find('input[id$=order]'), $b_order = $b.find('input[id$=order]'); var $a_order = $a.find('input[id$=order]'), $b_order = $b.find('input[id$=order]');
@ -123,7 +131,6 @@
} }
checker_precision($('#id_problem-data-checker')); checker_precision($('#id_problem-data-checker'));
checker_custom($('#id_problem-data-checker'), $('#id_problem-data-custom_checker'));
$table.on('add-row', function (e, $tr) { $table.on('add-row', function (e, $tr) {
var $order = $tr.find('input').filter('[id$=order]').attr('type', 'hidden').val(++order); var $order = $tr.find('input').filter('[id$=order]').attr('type', 'hidden').val(++order);
@ -225,7 +232,7 @@
$('a#fill-testcases').click(function () { $('a#fill-testcases').click(function () {
var inFiles = [], outFiles = []; var inFiles = [], outFiles = [];
for (var i = 0; i < window.valid_files.length; i++) { for (var i = 0; i < window.valid_files.length; i++) {
if (window.valid_files[i].endsWith(".in")) { if (window.valid_files[i].endsWith(".in") || window.valid_files[i].endsWith(".inp")) {
inFiles.push(window.valid_files[i]); inFiles.push(window.valid_files[i]);
} }
if (window.valid_files[i].endsWith(".out")) { if (window.valid_files[i].endsWith(".out")) {
@ -233,7 +240,7 @@
} }
} }
if (inFiles.length == 0) { if (inFiles.length == 0) {
alert("No input/output files. Make sure your files' suffices are .in/.out"); alert("No input/output files. Make sure your files' suffices are .in(p)/.out");
return false; return false;
} }
if (inFiles.length != outFiles.length) { if (inFiles.length != outFiles.length) {
@ -248,6 +255,9 @@
for (var i = 0; i < inFiles.length; i++) { for (var i = 0; i < inFiles.length; i++) {
$("#id_cases-" + i + "-input_file").val(inFiles[i]).change(); $("#id_cases-" + i + "-input_file").val(inFiles[i]).change();
} }
for (var i = 0; i < outFiles.length; i++) {
$("#id_cases-" + i + "-output_file").val(outFiles[i]).change();
}
// add points // add points
if ($('#problem-type').val() == "ICPC") { if ($('#problem-type').val() == "ICPC") {
for (i = 0; i + 1 < inFiles.length; i++) { for (i = 0; i + 1 < inFiles.length; i++) {
@ -303,26 +313,6 @@
$("input[name$='DELETE']").attr('checked', false); $("input[name$='DELETE']").attr('checked', false);
} }
}); });
var $controls = $('#column-visible');
var problem = $controls.attr('data-problem');
$controls.find('input').change(function () {
var $this = $(this), suffix = $this.attr('data-suffix'), checked = $this.is(':checked');
$table.find('.' + suffix.replace(/_/g, '-')).toggle(checked);
localStorage.setItem('data-visible:' + problem + ':' + suffix, checked ? '1' : '0')
}).each(function () {
var $this = $(this), suffix = $this.attr('data-suffix'), filled = false;
filled = localStorage.getItem('data-visible:' + problem + ':' + suffix);
if (filled !== null)
filled = filled == '1';
else {
filled = false;
$table.find('[id$=' + suffix + ']').each(function () {
filled |= !!$(this).val();
});
}
$this.prop('checked', filled).trigger('change');
});
}).change(); }).change();
</script> </script>
{% endblock %} {% endblock %}
@ -448,29 +438,7 @@
</a> </a>
</td> </td>
</table> </table>
<div id="column-visible" data-problem="{{ problem.code }}"> <input type="submit" value="{{ _('Apply!') }}" class="button" id="submit-button">
<strong>{{ _('Show columns:') }}</strong>
<label>
<input type="checkbox" data-suffix="output_prefix">
{{ _('Output prefix') }}
</label>
<label>
<input type="checkbox" data-suffix="output_limit">
{{ _('Output limit') }}
</label>
<label>
<input type="checkbox" data-suffix="checker">
{{ _('Checker') }}
</label>
<label>
<input type="checkbox" data-suffix="generator_args">
{{ _('Generator args') }}
</label>
<label>
<input type="checkbox" id="delete-all">
{{ _('Delete all') }}
</label>
</div>
<table id="case-table" class="table"> <table id="case-table" class="table">
<thead> <thead>
<tr> <tr>
@ -480,12 +448,10 @@
<th>{{ _('Output file') }}</th> <th>{{ _('Output file') }}</th>
<th>{{ _('Points') }}</th> <th>{{ _('Points') }}</th>
<th>{{ _('Pretest?') }}</th> <th>{{ _('Pretest?') }}</th>
<th class="output-prefix">{{ _('Output prefix') }}</th>
<th class="output-limit">{{ _('Output limit') }}</th>
<th class="checker">{{ _('Checker') }}</th>
<th class="generator-args">{{ _('Generator args') }}</th>
{% if cases_formset.can_delete %} {% if cases_formset.can_delete %}
<th>{{ _('Delete?') }} <th>{{ _('Delete?') }}
<br>
<input type="checkbox" name="delete-all" id="delete-all">
</th> </th>
{% endif %} {% endif %}
</tr> </tr>
@ -514,25 +480,15 @@
</td> </td>
<td>{{ form.points.errors }}{{ form.points }}</td> <td>{{ form.points.errors }}{{ form.points }}</td>
<td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td> <td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td>
<td class="output-prefix">{{ form.output_prefix.errors }}{{ form.output_prefix }}</td>
<td class="output-limit">{{ form.output_limit.errors }}{{ form.output_limit }}</td>
<td class="checker">
{{ form.checker.errors }}{{ form.checker }}{{ form.checker_args.errors }}{{ form.checker_args }}
</td>
<td class="generator-args">{{ form.generator_args.errors }}{{ form.generator_args }}
<a href="javascript:void(0)" class="edit-generator-args">
<i class="fa fa-pencil"></i>
{{ _('Edit') }}
</a>
</td>
{% if cases_formset.can_delete %} {% if cases_formset.can_delete %}
<td>{{ form.DELETE }}</td> <td>{{ form.DELETE }}
</td>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<input type="submit" value="{{ _('Submit!') }}" class="button" id="submit-button"> <input type="submit" value="{{ _('Apply!') }}" class="button" id="submit-button">
<a id="add-case-row" href="#"><i class="fa fa-plus"></i> {{ _('Add new case') }}</a> <a id="add-case-row" href="#"><i class="fa fa-plus"></i> {{ _('Add new case') }}</a>
</form> </form>
<div style="display: none" class="generator-args-editor"><textarea></textarea><a class="button">Save</a></div> <div style="display: none" class="generator-args-editor"><textarea></textarea><a class="button">Save</a></div>

View file

@ -38,7 +38,7 @@
</div> </div>
{% endif %} {% endif %}
<br> <br>
<hr class="half-hr"> <hr>
<br> <br>
<div class="source-wrap"> <div class="source-wrap">
<table> <table>

View file

@ -17,7 +17,7 @@
{% if submission.error %} {% if submission.error %}
<h3>{{ _('Compilation Warnings') }}</h3> <h3>{{ _('Compilation Warnings') }}</h3>
<pre>{{ submission.error|ansi2html }}</pre> <pre>{{ submission.error|ansi2html }}</pre>
<hr class="half-hr"><br> <hr><br>
{% endif %} {% endif %}
{% if is_pretest %} {% if is_pretest %}
<h3>{{ _('Pretest Execution Results') }}</h3> <h3>{{ _('Pretest Execution Results') }}</h3>
@ -33,7 +33,48 @@
<br> <br>
<div class="batch-cases"> <div class="batch-cases">
{% endif %} {% endif %}
<table>{% for case in batch.cases %} <table id="testcases-table">
{% if submission.is_graded %}
{% if submission.result != 'AB' %}
<thead>
<tr id="overall-row" class="case-row overall-result-{{submission.result}}">
<td><span class="col-title">{{_('Overall: ')}}</span>
{% if request.in_contest and submission.contest_or_none %}
{% with contest=submission.contest_or_none %}
({{ _('%(points)s/%(total)s points', points=contest.points|roundfloat(3),
total=contest.problem.points|floatformat(-1)) }})
{% endwith %}
{% else %}
{{ _('%(points)s/%(total)s', points=submission.points|roundfloat(3),
total=submission.problem.points|floatformat(-1)) }}
{% endif %}
</td>
<td><span class="case-{{submission.result}}">{{submission.long_status}}</span></td>
<td><span class="col-title">{{_('Point: ')}}</span>
{{ submission.case_points|floatformat(0) }}/{{ submission.case_total|floatformat(0) }}
</td>
<td><span class="col-title">{{_('Time: ')}}</span>
{% if submission.result == "TLE" %}
<span>---</span>
{% else %}
<span title="{{ submission.time }}s">{{ (submission.time * 1000)|floatformat(0) }} ms</span>
{% endif %}
</td>
<td><span class="col-title">{{_('Memory: ')}}</span>{{ submission.memory|kbdetailformat }}</td>
</tr>
</thead>
{% endif %}
{% endif %}
{% for case in batch.cases %}
<tr id="{{ case.id }}" class="case-row toggle closed"> <tr id="{{ case.id }}" class="case-row toggle closed">
<td> <td>
{%- if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) -%} {%- if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) -%}
@ -50,26 +91,25 @@
<td> <td>
<span title="{{ case.long_status }}" class="case-{{ case.status }}"> <span title="{{ case.long_status }}" class="case-{{ case.status }}">
{%- if case.status == 'SC' %}&mdash;{% else %}{{ case.status }}{% endif -%} {%- if case.status == 'SC' %}&mdash;{% else %}{{ case.long_status }}{% endif -%}
</span> </span>
{%- if case.feedback %}&nbsp;({{ case.feedback }}){% endif -%}
</td> </td>
{% if not batch.id %}
<td><span class="col-title">{{_('Point')}}: </span> {{ case.points|floatformat(0) }}/{{ case.total|floatformat(0) }}</td>
{% endif %}
<td> <td>
{%- if case.status != 'SC' -%} {%- if case.status != 'SC' -%}
{%- if case.status == 'TLE' -%} {%- if case.status == 'TLE' -%}
[&gt;<span>{{ time_limit|floatformat(3) }}s,</span> <span><span class="col-title">{{_('Time')}}: &gt;</span>{{ (time_limit * 1000)|floatformat(0) }} ms</span>
{%- else -%} {%- else -%}
[<span title="{{ case.time }}s">{{ case.time|floatformat(3) }}s,</span> <span title="{{ case.time }}s"><span class="col-title">{{_('Time')}}: </span>{{ (case.time * 1000)|floatformat(0) }} ms</span>
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
</td> </td>
<td>{% if case.status != 'SC' %}{{ case.memory|kbdetailformat }}]{% endif %}</td> <td>{% if case.status != 'SC' %}<span class="col-title">{{_('Memory')}}: </span> {{ case.memory|kbdetailformat }}{% endif %}</td>
{% if not batch.id %}
<td>({{ case.points|floatformat(0) }}/{{ case.total|floatformat(0) }})</td>
{% endif %}
</tr> </tr>
{% if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) %} {% if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) %}
@ -77,22 +117,30 @@
<td colspan="5"> <td colspan="5">
<div class="case-info"> <div class="case-info">
{% set curr_data = cases_data[case.case] %} {% set curr_data = cases_data[case.case] %}
{% if curr_data != null %}
<strong>{{ _('Input:') }}</strong> <strong>{{ _('Input:') }}</strong>
<pre class="case-output">{{ curr_data['input']|linebreaksbr }}</pre> <pre class="case-output">{{ curr_data['input']|linebreaksbr }}</pre>
<strong>{{ _('Your output:') }}</strong> {% endif %}
<strong>{{ _('Output:') }}</strong>
<pre class="case-output">{{ case.output|linebreaksbr }}</pre> <pre class="case-output">{{ case.output|linebreaksbr }}</pre>
{% if curr_data != null %}
<strong>{{ _('Answer:') }}</strong> <strong>{{ _('Answer:') }}</strong>
<pre class="case-output">{{ curr_data['answer']|linebreaksbr }}</pre> <pre class="case-output">{{ curr_data['answer']|linebreaksbr }}</pre>
</div>
</td>
{% if case.extended_feedback %}
<td colspan="5" class="case-ext-feedback">
<div class="case-info">
<strong>{{ _('Judge feedback') }}</strong>
<pre class="case-output">{{ case.extended_feedback|linebreaksbr }}</pre>
</div>
</td>
{% endif %} {% endif %}
{% if case.extended_feedback or case.feedback %}
<strong>{{ _('Judge feedback:') }}</strong>
{% if case.feedback %}
<pre class="case-output">{{ case.feedback|linebreaksbr }}</pre>
{% endif %}
{% if case.extended_feedback %}
<pre class="case-output">{{ case.extended_feedback|linebreaksbr }}</pre>
{% endif %}
{% endif %}
</div>
</td>
</tr> </tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -104,29 +152,6 @@
{% if submission.is_graded %} {% if submission.is_graded %}
<br> <br>
{% if submission.result != "AB" %} {% if submission.result != "AB" %}
<b>{{ _('Resources:') }}</b>
{% if submission.result == "TLE" %}
<span>---,</span>
{% else %}
<span title="{{ submission.time }}s">{{ submission.time|floatformat(3) }}s,</span>
{% endif %}
{{ submission.memory|kbdetailformat }}
<br>
{% if is_pretest %}
<b>{{ _('Final pretest score:') }}</b>
{% else %}
<b>{{ _('Final score:') }}</b>
{% endif %}
{{ submission.case_points|floatformat(0) }}/{{ submission.case_total|floatformat(0) }}
{% if request.in_contest and submission.contest_or_none %}
{% with contest=submission.contest_or_none %}
({{ _('%(points)s/%(total)s points', points=contest.points|roundfloat(3),
total=contest.problem.points|floatformat(-1)) }})
{% endwith %}
{% else %}
({{ _('%(points)s/%(total)s points', points=submission.points|roundfloat(3),
total=submission.problem.points|floatformat(-1)) }})
{% endif %}
{% if is_pretest and submission.result == "AC" %} {% if is_pretest and submission.result == "AC" %}
<br> <br>
<i>{{ _('Passing pretests does not guarantee a full score on system tests.') }}</i> <i>{{ _('Passing pretests does not guarantee a full score on system tests.') }}</i>

View file

@ -72,7 +72,7 @@
{% endif %} {% endif %}
<br> <br>
<hr class="half-hr"> <hr>
<br> <br>
<div id="test-cases">{% include "submission/status-testcases.html" %}</div> <div id="test-cases">{% include "submission/status-testcases.html" %}</div>
@ -81,7 +81,7 @@
{% if request.user == submission.user.user or perms.judge.abort_any_submission %} {% if request.user == submission.user.user or perms.judge.abort_any_submission %}
<div id="abort-button"> <div id="abort-button">
<br> <br>
<hr class="half-hr"> <hr>
<br> <br>
<form action="{{ url('submission_abort', submission.id) }}" method="post"> <form action="{{ url('submission_abort', submission.id) }}" method="post">
{% csrf_token %} {% csrf_token %}

View file

@ -0,0 +1,50 @@
import os
import subprocess
from dmoj.contrib import contrib_modules
from dmoj.error import InternalError
from dmoj.judgeenv import env, get_problem_root
from dmoj.result import CheckerResult
from dmoj.utils.helper_files import compile_with_auxiliary_files, mktemp
from dmoj.utils.unicode import utf8text
executor = None
def get_executor(files, lang, compiler_time_limit, problem_id):
global executor
if executor is None:
if not isinstance(files, list):
files = [files]
filenames = [os.path.join(get_problem_root(problem_id), f) for f in files]
executor = compile_with_auxiliary_files(filenames, lang, compiler_time_limit)
return executor
def check(process_output, judge_output, judge_input,
problem_id={{problemid}},
files={{filecpp}},
lang='CPP14',
time_limit=10,
memory_limit=1024**2,
compiler_time_limit=10,
feedback=True, type='default',
point_value=None, **kwargs) -> CheckerResult:
executor = get_executor(files, lang, compiler_time_limit, problem_id)
if type not in contrib_modules:
raise InternalError('%s is not a valid return code parser' % type)
with mktemp(judge_input) as input_file, mktemp(process_output) as output_file, mktemp(judge_output) as judge_file:
process = executor.launch(input_file.name, output_file.name, judge_file.name, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, memory=memory_limit, time=time_limit)
proc_output, error = map(utf8text, process.communicate())
return contrib_modules[type].ContribModule.parse_return_code(process, executor, point_value, time_limit,
memory_limit,
feedback=utf8text(proc_output)
if feedback else None, name='checker',
stderr=error)