Initial subdomain implementation
This commit is contained in:
parent
dea24f7f71
commit
1628e63084
17 changed files with 194 additions and 46 deletions
|
@ -262,6 +262,7 @@ MIDDLEWARE = (
|
||||||
"judge.middleware.DMOJImpersonationMiddleware",
|
"judge.middleware.DMOJImpersonationMiddleware",
|
||||||
"judge.middleware.ContestMiddleware",
|
"judge.middleware.ContestMiddleware",
|
||||||
"judge.middleware.DarkModeMiddleware",
|
"judge.middleware.DarkModeMiddleware",
|
||||||
|
"judge.middleware.SubdomainMiddleware",
|
||||||
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
|
||||||
"judge.social_auth.SocialAuthExceptionMiddleware",
|
"judge.social_auth.SocialAuthExceptionMiddleware",
|
||||||
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
|
"django.contrib.redirects.middleware.RedirectFallbackMiddleware",
|
||||||
|
|
|
@ -2,6 +2,10 @@ from django.conf import settings
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.urls import Resolver404, resolve, reverse
|
from django.urls import Resolver404, resolve, reverse
|
||||||
from django.utils.http import urlquote
|
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:
|
class ShortCircuitMiddleware:
|
||||||
|
@ -82,3 +86,30 @@ class DarkModeMiddleware(object):
|
||||||
reverse("toggle_darkmode") + "?next=" + urlquote(request.path)
|
reverse("toggle_darkmode") + "?next=" + urlquote(request.path)
|
||||||
)
|
)
|
||||||
return self.get_response(request)
|
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)
|
||||||
|
|
36
judge/migrations/0145_alter_organization_slug.py
Normal file
36
judge/migrations/0145_alter_organization_slug.py
Normal file
|
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -71,7 +71,7 @@ class Comment(MPTTModel):
|
||||||
order_insertion_by = ["-time"]
|
order_insertion_by = ["-time"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def most_recent(cls, user, n, batch=None):
|
def most_recent(cls, user, n, batch=None, organization=None):
|
||||||
queryset = (
|
queryset = (
|
||||||
cls.objects.filter(hidden=False)
|
cls.objects.filter(hidden=False)
|
||||||
.select_related("author__user")
|
.select_related("author__user")
|
||||||
|
@ -79,6 +79,9 @@ class Comment(MPTTModel):
|
||||||
.order_by("-id")
|
.order_by("-id")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if organization:
|
||||||
|
queryset = queryset.filter(author__in=organization.members.all())
|
||||||
|
|
||||||
problem_access = CacheDict(
|
problem_access = CacheDict(
|
||||||
lambda code: Problem.objects.get(code=code).is_accessible_by(user)
|
lambda code: Problem.objects.get(code=code).is_accessible_by(user)
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Organization(models.Model):
|
||||||
max_length=128,
|
max_length=128,
|
||||||
verbose_name=_("organization slug"),
|
verbose_name=_("organization slug"),
|
||||||
help_text=_("Organization name shown in URL"),
|
help_text=_("Organization name shown in URL"),
|
||||||
|
unique=True,
|
||||||
)
|
)
|
||||||
short_name = models.CharField(
|
short_name = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
|
@ -339,6 +340,16 @@ class Profile(models.Model):
|
||||||
ret.add(self.username)
|
ret.add(self.username)
|
||||||
return ret
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
("test_site", "Shows in-progress development stuff"),
|
("test_site", "Shows in-progress development stuff"),
|
||||||
|
|
|
@ -76,6 +76,10 @@ class FeedView(ListView):
|
||||||
.filter(is_visible=True)
|
.filter(is_visible=True)
|
||||||
.order_by("start_time")
|
.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(
|
context["current_contests"] = visible_contests.filter(
|
||||||
start_time__lte=now, end_time__gt=now
|
start_time__lte=now, end_time__gt=now
|
||||||
|
@ -84,10 +88,14 @@ class FeedView(ListView):
|
||||||
context[
|
context[
|
||||||
"recent_organizations"
|
"recent_organizations"
|
||||||
] = OrganizationProfile.get_most_recent_organizations(self.request.profile)
|
] = 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"
|
"-rating"
|
||||||
)[:10]
|
)[: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"
|
"-performance_points"
|
||||||
)[:10]
|
)[:10]
|
||||||
|
|
||||||
|
@ -108,6 +116,8 @@ class PostList(FeedView, PageVoteListView, BookMarkListView):
|
||||||
filter = Q(is_organization_private=False)
|
filter = Q(is_organization_private=False)
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
filter |= Q(organizations__in=self.request.profile.organizations.all())
|
filter |= Q(organizations__in=self.request.profile.organizations.all())
|
||||||
|
if self.request.organization:
|
||||||
|
filter &= Q(organizations=self.request.organization)
|
||||||
queryset = queryset.filter(filter)
|
queryset = queryset.filter(filter)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
@ -184,7 +194,9 @@ class CommentFeed(FeedView):
|
||||||
paginate_by = 50
|
paginate_by = 50
|
||||||
|
|
||||||
def get_queryset(self):
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(CommentFeed, self).get_context_data(**kwargs)
|
context = super(CommentFeed, self).get_context_data(**kwargs)
|
||||||
|
|
|
@ -179,6 +179,8 @@ class ContestList(
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(key__icontains=query) | Q(name__icontains=query)
|
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:
|
if self.org_query:
|
||||||
queryset = queryset.filter(organizations__in=self.org_query)
|
queryset = queryset.filter(organizations__in=self.org_query)
|
||||||
|
|
||||||
|
@ -404,6 +406,15 @@ class ContestDetail(
|
||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.object.name
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ContestDetail, self).get_context_data(**kwargs)
|
context = super(ContestDetail, self).get_context_data(**kwargs)
|
||||||
context["contest_problems"] = (
|
context["contest_problems"] = (
|
||||||
|
@ -421,6 +432,7 @@ class ContestDetail(
|
||||||
)
|
)
|
||||||
.add_i18n_name(self.request.LANGUAGE_CODE)
|
.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
)
|
)
|
||||||
|
context["editable_organizations"] = self.get_editable_organizations()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ from django.views.generic.detail import (
|
||||||
SingleObjectTemplateResponseMixin,
|
SingleObjectTemplateResponseMixin,
|
||||||
)
|
)
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
from reversion import revisions
|
from reversion import revisions
|
||||||
|
|
||||||
from judge.forms import (
|
from judge.forms import (
|
||||||
|
@ -68,12 +69,13 @@ from judge.utils.views import (
|
||||||
DiggPaginatorMixin,
|
DiggPaginatorMixin,
|
||||||
)
|
)
|
||||||
from judge.utils.problems import user_attempted_ids, user_completed_ids
|
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.contests import ContestList
|
||||||
from judge.views.submission import AllSubmissions, SubmissionsListBase
|
from judge.views.submission import AllSubmissions, SubmissionsListBase
|
||||||
from judge.views.pagevote import PageVoteListView
|
from judge.views.pagevote import PageVoteListView
|
||||||
from judge.views.bookmark import BookMarkListView
|
from judge.views.bookmark import BookMarkListView
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"OrganizationList",
|
"OrganizationList",
|
||||||
"OrganizationHome",
|
"OrganizationHome",
|
||||||
|
@ -96,14 +98,9 @@ class OrganizationBase(object):
|
||||||
def can_edit_organization(self, org=None):
|
def can_edit_organization(self, org=None):
|
||||||
if org is None:
|
if org is None:
|
||||||
org = self.object
|
org = self.object
|
||||||
if not self.request.user.is_authenticated:
|
if self.request.profile:
|
||||||
return False
|
return self.request.profile.can_edit_organization(org)
|
||||||
profile_id = self.request.profile.id
|
return False
|
||||||
return (
|
|
||||||
org.admins.filter(id=profile_id).exists()
|
|
||||||
or org.registrant_id == profile_id
|
|
||||||
or self.request.user.is_superuser
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_member(self, org=None):
|
def is_member(self, org=None):
|
||||||
if org is None:
|
if org is None:
|
||||||
|
@ -293,6 +290,9 @@ class OrganizationHome(OrganizationDetailView, PageVoteListView, BookMarkListVie
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(OrganizationHome, self).get_context_data(**kwargs)
|
context = super(OrganizationHome, self).get_context_data(**kwargs)
|
||||||
context["title"] = self.object.name
|
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["posts"], context["page_obj"] = self.get_posts_and_page_obj()
|
||||||
context = self.add_pagevote_context_data(context, "posts")
|
context = self.add_pagevote_context_data(context, "posts")
|
||||||
context = self.add_bookmark_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):
|
class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
|
||||||
template_name = "organization/problems.html"
|
template_name = "organization/problems.html"
|
||||||
|
filter_organization = True
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.org_query = [self.organization_id]
|
self.org_query = [self.organization_id]
|
||||||
|
@ -387,17 +388,6 @@ class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemL
|
||||||
self.setup_problem_list(request)
|
self.setup_problem_list(request)
|
||||||
return super().get(request, *args, **kwargs)
|
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):
|
def get_completed_problems(self):
|
||||||
return user_completed_ids(self.profile) if self.profile is not None else ()
|
return user_completed_ids(self.profile) if self.profile is not None else ()
|
||||||
|
|
||||||
|
@ -478,10 +468,11 @@ class OrganizationSubmissions(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_queryset(self):
|
def _get_queryset(self):
|
||||||
|
problems = get_problems_in_organization(self.request, self.organization)
|
||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
._get_entire_queryset()
|
._get_entire_queryset()
|
||||||
.filter(contest_object__organizations=self.organization)
|
.filter(user__organizations=self.organization, problem__in=problems)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
|
@ -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):
|
class ProblemMixin(object):
|
||||||
model = Problem
|
model = Problem
|
||||||
slug_url_kwarg = "problem"
|
slug_url_kwarg = "problem"
|
||||||
|
@ -145,10 +153,13 @@ class SolvedProblemMixin(object):
|
||||||
else:
|
else:
|
||||||
return user_attempted_ids(self.profile) if self.profile is not None 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:
|
if self.in_contest or not self.profile:
|
||||||
return ()
|
return ()
|
||||||
result = list(user_attempted_ids(self.profile).values())
|
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"])
|
result = sorted(result, key=lambda d: -d["last_submission"])
|
||||||
if limit:
|
if limit:
|
||||||
result = result[:limit]
|
result = result[:limit]
|
||||||
|
@ -454,6 +465,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
||||||
default_sort = "-date"
|
default_sort = "-date"
|
||||||
first_page_href = None
|
first_page_href = None
|
||||||
|
filter_organization = False
|
||||||
|
|
||||||
def get_paginator(
|
def get_paginator(
|
||||||
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
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")
|
user=self.profile, points=F("problem__points")
|
||||||
).values_list("problem__id", flat=True)
|
).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:
|
if self.org_query:
|
||||||
self.org_query = self.get_org_query(self.org_query)
|
self.org_query = self.get_org_query(self.org_query)
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
|
@ -652,6 +666,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(ProblemList, self).get_context_data(**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["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["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)
|
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["search_query"] = self.search_query
|
||||||
context["completed_problem_ids"] = self.get_completed_problems()
|
context["completed_problem_ids"] = self.get_completed_problems()
|
||||||
context["attempted_problems"] = self.get_attempted_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["page_type"] = "list"
|
||||||
context.update(self.get_sort_paginate_context())
|
context.update(self.get_sort_paginate_context())
|
||||||
if not self.in_contest:
|
if not self.in_contest:
|
||||||
|
|
|
@ -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 DiggPaginatorMixin
|
||||||
from judge.utils.views import TitleMixin
|
from judge.utils.views import TitleMixin
|
||||||
from judge.utils.timedelta import nice_repr
|
from judge.utils.timedelta import nice_repr
|
||||||
|
from judge.views.problem import get_problems_in_organization
|
||||||
|
|
||||||
|
|
||||||
MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000
|
MAX_NUMBER_OF_QUERY_SUBMISSIONS = 50000
|
||||||
|
@ -414,6 +415,13 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
def _get_queryset(self):
|
def _get_queryset(self):
|
||||||
queryset = self._get_entire_queryset()
|
queryset = self._get_entire_queryset()
|
||||||
if not self.in_contest:
|
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(
|
join_sql_subquery(
|
||||||
queryset,
|
queryset,
|
||||||
subquery=str(
|
subquery=str(
|
||||||
|
@ -785,7 +793,12 @@ class AllSubmissions(SubmissionsListBase):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def _get_result_data(self):
|
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()
|
return super(AllSubmissions, self)._get_result_data()
|
||||||
|
|
||||||
key = "global_submission_result_data"
|
key = "global_submission_result_data"
|
||||||
|
|
|
@ -454,7 +454,7 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
ret = (
|
queryset = (
|
||||||
Profile.objects.filter(is_unlisted=False)
|
Profile.objects.filter(is_unlisted=False)
|
||||||
.order_by(self.order, "id")
|
.order_by(self.order, "id")
|
||||||
.select_related("user")
|
.select_related("user")
|
||||||
|
@ -467,11 +467,13 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
|
||||||
"problem_count",
|
"problem_count",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if self.request.organization:
|
||||||
|
queryset = queryset.filter(organizations=self.request.organization)
|
||||||
if (self.request.GET.get("friend") == "true") and self.request.profile:
|
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
|
self.filter_friend = True
|
||||||
return ret
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserList, self).get_context_data(**kwargs)
|
context = super(UserList, self).get_context_data(**kwargs)
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
{% if request.organization %}
|
||||||
|
{% cache 3600 'organization_html' request.organization.id MATH_ENGINE %}
|
||||||
|
{{ request.organization.about|markdown|reference|str|safe }}
|
||||||
|
{% endcache %}
|
||||||
|
{% else %}
|
||||||
<h4>
|
<h4>
|
||||||
<a target="_blank" href="">LQDOJ (Le Quy Don Online Judge)</a> 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ở <a target="_blank" href="https://dmoj.ca/">DMOJ</a>. Đượ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 <a target="_blank" href="http://thpt-lequydon-danang.edu.vn/">trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng)</a>, 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 <a target="_blank" href="https://github.com/LQDJudge/online-judge">Github repo chính thức</a>. Mọi ý kiến đóng góp và thắc mắc xin gửi về:
|
<a target="_blank" href="">LQDOJ (Le Quy Don Online Judge)</a> 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ở <a target="_blank" href="https://dmoj.ca/">DMOJ</a>. Đượ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 <a target="_blank" href="http://thpt-lequydon-danang.edu.vn/">trường THPT chuyên Lê Quý Đôn (TP Đà Nẵng)</a>, 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 <a target="_blank" href="https://github.com/LQDJudge/online-judge">Github repo chính thức</a>. Mọi ý kiến đóng góp và thắc mắc xin gửi về:
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -9,5 +14,6 @@
|
||||||
<li><a target="_blank" href="https://www.facebook.com/profile.php?id=100011662657075">Lê Phước Định</a> (handle: <span class="rating rate-grandmaster user"><a target="_blank" href="../user/cuom1999">cuom1999</a></span>), email: <a href="mailto:dinh@lqdoj.edu.vn">dinh@lqdoj.edu.vn</a></li>
|
<li><a target="_blank" href="https://www.facebook.com/profile.php?id=100011662657075">Lê Phước Định</a> (handle: <span class="rating rate-grandmaster user"><a target="_blank" href="../user/cuom1999">cuom1999</a></span>), email: <a href="mailto:dinh@lqdoj.edu.vn">dinh@lqdoj.edu.vn</a></li>
|
||||||
<li><a target="_blank" href="https://www.facebook.com/doannguyenthanhluong">Đoàn Nguyễn Thành Lương</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/CaiWinDao">CaiWinDao</a></span>), email: <a href="mailto:luong@lqdoj.edu.vn">luong@lqdoj.edu.vn</a></li>
|
<li><a target="_blank" href="https://www.facebook.com/doannguyenthanhluong">Đoàn Nguyễn Thành Lương</a> (handle: <span class="rating rate-master user"><a target="_blank" href="../user/CaiWinDao">CaiWinDao</a></span>), email: <a href="mailto:luong@lqdoj.edu.vn">luong@lqdoj.edu.vn</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</h4>
|
</h4>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -31,7 +31,7 @@
|
||||||
{# Allow users to leave the virtual contest #}
|
{# Allow users to leave the virtual contest #}
|
||||||
{% if in_contest %}
|
{% if in_contest %}
|
||||||
<form action="{{ url('contest_leave', contest.key) }}" method="post"
|
<form action="{{ url('contest_leave', contest.key) }}" method="post"
|
||||||
class="contest-join-pseudotab btn-midnightblue">
|
class="contest-join-pseudotab btn-red">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="submit" class="leaving-forever" value="{{ _('Leave contest') }}">
|
<input type="submit" class="leaving-forever" value="{{ _('Leave contest') }}">
|
||||||
</form>
|
</form>
|
||||||
|
@ -77,12 +77,19 @@
|
||||||
<input type="submit" class="btn-midnightblue" value="{{ _('Login to participate') }}">
|
<input type="submit" class="btn-midnightblue" value="{{ _('Login to participate') }}">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="content-description">
|
<div class="content-description">
|
||||||
{% cache 3600 'contest_html' contest.id MATH_ENGINE %}
|
{% cache 3600 'contest_html' contest.id MATH_ENGINE %}
|
||||||
{{ contest.description|markdown|reference|str|safe }}
|
{{ contest.description|markdown|reference|str|safe }}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if editable_organizations %}
|
||||||
|
<div>
|
||||||
|
{% for org in editable_organizations %}
|
||||||
|
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if contest.ended or request.user.is_superuser or is_editor or is_tester %}
|
{% if contest.ended or request.user.is_superuser or is_editor or is_tester %}
|
||||||
<hr>
|
<hr>
|
||||||
|
|
|
@ -13,7 +13,14 @@
|
||||||
{% block middle_title %}
|
{% block middle_title %}
|
||||||
<div class="page-title">
|
<div class="page-title">
|
||||||
<div class="tabs" style="border: none;">
|
<div class="tabs" style="border: none;">
|
||||||
<h2><img src="{{logo_override_image}}" style="height: 3rem; vertical-align: middle"> <span>{{title}}</span></h2>
|
<h2><img src="{{logo_override_image}}" style="height: 3rem; vertical-align: middle">
|
||||||
|
{{title}}
|
||||||
|
</h2>
|
||||||
|
{% if is_member %}
|
||||||
|
<div>
|
||||||
|
<a href="{{organization_subdomain}}" target="_blank">(Subdomain)</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<span class="spacer"></span>
|
<span class="spacer"></span>
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{% if not request.in_contest_mode %}
|
{% if not show_contest_mode %}
|
||||||
<div class="left-sidebar">
|
<div class="left-sidebar">
|
||||||
{{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }}
|
{{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }}
|
||||||
{{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }}
|
{{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }}
|
||||||
|
{% if request.user.is_superuser %}
|
||||||
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_problem_changelist'), _('Admin')) }}
|
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_problem_changelist'), _('Admin')) }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -248,13 +248,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block left_sidebar %}
|
{% block left_sidebar %}
|
||||||
{% if not show_contest_mode %}
|
{% include "problem/left-sidebar.html" %}
|
||||||
<div class="left-sidebar">
|
|
||||||
{{ make_tab_item('feed', 'fa fa-pagelines', url('problem_feed'), _('Feed')) }}
|
|
||||||
{{ make_tab_item('list', 'fa fa-list', url('problem_list'), _('List')) }}
|
|
||||||
{{ make_tab_item('admin', 'fa fa-edit', url('admin:judge_problem_changelist'), _('Admin')) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block right_sidebar %}
|
{% block right_sidebar %}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{% if request.in_contest_mode and request.participation.contest.logo_override_image %}
|
{% if request.in_contest_mode and request.participation.contest.logo_override_image %}
|
||||||
<img data-src="{{ request.participation.contest.logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
|
<img src="{{ request.participation.contest.logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
|
||||||
|
{% elif request.organization %}
|
||||||
|
<img src="{{ request.organization.logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
|
||||||
{% elif logo_override_image is defined and logo_override_image %}
|
{% elif logo_override_image is defined and logo_override_image %}
|
||||||
<img data-src="{{ logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
|
<img src="{{ logo_override_image|camo }}" alt="{{ SITE_NAME }}" height="44" style="border: none">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ static('icons/logo.png') }}" alt="{{ SITE_NAME }}" height="44"
|
<img src="{{ static('icons/logo.png') }}" alt="{{ SITE_NAME }}" height="44"
|
||||||
onerror="this.src="{{ static('icons/logo.png') }}"; this.onerror=null" style="border: none">
|
onerror="this.src="{{ static('icons/logo.png') }}"; this.onerror=null" style="border: none">
|
||||||
|
|
Loading…
Reference in a new issue