Cloned DMOJ

This commit is contained in:
thanhluong 2020-01-21 15:35:58 +09:00
parent f623974b58
commit 49dc9ff10c
513 changed files with 132349 additions and 39 deletions

View file

@ -0,0 +1,4 @@
from judge.widgets.checkbox import CheckboxSelectMultipleWithSelectAll
from judge.widgets.mixins import CompressorWidgetMixin
from judge.widgets.pagedown import *
from judge.widgets.select2 import *

22
judge/widgets/checkbox.py Normal file
View file

@ -0,0 +1,22 @@
from django import forms
from django.core.exceptions import FieldError
from django.template.loader import get_template
from django.utils.safestring import mark_safe
class CheckboxSelectMultipleWithSelectAll(forms.CheckboxSelectMultiple):
def render(self, name, value, attrs=None, renderer=None):
if 'id' not in attrs:
raise FieldError('id required')
select_all_id = attrs['id'] + '_all'
select_all_name = name + '_all'
original = super(CheckboxSelectMultipleWithSelectAll, self).render(name, value, attrs, renderer)
template = get_template('widgets/select_all.html')
return mark_safe(template.render({
'original_widget': original,
'select_all_id': select_all_id,
'select_all_name': select_all_name,
'all_selected': all(choice[0] in value for choice in self.choices) if value else False,
'empty': not self.choices,
}))

47
judge/widgets/mixins.py Normal file
View file

@ -0,0 +1,47 @@
from textwrap import dedent
from django import forms
from django.conf import settings
from django.template import Context, Template
from lxml import html
class CompressorWidgetMixin(object):
__template_css = dedent('''\
{% compress css %}
{{ media.css }}
{% endcompress %}
''')
__template_js = dedent('''\
{% compress js %}
{{ media.js }}
{% endcompress %}
''')
__templates = {
(False, False): Template(''),
(True, False): Template('{% load compress %}' + __template_css),
(False, True): Template('{% load compress %}' + __template_js),
(True, True): Template('{% load compress %}' + __template_js + __template_css),
}
compress_css = False
compress_js = False
try:
import compressor
except ImportError:
pass
else:
if getattr(settings, 'COMPRESS_ENABLED', not settings.DEBUG):
@property
def media(self):
media = super().media
template = self.__templates[self.compress_css, self.compress_js]
result = html.fromstring(template.render(Context({'media': media})))
return forms.Media(
css={'all': [result.find('.//link').get('href')]} if self.compress_css else media._css,
js=[result.find('.//script').get('src')] if self.compress_js else media._js,
)

96
judge/widgets/pagedown.py Normal file
View file

@ -0,0 +1,96 @@
from django.contrib.admin import widgets as admin_widgets
from django.forms.utils import flatatt
from django.template.loader import get_template
from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from judge.widgets.mixins import CompressorWidgetMixin
__all__ = ['PagedownWidget', 'AdminPagedownWidget',
'MathJaxPagedownWidget', 'MathJaxAdminPagedownWidget',
'HeavyPreviewPageDownWidget', 'HeavyPreviewAdminPageDownWidget']
try:
from pagedown.widgets import PagedownWidget as OldPagedownWidget
except ImportError:
PagedownWidget = None
AdminPagedownWidget = None
MathJaxPagedownWidget = None
MathJaxAdminPagedownWidget = None
HeavyPreviewPageDownWidget = None
HeavyPreviewAdminPageDownWidget = None
else:
class PagedownWidget(CompressorWidgetMixin, OldPagedownWidget):
# The goal here is to compress all the pagedown JS into one file.
# We do not want any further compress down the chain, because
# 1. we'll creating multiple large JS files to download.
# 2. this is not a problem here because all the pagedown JS files will be used together.
compress_js = True
def __init__(self, *args, **kwargs):
kwargs.setdefault('css', ('pagedown_widget.css',))
super(PagedownWidget, self).__init__(*args, **kwargs)
class AdminPagedownWidget(PagedownWidget, admin_widgets.AdminTextareaWidget):
class Media:
css = {'all': [
'content-description.css',
'admin/css/pagedown.css',
]}
js = ['admin/js/pagedown.js']
class MathJaxPagedownWidget(PagedownWidget):
class Media:
js = [
'mathjax_config.js',
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML',
'pagedown_math.js',
]
class MathJaxAdminPagedownWidget(AdminPagedownWidget, MathJaxPagedownWidget):
pass
class HeavyPreviewPageDownWidget(PagedownWidget):
def __init__(self, *args, **kwargs):
kwargs.setdefault('template', 'pagedown.html')
self.preview_url = kwargs.pop('preview')
self.preview_timeout = kwargs.pop('preview_timeout', None)
self.hide_preview_button = kwargs.pop('hide_preview_button', False)
super(HeavyPreviewPageDownWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, {'name': name})
if 'class' not in final_attrs:
final_attrs['class'] = ''
final_attrs['class'] += ' wmd-input'
return get_template(self.template).render(self.get_template_context(final_attrs, value))
def get_template_context(self, attrs, value):
return {
'attrs': flatatt(attrs),
'body': conditional_escape(force_text(value)),
'id': attrs['id'],
'show_preview': self.show_preview,
'preview_url': self.preview_url,
'preview_timeout': self.preview_timeout,
'extra_classes': 'dmmd-no-button' if self.hide_preview_button else None,
}
class Media:
css = {'all': ['dmmd-preview.css']}
js = ['dmmd-preview.js']
class HeavyPreviewAdminPageDownWidget(AdminPagedownWidget, HeavyPreviewPageDownWidget):
class Media:
css = {'all': [
'pygment-github.css',
'table.css',
'ranks.css',
]}

