From c5b3da4b55564ccc87adefe5cb25225125b99219 Mon Sep 17 00:00:00 2001 From: Trung Nguyen Date: Sat, 22 Apr 2023 14:54:07 +0800 Subject: [PATCH 1/7] Change default sorting from point -> rating (#298) Current top users are cheaters, which is annoying --- judge/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/judge/views/user.py b/judge/views/user.py index 7f56a4278..b73a01d51 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -535,7 +535,7 @@ class UserList(QueryStringSortMixin, InfinitePaginationMixin, DiggPaginatorMixin paginate_by = 100 all_sorts = frozenset(('points', 'problem_count', 'rating', 'performance_points')) default_desc = all_sorts - default_sort = '-performance_points' + default_sort = '-rating' def get_queryset(self): return (Profile.objects.filter(is_unlisted=False).order_by(self.order) From e91c3e574713ccc7e3156c05c0c3a6d96f86a70a Mon Sep 17 00:00:00 2001 From: Quantum Date: Sun, 19 Mar 2023 01:00:57 -0400 Subject: [PATCH 2/7] Pin social-auth version to avoid CI breakage --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3abb8a165..7fafbc2d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,8 @@ dmoj-wpadmin @ git+https://github.com/DMOJ/dmoj-wpadmin.git lxml Pygments<2.12 mistune<2 -social-auth-app-django +social-auth-core==4.3.0 +social-auth-app-django==5.0.0 pytz django-statici18n pika From c7109f95add81c045b82557097d8482db58c3432 Mon Sep 17 00:00:00 2001 From: Quantum Date: Sun, 19 Mar 2023 00:50:29 -0400 Subject: [PATCH 3/7] Allow value attribute for
  • tags --- dmoj/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dmoj/settings.py b/dmoj/settings.py index 345cc7525..834aac9a0 100755 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -542,6 +542,7 @@ 'audio': ['autoplay', 'controls', 'crossorigin', 'muted', 'loop', 'preload', 'src'], 'video': ['autoplay', 'controls', 'crossorigin', 'height', 'muted', 'loop', 'poster', 'preload', 'src', 'width'], 'source': ['src', 'srcset', 'type'], + 'li': ['value'], } MARKDOWN_STAFF_EDITABLE_STYLE = { From 3c150f9ced5462e3655bb9869d0116901645c868 Mon Sep 17 00:00:00 2001 From: Evan Date: Thu, 30 Mar 2023 18:00:29 +0000 Subject: [PATCH 4/7] Autofocus TOTP input field when logging in --- judge/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/judge/forms.py b/judge/forms.py index 154c30d52..d2e5daca0 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -460,7 +460,7 @@ def widget_attrs(self, widget): class TOTPForm(Form): TOLERANCE = settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES - totp_or_scratch_code = NoAutoCompleteCharField(required=False) + totp_or_scratch_code = NoAutoCompleteCharField(required=False, widget=forms.TextInput(attrs={'autofocus': True})) def __init__(self, *args, **kwargs): self.profile = kwargs.pop('profile') From bf62a390a559fdcb966419c6de6c08b56bbf9905 Mon Sep 17 00:00:00 2001 From: Tudor Brindus Date: Sat, 8 Apr 2023 09:33:35 -0400 Subject: [PATCH 5/7] bleach: allow `img[align]` by default --- dmoj/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmoj/settings.py b/dmoj/settings.py index 834aac9a0..afd870930 100755 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -530,7 +530,7 @@ BLEACH_USER_SAFE_ATTRS = { '*': ['id', 'class', 'style', 'data', 'height'], - 'img': ['src', 'alt', 'title', 'width', 'height', 'data-src'], + 'img': ['src', 'alt', 'title', 'width', 'height', 'data-src', 'align'], 'a': ['href', 'alt', 'title'], 'iframe': ['src', 'height', 'width', 'allow'], 'abbr': ['title'], From 7c4221c97ee548c9596946b64e18a22087a063ab Mon Sep 17 00:00:00 2001 From: Bao-Hiep Le Date: Sat, 29 Apr 2023 23:14:50 +0700 Subject: [PATCH 6/7] import_polygon_package: Fix images in tutorial. Cleanup images on error (#300) --- .../commands/import_polygon_package.py | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/judge/management/commands/import_polygon_package.py b/judge/management/commands/import_polygon_package.py index 95beca95c..8dfb07630 100644 --- a/judge/management/commands/import_polygon_package.py +++ b/judge/management/commands/import_polygon_package.py @@ -11,6 +11,7 @@ from django.conf import settings from django.contrib.sites.models import Site from django.core.files import File +from django.core.files.storage import default_storage from django.core.management.base import BaseCommand, CommandError from django.db import transaction from django.urls import reverse @@ -255,23 +256,39 @@ def parse_statements(problem_meta, root, package): problem_meta['translations'] = [] problem_meta['tutorial'] = '' - image_cache = {} + def process_images(text): + image_cache = problem_meta['image_cache'] - def save_image(image_path): - norm_path = os.path.normpath(os.path.join(statement_folder, image_path)) - sha1 = hashlib.sha1() - sha1.update(package.open(norm_path, 'r').read()) - sha1 = sha1.hexdigest() + def save_image(image_path): + norm_path = os.path.normpath(os.path.join(statement_folder, image_path)) + sha1 = hashlib.sha1() + sha1.update(package.open(norm_path, 'r').read()) + sha1 = sha1.hexdigest() - if sha1 not in image_cache: - image = File( - file=package.open(norm_path, 'r'), - name=os.path.basename(image_path), + if sha1 not in image_cache: + image = File( + file=package.open(norm_path, 'r'), + name=os.path.basename(image_path), + ) + data = json.loads(django_uploader(image)) + image_cache[sha1] = data['link'] + + return image_cache[sha1] + + for image_path in set(re.findall(r'!\[image\]\((.+?)\)', text)): + text = text.replace( + f'![image]({image_path})', + f'![image]({save_image(image_path)})', ) - data = json.loads(django_uploader(image)) - image_cache[sha1] = data['link'] - return image_cache[sha1] + for img_tag in set(re.findall(r'<\s*img[^>]*>', text)): + image_path = re.search(r'<\s*img[^>]+src\s*=\s*(["\'])(.*?)\1[^>]*>', text).group(2) + text = text.replace( + img_tag, + img_tag.replace(image_path, save_image(image_path)), + ) + + return text def parse_problem_properties(problem_properties): description = '' @@ -304,20 +321,6 @@ def parse_problem_properties(problem_properties): description += '\n## Notes\n\n' description += pandoc_tex_to_markdown(problem_properties['notes']) - # Images - for image_path in set(re.findall(r'!\[image\]\((.+?)\)', description)): - description = description.replace( - f'![image]({image_path})', - f'![image]({save_image(image_path)})', - ) - - for img_tag in set(re.findall(r'<\s*img[^>]*>', description)): - image_path = re.search(r'<\s*img[^>]+src\s*=\s*(["\'])(.*?)\1[^>]*>', description).group(2) - description = description.replace( - img_tag, - img_tag.replace(image_path, save_image(image_path)), - ) - return description def input_choice(prompt, choices): @@ -351,7 +354,7 @@ def input_choice(prompt, choices): description = parse_problem_properties(problem_properties) translations.append({ 'language': language, - 'description': description, + 'description': process_images(description), }) tutorial = problem_properties['tutorial'] @@ -378,6 +381,9 @@ def input_choice(prompt, choices): elif len(tutorials) > 0: problem_meta['tutorial'] = tutorials[0]['tutorial'] + # Process images for only the selected tutorial + problem_meta['tutorial'] = process_images(problem_meta['tutorial']) + for t in translations: language = t['language'] description = t['description'] @@ -545,6 +551,7 @@ def handle(self, *args, **options): # A dictionary to hold all problem information. problem_meta = {} + problem_meta['image_cache'] = {} problem_meta['code'] = problem_code problem_meta['tmp_dir'] = tempfile.TemporaryDirectory() problem_meta['authors'] = problem_authors @@ -555,6 +562,11 @@ def handle(self, *args, **options): parse_statements(problem_meta, root, package) create_problem(problem_meta) except Exception: + # Remove imported images + for image_url in problem_meta['image_cache'].values(): + path = default_storage.path(os.path.join(settings.MARTOR_UPLOAD_MEDIA_DIR, os.path.basename(image_url))) + os.remove(path) + raise finally: problem_meta['tmp_dir'].cleanup() From d9169b4d34e2d09e9e5fb0b13b875a95b6cfe35d Mon Sep 17 00:00:00 2001 From: Ly Dinh Minh Man <85622996+winprn@users.noreply.github.com> Date: Tue, 2 May 2023 22:19:35 +0700 Subject: [PATCH 7/7] Allow superuser to delete all user's comments (#296) * allow superuser to delete all user's comments * fix lint * fix lint * fix lint --------- Co-authored-by: Le Duy Thuc --- judge/views/user.py | 15 +++++++++++++++ templates/user/comment.html | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/judge/views/user.py b/judge/views/user.py index b73a01d51..9bcc56319 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -23,6 +23,7 @@ from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone +from django.utils.decorators import method_decorator from django.utils.formats import date_format from django.utils.functional import cached_property from django.utils.safestring import mark_safe @@ -286,9 +287,23 @@ def get_context_data(self, **kwargs): return context + @method_decorator(require_POST) + def delete_comments(self, request, *args, **kwargs): + if not request.user.is_superuser: + raise PermissionDenied() + + user_id = User.objects.get(username=kwargs['user']).id + user = Profile.objects.get(user=user_id) + for comment in Comment.get_newest_visible_comments(viewer=request.user, author=user, + batch=2 * self.paginate_by): + comment.get_descendants(include_self=True).update(hidden=True) + return HttpResponseRedirect(reverse('user_comment', args=(user.user.username,))) + def dispatch(self, request, *args, **kwargs): if not self.request.user.is_superuser: raise PermissionDenied() + if request.method == 'POST': + return self.delete_comments(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs) diff --git a/templates/user/comment.html b/templates/user/comment.html index 106246ca2..a23124809 100644 --- a/templates/user/comment.html +++ b/templates/user/comment.html @@ -13,6 +13,10 @@ {% block body %} {% block before_comments %}{% endblock %} +
    + {% csrf_token %} + +
      {% set logged_in = request.user.is_authenticated %} {% for comment in comments %}