From 2259596ef760efc696953c52a6923b1ed877397a Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sat, 21 Jan 2023 12:51:45 -0600 Subject: [PATCH 01/83] Fix blog edit --- templates/blog/blog.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/blog/blog.html b/templates/blog/blog.html index 213a291..4b1abcd 100644 --- a/templates/blog/blog.html +++ b/templates/blog/blog.html @@ -29,8 +29,9 @@ {% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %} {% if post.is_editable_by(request.user) %} - [{{ _('Edit') }}] - {% elif valid_user_to_show_edit %} + [{{ _('Edit') }}] + {% endif %} + {% if valid_user_to_show_edit %} {% for org in valid_org_to_show_edit %} [{{ _('Edit in') }} {{org.slug}}] {% endfor %} From dea24f7f71a6f7cd8e2f56b2150dc84be1a4c4d5 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sun, 22 Jan 2023 19:18:14 -0600 Subject: [PATCH 02/83] Fix editor bug --- resources/dmmd-preview.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/dmmd-preview.js b/resources/dmmd-preview.js index 4c16839..2450ea8 100644 --- a/resources/dmmd-preview.js +++ b/resources/dmmd-preview.js @@ -23,6 +23,12 @@ $(function () { csrfmiddlewaretoken: $.cookie('csrftoken') }, function (result) { $content.html(result); + $(".dmmd-preview-content [data-src]img").each(function() { + $(this).attr("src", $(this).attr("data-src")); + }) + $(".dmmd-preview-content [data-src]iframe").each(function() { + $(this).attr("src", $(this).attr("data-src")); + }) $preview.addClass('dmmd-preview-has-content').removeClass('dmmd-preview-stale'); var $jax = $content.find('.require-mathjax-support'); From 1628e63084c242a957b34e04e8861bbdc10f533d Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 23 Jan 2023 20:36:44 -0600 Subject: [PATCH 03/83] Initial subdomain implementation --- dmoj/settings.py | 1 + judge/middleware.py | 31 ++++++++++++++++ .../0145_alter_organization_slug.py | 36 +++++++++++++++++++ judge/models/comment.py | 5 ++- judge/models/profile.py | 11 ++++++ judge/views/blog.py | 18 ++++++++-- judge/views/contests.py | 12 +++++++ judge/views/organization.py | 33 +++++++---------- judge/views/problem.py | 22 ++++++++++-- judge/views/submission.py | 15 +++++++- judge/views/user.py | 10 +++--- templates/about/about.html | 8 ++++- templates/contest/contest.html | 11 ++++-- templates/organization/home.html | 9 ++++- templates/problem/left-sidebar.html | 4 ++- templates/problem/list-base.html | 8 +---- templates/site-logo-fragment.html | 6 ++-- 17 files changed, 194 insertions(+), 46 deletions(-) create mode 100644 judge/migrations/0145_alter_organization_slug.py diff --git a/dmoj/settings.py b/dmoj/settings.py index 076ab39..80f42bc 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -262,6 +262,7 @@ MIDDLEWARE = ( "judge.middleware.DMOJImpersonationMiddleware", "judge.middleware.ContestMiddleware", "judge.middleware.DarkModeMiddleware", + "judge.middleware.SubdomainMiddleware", "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware", "judge.social_auth.SocialAuthExceptionMiddleware", "django.contrib.redirects.middleware.RedirectFallbackMiddleware", diff --git a/judge/middleware.py b/judge/middleware.py index 940b486..cf2d37f 100644 --- a/judge/middleware.py +++ b/judge/middleware.py @@ -2,6 +2,10 @@ from django.conf import settings from django.http import HttpResponseRedirect from django.urls import Resolver404, resolve, reverse from django.utils.http import urlquote +from django.contrib.sites.shortcuts import get_current_site +from django.core.exceptions import ObjectDoesNotExist + +from judge.models import Organization class ShortCircuitMiddleware: @@ -82,3 +86,30 @@ class DarkModeMiddleware(object): reverse("toggle_darkmode") + "?next=" + urlquote(request.path) ) return self.get_response(request) + + +class SubdomainMiddleware(object): + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + domain = request.get_host() + site = get_current_site(request).domain + subdomain = domain[: len(domain) - len(site)] + request.organization = None + if len(subdomain) > 1: + subdomain = subdomain[:-1] + try: + organization = Organization.objects.get(slug=subdomain) + if ( + request.profile + and organization in request.profile.organizations.all() + ): + request.organization = organization + elif not request.GET.get("next", None): + return HttpResponseRedirect( + reverse("auth_login") + "?next=" + urlquote(request.path) + ) + except ObjectDoesNotExist: + pass + return self.get_response(request) diff --git a/judge/migrations/0145_alter_organization_slug.py b/judge/migrations/0145_alter_organization_slug.py new file mode 100644 index 0000000..1cadbe2 --- /dev/null +++ b/judge/migrations/0145_alter_organization_slug.py @@ -0,0 +1,36 @@ +# Generated by Django 3.2.16 on 2023-01-23 23:39 + +from django.db import migrations, models + + +def make_slug_unique(apps, schema_editor): + Organization = apps.get_model("judge", "Organization") + slugs = Organization.objects.values_list("slug", flat=True) + slugs = set([i.lower() for i in slugs]) + for slug in slugs: + orgs = Organization.objects.filter(slug=slug) + if len(orgs) > 1: + for org in orgs: + org.slug += "-" + str(org.id) + org.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0144_auto_20230103_0523"), + ] + + operations = [ + migrations.RunPython(make_slug_unique, migrations.RunPython.noop, atomic=True), + migrations.AlterField( + model_name="organization", + name="slug", + field=models.SlugField( + help_text="Organization name shown in URL", + max_length=128, + unique=True, + verbose_name="organization slug", + ), + ), + ] diff --git a/judge/models/comment.py b/judge/models/comment.py index 9361434..64a94db 100644 --- a/judge/models/comment.py +++ b/judge/models/comment.py @@ -71,7 +71,7 @@ class Comment(MPTTModel): order_insertion_by = ["-time"] @classmethod - def most_recent(cls, user, n, batch=None): + def most_recent(cls, user, n, batch=None, organization=None): queryset = ( cls.objects.filter(hidden=False) .select_related("author__user") @@ -79,6 +79,9 @@ class Comment(MPTTModel): .order_by("-id") ) + if organization: + queryset = queryset.filter(author__in=organization.members.all()) + problem_access = CacheDict( lambda code: Problem.objects.get(code=code).is_accessible_by(user) ) diff --git a/judge/models/profile.py b/judge/models/profile.py index c8704cd..d14f9b8 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -33,6 +33,7 @@ class Organization(models.Model): max_length=128, verbose_name=_("organization slug"), help_text=_("Organization name shown in URL"), + unique=True, ) short_name = models.CharField( max_length=20, @@ -339,6 +340,16 @@ class Profile(models.Model): ret.add(self.username) return ret + def can_edit_organization(self, org): + if not self.user.is_authenticated: + return False + profile_id = self.id + return ( + org.admins.filter(id=profile_id).exists() + or org.registrant_id == profile_id + or self.user.is_superuser + ) + class Meta: permissions = ( ("test_site", "Shows in-progress development stuff"), diff --git a/judge/views/blog.py b/judge/views/blog.py index 2bcc0a6..66e55a9 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -76,6 +76,10 @@ class FeedView(ListView): .filter(is_visible=True) .order_by("start_time") ) + if self.request.organization: + visible_contests = visible_contests.filter( + is_organization_private=True, organizations=self.request.organization + ) context["current_contests"] = visible_contests.filter( start_time__lte=now, end_time__gt=now @@ -84,10 +88,14 @@ class FeedView(ListView): context[ "recent_organizations" ] = OrganizationProfile.get_most_recent_organizations(self.request.profile) - context["top_rated"] = Profile.objects.filter(is_unlisted=False).order_by( + + profile_queryset = Profile.objects + if self.request.organization: + profile_queryset = self.request.organization.members + context["top_rated"] = profile_queryset.filter(is_unlisted=False).order_by( "-rating" )[:10] - context["top_scorer"] = Profile.objects.filter(is_unlisted=False).order_by( + context["top_scorer"] = profile_queryset.filter(is_unlisted=False).order_by( "-performance_points" )[:10] @@ -108,6 +116,8 @@ class PostList(FeedView, PageVoteListView, BookMarkListView): filter = Q(is_organization_private=False) if self.request.user.is_authenticated: filter |= Q(organizations__in=self.request.profile.organizations.all()) + if self.request.organization: + filter &= Q(organizations=self.request.organization) queryset = queryset.filter(filter) return queryset @@ -184,7 +194,9 @@ class CommentFeed(FeedView): paginate_by = 50 def get_queryset(self): - return Comment.most_recent(self.request.user, 1000) + return Comment.most_recent( + self.request.user, 1000, organization=self.request.organization + ) def get_context_data(self, **kwargs): context = super(CommentFeed, self).get_context_data(**kwargs) diff --git a/judge/views/contests.py b/judge/views/contests.py index 65a8b36..381dfc4 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -179,6 +179,8 @@ class ContestList( queryset = queryset.filter( Q(key__icontains=query) | Q(name__icontains=query) ) + if not self.org_query and self.request.organization: + self.org_query = [self.request.organization.id] if self.org_query: queryset = queryset.filter(organizations__in=self.org_query) @@ -404,6 +406,15 @@ class ContestDetail( def get_title(self): return self.object.name + def get_editable_organizations(self): + if not self.request.profile: + return [] + res = [] + for organization in self.object.organizations.all(): + if self.request.profile.can_edit_organization(organization): + res.append(organization) + return res + def get_context_data(self, **kwargs): context = super(ContestDetail, self).get_context_data(**kwargs) context["contest_problems"] = ( @@ -421,6 +432,7 @@ class ContestDetail( ) .add_i18n_name(self.request.LANGUAGE_CODE) ) + context["editable_organizations"] = self.get_editable_organizations() return context diff --git a/judge/views/organization.py b/judge/views/organization.py index dd3eee7..1ea810b 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -35,6 +35,7 @@ from django.views.generic.detail import ( SingleObjectTemplateResponseMixin, ) from django.core.paginator import Paginator +from django.contrib.sites.shortcuts import get_current_site from reversion import revisions from judge.forms import ( @@ -68,12 +69,13 @@ from judge.utils.views import ( DiggPaginatorMixin, ) from judge.utils.problems import user_attempted_ids, user_completed_ids -from judge.views.problem import ProblemList +from judge.views.problem import ProblemList, get_problems_in_organization from judge.views.contests import ContestList from judge.views.submission import AllSubmissions, SubmissionsListBase from judge.views.pagevote import PageVoteListView from judge.views.bookmark import BookMarkListView + __all__ = [ "OrganizationList", "OrganizationHome", @@ -96,14 +98,9 @@ class OrganizationBase(object): def can_edit_organization(self, org=None): if org is None: org = self.object - if not self.request.user.is_authenticated: - return False - profile_id = self.request.profile.id - return ( - org.admins.filter(id=profile_id).exists() - or org.registrant_id == profile_id - or self.request.user.is_superuser - ) + if self.request.profile: + return self.request.profile.can_edit_organization(org) + return False def is_member(self, org=None): if org is None: @@ -293,6 +290,9 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie def get_context_data(self, **kwargs): context = super(OrganizationHome, self).get_context_data(**kwargs) context["title"] = self.object.name + context["organization_subdomain"] = ( + self.object.slug + "." + get_current_site(self.request).domain + ) context["posts"], context["page_obj"] = self.get_posts_and_page_obj() context = self.add_pagevote_context_data(context, "posts") context = self.add_bookmark_context_data(context, "posts") @@ -378,6 +378,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView): class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList): template_name = "organization/problems.html" + filter_organization = True def get_queryset(self): self.org_query = [self.organization_id] @@ -387,17 +388,6 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL self.setup_problem_list(request) return super().get(request, *args, **kwargs) - def get_latest_attempted_problems(self, limit=None): - if not self.profile: - return () - problems = set(self.get_queryset().values_list("code", flat=True)) - result = list(user_attempted_ids(self.profile).values()) - result = [i for i in result if i["code"] in problems] - result = sorted(result, key=lambda d: -d["last_submission"]) - if limit: - result = result[:limit] - return result - def get_completed_problems(self): return user_completed_ids(self.profile) if self.profile is not None else () @@ -478,10 +468,11 @@ class OrganizationSubmissions( return None def _get_queryset(self): + problems = get_problems_in_organization(self.request, self.organization) return ( super() ._get_entire_queryset() - .filter(contest_object__organizations=self.organization) + .filter(user__organizations=self.organization, problem__in=problems) ) def get_context_data(self, **kwargs): diff --git a/judge/views/problem.py b/judge/views/problem.py index 2b309a1..320113d 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -105,6 +105,14 @@ def get_contest_submission_count(problem, profile, virtual): ) +def get_problems_in_organization(request, organization): + problem_list = ProblemList(request=request) + problem_list.setup_problem_list(request) + problem_list.org_query = [organization.id] + problems = problem_list.get_normal_queryset() + return problems + + class ProblemMixin(object): model = Problem slug_url_kwarg = "problem" @@ -145,10 +153,13 @@ class SolvedProblemMixin(object): else: return user_attempted_ids(self.profile) if self.profile is not None else () - def get_latest_attempted_problems(self, limit=None): + def get_latest_attempted_problems(self, limit=None, queryset=None): if self.in_contest or not self.profile: return () result = list(user_attempted_ids(self.profile).values()) + if queryset: + queryset_ids = set([i.code for i in queryset]) + result = filter(lambda i: i["code"] in queryset_ids, result) result = sorted(result, key=lambda d: -d["last_submission"]) if limit: result = result[:limit] @@ -454,6 +465,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView default_desc = frozenset(("date", "points", "ac_rate", "user_count")) default_sort = "-date" first_page_href = None + filter_organization = False def get_paginator( self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs @@ -592,6 +604,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView user=self.profile, points=F("problem__points") ).values_list("problem__id", flat=True) ) + if not self.org_query and self.request.organization: + self.org_query = [self.request.organization.id] if self.org_query: self.org_query = self.get_org_query(self.org_query) queryset = queryset.filter( @@ -652,6 +666,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView def get_context_data(self, **kwargs): context = super(ProblemList, self).get_context_data(**kwargs) + if self.request.organization: + self.filter_organization = True context["hide_solved"] = 0 if self.in_contest else int(self.hide_solved) context["show_types"] = 0 if self.in_contest else int(self.show_types) context["full_text"] = 0 if self.in_contest else int(self.full_text) @@ -676,7 +692,9 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView context["search_query"] = self.search_query context["completed_problem_ids"] = self.get_completed_problems() context["attempted_problems"] = self.get_attempted_problems() - context["last_attempted_problems"] = self.get_latest_attempted_problems(15) + context["last_attempted_problems"] = self.get_latest_attempted_problems( + 15, context["problems"] if self.filter_organization else None + ) context["page_type"] = "list" context.update(self.get_sort_paginate_context()) if not self.in_contest: diff --git a/judge/views/submission.py b/judge/views/submission.py index 8ae921b..9f30fb2 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -49,6 +49,7 @@ from judge.utils.raw_sql import join_sql_subquery, use_straight_join from judge.utils.views import DiggPaginatorMixin from judge.utils.views import TitleMixin from judge.utils.timedelta import nice_repr +from judge.views.problem import get_problems_in_organization MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000 @@ -414,6 +415,13 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): def _get_queryset(self): queryset = self._get_entire_queryset() if not self.in_contest: + if self.request.organization: + problems = get_problems_in_organization( + self.request, self.request.organization + ) + queryset = queryset.filter( + user__organizations=self.request.organization, problem__in=problems + ) join_sql_subquery( queryset, subquery=str( @@ -785,7 +793,12 @@ class AllSubmissions(SubmissionsListBase): return context def _get_result_data(self): - if self.in_contest or self.selected_languages or self.selected_statuses: + if ( + self.request.organization + or self.in_contest + or self.selected_languages + or self.selected_statuses + ): return super(AllSubmissions, self)._get_result_data() key = "global_submission_result_data" diff --git a/judge/views/user.py b/judge/views/user.py index bd0d34b..80f9add 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -454,7 +454,7 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView): return ret def get_queryset(self): - ret = ( + queryset = ( Profile.objects.filter(is_unlisted=False) .order_by(self.order, "id") .select_related("user") @@ -467,11 +467,13 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView): "problem_count", ) ) - + if self.request.organization: + queryset = queryset.filter(organizations=self.request.organization) if (self.request.GET.get("friend") == "true") and self.request.profile: - ret = self.filter_friend_queryset(ret) + queryset = self.filter_friend_queryset(queryset) self.filter_friend = True - return ret + + return queryset def get_context_data(self, **kwargs): context = super(UserList, self).get_context_data(**kwargs) diff --git a/templates/about/about.html b/templates/about/about.html index 1fc305e..9547ce1 100644 --- a/templates/about/about.html +++ b/templates/about/about.html @@ -1,6 +1,11 @@ {% extends "base.html" %} {% block body %} +{% if request.organization %} + {% cache 3600 'organization_html' request.organization.id MATH_ENGINE %} + {{ request.organization.about|markdown|reference|str|safe }} + {% endcache %} +{% else %}

LQDOJ (Le Quy Don Online Judge) là một trang web chấm bài tự động được phát triển dựa trên nền tảng mã nguồn mở DMOJ. Được xây dựng với mục đích ban đầu là tạo ra một môi trường học tập cho học sinh khối chuyên Tin trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng), hiện nay LQDOJ đã cho phép đăng ký tự do để trở thành một sân chơi rộng mở cho toàn bộ cộng đồng học sinh yêu Tin học. Trang web cung cấp lượng bài luyện tập đồ sộ từ các kỳ thi HSG Quốc Gia, ACM ICPC, Olympic Duyên Hải Bắc Bộ, etc. cho đến các contest định kỳ để xếp loại khả năng (rating) giúp các bạn có thêm động lực cạnh tranh và khí thế phấn đấu rèn luyện nâng cao trình độ lập trình. Các bạn có thể tham khảo mã nguồn của trang web tại Github repo chính thức. Mọi ý kiến đóng góp và thắc mắc xin gửi về: -

+ +{% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/contest/contest.html b/templates/contest/contest.html index 79c4ce3..c2242c6 100644 --- a/templates/contest/contest.html +++ b/templates/contest/contest.html @@ -31,7 +31,7 @@ {# Allow users to leave the virtual contest #} {% if in_contest %}
+ class="contest-join-pseudotab btn-red"> {% csrf_token %}
@@ -77,12 +77,19 @@ {% endif %} - +
{% cache 3600 'contest_html' contest.id MATH_ENGINE %} {{ contest.description|markdown|reference|str|safe }} {% endcache %}
+ {% if editable_organizations %} +
+ {% for org in editable_organizations %} + [{{ _('Edit in') }} {{org.slug}}] + {% endfor %} +
+ {% endif %} {% if contest.ended or request.user.is_superuser or is_editor or is_tester %}
diff --git a/templates/organization/home.html b/templates/organization/home.html index 8a0d5e5..5d481b9 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -13,7 +13,14 @@ {% block middle_title %}
-

{{title}}

+

+ {{title}} +

+ {% if is_member %} + + {% endif %} {% if request.user.is_authenticated %} diff --git a/templates/problem/left-sidebar.html b/templates/problem/left-sidebar.html index 0083b41..97f5f7b 100644 --- a/templates/problem/left-sidebar.html +++ b/templates/problem/left-sidebar.html @@ -1,7 +1,9 @@ -{% if not request.in_contest_mode %} +{% if not show_contest_mode %} {% endif %} \ No newline at end of file diff --git a/templates/problem/list-base.html b/templates/problem/list-base.html index d6cee15..0e44083 100644 --- a/templates/problem/list-base.html +++ b/templates/problem/list-base.html @@ -248,13 +248,7 @@ {% endblock %} {% block left_sidebar %} - {% if not show_contest_mode %} - - {% endif %} + {% include "problem/left-sidebar.html" %} {% endblock %} {% block right_sidebar %} diff --git a/templates/site-logo-fragment.html b/templates/site-logo-fragment.html index f2099fc..b6a7698 100644 --- a/templates/site-logo-fragment.html +++ b/templates/site-logo-fragment.html @@ -1,7 +1,9 @@ {% if request.in_contest_mode and request.participation.contest.logo_override_image %} - {{ SITE_NAME }} + {{ SITE_NAME }} +{% elif request.organization %} + {{ SITE_NAME }} {% elif logo_override_image is defined and logo_override_image %} - {{ SITE_NAME }} + {{ SITE_NAME }} {% else %} {{ SITE_NAME }} From dc243dc136d7394c7989318a2dc4ed160817c8d6 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 23 Jan 2023 20:46:17 -0600 Subject: [PATCH 04/83] Fix a tag --- judge/views/organization.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index 1ea810b..af06ee4 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -290,8 +290,13 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie def get_context_data(self, **kwargs): context = super(OrganizationHome, self).get_context_data(**kwargs) context["title"] = self.object.name + http = "http" if settings.DMOJ_SSL == 0 else "https" context["organization_subdomain"] = ( - self.object.slug + "." + get_current_site(self.request).domain + http + + "://" + + self.object.slug + + "." + + get_current_site(self.request).domain ) context["posts"], context["page_obj"] = self.get_posts_and_page_obj() context = self.add_pagevote_context_data(context, "posts") From 22707304076689a92d3dba32128d1fad85f20ea6 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 23 Jan 2023 20:55:07 -0600 Subject: [PATCH 05/83] Optimize a query --- judge/views/organization.py | 2 +- judge/views/submission.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index af06ee4..051fe7a 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -477,7 +477,7 @@ class OrganizationSubmissions( return ( super() ._get_entire_queryset() - .filter(user__organizations=self.organization, problem__in=problems) + .filter(user__in=self.organization.members.all(), problem__in=problems) ) def get_context_data(self, **kwargs): diff --git a/judge/views/submission.py b/judge/views/submission.py index 9f30fb2..e2a091b 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -420,7 +420,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): self.request, self.request.organization ) queryset = queryset.filter( - user__organizations=self.request.organization, problem__in=problems + user__in=self.request.organization.members.all(), + problem__in=problems, ) join_sql_subquery( queryset, From 15913e51f3a5dc98c73731ddb6261958d8bf0d86 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 23 Jan 2023 21:00:11 -0600 Subject: [PATCH 06/83] Back to old query --- judge/views/organization.py | 5 ++--- judge/views/submission.py | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index 051fe7a..d36e7ab 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -69,7 +69,7 @@ from judge.utils.views import ( DiggPaginatorMixin, ) from judge.utils.problems import user_attempted_ids, user_completed_ids -from judge.views.problem import ProblemList, get_problems_in_organization +from judge.views.problem import ProblemList from judge.views.contests import ContestList from judge.views.submission import AllSubmissions, SubmissionsListBase from judge.views.pagevote import PageVoteListView @@ -473,11 +473,10 @@ class OrganizationSubmissions( return None def _get_queryset(self): - problems = get_problems_in_organization(self.request, self.organization) return ( super() ._get_entire_queryset() - .filter(user__in=self.organization.members.all(), problem__in=problems) + .filter(contest_object__organizations=self.organization) ) def get_context_data(self, **kwargs): diff --git a/judge/views/submission.py b/judge/views/submission.py index e2a091b..484d23e 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -49,7 +49,6 @@ from judge.utils.raw_sql import join_sql_subquery, use_straight_join from judge.utils.views import DiggPaginatorMixin from judge.utils.views import TitleMixin from judge.utils.timedelta import nice_repr -from judge.views.problem import get_problems_in_organization MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000 @@ -416,12 +415,8 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): queryset = self._get_entire_queryset() if not self.in_contest: if self.request.organization: - problems = get_problems_in_organization( - self.request, self.request.organization - ) queryset = queryset.filter( - user__in=self.request.organization.members.all(), - problem__in=problems, + contest_object__organizations=self.request.organization ) join_sql_subquery( queryset, From 3791d2e90f7cd10ea98607a8e5ab21d05c32b62c Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 23 Jan 2023 21:08:11 -0600 Subject: [PATCH 07/83] Fix subdomain login --- judge/middleware.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/judge/middleware.py b/judge/middleware.py index cf2d37f..f04b708 100644 --- a/judge/middleware.py +++ b/judge/middleware.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.urls import Resolver404, resolve, reverse from django.utils.http import urlquote from django.contrib.sites.shortcuts import get_current_site @@ -106,10 +106,13 @@ class SubdomainMiddleware(object): and organization in request.profile.organizations.all() ): request.organization = organization - elif not request.GET.get("next", None): - return HttpResponseRedirect( - reverse("auth_login") + "?next=" + urlquote(request.path) - ) + else: + if request.profile: + raise Http404 + if not request.GET.get("next", None): + return HttpResponseRedirect( + reverse("auth_login") + "?next=" + urlquote(request.path) + ) except ObjectDoesNotExist: pass return self.get_response(request) From 08fae0d0dcd7a95fc5603ff1f396075ed5a78a35 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 25 Jan 2023 13:13:42 -0600 Subject: [PATCH 08/83] Add slug validator for org --- .../0146_alter_organization_slug.py | 29 +++++++++++++++++++ judge/models/profile.py | 3 ++ 2 files changed, 32 insertions(+) create mode 100644 judge/migrations/0146_alter_organization_slug.py diff --git a/judge/migrations/0146_alter_organization_slug.py b/judge/migrations/0146_alter_organization_slug.py new file mode 100644 index 0000000..dbd475d --- /dev/null +++ b/judge/migrations/0146_alter_organization_slug.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.16 on 2023-01-25 19:12 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0145_alter_organization_slug"), + ] + + operations = [ + migrations.AlterField( + model_name="organization", + name="slug", + field=models.SlugField( + help_text="Organization name shown in URL", + max_length=128, + unique=True, + validators=[ + django.core.validators.RegexValidator( + "^[-a-zA-Z0-9]+$", "Only alphanumeric and hyphens" + ) + ], + verbose_name="organization slug", + ), + ), + ] diff --git a/judge/models/profile.py b/judge/models/profile.py index d14f9b8..79b6ad8 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -34,6 +34,9 @@ class Organization(models.Model): verbose_name=_("organization slug"), help_text=_("Organization name shown in URL"), unique=True, + validators=[ + RegexValidator("^[-a-zA-Z0-9]+$", _("Only alphanumeric and hyphens")) + ], ) short_name = models.CharField( max_length=20, From 840209b2cbf5d0dc3cea978352101254067afc0d Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Thu, 26 Jan 2023 11:53:24 -0600 Subject: [PATCH 09/83] Add comment count to actionbar --- templates/actionbar/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/actionbar/list.html b/templates/actionbar/list.html index d5ae2b1..06d6e29 100644 --- a/templates/actionbar/list.html +++ b/templates/actionbar/list.html @@ -20,7 +20,7 @@ - {{_("Comment")}} + {{_("Comment")}} {% if comment_list.count() %} ({{comment_list.count()}}) {% endif %} {% endif %} From 65eb49a840b547f88bc9b766f90db4fbe4068e28 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 27 Jan 2023 16:26:28 -0600 Subject: [PATCH 10/83] Fix ticket page --- resources/ticket.scss | 1 + templates/ticket/ticket.html | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/ticket.scss b/resources/ticket.scss index bba44a7..d7f9143 100644 --- a/resources/ticket.scss +++ b/resources/ticket.scss @@ -1,4 +1,5 @@ .ticket-container { + display: flex; #content > h2:first-child small { color: #999; font-size: 0.9em; diff --git a/templates/ticket/ticket.html b/templates/ticket/ticket.html index 4fec301..0ba7a21 100644 --- a/templates/ticket/ticket.html +++ b/templates/ticket/ticket.html @@ -144,10 +144,10 @@ {% endblock %} {% block content_title %} -
+ -
-
{{ ticket.title }}
#{{ ticket.id }} + + {{ ticket.title }}#{{ ticket.id }} {% endblock %} {% block body %} From 9a208ca1087885348f062377213709b34a48dde5 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 27 Jan 2023 16:52:35 -0600 Subject: [PATCH 11/83] Fix submission page bug in subdomain --- judge/middleware.py | 3 ++- judge/views/submission.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/judge/middleware.py b/judge/middleware.py index f04b708..73fb2c9 100644 --- a/judge/middleware.py +++ b/judge/middleware.py @@ -114,5 +114,6 @@ class SubdomainMiddleware(object): reverse("auth_login") + "?next=" + urlquote(request.path) ) except ObjectDoesNotExist: - pass + scheme = "https" if settings.DMOJ_SSL > 0 else "http" + return HttpResponseRedirect(scheme + "://" + site) return self.get_response(request) diff --git a/judge/views/submission.py b/judge/views/submission.py index 484d23e..e8e6489 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -783,7 +783,10 @@ class AllSubmissions(SubmissionsListBase): def get_context_data(self, **kwargs): context = super(AllSubmissions, self).get_context_data(**kwargs) - context["dynamic_update"] = context["page_obj"].number == 1 + print(self.request.organization) + context["dynamic_update"] = ( + context["page_obj"].number == 1 + ) and not self.request.organization context["last_msg"] = event.last() context["stats_update_interval"] = self.stats_update_interval return context From 52f1e77fe10d6ec27209c93aee2fdc5f26981174 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 27 Jan 2023 17:11:10 -0600 Subject: [PATCH 12/83] Reformat html files --- .pre-commit-config.yaml | 9 + requirements.txt | 3 +- templates/about/about.html | 14 +- templates/about/custom-checker-sample.html | 318 ++--- templates/actionbar/list.html | 70 +- templates/actionbar/media-css.html | 8 +- templates/actionbar/media-js.html | 242 ++-- templates/admin/auth/user/change_form.html | 24 +- .../admin/judge/contest/change_form.html | 30 +- .../admin/judge/contest/change_list.html | 16 +- templates/admin/judge/judge/change_form.html | 36 +- .../admin/judge/problem/change_form.html | 34 +- .../admin/judge/profile/change_form.html | 24 +- .../admin/judge/submission/change_form.html | 24 +- templates/base.html | 538 ++++----- templates/blog/blog.html | 74 +- templates/blog/content.html | 94 +- templates/blog/dashboard.html | 62 +- templates/blog/list.html | 194 +-- templates/blog/media-css.html | 48 +- templates/chat/chat.html | 1020 ++++++++-------- templates/chat/chat_css.html | 244 ++-- templates/chat/message.html | 54 +- templates/chat/message_list.html | 10 +- templates/chat/online_status.html | 72 +- templates/chat/user_online_status.html | 44 +- templates/comments/edit-ajax.html | 24 +- templates/comments/edit.html | 10 +- templates/comments/feed.html | 34 +- templates/comments/list.html | 318 ++--- templates/comments/math.html | 2 +- templates/comments/media-css.html | 2 +- templates/comments/media-js.html | 450 +++---- templates/comments/preview.html | 2 +- templates/comments/revision-ajax.html | 2 +- templates/comments/votes.html | 20 +- templates/common-content.html | 130 +- templates/contest/access_code.html | 52 +- templates/contest/calendar.html | 96 +- templates/contest/clarification.html | 80 +- templates/contest/clone.html | 62 +- templates/contest/contest-datetime.html | 104 +- templates/contest/contest-list-tabs.html | 6 +- templates/contest/contest-tabs.html | 64 +- templates/contest/contest.html | 238 ++-- templates/contest/list.html | 724 +++++------ templates/contest/media-js.html | 378 +++--- templates/contest/moss.html | 142 +-- templates/contest/preview.html | 2 +- templates/contest/private.html | 28 +- templates/contest/ranking-css.html | 204 ++-- templates/contest/ranking-table.html | 104 +- templates/contest/ranking.html | 270 ++--- templates/contest/stats.html | 126 +- templates/contest/tag-ajax.html | 6 +- templates/contest/tag-title.html | 12 +- templates/contest/tag.html | 4 +- templates/contests-countdown.html | 70 +- templates/error.html | 48 +- templates/fine_uploader/script.html | 58 +- templates/flatpages/admin_link.html | 6 +- templates/flatpages/default.html | 6 +- templates/flatpages/markdown.html | 4 +- templates/flatpages/markdown_math.html | 4 +- templates/generic-message.html | 2 +- templates/home.html | 36 +- templates/internal/base.html | 182 +-- templates/license.html | 14 +- templates/list-pages.html | 48 +- templates/messages.html | 10 +- templates/notification/list.html | 52 +- templates/organization/add-member.html | 18 +- templates/organization/add.html | 36 +- templates/organization/blog/add.html | 26 +- templates/organization/blog/edit.html | 44 +- templates/organization/blog/pending.html | 54 +- templates/organization/contest/add.html | 74 +- templates/organization/contest/edit.html | 174 +-- templates/organization/contests.html | 6 +- templates/organization/edit.html | 46 +- templates/organization/form.html | 42 +- templates/organization/home-base.html | 42 +- templates/organization/home-js.html | 44 +- templates/organization/home.html | 92 +- templates/organization/list.html | 68 +- templates/organization/new.html | 10 +- templates/organization/org-left-sidebar.html | 26 +- templates/organization/org-right-sidebar.html | 164 +-- templates/organization/preview.html | 2 +- templates/organization/problems.html | 6 +- templates/organization/requests/detail.html | 44 +- templates/organization/requests/log.html | 50 +- templates/organization/requests/pending.html | 82 +- templates/organization/requests/request.html | 30 +- templates/organization/requests/tabs.html | 28 +- templates/organization/submissions.html | 6 +- templates/organization/users-table.html | 24 +- templates/organization/users.html | 68 +- templates/pagedown.html | 60 +- templates/pagedown/widgets/default.html | 14 +- templates/problem/clone.html | 62 +- templates/problem/data.html | 1070 ++++++++--------- templates/problem/editorial.html | 56 +- templates/problem/feed.html | 280 ++--- templates/problem/left-sidebar.html | 16 +- templates/problem/list-base.html | 520 ++++---- templates/problem/list.html | 334 ++--- templates/problem/manage_submission.html | 316 ++--- templates/problem/preview.html | 2 +- templates/problem/problem-list-tabs.html | 8 +- templates/problem/problem.html | 760 ++++++------ templates/problem/raw.html | 146 +-- templates/problem/recent-attempt.html | 42 +- templates/problem/search-form.html | 208 ++-- templates/problem/submission-diff.html | 164 +-- templates/problem/submit.html | 426 +++---- templates/problem/yaml.html | 32 +- templates/recent-organization.html | 44 +- templates/registration/activate.html | 2 +- .../registration/activation_complete.html | 2 +- templates/registration/activation_email.html | 10 +- templates/registration/login.html | 92 +- templates/registration/logout.html | 2 +- .../registration/password_change_done.html | 2 +- .../registration/password_change_form.html | 12 +- templates/registration/password_reset.html | 10 +- .../registration/password_reset_complete.html | 4 +- .../registration/password_reset_confirm.html | 20 +- .../registration/password_reset_done.html | 4 +- .../registration/password_reset_email.html | 28 +- templates/registration/profile_creation.html | 82 +- .../registration/registration_closed.html | 2 +- .../registration/registration_complete.html | 2 +- templates/registration/registration_form.html | 190 +-- templates/registration/totp_auth.html | 64 +- templates/registration/totp_disable.html | 80 +- templates/registration/totp_enable.html | 144 +-- templates/registration/username_select.html | 10 +- templates/resolver/media-js.html | 840 ++++++------- templates/resolver/resolver.html | 24 +- templates/runtime-version-fragment.html | 16 +- templates/site-logo-fragment.html | 10 +- templates/solution-preview.html | 2 +- templates/stats/base.html | 10 +- templates/stats/language.html | 64 +- templates/stats/media-js.html | 204 ++-- templates/stats/site.html | 130 +- templates/stats/tab.html | 10 +- templates/status/judge-status-table.html | 120 +- templates/status/judge-status.html | 16 +- templates/status/language-list.html | 88 +- templates/status/media-css.html | 20 +- templates/status/media-js.html | 104 +- templates/status/status-tabs.html | 6 +- templates/status/versions.html | 64 +- templates/submission/info-base.html | 22 +- .../submission/internal-error-message.html | 26 +- templates/submission/list.html | 722 +++++------ templates/submission/row.html | 154 +-- templates/submission/source.html | 108 +- templates/submission/status-testcases.html | 342 +++--- templates/submission/status.html | 332 ++--- .../submission/submission-list-tabs.html | 26 +- templates/submission/user-ajax.html | 144 +-- templates/tabs-base.html | 26 +- templates/task_status.html | 146 +-- templates/three-column-content.html | 262 ++-- templates/ticket/edit-notes.html | 6 +- templates/ticket/feed.html | 48 +- templates/ticket/list.html | 496 ++++---- templates/ticket/message.html | 38 +- templates/ticket/new.html | 58 +- templates/ticket/new_problem.html | 16 +- templates/ticket/preview.html | 2 +- templates/ticket/row.html | 10 +- templates/ticket/ticket.html | 424 +++---- templates/time-remaining-fragment.html | 2 +- templates/timezone/media-css.html | 46 +- templates/timezone/media-js.html | 84 +- templates/top-users.html | 72 +- templates/two-column-content.html | 34 +- templates/user/base-users-js.html | 118 +- templates/user/base-users-table.html | 34 +- templates/user/base-users-two-col.html | 72 +- templates/user/edit-profile.html | 292 ++--- templates/user/import/index.html | 170 +-- templates/user/import/table_csv.html | 36 +- templates/user/link-list.html | 2 +- templates/user/list.html | 34 +- templates/user/pp-row.html | 46 +- templates/user/pp-table-body.html | 2 +- templates/user/preview.html | 2 +- templates/user/rating.html | 30 +- templates/user/user-about.html | 990 +++++++-------- templates/user/user-base.html | 236 ++-- templates/user/user-bookmarks.html | 156 +-- templates/user/user-left-sidebar.html | 14 +- templates/user/user-list-tabs.html | 12 +- templates/user/user-problems.html | 204 ++-- templates/user/user-tabs.html | 28 +- templates/user/users-table.html | 58 +- templates/widgets/datetimepicker.html | 40 +- templates/widgets/fine_uploader.html | 2 +- templates/widgets/relative-time.html | 8 +- templates/widgets/select_all.html | 52 +- 205 files changed, 11096 insertions(+), 11086 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a46ebba --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/rtts/djhtml + rev: 'main' # replace with the latest tag on GitHub + hooks: + - id: djhtml + entry: djhtml -i -t 2 + files: templates/. + - id: djcss + types: [scss] diff --git a/requirements.txt b/requirements.txt index 5000705..82f5384 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,4 +41,5 @@ markdown bleach pymdown-extensions mdx-breakless-lists -beautifulsoup4 \ No newline at end of file +beautifulsoup4 +pre-commit \ No newline at end of file diff --git a/templates/about/about.html b/templates/about/about.html index 9547ce1..7924c6e 100644 --- a/templates/about/about.html +++ b/templates/about/about.html @@ -1,19 +1,19 @@ {% extends "base.html" %} {% block body %} -{% if request.organization %} + {% if request.organization %} {% cache 3600 'organization_html' request.organization.id MATH_ENGINE %} - {{ request.organization.about|markdown|reference|str|safe }} + {{ request.organization.about|markdown|reference|str|safe }} {% endcache %} -{% else %} + {% else %}

- LQDOJ (Le Quy Don Online Judge) là một trang web chấm bài tự động được phát triển dựa trên nền tảng mã nguồn mở DMOJ. Được xây dựng với mục đích ban đầu là tạo ra một môi trường học tập cho học sinh khối chuyên Tin trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng), hiện nay LQDOJ đã cho phép đăng ký tự do để trở thành một sân chơi rộng mở cho toàn bộ cộng đồng học sinh yêu Tin học. Trang web cung cấp lượng bài luyện tập đồ sộ từ các kỳ thi HSG Quốc Gia, ACM ICPC, Olympic Duyên Hải Bắc Bộ, etc. cho đến các contest định kỳ để xếp loại khả năng (rating) giúp các bạn có thêm động lực cạnh tranh và khí thế phấn đấu rèn luyện nâng cao trình độ lập trình. Các bạn có thể tham khảo mã nguồn của trang web tại Github repo chính thức. Mọi ý kiến đóng góp và thắc mắc xin gửi về: -

-{% endif %} + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/about/custom-checker-sample.html b/templates/about/custom-checker-sample.html index 486e8bd..b6497df 100644 --- a/templates/about/custom-checker-sample.html +++ b/templates/about/custom-checker-sample.html @@ -1,62 +1,62 @@ {% extends "common-content.html" %} {% block description %} - -
-

1. Custom checker (PY)

-
-

- Đây là checker mặc định của website, cho phép người dùng cập nhật được nhiều thông tin nhất (chi tiết xem ở bên dưới). Chúng ta cần hoàn thành hàm check dưới đây: -

+ +
+

1. Custom checker (PY)

+
+

+ Đây là checker mặc định của website, cho phép người dùng cập nhật được nhiều thông tin nhất (chi tiết xem ở bên dưới). Chúng ta cần hoàn thành hàm check dưới đây: +

-{{ -""" -def check(process_output, judge_output, **kwargs): + {{ + """ + def check(process_output, judge_output, **kwargs): # return True/False -"""|highlight('py')}} - -

- Trong đó, **kwargs có thể chứa các biến sau: -

-
    -
  • process_output: output
  • -
  • judge_output: đáp án
  • -
  • submission_source: Code bài nộp
  • -
  • judge_input: input
  • -
  • point_value: điểm của test đang chấm
  • -
  • case_position: thứ tự của test
  • -
  • submission_language: ngôn ngữ của bài nộp
  • -
  • execution_time: thời gian chạy
  • -
-

Return:

-
    -
  • Cách 1: Trả về True/False
  • -
  • Cách 2: Trả về một object CheckerResult có thể được gọi như sau
    CheckerResult(case_passed_bool, points_awarded, feedback='')
  • -
+ """|highlight('py')}} -

Ví dụ:

-

Dưới đây là ví dụ cho bài toán: Input gồm 1 số nguyên n. In ra 2 số nguyên a, b sao cho a + b = n. -

-{{ -""" -from dmoj.result import CheckerResult +

+ Trong đó, **kwargs có thể chứa các biến sau: +

+
    +
  • process_output: output
  • +
  • judge_output: đáp án
  • +
  • submission_source: Code bài nộp
  • +
  • judge_input: input
  • +
  • point_value: điểm của test đang chấm
  • +
  • case_position: thứ tự của test
  • +
  • submission_language: ngôn ngữ của bài nộp
  • +
  • execution_time: thời gian chạy
  • +
+

Return:

+
    +
  • Cách 1: Trả về True/False
  • +
  • Cách 2: Trả về một object CheckerResult có thể được gọi như sau
    CheckerResult(case_passed_bool, points_awarded, feedback='')
  • +
+ +

Ví dụ:

+

Dưới đây là ví dụ cho bài toán: Input gồm 1 số nguyên n. In ra 2 số nguyên a, b sao cho a + b = n. +

+ {{ + """ + from dmoj.result import CheckerResult -def wa(feedback): + def wa(feedback): return CheckerResult(False, 0, feedback) -def check(process_output, judge_output, judge_input, **kwargs): + def check(process_output, judge_output, judge_input, **kwargs): # process the input input_arr = judge_input.split() assert(len(input_arr) == 1) @@ -66,143 +66,143 @@ def check(process_output, judge_output, judge_input, **kwargs): output_arr = process_output.split() if (len(output_arr) != 2): - return wa('Wrong output format') + return wa('Wrong output format') try: - a, b = int(output_arr[0]), int(output_arr[1]) + a, b = int(output_arr[0]), int(output_arr[1]) except: - return wa('Wrong output format') + return wa('Wrong output format') if (n == a + b): - return True + return True return wa('a + b != n') -"""| highlight('py')}} -
-
-

2. Custom validator (CPP)

-
-

- Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 3 arguments theo thứ tự input_file, output_file, ans_file tương ứng với các file input, output, đáp án. -

-

- Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows): -

+    """| highlight('py')}}
+  
+
+

2. Custom validator (CPP)

+
+

+ Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 3 arguments theo thứ tự input_file, output_file, ans_file tương ứng với các file input, output, đáp án. +

+

+ Để test chương trình trên máy tính, có thể dùng lệnh như sau (Windows): +

 main.exe [input_file] [output_file] [ans_file]
- hoặc thay bằng ./main trên Linux/MacOS. -

-

Return:

-

- Chương trình trả về giá trị: -

    -
  • 0 nếu AC (100% điểm)
  • -
  • 1 nếu WA (0 điểm)
  • -
  • 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC.
  • -
- Những thông tin được viết ra stdout (bằng cout) sẽ được in ra màn hình cho người nộp bài(feedback) -

+ hoặc thay bằng ./main trên Linux/MacOS. +

+

Return:

+

+ Chương trình trả về giá trị: +

    +
  • 0 nếu AC (100% điểm)
  • +
  • 1 nếu WA (0 điểm)
  • +
  • 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC.
  • +
+ Những thông tin được viết ra stdout (bằng cout) sẽ được in ra màn hình cho người nộp bài(feedback) +

-

Ví dụ:

-

Chương trình sau dùng để chấm bài toán: Cho n là một số nguyên dương. In ra hai số tự nhiên a, b sao cho a + b = n.

-

Nếu in ra a + b = n và a, b >= 0 thì được 100% số điểm, nếu a + b = n nhưng một trong 2 số a, b âm thì được 50% số điểm.

-{{ -""" -#include -using namespace std; +

Ví dụ:

+

Chương trình sau dùng để chấm bài toán: Cho n là một số nguyên dương. In ra hai số tự nhiên a, b sao cho a + b = n.

+

Nếu in ra a + b = n và a, b >= 0 thì được 100% số điểm, nếu a + b = n nhưng một trong 2 số a, b âm thì được 50% số điểm.

+ {{ + """ + #include + using namespace std; -int main(int argc, char** argv) { - ifstream inp(argv[1]); - ifstream out(argv[2]); - ifstream ans(argv[3]); + int main(int argc, char** argv) { + ifstream inp(argv[1]); + ifstream out(argv[2]); + ifstream ans(argv[3]); - int n, a, b, c, d; - - inp >> n; - out >> a >> b; - ans >> c >> d; + int n, a, b, c, d; - if (a + b == c + d) { - cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl; - - if (a >= 0 && b >= 0) { - return 0; // AC - } - else { - cerr << 0.5; - return 2; // PARTIAL - } - } - else { - cout << \"a + b = \" << a + b << \" != \" << n << endl; - return 1; // WA - } -} -""" | highlight('cpp')}} + inp >> n; + out >> a >> b; + ans >> c >> d; + + if (a + b == c + d) { + cout << a << \" + \" << b << \" = \" << c << \" + \" << d << endl; + + if (a >= 0 && b >= 0) { + return 0; // AC + } + else { + cerr << 0.5; + return 2; // PARTIAL + } + } + else { + cout << \"a + b = \" << a + b << \" != \" << n << endl; + return 1; // WA + } + } + """ | highlight('cpp')}}
-

3. Interactive (CPP)

-
-

+

3. Interactive (CPP)

+
+

Để sử dụng chức năng này, cần viết một chương trình C++ pass vào 2 arguments input_file answer_file tương ứng file input và đáp án (nếu cần thiết). -

-

- Để test chương trình trên máy tính với tư cách thí sinh, có thể dùng lệnh như sau (Windows): -

+      

+

+ Để test chương trình trên máy tính với tư cách thí sinh, có thể dùng lệnh như sau (Windows): +

 main.exe [input_file] [answer_file]
- hoặc thay bằng ./main trên Linux/MacOS. -

-

Return:

-

- Chương trình trả về giá trị: -

    -
  • 0 nếu AC (100% điểm)
  • -
  • 1 nếu WA (0 điểm)
  • -
  • 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC.
  • -
- Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng. -

+ hoặc thay bằng ./main trên Linux/MacOS. +

+

Return:

+

+ Chương trình trả về giá trị: +

    +
  • 0 nếu AC (100% điểm)
  • +
  • 1 nếu WA (0 điểm)
  • +
  • 2 nếu điểm thành phần. Khi đó cần in ra stderr một số thực trong đoạn [0, 1] thể hiện cho tỷ lệ điểm. Nếu điểm < 1 thì hiển thị WA, điểm = 1 thì hiển thị AC.
  • +
+ Thông tin được in ra trong stderr (bằng cerr) sẽ là feedback hiển thị cho người dùng. +

-

Ví dụ:

-

Chương trình sau dùng để chấm bài toán guessgame: Người chơi phải tìm 1 số bí mật n (n chứa trong file input). Mỗi lần họ được hỏi một số x, và chương trình sẽ trả về "SMALLER", "BIGGER" hoặc "HOLA" dựa trên giá trị của n và x. Cần tìm ra n sau không quá 31 câu hỏi.

-{{ -""" -#include -using namespace std; +

Ví dụ:

+

Chương trình sau dùng để chấm bài toán guessgame: Người chơi phải tìm 1 số bí mật n (n chứa trong file input). Mỗi lần họ được hỏi một số x, và chương trình sẽ trả về "SMALLER", "BIGGER" hoặc "HOLA" dựa trên giá trị của n và x. Cần tìm ra n sau không quá 31 câu hỏi.

+ {{ + """ + #include + using namespace std; -void quit(string reason) { - cerr << reason << endl; - exit(1); -} + void quit(string reason) { + cerr << reason << endl; + exit(1); + } -void read(long long& guess) { - if (!(cin >> guess)) exit(1); // Nếu không có dòng này, chương trình sẽ chờ vô hạn - if (guess < 1 || guess > 2e9) exit(1); -} + void read(long long& guess) { + if (!(cin >> guess)) exit(1); // Nếu không có dòng này, chương trình sẽ chờ vô hạn + if (guess < 1 || guess > 2e9) exit(1); + } -int main(int argc, char *argv[]) { - ifstream inp(argv[1]); - int N, guesses = 0; - long long guess; - inp >> N; + int main(int argc, char *argv[]) { + ifstream inp(argv[1]); + int N, guesses = 0; + long long guess; + inp >> N; - while (guess != N && guesses <= 31) { + while (guess != N && guesses <= 31) { read(guess); if (guess == N) { - cout << \"HOLA\" << endl; + cout << \"HOLA\" << endl; } else if (guess > N) { - cout << \"SMALLER\" << endl; + cout << \"SMALLER\" << endl; } else { - cout << \"BIGGER\" << endl; + cout << \"BIGGER\" << endl; } guesses++; - } - cerr << \"Number of used guesses: \" << guesses << endl; - if (guesses <= 31) + } + cerr << \"Number of used guesses: \" << guesses << endl; + if (guesses <= 31) return 0; // AC - else { + else { cerr << \"Used too many guesses\" << endl; return 1; // WA - } -} -""" | highlight('cpp')}} -
+ } + } + """ | highlight('cpp')}} +
{% endblock %} \ No newline at end of file diff --git a/templates/actionbar/list.html b/templates/actionbar/list.html index 06d6e29..24d07dc 100644 --- a/templates/actionbar/list.html +++ b/templates/actionbar/list.html @@ -1,51 +1,51 @@ {% set logged_in = request.user.is_authenticated %} {% set profile = request.profile if logged_in else None %} {% if logged_in %} -{% if include_hr %}
{% endif %} -
+ {% if include_hr %}
{% endif %} +
- - - - + + + + {% if not hide_actionbar_comment %} - + - - {{_("Comment")}} {% if comment_list.count() %} ({{comment_list.count()}}) {% endif %} + + {{_("Comment")}} {% if comment_list.count() %} ({{comment_list.count()}}) {% endif %} - + {% endif %} - - - {{_("Bookmark")}} - + + + {{_("Bookmark")}} + - - - {{_("Share")}} - + + + {{_("Share")}} + {% if actionbar_report_url %} - - - - {{_("Report")}} - - + + + + {{_("Report")}} + + {% endif %} -
+
{% endif %} \ No newline at end of file diff --git a/templates/actionbar/media-css.html b/templates/actionbar/media-css.html index 7e56f59..0ad2a51 100644 --- a/templates/actionbar/media-css.html +++ b/templates/actionbar/media-css.html @@ -1,7 +1,7 @@ diff --git a/templates/actionbar/media-js.html b/templates/actionbar/media-js.html index 1e3b083..78397e4 100644 --- a/templates/actionbar/media-js.html +++ b/templates/actionbar/media-js.html @@ -1,124 +1,124 @@ {% compress js %} - + } + + function ajax_bookmark(url, id, on_success) { + return $.ajax({ + url: url, + type: 'POST', + data: { + id: id + }, + success: function (data, textStatus, jqXHR) { + if (typeof on_success !== 'undefined') + on_success(); + }, + error: function (data, textStatus, jqXHR) { + alert('Could not bookmark: ' + data.responseText); + } + }); + } + + window.bookmark = function(id) { + var $bookmark = $('#bookmark-button-' + id); + if ($bookmark.hasClass('bookmarked')) { + ajax_bookmark('{{ url('undobookmark') }}', id, function () { + $bookmark.removeClass('bookmarked'); + }); + } else { + ajax_bookmark('{{ url('dobookmark') }}', id, function () { + if ($bookmark.hasClass('bookmarked')) + $bookmark.removeClass('bookmarked'); + $bookmark.addClass('bookmarked'); + }); + } + } + + + var get_$votes = function (id) { + var $post = $('#page-vote-' + id); + return { + upvote: $('#like-button-' + id), + downvote: $('#dislike-button-' + id), + }; + }; + + window.pagevote_upvote = function (id) { + var $votes = get_$votes(id); + if ($votes.upvote.hasClass('voted')) { + ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () { + $votes.upvote.removeClass('voted'); + }); + } + else { + var delta = 1; + if ($votes.downvote.hasClass('voted')) { + delta = 2; + } + for (let i = 0; i < delta; i++) { + ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () { + if ($votes.downvote.hasClass('voted')) + $votes.downvote.removeClass('voted'); + $votes.upvote.addClass('voted'); + }); + } + } + }; + + window.pagevote_downvote = function (id) { + var $votes = get_$votes(id); + if ($votes.downvote.hasClass('voted')) { + ajax_vote('{{ url('pagevote_upvote') }}', id, 1, function () { + $votes.downvote.removeClass('voted'); + }); + } + else { + var delta = -1; + if ($votes.upvote.hasClass('voted')) { + delta = -2; + } + for (let i = 0; i > delta; i--) { + ajax_vote('{{ url('pagevote_downvote') }}', id, -1, function () { + if ($votes.upvote.hasClass('voted')) + $votes.upvote.removeClass('voted'); + $votes.downvote.addClass('voted'); + }); + } + } + }; + $(".actionbar-share").click( function() { + link = $(this).attr("share-url") || window.location.href; + navigator.clipboard + .writeText(link) + .then(() => { + showTooltip(this, "Copied link", 'n'); + }); + }); + + $('.actionbar-comment').on('click', function() { + if ($('#comment-announcement').length) { + $('#comment-announcement').click(); + } + $('#write-comment').click(); + }) + }); + {% endcompress %} \ No newline at end of file diff --git a/templates/admin/auth/user/change_form.html b/templates/admin/auth/user/change_form.html index 207ab84..d5e581e 100644 --- a/templates/admin/auth/user/change_form.html +++ b/templates/admin/auth/user/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/contest/change_form.html b/templates/admin/judge/contest/change_form.html index 7f540f6..5c94aa4 100644 --- a/templates/admin/judge/contest/change_form.html +++ b/templates/admin/judge/contest/change_form.html @@ -2,22 +2,22 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original and original.is_rated and original.ended and perms.judge.contest_rating %} - - {% endif %} + {% if original and original.is_rated and original.ended and perms.judge.contest_rating %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/contest/change_list.html b/templates/admin/judge/contest/change_list.html index ae409fc..ec5c05d 100644 --- a/templates/admin/judge/contest/change_list.html +++ b/templates/admin/judge/contest/change_list.html @@ -2,12 +2,12 @@ {% load i18n %} {% block object-tools-items %} - {{ block.super }} - {% if not is_popup and perms.judge.contest_rating %} -
  • - - {% trans "Rate all ratable contests" %} - -
  • - {% endif %} + {{ block.super }} + {% if not is_popup and perms.judge.contest_rating %} +
  • + + {% trans "Rate all ratable contests" %} + +
  • + {% endif %} {% endblock %} diff --git a/templates/admin/judge/judge/change_form.html b/templates/admin/judge/judge/change_form.html index f477768..754c31d 100644 --- a/templates/admin/judge/judge/change_form.html +++ b/templates/admin/judge/judge/change_form.html @@ -2,25 +2,25 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - - {% endif %} + {% if original %} + + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/problem/change_form.html b/templates/admin/judge/problem/change_form.html index 8731e7f..c5792c1 100644 --- a/templates/admin/judge/problem/change_form.html +++ b/templates/admin/judge/problem/change_form.html @@ -2,24 +2,24 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/profile/change_form.html b/templates/admin/judge/profile/change_form.html index 9372383..0ac5f56 100644 --- a/templates/admin/judge/profile/change_form.html +++ b/templates/admin/judge/profile/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} diff --git a/templates/admin/judge/submission/change_form.html b/templates/admin/judge/submission/change_form.html index 6f3c5b7..dcb49c8 100644 --- a/templates/admin/judge/submission/change_form.html +++ b/templates/admin/judge/submission/change_form.html @@ -2,19 +2,19 @@ {% load i18n %} {% block extrahead %}{{ block.super }} - + {% endblock extrahead %} {% block after_field_sets %}{{ block.super }} - {% if original %} - - {% endif %} + {% if original %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 34b6cc9..f3e23bc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,13 +1,13 @@ - + {% block title %}{{ title }} - {{ SITE_LONG_NAME }}{% endblock %} {% if misc_config.meta_keywords %} - + {% endif %} {% if meta_description %} - + {% endif %} @@ -30,14 +30,14 @@ {# Chrome 39 for Android colour #} {% if og_image %} - + {% endif %} {% block og_title %}{% endblock %} + content="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}"> {% if meta_description %} - + {% endif %} {% block meta %}{% endblock %} {% if not INLINE_FONTAWESOME %} - + {% endif %} {% compress css %} - - {% if PYGMENT_THEME %} - - {% endif %}{% if INLINE_FONTAWESOME %} + + {% if PYGMENT_THEME %} + + {% endif %}{% if INLINE_FONTAWESOME %} {% endif %} - - - - + + + + {% endcompress %} + href="{{ DMOJ_SCHEME }}://{{ DMOJ_CANONICAL|default(site.domain) }}{{ request.get_full_path() }}"> {% if request.user.is_impersonate %} - + {% endif %} {% block media %}{% endblock %} {% if use_darkmode %} - {% compress css %} - - - {% endcompress %} + {% compress css %} + + + {% endcompress %} {% endif %} {% if not INLINE_JQUERY %} - + {% endif %} {% compress js %} - - {% if INLINE_JQUERY %} - - {% endif %} - - - - - - {% include "extra_js.html" %} - - - + + {% if INLINE_JQUERY %} + + {% endif %} + + + + + + {% include "extra_js.html" %} + + + {% endcompress %} {% block js_media %}{% endblock %} {% if request.in_contest %} - + + if (localStorage.getItem("contest_timer_position")) { + data = localStorage.getItem("contest_timer_position").split(":"); + $("#contest-info").css({ + left: data[0], + top: data[1] + }); + } + + $("#contest-info").show(); + + $("#contest-info-toggle").on('click', function() { + $.post("{{url('contest_mode_ajax')}}", function() { + window.location.reload(); + }) + }); + + $(document).mousemove(function (e) { + x_pos = e.screenX; + y_pos = e.screenY; + + if (selected !== null) { + left_px = (x_pos - x_elem); + top_px = (y_pos - y_elem); + left_px = Math.max(Math.min(left_px, window.innerWidth), 0) / window.innerWidth * 100 + '%'; + top_px = Math.max(Math.min(top_px, window.innerHeight), 0) / window.innerHeight * 100 + '%'; + localStorage.setItem("contest_timer_position", left_px + ":" + top_px); + + selected.css({ + left: left_px, + top: top_px + }); + } + }); + + $(document).mouseup(function () { + selected = null; + }) + }); + {% endif %} {% if request.user.is_authenticated %} - + {% else %} - + {% endif %} {% if misc_config.analytics %} - {{ misc_config.analytics|safe }} + {{ misc_config.analytics|safe }} {% endif %} {# Don't run userscript since it may be malicious #} {% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %} - + {% endif %} - - - - + + + + - - - + {% if request.in_contest %} + -{% endif %} -
    -
    + {% endif %} +
    + -
    -
    + +
    +
    {% block title_row %} -

    - {% block content_title %} - {% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %} - {% endblock %} -

    +

    + {% block content_title %} + {% if content_title %}{{ content_title }}{% else %}{{ title }}{% endif %} + {% endblock %} +

    {% endblock %} {% block header %}{% endblock %} {% block title_ruler %} -
    +
    {% endblock %}
    {% block body %}{% endblock %}
    -
    +
    - {% if i18n_config.announcement %} + {% if i18n_config.announcement %}
    {{ i18n_config.announcement|safe }}
    - {% endif %} + {% endif %} - {% block bodyend %}{% endblock %} - {% block footer %} -
    - + {% block bodyend %}{% endblock %} + {% block footer %} +
    +
    proudly powered by DMOJ | developed by LQDJudge team | {% if i18n_config.footer %} - {{ i18n_config.footer|safe }} | + {{ i18n_config.footer|safe }} | {% endif %}
    - {% csrf_token %} - - + -
    -
    -
    - {% endblock %} -
    + + + + + {% endblock %} +
    - + diff --git a/templates/blog/blog.html b/templates/blog/blog.html index 4b1abcd..fa97dac 100644 --- a/templates/blog/blog.html +++ b/templates/blog/blog.html @@ -1,13 +1,13 @@ {% extends "base.html" %} {% block js_media %} - {% include "comments/media-js.html" %} - {% include "actionbar/media-js.html" %} + {% include "comments/media-js.html" %} + {% include "actionbar/media-js.html" %} {% endblock %} {% block media %} - {% include "comments/media-css.html" %} - {% include "actionbar/media-css.html" %} + {% include "comments/media-css.html" %} + {% include "actionbar/media-css.html" %} {% endblock %} {% block title_row %} @@ -17,41 +17,41 @@ {% endblock %} {% block body %} -
    -
    {{ title }}
    -
    - {% with authors=post.authors.all() %} - {% if authors %} - - {% endif %} - {% endwith %} - - {% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %} - - {% if post.is_editable_by(request.user) %} - [{{ _('Edit') }}] - {% endif %} - {% if valid_user_to_show_edit %} - {% for org in valid_org_to_show_edit %} - [{{ _('Edit in') }} {{org.slug}}] - {% endfor %} - {% endif %} -
    -
    - {% cache 86400 'post_content' post.id MATH_ENGINE %} - {{ post.content|markdown|reference|str|safe}} - {% endcache %} -
    - {% include "actionbar/list.html" %} +
    +
    {{ title }}
    +
    + {% with authors=post.authors.all() %} + {% if authors %} + + {% endif %} + {% endwith %} + + {% trans time=post.publish_on|date(_("N j, Y, g:i a")) %} posted on {{ time }}{% endtrans %} + + {% if post.is_editable_by(request.user) %} + [{{ _('Edit') }}] + {% endif %} + {% if valid_user_to_show_edit %} + {% for org in valid_org_to_show_edit %} + [{{ _('Edit in') }} {{org.slug}}] + {% endfor %} + {% endif %}
    -
    - {% include "comments/list.html" %} +
    + {% cache 86400 'post_content' post.id MATH_ENGINE %} + {{ post.content|markdown|reference|str|safe}} + {% endcache %} +
    + {% include "actionbar/list.html" %} +
    +
    + {% include "comments/list.html" %} {% endblock %} {% block bodyend %} - {{ super() }} - {% if REQUIRE_JAX %} - {% include "mathjax-load.html" %} - {% endif %} - {% include "comments/math.html" %} + {{ super() }} + {% if REQUIRE_JAX %} + {% include "mathjax-load.html" %} + {% endif %} + {% include "comments/math.html" %} {% endblock %} \ No newline at end of file diff --git a/templates/blog/content.html b/templates/blog/content.html index 506271e..b9a23fa 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -1,52 +1,52 @@
    -
    - - {% with authors=post.authors.all() %} - {%- if authors -%} - - - {%- endif -%} - {% endwith %} - • - {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} - {%- if post.sticky %} • - {% endif -%} - {% if post.is_organization_private and show_organization_private_icon %} - • - - {% for org in post.organizations.all() %} - - - {{ org.name }} - - - {% endfor %} - - {% endif %} +
    + + {% with authors=post.authors.all() %} + {%- if authors -%} + + + {%- endif -%} + {% endwith %} + • + {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} + {%- if post.sticky %} • + {% endif -%} + {% if post.is_organization_private and show_organization_private_icon %} + • + + {% for org in post.organizations.all() %} + + + {{ org.name }} + + + {% endfor %} - - - - - {{- post_comment_counts[post.id] or 0 -}} - - + {% endif %} + + + + + + {{- post_comment_counts[post.id] or 0 -}} + + +
    +

    + {{ post.title }} +

    +
    +
    + {% cache 86400 'post_summary' post.id %} + {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} + {% endcache %}
    -

    - {{ post.title }} -

    -
    -
    - {% cache 86400 'post_summary' post.id %} - {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} - {% endcache %} -
    - {% set pagevote = post.pagevote %} - {% set bookmark = post.bookmark %} - {% set hide_actionbar_comment = True %} - {% set include_hr = True %} - {% set share_url = request.build_absolute_uri(post.get_absolute_url()) %} - {% include "actionbar/list.html" %} -
    + {% set pagevote = post.pagevote %} + {% set bookmark = post.bookmark %} + {% set hide_actionbar_comment = True %} + {% set include_hr = True %} + {% set share_url = request.build_absolute_uri(post.get_absolute_url()) %} + {% include "actionbar/list.html" %} +
    \ No newline at end of file diff --git a/templates/blog/dashboard.html b/templates/blog/dashboard.html index 7ec50e4..b34d406 100644 --- a/templates/blog/dashboard.html +++ b/templates/blog/dashboard.html @@ -1,34 +1,34 @@