272
judge/widgets/select2.py Normal file
View file

@ -0,0 +1,272 @@
# -*- coding: utf-8 -*-
"""
Select2 Widgets based on https://github.com/applegrew/django-select2.
These components are responsible for rendering
the necessary HTML data markups. Since this whole
package is to render choices using Select2 JavaScript
library, hence these components are meant to be used
with choice fields.
Widgets are generally of two types:
1. **Light** --
They are not meant to be used when there
are too many options, say, in thousands.
This is because all those options would
have to be pre-rendered onto the page
and JavaScript would be used to search
through them. Said that, they are also one
the most easiest to use. They are a
drop-in-replacement for Django's default
select widgets.
2. **Heavy** --
They are suited for scenarios when the number of options
are large and need complex queries (from maybe different
sources) to get the options.
This dynamic fetching of options undoubtedly requires
Ajax communication with the server. Django-Select2 includes
a helper JS file which is included automatically,
so you need not worry about writing any Ajax related JS code.
Although on the server side you do need to create a view
specifically to respond to the queries.
Heavy widgets have the word 'Heavy' in their name.
Light widgets are normally named, i.e. there is no
'Light' word in their names.
"""
from copy import copy
from itertools import chain
from django import forms
from django.conf import settings
from django.core import signing
from django.forms.models import ModelChoiceIterator
from django.urls import reverse_lazy
DEFAULT_SELECT2_JS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
DEFAULT_SELECT2_CSS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
__all__ = ['Select2Widget', 'Select2MultipleWidget', 'Select2TagWidget',
'HeavySelect2Widget', 'HeavySelect2MultipleWidget', 'HeavySelect2TagWidget',
'AdminSelect2Widget', 'AdminSelect2MultipleWidget', 'AdminHeavySelect2Widget',
'AdminHeavySelect2MultipleWidget']
class Select2Mixin(object):
"""
The base mixin of all Select2 widgets.
This mixin is responsible for rendering the necessary
data attributes for select2 as well as adding the static
form media.
"""
def build_attrs(self, base_attrs, extra_attrs=None):
"""Add select2 data attributes."""
attrs = super(Select2Mixin, self).build_attrs(base_attrs, extra_attrs)
if self.is_required:
attrs.setdefault('data-allow-clear', 'false')
else:
attrs.setdefault('data-allow-clear', 'true')
attrs.setdefault('data-placeholder', '')
attrs.setdefault('data-minimum-input-length', 0)
if 'class' in attrs:
attrs['class'] += ' django-select2'
else:
attrs['class'] = 'django-select2'
return attrs
def optgroups(self, name, value, attrs=None):
"""Add empty option for clearable selects."""
if not self.is_required and not self.allow_multiple_selected:
self.choices = list(chain([('', '')], self.choices))
return super(Select2Mixin, self).optgroups(name, value, attrs=attrs)
@property
def media(self):
"""
Construct Media as a dynamic property.
.. Note:: For more information visit
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
"""
return forms.Media(
js=[settings.SELECT2_JS_URL, 'django_select2.js'],
css={'screen': [settings.SELECT2_CSS_URL]},
)
class AdminSelect2Mixin(Select2Mixin):
@property
def media(self):
return forms.Media(
js=['admin/js/jquery.init.js', settings.SELECT2_JS_URL, 'django_select2.js'],
css={'screen': [settings.SELECT2_CSS_URL]},
)
class Select2TagMixin(object):
"""Mixin to add select2 tag functionality."""
def build_attrs(self, base_attrs, extra_attrs=None):
"""Add select2's tag attributes."""
extra_attrs = extra_attrs or {}
extra_attrs.setdefault('data-minimum-input-length', 1)
extra_attrs.setdefault('data-tags', 'true')
extra_attrs.setdefault('data-token-separators', [",", " "])
return super(Select2TagMixin, self).build_attrs(base_attrs, extra_attrs)
class Select2Widget(Select2Mixin, forms.Select):
"""
Select2 drop in widget.
Example usage::
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('my_field', )
widgets = {
'my_field': Select2Widget
}
or::
class MyForm(forms.Form):
my_choice = forms.ChoiceField(widget=Select2Widget)
"""
pass
class Select2MultipleWidget(Select2Mixin, forms.SelectMultiple):
"""
Select2 drop in widget for multiple select.
Works just like :class:`.Select2Widget` but for multi select.
"""
pass
class Select2TagWidget(Select2TagMixin, Select2Mixin, forms.SelectMultiple):
"""
Select2 drop in widget for for tagging.
Example for :class:`.django.contrib.postgres.fields.ArrayField`::
class MyWidget(Select2TagWidget):
def value_from_datadict(self, data, files, name):
values = super(MyWidget, self).value_from_datadict(data, files, name):
return ",".join(values)
"""
pass
class HeavySelect2Mixin(Select2Mixin):
"""Mixin that adds select2's ajax options."""
def __init__(self, attrs=None, choices=(), **kwargs):
self.choices = choices
if attrs is not None:
self.attrs = attrs.copy()
else:
self.attrs = {}
self.data_view = kwargs.pop('data_view', None)
self.data_url = kwargs.pop('data_url', None)
if not (self.data_view or self.data_url):
raise ValueError('You must ether specify "data_view" or "data_url".')
def get_url(self):
"""Return url from instance or by reversing :attr:`.data_view`."""
if self.data_url:
return self.data_url
return reverse_lazy(self.data_view)
def build_attrs(self, base_attrs, extra_attrs=None):
"""Set select2's ajax attributes."""
attrs = super(HeavySelect2Mixin, self).build_attrs(base_attrs, extra_attrs)
# encrypt instance Id
self.widget_id = signing.dumps(id(self))
attrs['data-field_id'] = self.widget_id
attrs.setdefault('data-ajax--url', self.get_url())
attrs.setdefault('data-ajax--cache', "true")
attrs.setdefault('data-ajax--type', "GET")
attrs.setdefault('data-minimum-input-length', 2)
attrs['class'] += ' django-select2-heavy'
return attrs
def format_value(self, value):
result = super(HeavySelect2Mixin, self).format_value(value)
if isinstance(self.choices, ModelChoiceIterator):
chosen = copy(self.choices)
chosen.queryset = chosen.queryset.filter(pk__in=[
int(i) for i in result if isinstance(i, int) or i.isdigit()
])
self.choices = set(chosen)
return result
class HeavySelect2Widget(HeavySelect2Mixin, forms.Select):
"""
Select2 widget with AJAX support.
Usage example::
class MyWidget(HeavySelectWidget):
data_view = 'my_view_name'
or::
class MyForm(forms.Form):
my_field = forms.ChoicesField(
widget=HeavySelectWidget(
data_url='/url/to/json/response'
)
)
"""
pass
class HeavySelect2MultipleWidget(HeavySelect2Mixin, forms.SelectMultiple):
"""Select2 multi select widget similar to :class:`.HeavySelect2Widget`."""
pass
class HeavySelect2TagWidget(Select2TagMixin, HeavySelect2MultipleWidget):
"""Select2 tag widget."""
pass
class AdminSelect2Widget(AdminSelect2Mixin, Select2Widget):
pass
class AdminSelect2MultipleWidget(AdminSelect2Mixin, Select2MultipleWidget):
pass
class AdminHeavySelect2Widget(AdminSelect2Mixin, HeavySelect2Widget):
pass
class AdminHeavySelect2MultipleWidget(AdminSelect2Mixin, HeavySelect2MultipleWidget):
pass