Reformat using black

This commit is contained in:
cuom1999 2022-05-14 12:57:27 -05:00
parent efee4ad081
commit a87fb49918
221 changed files with 19127 additions and 7310 deletions

View file

@ -5,6 +5,6 @@ class TitledTemplateView(TemplateView):
title = None
def get_context_data(self, **kwargs):
if 'title' not in kwargs and self.title is not None:
kwargs['title'] = self.title
if "title" not in kwargs and self.title is not None:
kwargs["title"] = self.title
return super(TitledTemplateView, self).get_context_data(**kwargs)

View file

@ -3,12 +3,20 @@ from django.utils.translation import gettext as _
def about(request):
return render(request, 'about/about.html', {
'title': _('About'),
})
return render(
request,
"about/about.html",
{
"title": _("About"),
},
)
def custom_checker_sample(request):
return render(request, 'about/custom-checker-sample.html', {
'title': _('Custom Checker Sample'),
})
return render(
request,
"about/custom-checker-sample.html",
{
"title": _("Custom Checker Sample"),
},
)

View file

@ -5,27 +5,40 @@ from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404
from dmoj import settings
from judge.models import Contest, ContestParticipation, ContestTag, Problem, Profile, Submission
from judge.models import (
Contest,
ContestParticipation,
ContestTag,
Problem,
Profile,
Submission,
)
def sane_time_repr(delta):
days = delta.days
hours = delta.seconds / 3600
minutes = (delta.seconds % 3600) / 60
return '%02d:%02d:%02d' % (days, hours, minutes)
return "%02d:%02d:%02d" % (days, hours, minutes)
def api_v1_contest_list(request):
queryset = Contest.get_visible_contests(request.user).prefetch_related(
Prefetch('tags', queryset=ContestTag.objects.only('name'), to_attr='tag_list'))
return JsonResponse({c.key: {
'name': c.name,
'start_time': c.start_time.isoformat(),
'end_time': c.end_time.isoformat(),
'time_limit': c.time_limit and sane_time_repr(c.time_limit),
'labels': list(map(attrgetter('name'), c.tag_list)),
} for c in queryset})
Prefetch("tags", queryset=ContestTag.objects.only("name"), to_attr="tag_list")
)
return JsonResponse(
{
c.key: {
"name": c.name,
"start_time": c.start_time.isoformat(),
"end_time": c.end_time.isoformat(),
"time_limit": c.time_limit and sane_time_repr(c.time_limit),
"labels": list(map(attrgetter("name"), c.tag_list)),
}
for c in queryset
}
)
def api_v1_contest_detail(request, contest):
@ -33,61 +46,87 @@ def api_v1_contest_detail(request, contest):
in_contest = contest.is_in_contest(request.user)
can_see_rankings = contest.can_see_full_scoreboard(request.user)
problems = list(contest.contest_problems.select_related('problem')
.defer('problem__description').order_by('order'))
participations = (contest.users.filter(virtual=0)
.prefetch_related('user__organizations')
.annotate(username=F('user__user__username'))
.order_by('-score', 'cumtime') if can_see_rankings else [])
problems = list(
contest.contest_problems.select_related("problem")
.defer("problem__description")
.order_by("order")
)
participations = (
contest.users.filter(virtual=0)
.prefetch_related("user__organizations")
.annotate(username=F("user__user__username"))
.order_by("-score", "cumtime")
if can_see_rankings
else []
)
can_see_problems = (in_contest or contest.ended or contest.is_editable_by(request.user))
can_see_problems = (
in_contest or contest.ended or contest.is_editable_by(request.user)
)
return JsonResponse({
'time_limit': contest.time_limit and contest.time_limit.total_seconds(),
'start_time': contest.start_time.isoformat(),
'end_time': contest.end_time.isoformat(),
'tags': list(contest.tags.values_list('name', flat=True)),
'is_rated': contest.is_rated,
'rate_all': contest.is_rated and contest.rate_all,
'has_rating': contest.ratings.exists(),
'rating_floor': contest.rating_floor,
'rating_ceiling': contest.rating_ceiling,
'format': {
'name': contest.format_name,
'config': contest.format_config,
},
'problems': [
{
'points': int(problem.points),
'partial': problem.partial,
'name': problem.problem.name,
'code': problem.problem.code,
} for problem in problems] if can_see_problems else [],
'rankings': [
{
'user': participation.username,
'points': participation.score,
'cumtime': participation.cumtime,
'is_disqualified': participation.is_disqualified,
'solutions': contest.format.get_problem_breakdown(participation, problems),
} for participation in participations],
})
return JsonResponse(
{
"time_limit": contest.time_limit and contest.time_limit.total_seconds(),
"start_time": contest.start_time.isoformat(),
"end_time": contest.end_time.isoformat(),
"tags": list(contest.tags.values_list("name", flat=True)),
"is_rated": contest.is_rated,
"rate_all": contest.is_rated and contest.rate_all,
"has_rating": contest.ratings.exists(),
"rating_floor": contest.rating_floor,
"rating_ceiling": contest.rating_ceiling,
"format": {
"name": contest.format_name,
"config": contest.format_config,
},
"problems": [
{
"points": int(problem.points),
"partial": problem.partial,
"name": problem.problem.name,
"code": problem.problem.code,
}
for problem in problems
]
if can_see_problems
else [],
"rankings": [
{
"user": participation.username,
"points": participation.score,
"cumtime": participation.cumtime,
"is_disqualified": participation.is_disqualified,
"solutions": contest.format.get_problem_breakdown(
participation, problems
),
}
for participation in participations
],
}
)
def api_v1_problem_list(request):
queryset = Problem.objects.filter(is_public=True, is_organization_private=False)
if settings.ENABLE_FTS and 'search' in request.GET:
query = ' '.join(request.GET.getlist('search')).strip()
if settings.ENABLE_FTS and "search" in request.GET:
query = " ".join(request.GET.getlist("search")).strip()
if query:
queryset = queryset.search(query)
queryset = queryset.values_list('code', 'points', 'partial', 'name', 'group__full_name')
queryset = queryset.values_list(
"code", "points", "partial", "name", "group__full_name"
)
return JsonResponse({code: {
'points': points,
'partial': partial,
'name': name,
'group': group,
} for code, points, partial, name, group in queryset})
return JsonResponse(
{
code: {
"points": points,
"partial": partial,
"name": name,
"group": group,
}
for code, points, partial, name, group in queryset
}
)
def api_v1_problem_info(request, problem):
@ -95,60 +134,83 @@ def api_v1_problem_info(request, problem):
if not p.is_accessible_by(request.user):
raise Http404()
return JsonResponse({
'name': p.name,
'authors': list(p.authors.values_list('user__username', flat=True)),
'types': list(p.types.values_list('full_name', flat=True)),
'group': p.group.full_name,
'time_limit': p.time_limit,
'memory_limit': p.memory_limit,
'points': p.points,
'partial': p.partial,
'languages': list(p.allowed_languages.values_list('key', flat=True)),
})
return JsonResponse(
{
"name": p.name,
"authors": list(p.authors.values_list("user__username", flat=True)),
"types": list(p.types.values_list("full_name", flat=True)),
"group": p.group.full_name,
"time_limit": p.time_limit,
"memory_limit": p.memory_limit,
"points": p.points,
"partial": p.partial,
"languages": list(p.allowed_languages.values_list("key", flat=True)),
}
)
def api_v1_user_list(request):
queryset = Profile.objects.filter(is_unlisted=False).values_list('user__username', 'points', 'performance_points',
'display_rank')
return JsonResponse({username: {
'points': points,
'performance_points': performance_points,
'rank': rank,
} for username, points, performance_points, rank in queryset})
queryset = Profile.objects.filter(is_unlisted=False).values_list(
"user__username", "points", "performance_points", "display_rank"
)
return JsonResponse(
{
username: {
"points": points,
"performance_points": performance_points,
"rank": rank,
}
for username, points, performance_points, rank in queryset
}
)
def api_v1_user_info(request, user):
profile = get_object_or_404(Profile, user__username=user)
submissions = list(Submission.objects.filter(case_points=F('case_total'), user=profile, problem__is_public=True,
problem__is_organization_private=False)
.values('problem').distinct().values_list('problem__code', flat=True))
submissions = list(
Submission.objects.filter(
case_points=F("case_total"),
user=profile,
problem__is_public=True,
problem__is_organization_private=False,
)
.values("problem")
.distinct()
.values_list("problem__code", flat=True)
)
resp = {
'points': profile.points,
'performance_points': profile.performance_points,
'rank': profile.display_rank,
'solved_problems': submissions,
'organizations': list(profile.organizations.values_list('id', flat=True)),
"points": profile.points,
"performance_points": profile.performance_points,
"rank": profile.display_rank,
"solved_problems": submissions,
"organizations": list(profile.organizations.values_list("id", flat=True)),
}
last_rating = profile.ratings.last()
contest_history = {}
participations = ContestParticipation.objects.filter(user=profile, virtual=0, contest__is_visible=True,
contest__is_private=False,
contest__is_organization_private=False)
participations = ContestParticipation.objects.filter(
user=profile,
virtual=0,
contest__is_visible=True,
contest__is_private=False,
contest__is_organization_private=False,
)
for contest_key, rating, mean, performance in participations.values_list(
'contest__key', 'rating__rating', 'rating__mean', 'rating__performance',
"contest__key",
"rating__rating",
"rating__mean",
"rating__performance",
):
contest_history[contest_key] = {
'rating': rating,
'raw_rating': mean,
'performance': performance,
"rating": rating,
"raw_rating": mean,
"performance": performance,
}
resp['contests'] = {
'current_rating': last_rating.rating if last_rating else None,
'history': contest_history,
resp["contests"] = {
"current_rating": last_rating.rating if last_rating else None,
"history": contest_history,
}
return JsonResponse(resp)
@ -156,14 +218,30 @@ def api_v1_user_info(request, user):
def api_v1_user_submissions(request, user):
profile = get_object_or_404(Profile, user__username=user)
subs = Submission.objects.filter(user=profile, problem__is_public=True, problem__is_organization_private=False)
subs = Submission.objects.filter(
user=profile, problem__is_public=True, problem__is_organization_private=False
)
return JsonResponse({sub['id']: {
'problem': sub['problem__code'],
'time': sub['time'],
'memory': sub['memory'],
'points': sub['points'],
'language': sub['language__key'],
'status': sub['status'],
'result': sub['result'],
} for sub in subs.values('id', 'problem__code', 'time', 'memory', 'points', 'language__key', 'status', 'result')})
return JsonResponse(
{
sub["id"]: {
"problem": sub["problem__code"],
"time": sub["time"],
"memory": sub["memory"],
"points": sub["points"],
"language": sub["language__key"],
"status": sub["status"],
"result": sub["result"],
}
for sub in subs.values(
"id",
"problem__code",
"time",
"memory",
"points",
"language__key",
"status",
"result",
)
}
)

View file

@ -9,7 +9,7 @@ from judge.views.contests import contest_ranking_list
def error(message):
return JsonResponse({'error': message}, status=422)
return JsonResponse({"error": message}, status=422)
def api_v2_user_info(request):
@ -44,9 +44,9 @@ def api_v2_user_info(request):
// ...
]
}
"""
"""
try:
username = request.GET['username']
username = request.GET["username"]
except KeyError:
return error("no username passed")
if not username:
@ -56,66 +56,90 @@ def api_v2_user_info(request):
except Profile.DoesNotExist:
return error("no such user")
last_rating = list(profile.ratings.order_by('-contest__end_time'))
last_rating = list(profile.ratings.order_by("-contest__end_time"))
resp = {
"rank": profile.display_rank,
"organizations": list(profile.organizations.values_list('key', flat=True)),
"organizations": list(profile.organizations.values_list("key", flat=True)),
}
contest_history = []
for participation in (ContestParticipation.objects.filter(user=profile, virtual=0, contest__is_visible=True)
.order_by('-contest__end_time')):
for participation in ContestParticipation.objects.filter(
user=profile, virtual=0, contest__is_visible=True
).order_by("-contest__end_time"):
contest = participation.contest
problems = list(contest.contest_problems.select_related('problem').defer('problem__description')
.order_by('order'))
rank, result = next(filter(lambda data: data[1].user == profile.user,
ranker(contest_ranking_list(contest, problems),
key=attrgetter('points', 'cumtime'))))
problems = list(
contest.contest_problems.select_related("problem")
.defer("problem__description")
.order_by("order")
)
rank, result = next(
filter(
lambda data: data[1].user == profile.user,
ranker(
contest_ranking_list(contest, problems),
key=attrgetter("points", "cumtime"),
),
)
)
contest_history.append({
'contest': {
'code': contest.key,
'name': contest.name,
'tags': list(contest.tags.values_list('name', flat=True)),
'time_limit': contest.time_limit and contest.time_limit.total_seconds(),
'start_time': contest.start_time.isoformat(),
'end_time': contest.end_time.isoformat(),
},
'rank': rank,
'rating': result.participation_rating,
})
contest_history.append(
{
"contest": {
"code": contest.key,
"name": contest.name,
"tags": list(contest.tags.values_list("name", flat=True)),
"time_limit": contest.time_limit
and contest.time_limit.total_seconds(),
"start_time": contest.start_time.isoformat(),
"end_time": contest.end_time.isoformat(),
},
"rank": rank,
"rating": result.participation_rating,
}
)
resp['contests'] = {
resp["contests"] = {
"current_rating": last_rating[0].rating if last_rating else None,
'history': contest_history,
"history": contest_history,
}
solved_problems = []
attempted_problems = []
problem_data = (Submission.objects.filter(points__gt=0, user=profile, problem__is_public=True,
problem__is_organization_private=False)
.annotate(max_pts=Max('points'))
.values_list('max_pts', 'problem__points', 'problem__code')
.distinct())
problem_data = (
Submission.objects.filter(
points__gt=0,
user=profile,
problem__is_public=True,
problem__is_organization_private=False,
)
.annotate(max_pts=Max("points"))
.values_list("max_pts", "problem__points", "problem__code")
.distinct()
)
for awarded_pts, max_pts, problem in problem_data:
if awarded_pts == max_pts:
solved_problems.append(problem)
else:
attempted_problems.append({
'awarded': awarded_pts,
'max': max_pts,
'problem': problem,
})
attempted_problems.append(
{
"awarded": awarded_pts,
"max": max_pts,
"problem": problem,
}
)
resp['problems'] = {
'points': profile.points,
'solved': solved_problems,
'attempted': attempted_problems,
'authored': list(Problem.objects.filter(is_public=True, is_organization_private=False, authors=profile)
.values_list('code', flat=True)),
resp["problems"] = {
"points": profile.points,
"solved": solved_problems,
"attempted": attempted_problems,
"authored": list(
Problem.objects.filter(
is_public=True, is_organization_private=False, authors=profile
).values_list("code", flat=True)
),
}
return JsonResponse(resp)

View file

@ -8,8 +8,17 @@ from django.utils.translation import ugettext as _
from django.views.generic import ListView
from judge.comments import CommentedDetailView
from judge.models import BlogPost, Comment, Contest, Language, Problem, ProblemClarification, Profile, Submission, \
Ticket
from judge.models import (
BlogPost,
Comment,
Contest,
Language,
Problem,
ProblemClarification,
Profile,
Submission,
Ticket,
)
from judge.utils.cachedict import CacheDict
from judge.utils.diggpaginator import DiggPaginator
from judge.utils.problems import user_completed_ids
@ -19,32 +28,44 @@ from judge.utils.views import TitleMixin
# General view for all content list on home feed
class FeedView(ListView):
template_name = 'blog/list.html'
template_name = "blog/list.html"
title = None
def get_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs):
return DiggPaginator(queryset, per_page, body=6, padding=2,
orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs)
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
return DiggPaginator(
queryset,
per_page,
body=6,
padding=2,
orphans=orphans,
allow_empty_first_page=allow_empty_first_page,
**kwargs
)
def get_context_data(self, **kwargs):
context = super(FeedView, self).get_context_data(**kwargs)
context['has_clarifications'] = False
context["has_clarifications"] = False
if self.request.user.is_authenticated:
participation = self.request.profile.current_contest
if participation:
clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all())
context['has_clarifications'] = clarifications.count() > 0
context['clarifications'] = clarifications.order_by('-date')
clarifications = ProblemClarification.objects.filter(
problem__in=participation.contest.problems.all()
)
context["has_clarifications"] = clarifications.count() > 0
context["clarifications"] = clarifications.order_by("-date")
if participation.contest.is_editable_by(self.request.user):
context['can_edit_contest'] = True
context["can_edit_contest"] = True
context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page))
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
context['user_count'] = lazy(Profile.objects.count, int, int)
context['problem_count'] = lazy(Problem.objects.filter(is_public=True).count, int, int)
context['submission_count'] = lazy(Submission.objects.count, int, int)
context['language_count'] = lazy(Language.objects.count, int, int)
context["user_count"] = lazy(Profile.objects.count, int, int)
context["problem_count"] = lazy(
Problem.objects.filter(is_public=True).count, int, int
)
context["submission_count"] = lazy(Submission.objects.count, int, int)
context["language_count"] = lazy(Language.objects.count, int, int)
now = timezone.now()
@ -57,31 +78,44 @@ class FeedView(ListView):
# .annotate(points=Max('points'), latest=Max('date'))
# .order_by('-latest')
# [:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT])
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) \
.order_by('start_time')
context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now)
context['future_contests'] = visible_contests.filter(start_time__gt=now)
visible_contests = (
Contest.get_visible_contests(self.request.user)
.filter(is_visible=True)
.order_by("start_time")
)
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True)
context["current_contests"] = visible_contests.filter(
start_time__lte=now, end_time__gt=now
)
context["future_contests"] = visible_contests.filter(start_time__gt=now)
context['top_rated'] = Profile.objects.filter(is_unlisted=False).order_by('-rating')[:10]
context['top_scorer'] = Profile.objects.filter(is_unlisted=False).order_by('-performance_points')[:10]
visible_contests = Contest.get_visible_contests(self.request.user).filter(
is_visible=True
)
context["top_rated"] = Profile.objects.filter(is_unlisted=False).order_by(
"-rating"
)[:10]
context["top_scorer"] = Profile.objects.filter(is_unlisted=False).order_by(
"-performance_points"
)[:10]
return context
class PostList(FeedView):
model = BlogPost
paginate_by = 10
context_object_name = 'posts'
context_object_name = "posts"
def get_queryset(self):
queryset = BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()) \
.order_by('-sticky', '-publish_on') \
.prefetch_related('authors__user', 'organizations')
if not self.request.user.has_perm('judge.edit_all_post'):
queryset = (
BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now())
.order_by("-sticky", "-publish_on")
.prefetch_related("authors__user", "organizations")
)
if not self.request.user.has_perm("judge.edit_all_post"):
filter = Q(is_organization_private=False)
if self.request.user.is_authenticated:
filter |= Q(organizations__in=self.request.profile.organizations.all())
@ -90,15 +124,20 @@ class PostList(FeedView):
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data(**kwargs)
context['title'] = self.title or _('Page %d of Posts') % context['page_obj'].number
context['first_page_href'] = reverse('home')
context['page_prefix'] = reverse('blog_post_list')
context['page_type'] = 'blog'
context['post_comment_counts'] = {
int(page[2:]): count for page, count in
Comment.objects
.filter(page__in=['b:%d' % post.id for post in context['posts']], hidden=False)
.values_list('page').annotate(count=Count('page')).order_by()
context["title"] = (
self.title or _("Page %d of Posts") % context["page_obj"].number
)
context["first_page_href"] = reverse("home")
context["page_prefix"] = reverse("blog_post_list")
context["page_type"] = "blog"
context["post_comment_counts"] = {
int(page[2:]): count
for page, count in Comment.objects.filter(
page__in=["b:%d" % post.id for post in context["posts"]], hidden=False
)
.values_list("page")
.annotate(count=Count("page"))
.order_by()
}
return context
@ -106,39 +145,49 @@ class PostList(FeedView):
class TicketFeed(FeedView):
model = Ticket
context_object_name = 'tickets'
context_object_name = "tickets"
paginate_by = 30
def get_queryset(self, is_own=True):
profile = self.request.profile
if is_own:
if self.request.user.is_authenticated:
return (Ticket.objects.filter(Q(user=profile) | Q(assignees__in=[profile]), is_open=True).order_by('-id')
.prefetch_related('linked_item').select_related('user__user'))
return (
Ticket.objects.filter(
Q(user=profile) | Q(assignees__in=[profile]), is_open=True
)
.order_by("-id")
.prefetch_related("linked_item")
.select_related("user__user")
)
else:
return []
else:
# Superusers better be staffs, not the spell-casting kind either.
if self.request.user.is_staff:
tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item')
.select_related('user__user'))
tickets = (
Ticket.objects.order_by("-id")
.filter(is_open=True)
.prefetch_related("linked_item")
.select_related("user__user")
)
return filter_visible_tickets(tickets, self.request.user, profile)
else:
return []
def get_context_data(self, **kwargs):
context = super(TicketFeed, self).get_context_data(**kwargs)
context['page_type'] = 'ticket'
context['first_page_href'] = self.request.path
context['page_prefix'] = '?page='
context['title'] = _('Ticket feed')
context["page_type"] = "ticket"
context["first_page_href"] = self.request.path
context["page_prefix"] = "?page="
context["title"] = _("Ticket feed")
return context
class CommentFeed(FeedView):
model = Comment
context_object_name = 'comments'
context_object_name = "comments"
paginate_by = 50
def get_queryset(self):
@ -146,29 +195,29 @@ class CommentFeed(FeedView):
def get_context_data(self, **kwargs):
context = super(CommentFeed, self).get_context_data(**kwargs)
context['page_type'] = 'comment'
context['first_page_href'] = self.request.path
context['page_prefix'] = '?page='
context['title'] = _('Comment feed')
context["page_type"] = "comment"
context["first_page_href"] = self.request.path
context["page_prefix"] = "?page="
context["title"] = _("Comment feed")
return context
class PostView(TitleMixin, CommentedDetailView):
model = BlogPost
pk_url_kwarg = 'id'
context_object_name = 'post'
template_name = 'blog/blog.html'
pk_url_kwarg = "id"
context_object_name = "post"
template_name = "blog/blog.html"
def get_title(self):
return self.object.title
def get_comment_page(self):
return 'b:%s' % self.object.id
return "b:%s" % self.object.id
def get_context_data(self, **kwargs):
context = super(PostView, self).get_context_data(**kwargs)
context['og_image'] = self.object.og_image
context["og_image"] = self.object.og_image
return context
def get_object(self, queryset=None):

View file

@ -4,7 +4,12 @@ from django.core.exceptions import PermissionDenied
from django.db import IntegrityError, transaction
from django.db.models import F
from django.forms.models import ModelForm
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
)
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
@ -18,27 +23,41 @@ from judge.utils.views import TitleMixin
from judge.widgets import MathJaxPagedownWidget
from judge.comments import add_mention_notifications, del_mention_notifications
__all__ = ['upvote_comment', 'downvote_comment', 'CommentEditAjax', 'CommentContent',
'CommentEdit']
__all__ = [
"upvote_comment",
"downvote_comment",
"CommentEditAjax",
"CommentContent",
"CommentEdit",
]
@login_required
def vote_comment(request, delta):
if abs(delta) != 1:
return HttpResponseBadRequest(_('Messing around, are we?'), content_type='text/plain')
return HttpResponseBadRequest(
_("Messing around, are we?"), content_type="text/plain"
)
if request.method != 'POST':
if request.method != "POST":
return HttpResponseForbidden()
if 'id' not in request.POST:
if "id" not in request.POST:
return HttpResponseBadRequest()
if not request.user.is_staff and not request.profile.submission_set.filter(points=F('problem__points')).exists():
return HttpResponseBadRequest(_('You must solve at least one problem before you can vote.'),
content_type='text/plain')
if (
not request.user.is_staff
and not request.profile.submission_set.filter(
points=F("problem__points")
).exists()
):
return HttpResponseBadRequest(
_("You must solve at least one problem before you can vote."),
content_type="text/plain",
)
try:
comment_id = int(request.POST['id'])
comment_id = int(request.POST["id"])
except ValueError:
return HttpResponseBadRequest()
else:
@ -56,18 +75,22 @@ def vote_comment(request, delta):
except IntegrityError:
with LockModel(write=(CommentVote,)):
try:
vote = CommentVote.objects.get(comment_id=comment_id, voter=request.profile)
vote = CommentVote.objects.get(
comment_id=comment_id, voter=request.profile
)
except CommentVote.DoesNotExist:
# We must continue racing in case this is exploited to manipulate votes.
continue
if -vote.score != delta:
return HttpResponseBadRequest(_('You already voted.'), content_type='text/plain')
return HttpResponseBadRequest(
_("You already voted."), content_type="text/plain"
)
vote.delete()
Comment.objects.filter(id=comment_id).update(score=F('score') - vote.score)
Comment.objects.filter(id=comment_id).update(score=F("score") - vote.score)
else:
Comment.objects.filter(id=comment_id).update(score=F('score') + delta)
Comment.objects.filter(id=comment_id).update(score=F("score") + delta)
break
return HttpResponse('success', content_type='text/plain')
return HttpResponse("success", content_type="text/plain")
def upvote_comment(request):
@ -80,26 +103,28 @@ def downvote_comment(request):
class CommentMixin(object):
model = Comment
pk_url_kwarg = 'id'
context_object_name = 'comment'
pk_url_kwarg = "id"
context_object_name = "comment"
class CommentRevisionAjax(CommentMixin, DetailView):
template_name = 'comments/revision-ajax.html'
template_name = "comments/revision-ajax.html"
def get_context_data(self, **kwargs):
context = super(CommentRevisionAjax, self).get_context_data(**kwargs)
revisions = Version.objects.get_for_object(self.object).order_by('-revision')
revisions = Version.objects.get_for_object(self.object).order_by("-revision")
try:
wanted = min(max(int(self.request.GET.get('revision', 0)), 0), len(revisions) - 1)
wanted = min(
max(int(self.request.GET.get("revision", 0)), 0), len(revisions) - 1
)
except ValueError:
raise Http404
context['revision'] = revisions[wanted]
context["revision"] = revisions[wanted]
return context
def get_object(self, queryset=None):
comment = super(CommentRevisionAjax, self).get_object(queryset)
if comment.hidden and not self.request.user.has_perm('judge.change_comment'):
if comment.hidden and not self.request.user.has_perm("judge.change_comment"):
raise Http404()
return comment
@ -107,13 +132,15 @@ class CommentRevisionAjax(CommentMixin, DetailView):
class CommentEditForm(ModelForm):
class Meta:
model = Comment
fields = ['body']
fields = ["body"]
if MathJaxPagedownWidget is not None:
widgets = {'body': MathJaxPagedownWidget(attrs={'id': 'id-edit-comment-body'})}
widgets = {
"body": MathJaxPagedownWidget(attrs={"id": "id-edit-comment-body"})
}
class CommentEditAjax(LoginRequiredMixin, CommentMixin, UpdateView):
template_name = 'comments/edit-ajax.html'
template_name = "comments/edit-ajax.html"
form_class = CommentEditForm
def form_valid(self, form):
@ -123,7 +150,7 @@ class CommentEditAjax(LoginRequiredMixin, CommentMixin, UpdateView):
add_mention_notifications(comment)
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_('Edited from site'))
revisions.set_comment(_("Edited from site"))
revisions.set_user(self.request.user)
return super(CommentEditAjax, self).form_valid(form)
@ -132,7 +159,7 @@ class CommentEditAjax(LoginRequiredMixin, CommentMixin, UpdateView):
def get_object(self, queryset=None):
comment = super(CommentEditAjax, self).get_object(queryset)
if self.request.user.has_perm('judge.change_comment'):
if self.request.user.has_perm("judge.change_comment"):
return comment
profile = self.request.profile
if profile != comment.author or profile.mute or comment.hidden:
@ -141,36 +168,37 @@ class CommentEditAjax(LoginRequiredMixin, CommentMixin, UpdateView):
class CommentEdit(TitleMixin, CommentEditAjax):
template_name = 'comments/edit.html'
template_name = "comments/edit.html"
def get_title(self):
return _('Editing comment')
return _("Editing comment")
class CommentContent(CommentMixin, DetailView):
template_name = 'comments/content.html'
template_name = "comments/content.html"
class CommentVotesAjax(PermissionRequiredMixin, CommentMixin, DetailView):
template_name = 'comments/votes.html'
permission_required = 'judge.change_commentvote'
template_name = "comments/votes.html"
permission_required = "judge.change_commentvote"
def get_context_data(self, **kwargs):
context = super(CommentVotesAjax, self).get_context_data(**kwargs)
context['votes'] = (self.object.votes.select_related('voter__user')
.only('id', 'voter__display_rank', 'voter__user__username', 'score'))
context["votes"] = self.object.votes.select_related("voter__user").only(
"id", "voter__display_rank", "voter__user__username", "score"
)
return context
@require_POST
def comment_hide(request):
if not request.user.has_perm('judge.change_comment'):
if not request.user.has_perm("judge.change_comment"):
raise PermissionDenied()
try:
comment_id = int(request.POST['id'])
comment_id = int(request.POST["id"])
except ValueError:
return HttpResponseBadRequest()
comment = get_object_or_404(Comment, id=comment_id)
comment.get_descendants(include_self=True).update(hidden=True)
return HttpResponse('ok')
return HttpResponse("ok")

File diff suppressed because it is too large Load diff

View file

@ -5,29 +5,42 @@ from django.utils.translation import gettext as _
def error(request, context, status):
return render(request, 'error.html', context=context, status=status)
return render(request, "error.html", context=context, status=status)
def error404(request, exception=None):
# TODO: "panic: go back"
return render(request, 'generic-message.html', {
'title': _('404 error'),
'message': _('Could not find page "%s"') % request.path,
}, status=404)
return render(
request,
"generic-message.html",
{
"title": _("404 error"),
"message": _('Could not find page "%s"') % request.path,
},
status=404,
)
def error403(request, exception=None):
return error(request, {
'id': 'unauthorized_access',
'description': _('no permission for %s') % request.path,
'code': 403,
}, 403)
return error(
request,
{
"id": "unauthorized_access",
"description": _("no permission for %s") % request.path,
"code": 403,
},
403,
)
def error500(request):
return error(request, {
'id': 'invalid_state',
'description': _('corrupt page %s') % request.path,
'traceback': traceback.format_exc(),
'code': 500,
}, 500)
return error(
request,
{
"id": "invalid_state",
"description": _("corrupt page %s") % request.path,
"traceback": traceback.format_exc(),
"code": 500,
},
500,
)

View file

@ -6,30 +6,42 @@ from django.http import HttpResponseForbidden
from judge.utils.diggpaginator import DiggPaginator
from judge.models import VolunteerProblemVote, Problem
class InternalProblem(ListView):
model = Problem
title = _('Internal problems')
template_name = 'internal/base.html'
title = _("Internal problems")
template_name = "internal/base.html"
paginate_by = 100
context_object_name = 'problems'
context_object_name = "problems"
def get_paginator(
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
):
return DiggPaginator(
queryset,
per_page,
body=6,
padding=2,
orphans=orphans,
allow_empty_first_page=allow_empty_first_page,
**kwargs
)
def get_paginator(self, queryset, per_page, orphans=0,
allow_empty_first_page=True, **kwargs):
return DiggPaginator(queryset, per_page, body=6, padding=2, orphans=orphans,
allow_empty_first_page=allow_empty_first_page, **kwargs)
def get_queryset(self):
queryset = Problem.objects.annotate(vote_count=Count('volunteer_user_votes')) \
.filter(vote_count__gte=1).order_by('-vote_count')
queryset = (
Problem.objects.annotate(vote_count=Count("volunteer_user_votes"))
.filter(vote_count__gte=1)
.order_by("-vote_count")
)
return queryset
def get_context_data(self, **kwargs):
context = super(InternalProblem, self).get_context_data(**kwargs)
context['page_type'] = 'problem'
context['title'] = self.title
context["page_type"] = "problem"
context["title"] = self.title
return context
def get(self, request, *args, **kwargs):
if request.user.is_superuser:
return super(InternalProblem, self).get(request, *args, **kwargs)
return HttpResponseForbidden()
return HttpResponseForbidden()

View file

@ -7,12 +7,12 @@ from judge.utils.views import TitleMixin
class LanguageList(TitleMixin, ListView):
model = Language
context_object_name = 'languages'
template_name = 'status/language-list.html'
title = gettext_lazy('Runtimes')
context_object_name = "languages"
template_name = "status/language-list.html"
title = gettext_lazy("Runtimes")
def get_queryset(self):
queryset = super().get_queryset().prefetch_related('runtimeversion_set')
queryset = super().get_queryset().prefetch_related("runtimeversion_set")
if not self.request.user.is_superuser and not self.request.user.is_staff:
queryset = queryset.filter(judges__online=True).distinct()
return queryset

View file

@ -6,9 +6,9 @@ from judge.utils.views import TitleMixin
class LicenseDetail(TitleMixin, DetailView):
model = License
slug_field = slug_url_kwarg = 'key'
context_object_name = 'license'
template_name = 'license.html'
slug_field = slug_url_kwarg = "key"
context_object_name = "license"
template_name = "license.html"
def get_title(self):
return self.object.name

View file

@ -15,49 +15,77 @@ from registration.models import RegistrationProfile
from judge.utils.unicode import utf8bytes
logger = logging.getLogger('judge.mail.activate')
logger = logging.getLogger("judge.mail.activate")
class MailgunActivationView(View):
if hasattr(settings, 'MAILGUN_ACCESS_KEY'):
if hasattr(settings, "MAILGUN_ACCESS_KEY"):
def post(self, request, *args, **kwargs):
params = request.POST
timestamp = params.get('timestamp', '')
token = params.get('token', '')
signature = params.get('signature', '')
timestamp = params.get("timestamp", "")
token = params.get("token", "")
signature = params.get("signature", "")
logger.debug('Received request: %s', params)
logger.debug("Received request: %s", params)
if signature != hmac.new(key=utf8bytes(settings.MAILGUN_ACCESS_KEY),
msg=utf8bytes('%s%s' % (timestamp, token)), digestmod=hashlib.sha256).hexdigest():
logger.info('Rejected request: signature: %s, timestamp: %s, token: %s', signature, timestamp, token)
if (
signature
!= hmac.new(
key=utf8bytes(settings.MAILGUN_ACCESS_KEY),
msg=utf8bytes("%s%s" % (timestamp, token)),
digestmod=hashlib.sha256,
).hexdigest()
):
logger.info(
"Rejected request: signature: %s, timestamp: %s, token: %s",
signature,
timestamp,
token,
)
raise PermissionDenied()
_, sender = parseaddr(params.get('from'))
_, sender = parseaddr(params.get("from"))
if not sender:
logger.info('Rejected invalid sender: %s', params.get('from'))
logger.info("Rejected invalid sender: %s", params.get("from"))
return HttpResponse(status=406)
try:
user = User.objects.get(email__iexact=sender)
except (User.DoesNotExist, User.MultipleObjectsReturned):
logger.info('Rejected unknown sender: %s: %s', sender, params.get('from'))
logger.info(
"Rejected unknown sender: %s: %s", sender, params.get("from")
)
return HttpResponse(status=406)
try:
registration = RegistrationProfile.objects.get(user=user)
except RegistrationProfile.DoesNotExist:
logger.info('Rejected sender without RegistrationProfile: %s: %s', sender, params.get('from'))
logger.info(
"Rejected sender without RegistrationProfile: %s: %s",
sender,
params.get("from"),
)
return HttpResponse(status=406)
if registration.activated:
logger.info('Rejected activated sender: %s: %s', sender, params.get('from'))
logger.info(
"Rejected activated sender: %s: %s", sender, params.get("from")
)
return HttpResponse(status=406)
key = registration.activation_key
if key in params.get('body-plain', '') or key in params.get('body-html', ''):
if RegistrationProfile.objects.activate_user(key, get_current_site(request)):
logger.info('Activated sender: %s: %s', sender, params.get('from'))
return HttpResponse('Activated', status=200)
logger.info('Failed to activate sender: %s: %s', sender, params.get('from'))
if key in params.get("body-plain", "") or key in params.get(
"body-html", ""
):
if RegistrationProfile.objects.activate_user(
key, get_current_site(request)
):
logger.info("Activated sender: %s: %s", sender, params.get("from"))
return HttpResponse("Activated", status=200)
logger.info(
"Failed to activate sender: %s: %s", sender, params.get("from")
)
else:
logger.info('Activation key not found: %s: %s', sender, params.get('from'))
logger.info(
"Activation key not found: %s: %s", sender, params.get("from")
)
return HttpResponse(status=406)
@method_decorator(csrf_exempt)

View file

@ -7,46 +7,48 @@ from django.db.models import BooleanField, Value
from judge.utils.cachedict import CacheDict
from judge.models import Profile, Comment, Notification
__all__ = ['NotificationList']
__all__ = ["NotificationList"]
class NotificationList(ListView):
model = Notification
context_object_name = 'notifications'
template_name = 'notification/list.html'
context_object_name = "notifications"
template_name = "notification/list.html"
def get_queryset(self):
self.unseen_cnt = self.request.profile.count_unseen_notifications
query = {
'owner': self.request.profile,
"owner": self.request.profile,
}
self.queryset = Notification.objects.filter(**query)\
.order_by('-time')[:100] \
.annotate(seen=Value(True, output_field=BooleanField()))
self.queryset = (
Notification.objects.filter(**query)
.order_by("-time")[:100]
.annotate(seen=Value(True, output_field=BooleanField()))
)
# Mark the several first unseen
for cnt, q in enumerate(self.queryset):
if (cnt < self.unseen_cnt):
if cnt < self.unseen_cnt:
q.seen = False
else:
break
return self.queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['unseen_count'] = self.unseen_cnt
context['title'] = _('Notifications (%d unseen)' % context['unseen_count'])
context['has_notifications'] = self.queryset.exists()
context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page))
context["unseen_count"] = self.unseen_cnt
context["title"] = _("Notifications (%d unseen)" % context["unseen_count"])
context["has_notifications"] = self.queryset.exists()
context["page_titles"] = CacheDict(lambda page: Comment.get_page_title(page))
return context
def get(self, request, *args, **kwargs):
ret = super().get(request, *args, **kwargs)
# update after rendering
Notification.objects.filter(owner=self.request.profile).update(read=True)
return ret
return ret

View file

@ -14,18 +14,45 @@ from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, gettext_lazy, ungettext
from django.views.generic import DetailView, FormView, ListView, UpdateView, View
from django.views.generic.detail import SingleObjectMixin, SingleObjectTemplateResponseMixin
from django.views.generic.detail import (
SingleObjectMixin,
SingleObjectTemplateResponseMixin,
)
from reversion import revisions
from judge.forms import EditOrganizationForm
from judge.models import BlogPost, Comment, Organization, OrganizationRequest, Problem, Profile, Contest
from judge.models import (
BlogPost,
Comment,
Organization,
OrganizationRequest,
Problem,
Profile,
Contest,
)
from judge.utils.ranker import ranker
from judge.utils.views import TitleMixin, generic_message, QueryStringSortMixin, DiggPaginatorMixin
from judge.utils.views import (
TitleMixin,
generic_message,
QueryStringSortMixin,
DiggPaginatorMixin,
)
__all__ = [
"OrganizationList",
"OrganizationHome",
"OrganizationUsers",
"OrganizationMembershipChange",
"JoinOrganization",
"LeaveOrganization",
"EditOrganization",
"RequestJoinOrganization",
"OrganizationRequestDetail",
"OrganizationRequestView",
"OrganizationRequestLog",
"KickUserWidgetView",
]
__all__ = ['OrganizationList', 'OrganizationHome', 'OrganizationUsers', 'OrganizationMembershipChange',
'JoinOrganization', 'LeaveOrganization', 'EditOrganization', 'RequestJoinOrganization',
'OrganizationRequestDetail', 'OrganizationRequestView', 'OrganizationRequestLog',
'KickUserWidgetView']
class OrganizationBase(object):
def can_edit_organization(self, org=None):
@ -34,20 +61,25 @@ class OrganizationBase(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
return (
org.admins.filter(id=profile_id).exists() or org.registrant_id == profile_id
)
def is_member(self, org=None):
if org is None:
org = self.object
return self.request.profile in org if self.request.user.is_authenticated else False
return (
self.request.profile in org if self.request.user.is_authenticated else False
)
class OrganizationMixin(OrganizationBase):
context_object_name = 'organization'
context_object_name = "organization"
model = Organization
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['logo_override_image'] = self.object.logo_override_image
context["logo_override_image"] = self.object.logo_override_image
return context
def dispatch(self, request, *args, **kwargs):
@ -56,92 +88,131 @@ class OrganizationMixin(OrganizationBase):
except Http404:
key = kwargs.get(self.slug_url_kwarg, None)
if key:
return generic_message(request, _('No such organization'),
_('Could not find an organization with the key "%s".') % key)
return generic_message(
request,
_("No such organization"),
_('Could not find an organization with the key "%s".') % key,
)
else:
return generic_message(request, _('No such organization'),
_('Could not find such organization.'))
return generic_message(
request,
_("No such organization"),
_("Could not find such organization."),
)
class OrganizationDetailView(OrganizationMixin, DetailView):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.slug != kwargs['slug']:
return HttpResponsePermanentRedirect(request.get_full_path().replace(kwargs['slug'], self.object.slug))
if self.object.slug != kwargs["slug"]:
return HttpResponsePermanentRedirect(
request.get_full_path().replace(kwargs["slug"], self.object.slug)
)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
class OrganizationList(TitleMixin, ListView, OrganizationBase):
model = Organization
context_object_name = 'organizations'
template_name = 'organization/list.html'
title = gettext_lazy('Organizations')
context_object_name = "organizations"
template_name = "organization/list.html"
title = gettext_lazy("Organizations")
def get_queryset(self):
return super(OrganizationList, self).get_queryset().annotate(member_count=Count('member'))
return (
super(OrganizationList, self)
.get_queryset()
.annotate(member_count=Count("member"))
)
def get_context_data(self, **kwargs):
context = super(OrganizationList, self).get_context_data(**kwargs)
context['my_organizations'] = set()
context["my_organizations"] = set()
for organization in context['organizations']:
for organization in context["organizations"]:
if self.can_edit_organization(organization) or self.is_member(organization):
context['my_organizations'].add(organization)
context["my_organizations"].add(organization)
return context
class OrganizationHome(OrganizationDetailView):
template_name = 'organization/home.html'
template_name = "organization/home.html"
def get_context_data(self, **kwargs):
context = super(OrganizationHome, self).get_context_data(**kwargs)
context['title'] = self.object.name
context['can_edit'] = self.can_edit_organization()
context['is_member'] = self.is_member()
context['new_problems'] = Problem.objects.filter(is_public=True, is_organization_private=True,
organizations=self.object) \
.order_by('-date', '-id')[:settings.DMOJ_BLOG_NEW_PROBLEM_COUNT]
context['new_contests'] = Contest.objects.filter(is_visible=True, is_organization_private=True,
organizations=self.object) \
.order_by('-end_time', '-id')[:settings.DMOJ_BLOG_NEW_CONTEST_COUNT]
context["title"] = self.object.name
context["can_edit"] = self.can_edit_organization()
context["is_member"] = self.is_member()
context["new_problems"] = Problem.objects.filter(
is_public=True, is_organization_private=True, organizations=self.object
).order_by("-date", "-id")[: settings.DMOJ_BLOG_NEW_PROBLEM_COUNT]
context["new_contests"] = Contest.objects.filter(
is_visible=True, is_organization_private=True, organizations=self.object
).order_by("-end_time", "-id")[: settings.DMOJ_BLOG_NEW_CONTEST_COUNT]
context['posts'] = BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now(),
is_organization_private=True, organizations=self.object) \
.order_by('-sticky', '-publish_on') \
.prefetch_related('authors__user', 'organizations')
context['post_comment_counts'] = {
int(page[2:]): count for page, count in
Comment.objects.filter(page__in=['b:%d' % post.id for post in context['posts']], hidden=False)
.values_list('page').annotate(count=Count('page')).order_by()
context["posts"] = (
BlogPost.objects.filter(
visible=True,
publish_on__lte=timezone.now(),
is_organization_private=True,
organizations=self.object,
)
.order_by("-sticky", "-publish_on")
.prefetch_related("authors__user", "organizations")
)
context["post_comment_counts"] = {
int(page[2:]): count
for page, count in Comment.objects.filter(
page__in=["b:%d" % post.id for post in context["posts"]], hidden=False
)
.values_list("page")
.annotate(count=Count("page"))
.order_by()
}
context['pending_count'] = OrganizationRequest.objects.filter(state='P', organization=self.object).count()
context["pending_count"] = OrganizationRequest.objects.filter(
state="P", organization=self.object
).count()
return context
class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
template_name = 'organization/users.html'
all_sorts = frozenset(('points', 'problem_count', 'rating', 'performance_points'))
template_name = "organization/users.html"
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
default_desc = all_sorts
default_sort = '-performance_points'
default_sort = "-performance_points"
def get_context_data(self, **kwargs):
context = super(OrganizationUsers, self).get_context_data(**kwargs)
context['title'] = _('%s Members') % self.object.name
context['partial'] = True
context['is_admin'] = self.can_edit_organization()
context['kick_url'] = reverse('organization_user_kick', args=[self.object.id, self.object.slug])
context["title"] = _("%s Members") % self.object.name
context["partial"] = True
context["is_admin"] = self.can_edit_organization()
context["kick_url"] = reverse(
"organization_user_kick", args=[self.object.id, self.object.slug]
)
context['users'] = ranker(
self.get_object().members.filter(is_unlisted=False).order_by(self.order, 'id').select_related('user') \
.only('display_rank', 'user__username', 'points', 'rating', 'performance_points', 'problem_count')
context["users"] = ranker(
self.get_object()
.members.filter(is_unlisted=False)
.order_by(self.order, "id")
.select_related("user")
.only(
"display_rank",
"user__username",
"points",
"rating",
"performance_points",
"problem_count",
)
context['first_page_href'] = '.'
)
context["first_page_href"] = "."
context.update(self.get_sort_context())
return context
class OrganizationMembershipChange(LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View):
class OrganizationMembershipChange(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
):
def post(self, request, *args, **kwargs):
org = self.get_object()
response = self.handle(request, org, request.profile)
@ -156,29 +227,42 @@ class OrganizationMembershipChange(LoginRequiredMixin, OrganizationMixin, Single
class JoinOrganization(OrganizationMembershipChange):
def handle(self, request, org, profile):
if profile.organizations.filter(id=org.id).exists():
return generic_message(request, _('Joining organization'), _('You are already in the organization.'))
return generic_message(
request,
_("Joining organization"),
_("You are already in the organization."),
)
if not org.is_open:
return generic_message(request, _('Joining organization'), _('This organization is not open.'))
return generic_message(
request, _("Joining organization"), _("This organization is not open.")
)
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if profile.organizations.filter(is_open=True).count() >= max_orgs:
return generic_message(
request, _('Joining organization'),
_('You may not be part of more than {count} public organizations.').format(count=max_orgs),
request,
_("Joining organization"),
_(
"You may not be part of more than {count} public organizations."
).format(count=max_orgs),
)
profile.organizations.add(org)
profile.save()
cache.delete(make_template_fragment_key('org_member_count', (org.id,)))
cache.delete(make_template_fragment_key("org_member_count", (org.id,)))
class LeaveOrganization(OrganizationMembershipChange):
def handle(self, request, org, profile):
if not profile.organizations.filter(id=org.id).exists():
return generic_message(request, _('Leaving organization'), _('You are not in "%s".') % org.short_name)
return generic_message(
request,
_("Leaving organization"),
_('You are not in "%s".') % org.short_name,
)
profile.organizations.remove(org)
cache.delete(make_template_fragment_key('org_member_count', (org.id,)))
cache.delete(make_template_fragment_key("org_member_count", (org.id,)))
class OrganizationRequestForm(Form):
@ -187,9 +271,9 @@ class OrganizationRequestForm(Form):
class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
model = Organization
slug_field = 'key'
slug_url_kwarg = 'key'
template_name = 'organization/requests/request.html'
slug_field = "key"
slug_url_kwarg = "key"
template_name = "organization/requests/request.html"
form_class = OrganizationRequestForm
def dispatch(self, request, *args, **kwargs):
@ -200,75 +284,99 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
context = super(RequestJoinOrganization, self).get_context_data(**kwargs)
if self.object.is_open:
raise Http404()
context['title'] = _('Request to join %s') % self.object.name
context["title"] = _("Request to join %s") % self.object.name
return context
def form_valid(self, form):
request = OrganizationRequest()
request.organization = self.get_object()
request.user = self.request.profile
request.reason = form.cleaned_data['reason']
request.state = 'P'
request.reason = form.cleaned_data["reason"]
request.state = "P"
request.save()
return HttpResponseRedirect(reverse('request_organization_detail', args=(
request.organization.id, request.organization.slug, request.id,
)))
return HttpResponseRedirect(
reverse(
"request_organization_detail",
args=(
request.organization.id,
request.organization.slug,
request.id,
),
)
)
class OrganizationRequestDetail(LoginRequiredMixin, TitleMixin, DetailView):
model = OrganizationRequest
template_name = 'organization/requests/detail.html'
title = gettext_lazy('Join request detail')
pk_url_kwarg = 'rpk'
template_name = "organization/requests/detail.html"
title = gettext_lazy("Join request detail")
pk_url_kwarg = "rpk"
def get_object(self, queryset=None):
object = super(OrganizationRequestDetail, self).get_object(queryset)
profile = self.request.profile
if object.user_id != profile.id and not object.organization.admins.filter(id=profile.id).exists():
if (
object.user_id != profile.id
and not object.organization.admins.filter(id=profile.id).exists()
):
raise PermissionDenied()
return object
OrganizationRequestFormSet = modelformset_factory(OrganizationRequest, extra=0, fields=('state',), can_delete=True)
OrganizationRequestFormSet = modelformset_factory(
OrganizationRequest, extra=0, fields=("state",), can_delete=True
)
class OrganizationRequestBaseView(TitleMixin, LoginRequiredMixin, SingleObjectTemplateResponseMixin, SingleObjectMixin, View):
class OrganizationRequestBaseView(
TitleMixin,
LoginRequiredMixin,
SingleObjectTemplateResponseMixin,
SingleObjectMixin,
View,
):
model = Organization
slug_field = 'key'
slug_url_kwarg = 'key'
slug_field = "key"
slug_url_kwarg = "key"
tab = None
def get_object(self, queryset=None):
organization = super(OrganizationRequestBaseView, self).get_object(queryset)
if not (organization.admins.filter(id=self.request.profile.id).exists() or
organization.registrant_id == self.request.profile.id):
if not (
organization.admins.filter(id=self.request.profile.id).exists()
or organization.registrant_id == self.request.profile.id
):
raise PermissionDenied()
return organization
def get_content_title(self):
href = reverse('organization_home', args=[self.object.id, self.object.slug])
return mark_safe(f'Manage join requests for <a href="{href}">{self.object.name}</a>')
href = reverse("organization_home", args=[self.object.id, self.object.slug])
return mark_safe(
f'Manage join requests for <a href="{href}">{self.object.name}</a>'
)
def get_context_data(self, **kwargs):
context = super(OrganizationRequestBaseView, self).get_context_data(**kwargs)
context['title'] = _('Managing join requests for %s') % self.object.name
context['tab'] = self.tab
context["title"] = _("Managing join requests for %s") % self.object.name
context["tab"] = self.tab
return context
class OrganizationRequestView(OrganizationRequestBaseView):
template_name = 'organization/requests/pending.html'
tab = 'pending'
template_name = "organization/requests/pending.html"
tab = "pending"
def get_context_data(self, **kwargs):
context = super(OrganizationRequestView, self).get_context_data(**kwargs)
context['formset'] = self.formset
context["formset"] = self.formset
return context
def get(self, request, *args, **kwargs):
self.object = self.get_object()
self.formset = OrganizationRequestFormSet(
queryset=OrganizationRequest.objects.filter(state='P', organization=self.object),
queryset=OrganizationRequest.objects.filter(
state="P", organization=self.object
),
)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
@ -279,24 +387,43 @@ class OrganizationRequestView(OrganizationRequestBaseView):
if formset.is_valid():
if organization.slots is not None:
deleted_set = set(formset.deleted_forms)
to_approve = sum(form.cleaned_data['state'] == 'A' for form in formset.forms if form not in deleted_set)
to_approve = sum(
form.cleaned_data["state"] == "A"
for form in formset.forms
if form not in deleted_set
)
can_add = organization.slots - organization.members.count()
if to_approve > can_add:
messages.error(request, _('Your organization can only receive %d more members. '
'You cannot approve %d users.') % (can_add, to_approve))
return self.render_to_response(self.get_context_data(object=organization))
messages.error(
request,
_(
"Your organization can only receive %d more members. "
"You cannot approve %d users."
)
% (can_add, to_approve),
)
return self.render_to_response(
self.get_context_data(object=organization)
)
approved, rejected = 0, 0
for obj in formset.save():
if obj.state == 'A':
if obj.state == "A":
obj.user.organizations.add(obj.organization)
approved += 1
elif obj.state == 'R':
elif obj.state == "R":
rejected += 1
messages.success(request,
ungettext('Approved %d user.', 'Approved %d users.', approved) % approved + '\n' +
ungettext('Rejected %d user.', 'Rejected %d users.', rejected) % rejected)
cache.delete(make_template_fragment_key('org_member_count', (organization.id,)))
messages.success(
request,
ungettext("Approved %d user.", "Approved %d users.", approved)
% approved
+ "\n"
+ ungettext("Rejected %d user.", "Rejected %d users.", rejected)
% rejected,
)
cache.delete(
make_template_fragment_key("org_member_count", (organization.id,))
)
return HttpResponseRedirect(request.get_full_path())
return self.render_to_response(self.get_context_data(object=organization))
@ -304,9 +431,9 @@ class OrganizationRequestView(OrganizationRequestBaseView):
class OrganizationRequestLog(OrganizationRequestBaseView):
states = ('A', 'R')
tab = 'log'
template_name = 'organization/requests/log.html'
states = ("A", "R")
tab = "log"
template_name = "organization/requests/log.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@ -315,17 +442,17 @@ class OrganizationRequestLog(OrganizationRequestBaseView):
def get_context_data(self, **kwargs):
context = super(OrganizationRequestLog, self).get_context_data(**kwargs)
context['requests'] = self.object.requests.filter(state__in=self.states)
context["requests"] = self.object.requests.filter(state__in=self.states)
return context
class EditOrganization(LoginRequiredMixin, TitleMixin, OrganizationMixin, UpdateView):
template_name = 'organization/edit.html'
template_name = "organization/edit.html"
model = Organization
form_class = EditOrganizationForm
def get_title(self):
return _('Editing %s') % self.object.name
return _("Editing %s") % self.object.name
def get_object(self, queryset=None):
object = super(EditOrganization, self).get_object()
@ -335,13 +462,14 @@ class EditOrganization(LoginRequiredMixin, TitleMixin, OrganizationMixin, Update
def get_form(self, form_class=None):
form = super(EditOrganization, self).get_form(form_class)
form.fields['admins'].queryset = \
Profile.objects.filter(Q(organizations=self.object) | Q(admin_of=self.object)).distinct()
form.fields["admins"].queryset = Profile.objects.filter(
Q(organizations=self.object) | Q(admin_of=self.object)
).distinct()
return form
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_('Edited from site'))
revisions.set_comment(_("Edited from site"))
revisions.set_user(self.request.user)
return super(EditOrganization, self).form_valid(form)
@ -349,27 +477,45 @@ class EditOrganization(LoginRequiredMixin, TitleMixin, OrganizationMixin, Update
try:
return super(EditOrganization, self).dispatch(request, *args, **kwargs)
except PermissionDenied:
return generic_message(request, _("Can't edit organization"),
_('You are not allowed to edit this organization.'), status=403)
return generic_message(
request,
_("Can't edit organization"),
_("You are not allowed to edit this organization."),
status=403,
)
class KickUserWidgetView(LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View):
class KickUserWidgetView(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
):
def post(self, request, *args, **kwargs):
organization = self.get_object()
if not self.can_edit_organization(organization):
return generic_message(request, _("Can't edit organization"),
_('You are not allowed to kick people from this organization.'), status=403)
return generic_message(
request,
_("Can't edit organization"),
_("You are not allowed to kick people from this organization."),
status=403,
)
try:
user = Profile.objects.get(id=request.POST.get('user', None))
user = Profile.objects.get(id=request.POST.get("user", None))
except Profile.DoesNotExist:
return generic_message(request, _("Can't kick user"),
_('The user you are trying to kick does not exist!'), status=400)
return generic_message(
request,
_("Can't kick user"),
_("The user you are trying to kick does not exist!"),
status=400,
)
if not organization.members.filter(id=user.id).exists():
return generic_message(request, _("Can't kick user"),
_('The user you are trying to kick is not in organization: %s.') %
organization.name, status=400)
return generic_message(
request,
_("Can't kick user"),
_("The user you are trying to kick is not in organization: %s.")
% organization.name,
status=400,
)
organization.members.remove(user)
return HttpResponseRedirect(organization.get_users_url())

View file

@ -5,46 +5,48 @@ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View
class MarkdownPreviewView(TemplateResponseMixin, ContextMixin, View):
def post(self, request, *args, **kwargs):
try:
self.preview_data = data = request.POST['preview']
self.preview_data = data = request.POST["preview"]
except KeyError:
return HttpResponseBadRequest('No preview data specified.')
return HttpResponseBadRequest("No preview data specified.")
return self.render_to_response(self.get_context_data(
preview_data=data,
))
return self.render_to_response(
self.get_context_data(
preview_data=data,
)
)
class ProblemMarkdownPreviewView(MarkdownPreviewView):
template_name = 'problem/preview.html'
template_name = "problem/preview.html"
class BlogMarkdownPreviewView(MarkdownPreviewView):
template_name = 'blog/preview.html'
template_name = "blog/preview.html"
class ContestMarkdownPreviewView(MarkdownPreviewView):
template_name = 'contest/preview.html'
template_name = "contest/preview.html"
class CommentMarkdownPreviewView(MarkdownPreviewView):
template_name = 'comments/preview.html'
template_name = "comments/preview.html"
class ProfileMarkdownPreviewView(MarkdownPreviewView):
template_name = 'user/preview.html'
template_name = "user/preview.html"
class OrganizationMarkdownPreviewView(MarkdownPreviewView):
template_name = 'organization/preview.html'
template_name = "organization/preview.html"
class SolutionMarkdownPreviewView(MarkdownPreviewView):
template_name = 'solution-preview.html'
template_name = "solution-preview.html"
class LicenseMarkdownPreviewView(MarkdownPreviewView):
template_name = 'license-preview.html'
template_name = "license-preview.html"
class TicketMarkdownPreviewView(MarkdownPreviewView):
template_name = 'ticket/preview.html'
template_name = "ticket/preview.html"

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,15 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.files import File
from django.core.exceptions import ValidationError
from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory, FileInput
from django.forms import (
BaseModelFormSet,
HiddenInput,
ModelForm,
NumberInput,
Select,
formset_factory,
FileInput,
)
from django.http import Http404, HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
@ -28,46 +36,65 @@ from django.utils.translation import gettext as _
from django.views.generic import DetailView
from judge.highlight_code import highlight_code
from judge.models import Problem, ProblemData, ProblemTestCase, Submission, problem_data_storage
from judge.models import (
Problem,
ProblemData,
ProblemTestCase,
Submission,
problem_data_storage,
)
from judge.utils.problem_data import ProblemDataCompiler
from judge.utils.unicode import utf8text
from judge.utils.views import TitleMixin
from judge.utils.fine_uploader import combine_chunks, save_upload, handle_upload, FineUploadFileInput, FineUploadForm
from judge.utils.fine_uploader import (
combine_chunks,
save_upload,
handle_upload,
FineUploadFileInput,
FineUploadForm,
)
from judge.views.problem import ProblemMixin
mimetypes.init()
mimetypes.add_type('application/x-yaml', '.yml')
mimetypes.add_type("application/x-yaml", ".yml")
def checker_args_cleaner(self):
data = self.cleaned_data['checker_args']
data = self.cleaned_data["checker_args"]
if not data or data.isspace():
return ''
return ""
try:
if not isinstance(json.loads(data), dict):
raise ValidationError(_('Checker arguments must be a JSON object'))
raise ValidationError(_("Checker arguments must be a JSON object"))
except ValueError:
raise ValidationError(_('Checker arguments is invalid JSON'))
raise ValidationError(_("Checker arguments is invalid JSON"))
return data
class ProblemDataForm(ModelForm):
def clean_zipfile(self):
if hasattr(self, 'zip_valid') and not self.zip_valid:
raise ValidationError(_('Your zip file is invalid!'))
return self.cleaned_data['zipfile']
if hasattr(self, "zip_valid") and not self.zip_valid:
raise ValidationError(_("Your zip file is invalid!"))
return self.cleaned_data["zipfile"]
clean_checker_args = checker_args_cleaner
class Meta:
model = ProblemData
fields = ['zipfile', 'checker', 'checker_args', 'custom_checker', 'custom_validator', 'interactive_judge']
fields = [
"zipfile",
"checker",
"checker_args",
"custom_checker",
"custom_validator",
"interactive_judge",
]
widgets = {
'zipfile': FineUploadFileInput,
'checker_args': HiddenInput,
'generator': HiddenInput,
'output_limit': HiddenInput,
'output_prefix': HiddenInput,
"zipfile": FineUploadFileInput,
"checker_args": HiddenInput,
"generator": HiddenInput,
"output_limit": HiddenInput,
"output_prefix": HiddenInput,
}
@ -76,25 +103,35 @@ class ProblemCaseForm(ModelForm):
class Meta:
model = ProblemTestCase
fields = ('order', 'type', 'input_file', 'output_file', 'points',
'is_pretest', 'checker', 'checker_args') #, 'output_limit', 'output_prefix', 'generator_args')
fields = (
"order",
"type",
"input_file",
"output_file",
"points",
"is_pretest",
"checker",
"checker_args",
) # , 'output_limit', 'output_prefix', 'generator_args')
widgets = {
# 'generator_args': HiddenInput,
'type': Select(attrs={'style': 'width: 100%'}),
'points': NumberInput(attrs={'style': 'width: 4em'}),
"type": Select(attrs={"style": "width: 100%"}),
"points": NumberInput(attrs={"style": "width: 4em"}),
# 'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}),
# 'output_limit': NumberInput(attrs={'style': 'width: 6em'}),
# 'checker_args': HiddenInput,
}
class ProblemCaseFormSet(formset_factory(ProblemCaseForm, formset=BaseModelFormSet, extra=1, max_num=1,
can_delete=True)):
class ProblemCaseFormSet(
formset_factory(
ProblemCaseForm, formset=BaseModelFormSet, extra=1, max_num=1, can_delete=True
)
):
model = ProblemTestCase
def __init__(self, *args, **kwargs):
self.valid_files = kwargs.pop('valid_files', None)
self.valid_files = kwargs.pop("valid_files", None)
super(ProblemCaseFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, i, **kwargs):
@ -114,14 +151,17 @@ class ProblemManagerMixin(LoginRequiredMixin, ProblemMixin, DetailView):
class ProblemSubmissionDiff(TitleMixin, ProblemMixin, DetailView):
template_name = 'problem/submission-diff.html'
template_name = "problem/submission-diff.html"
def get_title(self):
return _('Comparing submissions for {0}').format(self.object.name)
return _("Comparing submissions for {0}").format(self.object.name)
def get_content_title(self):
return format_html(_('Comparing submissions for <a href="{1}">{0}</a>'), self.object.name,
reverse('problem_detail', args=[self.object.code]))
return format_html(
_('Comparing submissions for <a href="{1}">{0}</a>'),
self.object.name,
reverse("problem_detail", args=[self.object.code]),
)
def get_object(self, queryset=None):
problem = super(ProblemSubmissionDiff, self).get_object(queryset)
@ -132,51 +172,67 @@ class ProblemSubmissionDiff(TitleMixin, ProblemMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(ProblemSubmissionDiff, self).get_context_data(**kwargs)
try:
ids = self.request.GET.getlist('id')
ids = self.request.GET.getlist("id")
subs = Submission.objects.filter(id__in=ids)
except ValueError:
raise Http404
if not subs:
raise Http404
context['submissions'] = subs
context["submissions"] = subs
# If we have associated data we can do better than just guess
data = ProblemTestCase.objects.filter(dataset=self.object, type='C')
data = ProblemTestCase.objects.filter(dataset=self.object, type="C")
if data:
num_cases = data.count()
else:
num_cases = subs.first().test_cases.count()
context['num_cases'] = num_cases
context["num_cases"] = num_cases
return context
class ProblemDataView(TitleMixin, ProblemManagerMixin):
template_name = 'problem/data.html'
template_name = "problem/data.html"
def get_title(self):
return _('Editing data for {0}').format(self.object.name)
return _("Editing data for {0}").format(self.object.name)
def get_content_title(self):
return mark_safe(escape(_('Editing data for %s')) % (
format_html('<a href="{1}">{0}</a>', self.object.name,
reverse('problem_detail', args=[self.object.code]))))
return mark_safe(
escape(_("Editing data for %s"))
% (
format_html(
'<a href="{1}">{0}</a>',
self.object.name,
reverse("problem_detail", args=[self.object.code]),
)
)
)
def get_data_form(self, post=False):
return ProblemDataForm(data=self.request.POST if post else None, prefix='problem-data',
files=self.request.FILES if post else None,
instance=ProblemData.objects.get_or_create(problem=self.object)[0])
return ProblemDataForm(
data=self.request.POST if post else None,
prefix="problem-data",
files=self.request.FILES if post else None,
instance=ProblemData.objects.get_or_create(problem=self.object)[0],
)
def get_case_formset(self, files, post=False):
return ProblemCaseFormSet(data=self.request.POST if post else None, prefix='cases', valid_files=files,
queryset=ProblemTestCase.objects.filter(dataset_id=self.object.pk).order_by('order'))
return ProblemCaseFormSet(
data=self.request.POST if post else None,
prefix="cases",
valid_files=files,
queryset=ProblemTestCase.objects.filter(dataset_id=self.object.pk).order_by(
"order"
),
)
def get_valid_files(self, data, post=False):
try:
if post and 'problem-data-zipfile-clear' in self.request.POST:
if post and "problem-data-zipfile-clear" in self.request.POST:
return []
elif post and 'problem-data-zipfile' in self.request.FILES:
return ZipFile(self.request.FILES['problem-data-zipfile']).namelist()
elif post and "problem-data-zipfile" in self.request.FILES:
return ZipFile(self.request.FILES["problem-data-zipfile"]).namelist()
elif data.zipfile:
return ZipFile(data.zipfile.path).namelist()
except BadZipfile:
@ -185,14 +241,18 @@ class ProblemDataView(TitleMixin, ProblemManagerMixin):
def get_context_data(self, **kwargs):
context = super(ProblemDataView, self).get_context_data(**kwargs)
if 'data_form' not in context:
context['data_form'] = self.get_data_form()
valid_files = context['valid_files'] = self.get_valid_files(context['data_form'].instance)
context['data_form'].zip_valid = valid_files is not False
context['cases_formset'] = self.get_case_formset(valid_files)
context['valid_files_json'] = mark_safe(json.dumps(context['valid_files']))
context['valid_files'] = set(context['valid_files'])
context['all_case_forms'] = chain(context['cases_formset'], [context['cases_formset'].empty_form])
if "data_form" not in context:
context["data_form"] = self.get_data_form()
valid_files = context["valid_files"] = self.get_valid_files(
context["data_form"].instance
)
context["data_form"].zip_valid = valid_files is not False
context["cases_formset"] = self.get_case_formset(valid_files)
context["valid_files_json"] = mark_safe(json.dumps(context["valid_files"]))
context["valid_files"] = set(context["valid_files"])
context["all_case_forms"] = chain(
context["cases_formset"], [context["cases_formset"].empty_form]
)
return context
def post(self, request, *args, **kwargs):
@ -208,10 +268,17 @@ class ProblemDataView(TitleMixin, ProblemManagerMixin):
case.save()
for case in cases_formset.deleted_objects:
case.delete()
ProblemDataCompiler.generate(problem, data, problem.cases.order_by('order'), valid_files)
ProblemDataCompiler.generate(
problem, data, problem.cases.order_by("order"), valid_files
)
return HttpResponseRedirect(request.get_full_path())
return self.render_to_response(self.get_context_data(data_form=data_form, cases_formset=cases_formset,
valid_files=valid_files))
return self.render_to_response(
self.get_context_data(
data_form=data_form,
cases_formset=cases_formset,
valid_files=valid_files,
)
)
put = post
@ -223,16 +290,22 @@ def problem_data_file(request, problem, path):
raise Http404()
response = HttpResponse()
if hasattr(settings, 'DMOJ_PROBLEM_DATA_INTERNAL') and request.META.get('SERVER_SOFTWARE', '').startswith('nginx/'):
response['X-Accel-Redirect'] = '%s/%s/%s' % (settings.DMOJ_PROBLEM_DATA_INTERNAL, problem, path)
if hasattr(settings, "DMOJ_PROBLEM_DATA_INTERNAL") and request.META.get(
"SERVER_SOFTWARE", ""
).startswith("nginx/"):
response["X-Accel-Redirect"] = "%s/%s/%s" % (
settings.DMOJ_PROBLEM_DATA_INTERNAL,
problem,
path,
)
else:
try:
with problem_data_storage.open(os.path.join(problem, path), 'rb') as f:
with problem_data_storage.open(os.path.join(problem, path), "rb") as f:
response.content = f.read()
except IOError:
raise Http404()
response['Content-Type'] = 'application/octet-stream'
response["Content-Type"] = "application/octet-stream"
return response
@ -243,39 +316,53 @@ def problem_init_view(request, problem):
raise Http404()
try:
with problem_data_storage.open(os.path.join(problem.code, 'init.yml'), 'rb') as f:
data = utf8text(f.read()).rstrip('\n')
with problem_data_storage.open(
os.path.join(problem.code, "init.yml"), "rb"
) as f:
data = utf8text(f.read()).rstrip("\n")
except IOError:
raise Http404()
return render(request, 'problem/yaml.html', {
'raw_source': data, 'highlighted_source': highlight_code(data, 'yaml', linenos=False),
'title': _('Generated init.yml for %s') % problem.name,
'content_title': mark_safe(escape(_('Generated init.yml for %s')) % (
format_html('<a href="{1}">{0}</a>', problem.name,
reverse('problem_detail', args=[problem.code])))),
})
return render(
request,
"problem/yaml.html",
{
"raw_source": data,
"highlighted_source": highlight_code(data, "yaml", linenos=False),
"title": _("Generated init.yml for %s") % problem.name,
"content_title": mark_safe(
escape(_("Generated init.yml for %s"))
% (
format_html(
'<a href="{1}">{0}</a>',
problem.name,
reverse("problem_detail", args=[problem.code]),
)
)
),
},
)
class ProblemZipUploadView(ProblemManagerMixin, View):
def dispatch(self, *args, **kwargs):
return super(ProblemZipUploadView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = problem = self.get_object()
problem_data = get_object_or_404(ProblemData, problem=self.object)
form = FineUploadForm(request.POST, request.FILES)
if form.is_valid():
fileuid = form.cleaned_data['qquuid']
filename = form.cleaned_data['qqfilename']
fileuid = form.cleaned_data["qquuid"]
filename = form.cleaned_data["qqfilename"]
dest = os.path.join(gettempdir(), fileuid)
def post_upload():
zip_dest = os.path.join(dest, filename)
try:
ZipFile(zip_dest).namelist() # check if this file is valid
with open(zip_dest, 'rb') as f:
ZipFile(zip_dest).namelist() # check if this file is valid
with open(zip_dest, "rb") as f:
problem_data.zipfile.delete()
problem_data.zipfile.save(filename, File(f))
f.close()
@ -283,11 +370,16 @@ class ProblemZipUploadView(ProblemManagerMixin, View):
raise Exception(e)
finally:
shutil.rmtree(dest)
try:
handle_upload(request.FILES['qqfile'], form.cleaned_data, dest, post_upload=post_upload)
handle_upload(
request.FILES["qqfile"],
form.cleaned_data,
dest,
post_upload=post_upload,
)
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
return JsonResponse({'success': True})
return JsonResponse({"success": False, "error": str(e)})
return JsonResponse({"success": True})
else:
return HttpResponse(status_code=400)
return HttpResponse(status_code=400)

View file

@ -5,7 +5,12 @@ import zipfile, tempfile
from celery.result import AsyncResult
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.urls import reverse
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
@ -46,33 +51,46 @@ class ManageProblemSubmissionActionMixin(ManageProblemSubmissionMixin):
class ManageProblemSubmissionView(TitleMixin, ManageProblemSubmissionMixin, DetailView):
template_name = 'problem/manage_submission.html'
template_name = "problem/manage_submission.html"
def get_title(self):
return _('Managing submissions for %s') % (self.object.name,)
return _("Managing submissions for %s") % (self.object.name,)
def get_content_title(self):
return mark_safe(escape(_('Managing submissions for %s')) % (
format_html('<a href="{1}">{0}</a>', self.object.name,
reverse('problem_detail', args=[self.object.code]))))
return mark_safe(
escape(_("Managing submissions for %s"))
% (
format_html(
'<a href="{1}">{0}</a>',
self.object.name,
reverse("problem_detail", args=[self.object.code]),
)
)
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['submission_count'] = self.object.submission_set.count()
context['languages'] = [(lang_id, short_name or key) for lang_id, key, short_name in
Language.objects.values_list('id', 'key', 'short_name')]
context['results'] = sorted(map(itemgetter(0), Submission.RESULT))
context["submission_count"] = self.object.submission_set.count()
context["languages"] = [
(lang_id, short_name or key)
for lang_id, key, short_name in Language.objects.values_list(
"id", "key", "short_name"
)
]
context["results"] = sorted(map(itemgetter(0), Submission.RESULT))
return context
class BaseActionSubmissionsView(PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView):
permission_required = 'judge.rejudge_submission_lot'
class BaseActionSubmissionsView(
PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView
):
permission_required = "judge.rejudge_submission_lot"
def perform_action(self):
if self.request.POST.get('use_range', 'off') == 'on':
if self.request.POST.get("use_range", "off") == "on":
try:
start = int(self.request.POST.get('start'))
end = int(self.request.POST.get('end'))
start = int(self.request.POST.get("start"))
end = int(self.request.POST.get("end"))
except (KeyError, ValueError):
return HttpResponseBadRequest()
id_range = (start, end)
@ -80,11 +98,13 @@ class BaseActionSubmissionsView(PermissionRequiredMixin, ManageProblemSubmission
id_range = None
try:
languages = list(map(int, self.request.POST.getlist('language')))
languages = list(map(int, self.request.POST.getlist("language")))
except ValueError:
return HttpResponseBadRequest()
return self.generate_response(id_range, languages, self.request.POST.getlist('result'))
return self.generate_response(
id_range, languages, self.request.POST.getlist("result")
)
def generate_response(self, id_range, languages, results):
raise NotImplementedError()
@ -92,45 +112,64 @@ class BaseActionSubmissionsView(PermissionRequiredMixin, ManageProblemSubmission
class ActionSubmissionsView(BaseActionSubmissionsView):
def rejudge_response(self, id_range, languages, results):
status = rejudge_problem_filter.delay(self.object.id, id_range, languages, results)
status = rejudge_problem_filter.delay(
self.object.id, id_range, languages, results
)
return redirect_to_task_status(
status, message=_('Rejudging selected submissions for %s...') % (self.object.name,),
redirect=reverse('problem_submissions_rejudge_success', args=[self.object.code, status.id]),
status,
message=_("Rejudging selected submissions for %s...") % (self.object.name,),
redirect=reverse(
"problem_submissions_rejudge_success",
args=[self.object.code, status.id],
),
)
def download_response(self, id_range, languages, results):
if not self.request.user.is_superuser:
raise Http404
queryset = Submission.objects.filter(problem_id=self.object.id)
submissions = apply_submission_filter(queryset, id_range, languages, results)
with tempfile.SpooledTemporaryFile() as tmp:
with zipfile.ZipFile(tmp, 'w', zipfile.ZIP_DEFLATED) as archive:
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as archive:
for submission in submissions:
file_name = str(submission.id) + '_' + str(submission.user) + '.' + str(submission.language.key)
file_name = (
str(submission.id)
+ "_"
+ str(submission.user)
+ "."
+ str(submission.language.key)
)
archive.writestr(file_name, submission.source.source)
# Reset file pointer
tmp.seek(0)
# Write file data to response
response = HttpResponse(tmp.read(), content_type='application/x-zip-compressed')
response['Content-Disposition'] = 'attachment; filename="%s"' % (str(self.object.code) + '_submissions.zip')
response = HttpResponse(
tmp.read(), content_type="application/x-zip-compressed"
)
response["Content-Disposition"] = 'attachment; filename="%s"' % (
str(self.object.code) + "_submissions.zip"
)
return response
def generate_response(self, id_range, languages, results):
action = self.request.POST.get('action')
if (action == 'rejudge'):
action = self.request.POST.get("action")
if action == "rejudge":
return self.rejudge_response(id_range, languages, results)
elif (action == 'download'):
elif action == "download":
return self.download_response(id_range, languages, results)
else:
return Http404()
class PreviewActionSubmissionsView(BaseActionSubmissionsView):
def generate_response(self, id_range, languages, results):
queryset = apply_submission_filter(self.object.submission_set.all(), id_range, languages, results)
queryset = apply_submission_filter(
self.object.submission_set.all(), id_range, languages, results
)
return HttpResponse(str(queryset.count()))
@ -138,8 +177,12 @@ class RescoreAllSubmissionsView(ManageProblemSubmissionActionMixin, BaseDetailVi
def perform_action(self):
status = rescore_problem.delay(self.object.id)
return redirect_to_task_status(
status, message=_('Rescoring all submissions for %s...') % (self.object.name,),
redirect=reverse('problem_submissions_rescore_success', args=[self.object.code, status.id]),
status,
message=_("Rescoring all submissions for %s...") % (self.object.name,),
redirect=reverse(
"problem_submissions_rescore_success",
args=[self.object.code, status.id],
),
)
@ -147,15 +190,29 @@ def rejudge_success(request, problem, task_id):
count = AsyncResult(task_id).result
if not isinstance(count, int):
raise Http404()
messages.success(request, ngettext('Successfully scheduled %d submission for rejudging.',
'Successfully scheduled %d submissions for rejudging.', count) % (count,))
return HttpResponseRedirect(reverse('problem_manage_submissions', args=[problem]))
messages.success(
request,
ngettext(
"Successfully scheduled %d submission for rejudging.",
"Successfully scheduled %d submissions for rejudging.",
count,
)
% (count,),
)
return HttpResponseRedirect(reverse("problem_manage_submissions", args=[problem]))
def rescore_success(request, problem, task_id):
count = AsyncResult(task_id).result
if not isinstance(count, int):
raise Http404()
messages.success(request, ngettext('%d submission were successfully rescored.',
'%d submissions were successfully rescored.', count) % (count,))
return HttpResponseRedirect(reverse('problem_manage_submissions', args=[problem]))
messages.success(
request,
ngettext(
"%d submission were successfully rescored.",
"%d submissions were successfully rescored.",
count,
)
% (count,),
)
return HttpResponseRedirect(reverse("problem_manage_submissions", args=[problem]))

View file

@ -7,27 +7,31 @@ from judge.utils.problems import get_result_data
from judge.utils.raw_sql import join_sql_subquery
from judge.views.submission import ForceContestMixin, ProblemSubmissions
__all__ = ['RankedSubmissions', 'ContestRankedSubmission']
__all__ = ["RankedSubmissions", "ContestRankedSubmission"]
class RankedSubmissions(ProblemSubmissions):
tab = 'best_submissions_list'
tab = "best_submissions_list"
dynamic_update = False
def get_queryset(self):
if self.in_contest:
contest_join = '''INNER JOIN judge_contestsubmission AS cs ON (sub.id = cs.submission_id)
INNER JOIN judge_contestparticipation AS cp ON (cs.participation_id = cp.id)'''
points = 'cs.points'
constraint = 'AND cp.contest_id = %s'
contest_join = """INNER JOIN judge_contestsubmission AS cs ON (sub.id = cs.submission_id)
INNER JOIN judge_contestparticipation AS cp ON (cs.participation_id = cp.id)"""
points = "cs.points"
constraint = "AND cp.contest_id = %s"
else:
contest_join = ''
points = 'sub.points'
constraint = ''
queryset = super(RankedSubmissions, self).get_queryset().filter(user__is_unlisted=False)
contest_join = ""
points = "sub.points"
constraint = ""
queryset = (
super(RankedSubmissions, self)
.get_queryset()
.filter(user__is_unlisted=False)
)
join_sql_subquery(
queryset,
subquery='''
subquery="""
SELECT sub.id AS id
FROM (
SELECT sub.user_id AS uid, MAX(sub.points) AS points
@ -44,22 +48,30 @@ class RankedSubmissions(ProblemSubmissions):
ON (sub.user_id = fastest.uid AND sub.time = fastest.time) {contest_join}
WHERE sub.problem_id = %s AND {points} > 0 {constraint}
GROUP BY sub.user_id
'''.format(points=points, contest_join=contest_join, constraint=constraint),
params=[self.problem.id, self.contest.id] * 3 if self.in_contest else [self.problem.id] * 3,
alias='best_subs', join_fields=[('id', 'id')],
""".format(
points=points, contest_join=contest_join, constraint=constraint
),
params=[self.problem.id, self.contest.id] * 3
if self.in_contest
else [self.problem.id] * 3,
alias="best_subs",
join_fields=[("id", "id")],
)
if self.in_contest:
return queryset.order_by('-contest__points', 'time')
return queryset.order_by("-contest__points", "time")
else:
return queryset.order_by('-points', 'time')
return queryset.order_by("-points", "time")
def get_title(self):
return _('Best solutions for %s') % self.problem_name
return _("Best solutions for %s") % self.problem_name
def get_content_title(self):
return format_html(_('Best solutions for <a href="{1}">{0}</a>'), self.problem_name,
reverse('problem_detail', args=[self.problem.code]))
return format_html(
_('Best solutions for <a href="{1}">{0}</a>'),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
def _get_result_data(self):
return get_result_data(super(RankedSubmissions, self).get_queryset().order_by())
@ -68,22 +80,35 @@ class RankedSubmissions(ProblemSubmissions):
class ContestRankedSubmission(ForceContestMixin, RankedSubmissions):
def get_title(self):
if self.problem.is_accessible_by(self.request.user):
return _('Best solutions for %(problem)s in %(contest)s') % {
'problem': self.problem_name, 'contest': self.contest.name,
return _("Best solutions for %(problem)s in %(contest)s") % {
"problem": self.problem_name,
"contest": self.contest.name,
}
return _('Best solutions for problem %(number)s in %(contest)s') % {
'number': self.get_problem_number(self.problem), 'contest': self.contest.name,
return _("Best solutions for problem %(number)s in %(contest)s") % {
"number": self.get_problem_number(self.problem),
"contest": self.contest.name,
}
def get_content_title(self):
if self.problem.is_accessible_by(self.request.user):
return format_html(_('Best solutions for <a href="{1}">{0}</a> in <a href="{3}">{2}</a>'),
self.problem_name, reverse('problem_detail', args=[self.problem.code]),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
return format_html(_('Best solutions for problem {0} in <a href="{2}">{1}</a>'),
self.get_problem_number(self.problem), self.contest.name,
reverse('contest_view', args=[self.contest.key]))
return format_html(
_('Best solutions for <a href="{1}">{0}</a> in <a href="{3}">{2}</a>'),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
return format_html(
_('Best solutions for problem {0} in <a href="{2}">{1}</a>'),
self.get_problem_number(self.problem),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
def _get_result_data(self):
return get_result_data(Submission.objects.filter(
problem_id=self.problem.id, contest__participation__contest_id=self.contest.id))
return get_result_data(
Submission.objects.filter(
problem_id=self.problem.id,
contest__participation__contest_id=self.contest.id,
)
)

View file

@ -8,8 +8,10 @@ from django.contrib.auth.password_validation import get_default_password_validat
from django.forms import ChoiceField, ModelChoiceField
from django.shortcuts import render
from django.utils.translation import gettext, gettext_lazy as _
from registration.backends.default.views import (ActivationView as OldActivationView,
RegistrationView as OldRegistrationView)
from registration.backends.default.views import (
ActivationView as OldActivationView,
RegistrationView as OldRegistrationView,
)
from registration.forms import RegistrationForm
from sortedm2m.forms import SortedMultipleChoiceField
@ -18,101 +20,138 @@ from judge.utils.recaptcha import ReCaptchaField, ReCaptchaWidget
from judge.utils.subscription import Subscription, newsletter_id
from judge.widgets import Select2MultipleWidget, Select2Widget
valid_id = re.compile(r'^\w+$')
valid_id = re.compile(r"^\w+$")
bad_mail_regex = list(map(re.compile, settings.BAD_MAIL_PROVIDER_REGEX))
class CustomRegistrationForm(RegistrationForm):
username = forms.RegexField(regex=r'^\w+$', max_length=30, label=_('Username'),
error_messages={'invalid': _('A username must contain letters, '
'numbers, or underscores')})
timezone = ChoiceField(label=_('Timezone'), choices=TIMEZONE,
widget=Select2Widget(attrs={'style': 'width:100%'}))
language = ModelChoiceField(queryset=Language.objects.all(), label=_('Preferred language'), empty_label=None,
widget=Select2Widget(attrs={'style': 'width:100%'}))
organizations = SortedMultipleChoiceField(queryset=Organization.objects.filter(is_open=True),
label=_('Organizations'), required=False,
widget=Select2MultipleWidget(attrs={'style': 'width:100%'}))
username = forms.RegexField(
regex=r"^\w+$",
max_length=30,
label=_("Username"),
error_messages={
"invalid": _("A username must contain letters, " "numbers, or underscores")
},
)
timezone = ChoiceField(
label=_("Timezone"),
choices=TIMEZONE,
widget=Select2Widget(attrs={"style": "width:100%"}),
)
language = ModelChoiceField(
queryset=Language.objects.all(),
label=_("Preferred language"),
empty_label=None,
widget=Select2Widget(attrs={"style": "width:100%"}),
)
organizations = SortedMultipleChoiceField(
queryset=Organization.objects.filter(is_open=True),
label=_("Organizations"),
required=False,
widget=Select2MultipleWidget(attrs={"style": "width:100%"}),
)
if newsletter_id is not None:
newsletter = forms.BooleanField(label=_('Subscribe to newsletter?'), initial=True, required=False)
newsletter = forms.BooleanField(
label=_("Subscribe to newsletter?"), initial=True, required=False
)
if ReCaptchaField is not None:
captcha = ReCaptchaField(widget=ReCaptchaWidget())
def clean_organizations(self):
organizations = self.cleaned_data.get('organizations') or []
organizations = self.cleaned_data.get("organizations") or []
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if sum(org.is_open for org in organizations) > max_orgs:
raise forms.ValidationError(
_('You may not be part of more than {count} public organizations.').format(count=max_orgs))
_(
"You may not be part of more than {count} public organizations."
).format(count=max_orgs)
)
return self.cleaned_data['organizations']
return self.cleaned_data["organizations"]
def clean_email(self):
if User.objects.filter(email=self.cleaned_data['email']).exists():
raise forms.ValidationError(gettext('The email address "%s" is already taken. Only one registration '
'is allowed per address.') % self.cleaned_data['email'])
if '@' in self.cleaned_data['email']:
domain = self.cleaned_data['email'].split('@')[-1].lower()
if (domain in settings.BAD_MAIL_PROVIDERS or
any(regex.match(domain) for regex in bad_mail_regex)):
raise forms.ValidationError(gettext('Your email provider is not allowed due to history of abuse. '
'Please use a reputable email provider.'))
return self.cleaned_data['email']
if User.objects.filter(email=self.cleaned_data["email"]).exists():
raise forms.ValidationError(
gettext(
'The email address "%s" is already taken. Only one registration '
"is allowed per address."
)
% self.cleaned_data["email"]
)
if "@" in self.cleaned_data["email"]:
domain = self.cleaned_data["email"].split("@")[-1].lower()
if domain in settings.BAD_MAIL_PROVIDERS or any(
regex.match(domain) for regex in bad_mail_regex
):
raise forms.ValidationError(
gettext(
"Your email provider is not allowed due to history of abuse. "
"Please use a reputable email provider."
)
)
return self.cleaned_data["email"]
class RegistrationView(OldRegistrationView):
title = _('Registration')
title = _("Registration")
form_class = CustomRegistrationForm
template_name = 'registration/registration_form.html'
template_name = "registration/registration_form.html"
def get_context_data(self, **kwargs):
if 'title' not in kwargs:
kwargs['title'] = self.title
if "title" not in kwargs:
kwargs["title"] = self.title
tzmap = settings.TIMEZONE_MAP
kwargs['TIMEZONE_MAP'] = tzmap or 'http://momentjs.com/static/img/world.png'
kwargs['TIMEZONE_BG'] = settings.TIMEZONE_BG if tzmap else '#4E7CAD'
kwargs['password_validators'] = get_default_password_validators()
kwargs['tos_url'] = settings.TERMS_OF_SERVICE_URL
kwargs["TIMEZONE_MAP"] = tzmap or "http://momentjs.com/static/img/world.png"
kwargs["TIMEZONE_BG"] = settings.TIMEZONE_BG if tzmap else "#4E7CAD"
kwargs["password_validators"] = get_default_password_validators()
kwargs["tos_url"] = settings.TERMS_OF_SERVICE_URL
return super(RegistrationView, self).get_context_data(**kwargs)
def register(self, form):
user = super(RegistrationView, self).register(form)
profile, _ = Profile.objects.get_or_create(user=user, defaults={
'language': Language.get_default_language(),
})
profile, _ = Profile.objects.get_or_create(
user=user,
defaults={
"language": Language.get_default_language(),
},
)
cleaned_data = form.cleaned_data
profile.timezone = cleaned_data['timezone']
profile.language = cleaned_data['language']
profile.organizations.add(*cleaned_data['organizations'])
profile.timezone = cleaned_data["timezone"]
profile.language = cleaned_data["language"]
profile.organizations.add(*cleaned_data["organizations"])
profile.save()
if newsletter_id is not None and cleaned_data['newsletter']:
if newsletter_id is not None and cleaned_data["newsletter"]:
Subscription(user=user, newsletter_id=newsletter_id, subscribed=True).save()
return user
def get_initial(self, *args, **kwargs):
initial = super(RegistrationView, self).get_initial(*args, **kwargs)
initial['timezone'] = settings.DEFAULT_USER_TIME_ZONE
initial['language'] = Language.objects.get(key=settings.DEFAULT_USER_LANGUAGE)
initial["timezone"] = settings.DEFAULT_USER_TIME_ZONE
initial["language"] = Language.objects.get(key=settings.DEFAULT_USER_LANGUAGE)
return initial
class ActivationView(OldActivationView):
title = _('Registration')
template_name = 'registration/activate.html'
title = _("Registration")
template_name = "registration/activate.html"
def get_context_data(self, **kwargs):
if 'title' not in kwargs:
kwargs['title'] = self.title
if "title" not in kwargs:
kwargs["title"] = self.title
return super(ActivationView, self).get_context_data(**kwargs)
def social_auth_error(request):
return render(request, 'generic-message.html', {
'title': gettext('Authentication failure'),
'message': request.GET.get('message'),
})
return render(
request,
"generic-message.html",
{
"title": gettext("Authentication failure"),
"message": request.GET.get("message"),
},
)

View file

@ -12,7 +12,7 @@ from judge.models import Comment, Contest, Organization, Problem, Profile
def _get_user_queryset(term):
qs = Profile.objects
if term.endswith(' '):
if term.endswith(" "):
qs = qs.filter(user__username=term.strip())
else:
qs = qs.filter(user__username__icontains=term)
@ -24,18 +24,22 @@ class Select2View(BaseListView):
def get(self, request, *args, **kwargs):
self.request = request
self.term = kwargs.get('term', request.GET.get('term', ''))
self.term = kwargs.get("term", request.GET.get("term", ""))
self.object_list = self.get_queryset()
context = self.get_context_data()
return JsonResponse({
'results': [
{
'text': smart_text(self.get_name(obj)),
'id': obj.pk,
} for obj in context['object_list']],
'more': context['page_obj'].has_next(),
})
return JsonResponse(
{
"results": [
{
"text": smart_text(self.get_name(obj)),
"id": obj.pk,
}
for obj in context["object_list"]
],
"more": context["page_obj"].has_next(),
}
)
def get_name(self, obj):
return str(obj)
@ -43,7 +47,11 @@ class Select2View(BaseListView):
class UserSelect2View(Select2View):
def get_queryset(self):
return _get_user_queryset(self.term).annotate(username=F('user__username')).only('id')
return (
_get_user_queryset(self.term)
.annotate(username=F("user__username"))
.only("id")
)
def get_name(self, obj):
return obj.username
@ -56,14 +64,18 @@ class OrganizationSelect2View(Select2View):
class ProblemSelect2View(Select2View):
def get_queryset(self):
return Problem.get_visible_problems(self.request.user) \
.filter(Q(code__icontains=self.term) | Q(name__icontains=self.term)).distinct()
return (
Problem.get_visible_problems(self.request.user)
.filter(Q(code__icontains=self.term) | Q(name__icontains=self.term))
.distinct()
)
class ContestSelect2View(Select2View):
def get_queryset(self):
return Contest.get_visible_contests(self.request.user) \
.filter(Q(key__icontains=self.term) | Q(name__icontains=self.term))
return Contest.get_visible_contests(self.request.user).filter(
Q(key__icontains=self.term) | Q(name__icontains=self.term)
)
class CommentSelect2View(Select2View):
@ -80,24 +92,32 @@ class UserSearchSelect2View(BaseListView):
def get(self, request, *args, **kwargs):
self.request = request
self.kwargs = kwargs
self.term = kwargs.get('term', request.GET.get('term', ''))
self.gravatar_size = request.GET.get('gravatar_size', 128)
self.gravatar_default = request.GET.get('gravatar_default', None)
self.term = kwargs.get("term", request.GET.get("term", ""))
self.gravatar_size = request.GET.get("gravatar_size", 128)
self.gravatar_default = request.GET.get("gravatar_default", None)
self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank')
self.object_list = self.get_queryset().values_list(
"pk", "user__username", "user__email", "display_rank"
)
context = self.get_context_data()
return JsonResponse({
'results': [
{
'text': username,
'id': username,
'gravatar_url': gravatar(email, self.gravatar_size, self.gravatar_default),
'display_rank': display_rank,
} for pk, username, email, display_rank in context['object_list']],
'more': context['page_obj'].has_next(),
})
return JsonResponse(
{
"results": [
{
"text": username,
"id": username,
"gravatar_url": gravatar(
email, self.gravatar_size, self.gravatar_default
),
"display_rank": display_rank,
}
for pk, username, email, display_rank in context["object_list"]
],
"more": context["page_obj"].has_next(),
}
)
def get_name(self, obj):
return str(obj)
@ -105,53 +125,66 @@ class UserSearchSelect2View(BaseListView):
class ContestUserSearchSelect2View(UserSearchSelect2View):
def get_queryset(self):
contest = get_object_or_404(Contest, key=self.kwargs['contest'])
if not contest.is_accessible_by(self.request.user) or not contest.can_see_full_scoreboard(self.request.user):
contest = get_object_or_404(Contest, key=self.kwargs["contest"])
if not contest.is_accessible_by(
self.request.user
) or not contest.can_see_full_scoreboard(self.request.user):
raise Http404()
return Profile.objects.filter(contest_history__contest=contest,
user__username__icontains=self.term).distinct()
return Profile.objects.filter(
contest_history__contest=contest, user__username__icontains=self.term
).distinct()
class TicketUserSelect2View(UserSearchSelect2View):
def get_queryset(self):
return Profile.objects.filter(tickets__isnull=False,
user__username__icontains=self.term).distinct()
return Profile.objects.filter(
tickets__isnull=False, user__username__icontains=self.term
).distinct()
class AssigneeSelect2View(UserSearchSelect2View):
def get_queryset(self):
return Profile.objects.filter(assigned_tickets__isnull=False,
user__username__icontains=self.term).distinct()
return Profile.objects.filter(
assigned_tickets__isnull=False, user__username__icontains=self.term
).distinct()
class ChatUserSearchSelect2View(BaseListView):
paginate_by = 20
def get_queryset(self): # TODO: add block
def get_queryset(self): # TODO: add block
return _get_user_queryset(self.term)
def get(self, request, *args, **kwargs):
self.request = request
self.kwargs = kwargs
self.term = kwargs.get('term', request.GET.get('term', ''))
self.gravatar_size = request.GET.get('gravatar_size', 128)
self.gravatar_default = request.GET.get('gravatar_default', None)
self.term = kwargs.get("term", request.GET.get("term", ""))
self.gravatar_size = request.GET.get("gravatar_size", 128)
self.gravatar_default = request.GET.get("gravatar_default", None)
self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank')
self.object_list = self.get_queryset().values_list(
"pk", "user__username", "user__email", "display_rank"
)
context = self.get_context_data()
return JsonResponse({
'results': [
{
'text': username,
'id': encrypt_url(request.profile.id, pk),
'gravatar_url': gravatar(email, self.gravatar_size, self.gravatar_default),
'display_rank': display_rank,
} for pk, username, email, display_rank in context['object_list']],
'more': context['page_obj'].has_next(),
})
return JsonResponse(
{
"results": [
{
"text": username,
"id": encrypt_url(request.profile.id, pk),
"gravatar_url": gravatar(
email, self.gravatar_size, self.gravatar_default
),
"display_rank": display_rank,
}
for pk, username, email, display_rank in context["object_list"]
],
"more": context["page_obj"].has_next(),
}
)
def get_name(self, obj):
return str(obj)
return str(obj)

View file

@ -9,31 +9,50 @@ from django.shortcuts import render
from django.utils.translation import gettext as _
from judge.models import Language, Submission
from judge.utils.stats import chart_colors, get_bar_chart, get_pie_chart, highlight_colors
from judge.utils.stats import (
chart_colors,
get_bar_chart,
get_pie_chart,
highlight_colors,
)
ac_count = Count(Case(When(submission__result='AC', then=Value(1)), output_field=IntegerField()))
ac_count = Count(
Case(When(submission__result="AC", then=Value(1)), output_field=IntegerField())
)
def repeat_chain(iterable):
return chain.from_iterable(repeat(iterable))
def language_data(request, language_count=Language.objects.annotate(count=Count('submission'))):
languages = language_count.filter(count__gt=0).values('key', 'name', 'count').order_by('-count')
def language_data(
request, language_count=Language.objects.annotate(count=Count("submission"))
):
languages = (
language_count.filter(count__gt=0)
.values("key", "name", "count")
.order_by("-count")
)
num_languages = min(len(languages), settings.DMOJ_STATS_LANGUAGE_THRESHOLD)
other_count = sum(map(itemgetter('count'), languages[num_languages:]))
other_count = sum(map(itemgetter("count"), languages[num_languages:]))
return JsonResponse({
'labels': list(map(itemgetter('name'), languages[:num_languages])) + ['Other'],
'datasets': [
{
'backgroundColor': chart_colors[:num_languages] + ['#FDB45C'],
'highlightBackgroundColor': highlight_colors[:num_languages] + ['#FFC870'],
'data': list(map(itemgetter('count'), languages[:num_languages])) + [other_count],
},
],
}, safe=False)
return JsonResponse(
{
"labels": list(map(itemgetter("name"), languages[:num_languages]))
+ ["Other"],
"datasets": [
{
"backgroundColor": chart_colors[:num_languages] + ["#FDB45C"],
"highlightBackgroundColor": highlight_colors[:num_languages]
+ ["#FFC870"],
"data": list(map(itemgetter("count"), languages[:num_languages]))
+ [other_count],
},
],
},
safe=False,
)
def ac_language_data(request):
@ -42,27 +61,42 @@ def ac_language_data(request):
def status_data(request, statuses=None):
if not statuses:
statuses = (Submission.objects.values('result').annotate(count=Count('result'))
.values('result', 'count').order_by('-count'))
statuses = (
Submission.objects.values("result")
.annotate(count=Count("result"))
.values("result", "count")
.order_by("-count")
)
data = []
for status in statuses:
res = status['result']
res = status["result"]
if not res:
continue
count = status['count']
count = status["count"]
data.append((str(Submission.USER_DISPLAY_CODES[res]), count))
return JsonResponse(get_pie_chart(data), safe=False)
def ac_rate(request):
rate = CombinedExpression(ac_count / Count('submission'), '*', Value(100.0), output_field=FloatField())
data = Language.objects.annotate(total=Count('submission'), ac_rate=rate).filter(total__gt=0) \
.order_by('total').values_list('name', 'ac_rate')
rate = CombinedExpression(
ac_count / Count("submission"), "*", Value(100.0), output_field=FloatField()
)
data = (
Language.objects.annotate(total=Count("submission"), ac_rate=rate)
.filter(total__gt=0)
.order_by("total")
.values_list("name", "ac_rate")
)
return JsonResponse(get_bar_chart(list(data)))
def language(request):
return render(request, 'stats/language.html', {
'title': _('Language statistics'), 'tab': 'language',
})
return render(
request,
"stats/language.html",
{
"title": _("Language statistics"),
"tab": "language",
},
)

View file

@ -8,35 +8,43 @@ from packaging import version
from judge.models import Judge, Language, RuntimeVersion
__all__ = ['status_all', 'status_table']
__all__ = ["status_all", "status_table"]
def get_judges(request):
if request.user.is_superuser or request.user.is_staff:
return True, Judge.objects.order_by('-online', 'name')
return True, Judge.objects.order_by("-online", "name")
else:
return False, Judge.objects.filter(online=True)
def status_all(request):
see_all, judges = get_judges(request)
return render(request, 'status/judge-status.html', {
'title': _('Status'),
'judges': judges,
'see_all_judges': see_all,
})
return render(
request,
"status/judge-status.html",
{
"title": _("Status"),
"judges": judges,
"see_all_judges": see_all,
},
)
def status_table(request):
see_all, judges = get_judges(request)
return render(request, 'status/judge-status-table.html', {
'judges': judges,
'see_all_judges': see_all,
})
return render(
request,
"status/judge-status-table.html",
{
"judges": judges,
"see_all_judges": see_all,
},
)
class LatestList(list):
__slots__ = ('versions', 'is_latest')
__slots__ = ("versions", "is_latest")
def compare_version_list(x, y):
@ -61,11 +69,13 @@ def version_matrix(request):
judges = {judge.id: judge.name for judge in Judge.objects.filter(online=True)}
languages = Language.objects.filter(judges__online=True).distinct()
for runtime in RuntimeVersion.objects.filter(judge__online=True).order_by('priority'):
for runtime in RuntimeVersion.objects.filter(judge__online=True).order_by(
"priority"
):
matrix[runtime.judge_id][runtime.language_id].append(runtime)
for judge, data in six.iteritems(matrix):
name_tuple = judges[judge].rpartition('.')
name_tuple = judges[judge].rpartition(".")
groups[name_tuple[0] or name_tuple[-1]].append((judges[judge], data))
matrix = {}
@ -103,9 +113,13 @@ def version_matrix(request):
versions.is_latest = versions.versions == latest[language]
languages = sorted(languages, key=lambda lang: version.parse(lang.name))
return render(request, 'status/versions.html', {
'title': _('Version matrix'),
'judges': sorted(matrix.keys()),
'languages': languages,
'matrix': matrix,
})
return render(
request,
"status/versions.html",
{
"title": _("Version matrix"),
"judges": sorted(matrix.keys()),
"languages": languages,
"matrix": matrix,
},
)

View file

@ -50,16 +50,33 @@ from judge.utils.views import TitleMixin
def submission_related(queryset):
return queryset.select_related('user__user', 'problem', 'language') \
.only('id', 'user__user__username', 'user__display_rank', 'user__rating', 'problem__name',
'problem__code', 'problem__is_public', 'language__short_name', 'language__key', 'date', 'time', 'memory',
'points', 'result', 'status', 'case_points', 'case_total', 'current_testcase', 'contest_object')
return queryset.select_related("user__user", "problem", "language").only(
"id",
"user__user__username",
"user__display_rank",
"user__rating",
"problem__name",
"problem__code",
"problem__is_public",
"language__short_name",
"language__key",
"date",
"time",
"memory",
"points",
"result",
"status",
"case_points",
"case_total",
"current_testcase",
"contest_object",
)
class SubmissionMixin(object):
model = Submission
context_object_name = 'submission'
pk_url_kwarg = 'submission'
context_object_name = "submission"
pk_url_kwarg = "submission"
class SubmissionDetailBase(LoginRequiredMixin, TitleMixin, SubmissionMixin, DetailView):
@ -67,63 +84,76 @@ class SubmissionDetailBase(LoginRequiredMixin, TitleMixin, SubmissionMixin, Deta
submission = super(SubmissionDetailBase, self).get_object(queryset)
profile = self.request.profile
problem = submission.problem
if self.request.user.has_perm('judge.view_all_submission'):
if self.request.user.has_perm("judge.view_all_submission"):
return submission
if submission.user_id == profile.id:
return submission
if problem.is_editor(profile):
return submission
if problem.is_public or problem.testers.filter(id=profile.id).exists():
if Submission.objects.filter(user_id=profile.id, result='AC', problem_id=problem.id,
points=problem.points).exists():
if Submission.objects.filter(
user_id=profile.id,
result="AC",
problem_id=problem.id,
points=problem.points,
).exists():
return submission
if (hasattr(submission, 'contest') and
submission.contest.participation.contest.is_editable_by(self.request.user)):
if hasattr(
submission, "contest"
) and submission.contest.participation.contest.is_editable_by(
self.request.user
):
return submission
raise PermissionDenied()
def get_title(self):
submission = self.object
return _('Submission of %(problem)s by %(user)s') % {
'problem': submission.problem.translated_name(self.request.LANGUAGE_CODE),
'user': submission.user.user.username,
return _("Submission of %(problem)s by %(user)s") % {
"problem": submission.problem.translated_name(self.request.LANGUAGE_CODE),
"user": submission.user.user.username,
}
def get_content_title(self):
submission = self.object
return mark_safe(escape(_('Submission of %(problem)s by %(user)s')) % {
'problem': format_html('<a href="{0}">{1}</a>',
reverse('problem_detail', args=[
submission.problem.code]),
submission.problem.translated_name(self.request.LANGUAGE_CODE)),
'user': format_html('<a href="{0}">{1}</a>',
reverse('user_page', args=[
submission.user.user.username]),
submission.user.user.username),
})
return mark_safe(
escape(_("Submission of %(problem)s by %(user)s"))
% {
"problem": format_html(
'<a href="{0}">{1}</a>',
reverse("problem_detail", args=[submission.problem.code]),
submission.problem.translated_name(self.request.LANGUAGE_CODE),
),
"user": format_html(
'<a href="{0}">{1}</a>',
reverse("user_page", args=[submission.user.user.username]),
submission.user.user.username,
),
}
)
class SubmissionSource(SubmissionDetailBase):
template_name = 'submission/source.html'
template_name = "submission/source.html"
def get_queryset(self):
return super().get_queryset().select_related('source')
return super().get_queryset().select_related("source")
def get_context_data(self, **kwargs):
context = super(SubmissionSource, self).get_context_data(**kwargs)
submission = self.object
context['raw_source'] = submission.source.source.rstrip('\n')
context['highlighted_source'] = highlight_code(
submission.source.source, submission.language.pygments, linenos=False)
context["raw_source"] = submission.source.source.rstrip("\n")
context["highlighted_source"] = highlight_code(
submission.source.source, submission.language.pygments, linenos=False
)
return context
def make_batch(batch, cases):
result = {'id': batch, 'cases': cases}
result = {"id": batch, "cases": cases}
if batch:
result['points'] = min(map(attrgetter('points'), cases))
result['total'] = max(map(attrgetter('total'), cases))
result["points"] = min(map(attrgetter("points"), cases))
result["total"] = max(map(attrgetter("total"), cases))
return result
@ -143,33 +173,37 @@ def group_test_cases(cases):
def get_cases_data(submission):
testcases = ProblemTestCase.objects.filter(dataset=submission.problem)\
.order_by('order')
if (submission.is_pretested):
testcases = ProblemTestCase.objects.filter(dataset=submission.problem).order_by(
"order"
)
if submission.is_pretested:
testcases = testcases.filter(is_pretest=True)
files = []
for case in testcases:
if case.input_file: files.append(case.input_file)
if case.output_file: files.append(case.output_file)
if case.input_file:
files.append(case.input_file)
if case.output_file:
files.append(case.output_file)
case_data = get_problem_case(submission.problem, files)
problem_data = {}
count = 0
for case in testcases:
if case.type != 'C': continue
if case.type != "C":
continue
count += 1
problem_data[count] = {
'input': case_data[case.input_file] if case.input_file else '',
'answer': case_data[case.output_file] if case.output_file else '',
"input": case_data[case.input_file] if case.input_file else "",
"answer": case_data[case.output_file] if case.output_file else "",
}
return problem_data
class SubmissionStatus(SubmissionDetailBase):
template_name = 'submission/status.html'
template_name = "submission/status.html"
def access_testcases_in_contest(self):
contest = self.object.contest_or_none
@ -186,46 +220,48 @@ class SubmissionStatus(SubmissionDetailBase):
def get_context_data(self, **kwargs):
context = super(SubmissionStatus, self).get_context_data(**kwargs)
submission = self.object
context['last_msg'] = event.last()
context['batches'] = group_test_cases(submission.test_cases.all())
context['time_limit'] = submission.problem.time_limit
context['can_see_testcases'] = False
context["last_msg"] = event.last()
context["batches"] = group_test_cases(submission.test_cases.all())
context["time_limit"] = submission.problem.time_limit
context["can_see_testcases"] = False
contest = submission.contest_or_none
prefix_length = 0
can_see_testcases = self.access_testcases_in_contest()
if (contest is not None):
if contest is not None:
prefix_length = contest.problem.output_prefix_override
if contest is None or prefix_length > 0 or can_see_testcases:
context['cases_data'] = get_cases_data(submission)
context['can_see_testcases'] = True
context["cases_data"] = get_cases_data(submission)
context["can_see_testcases"] = True
try:
lang_limit = submission.problem.language_limits.get(
language=submission.language)
language=submission.language
)
except ObjectDoesNotExist:
pass
else:
context['time_limit'] = lang_limit.time_limit
context["time_limit"] = lang_limit.time_limit
return context
class SubmissionTestCaseQuery(SubmissionStatus):
template_name = 'submission/status-testcases.html'
template_name = "submission/status-testcases.html"
def get(self, request, *args, **kwargs):
if 'id' not in request.GET or not request.GET['id'].isdigit():
if "id" not in request.GET or not request.GET["id"].isdigit():
return HttpResponseBadRequest()
self.kwargs[self.pk_url_kwarg] = kwargs[self.pk_url_kwarg] = int(
request.GET['id'])
request.GET["id"]
)
return super(SubmissionTestCaseQuery, self).get(request, *args, **kwargs)
class SubmissionSourceRaw(SubmissionSource):
def get(self, request, *args, **kwargs):
submission = self.get_object()
return HttpResponse(submission.source.source, content_type='text/plain')
return HttpResponse(submission.source.source, content_type="text/plain")
@require_POST
@ -234,28 +270,29 @@ def abort_submission(request, submission):
# if (not request.user.is_authenticated or (submission.was_rejudged or (request.profile != submission.user)) and
# not request.user.has_perm('abort_any_submission')):
# raise PermissionDenied()
if (not request.user.is_authenticated
or not request.user.has_perm('abort_any_submission')):
if not request.user.is_authenticated or not request.user.has_perm(
"abort_any_submission"
):
raise PermissionDenied()
submission.abort()
return HttpResponseRedirect(reverse('submission_status', args=(submission.id,)))
return HttpResponseRedirect(reverse("submission_status", args=(submission.id,)))
class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
model = Submission
paginate_by = 50
show_problem = True
title = gettext_lazy('All submissions')
content_title = gettext_lazy('All submissions')
tab = 'all_submissions_list'
template_name = 'submission/list.html'
context_object_name = 'submissions'
title = gettext_lazy("All submissions")
content_title = gettext_lazy("All submissions")
tab = "all_submissions_list"
template_name = "submission/list.html"
context_object_name = "submissions"
first_page_href = None
def get_result_data(self):
result = self._get_result_data()
for category in result['categories']:
category['name'] = _(category['name'])
for category in result["categories"]:
category["name"] = _(category["name"])
return result
def _get_result_data(self):
@ -266,8 +303,11 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
@cached_property
def in_contest(self):
return self.request.user.is_authenticated and self.request.profile.current_contest is not None \
return (
self.request.user.is_authenticated
and self.request.profile.current_contest is not None
and self.request.in_contest_mode
)
@cached_property
def contest(self):
@ -276,34 +316,46 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
def _get_queryset(self):
queryset = Submission.objects.all()
use_straight_join(queryset)
queryset = submission_related(queryset.order_by('-id'))
queryset = submission_related(queryset.order_by("-id"))
if self.show_problem:
queryset = queryset.prefetch_related(Prefetch('problem__translations',
queryset=ProblemTranslation.objects.filter(
language=self.request.LANGUAGE_CODE), to_attr='_trans'))
queryset = queryset.prefetch_related(
Prefetch(
"problem__translations",
queryset=ProblemTranslation.objects.filter(
language=self.request.LANGUAGE_CODE
),
to_attr="_trans",
)
)
if self.in_contest:
queryset = queryset.filter(contest_object=self.contest)
if not self.contest.can_see_full_scoreboard(self.request.user):
queryset = queryset.filter(user=self.request.profile)
else:
queryset = queryset.select_related(
'contest_object').defer('contest_object__description')
queryset = queryset.select_related("contest_object").defer(
"contest_object__description"
)
# This is not technically correct since contest organizers *should* see these, but
# the join would be far too messy
if not self.request.user.has_perm('judge.see_private_contest'):
if not self.request.user.has_perm("judge.see_private_contest"):
# Show submissions for any contest you can edit or visible scoreboard
contest_queryset = Contest.objects.filter(Q(authors=self.request.profile) |
Q(curators=self.request.profile) |
Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE) |
Q(end_time__lt=timezone.now())).distinct()
queryset = queryset.filter(Q(user=self.request.profile) |
Q(contest_object__in=contest_queryset) |
Q(contest_object__isnull=True))
contest_queryset = Contest.objects.filter(
Q(authors=self.request.profile)
| Q(curators=self.request.profile)
| Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE)
| Q(end_time__lt=timezone.now())
).distinct()
queryset = queryset.filter(
Q(user=self.request.profile)
| Q(contest_object__in=contest_queryset)
| Q(contest_object__isnull=True)
)
if self.selected_languages:
queryset = queryset.filter(
language__in=Language.objects.filter(key__in=self.selected_languages))
language__in=Language.objects.filter(key__in=self.selected_languages)
)
if self.selected_statuses:
queryset = queryset.filter(result__in=self.selected_statuses)
@ -314,10 +366,15 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
if not self.in_contest:
join_sql_subquery(
queryset,
subquery=str(Problem.get_visible_problems(self.request.user).distinct().only('id').query),
subquery=str(
Problem.get_visible_problems(self.request.user)
.distinct()
.only("id")
.query
),
params=[],
join_fields=[('problem_id', 'id')],
alias='visible_problems',
join_fields=[("problem_id", "id")],
alias="visible_problems",
)
return queryset
@ -325,43 +382,49 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
return None
def get_all_submissions_page(self):
return reverse('all_submissions')
return reverse("all_submissions")
def get_searchable_status_codes(self):
hidden_codes = ['SC']
hidden_codes = ["SC"]
if not self.request.user.is_superuser and not self.request.user.is_staff:
hidden_codes += ['IE']
return [(key, value) for key, value in Submission.RESULT if key not in hidden_codes]
hidden_codes += ["IE"]
return [
(key, value) for key, value in Submission.RESULT if key not in hidden_codes
]
def get_context_data(self, **kwargs):
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
authenticated = self.request.user.is_authenticated
context['dynamic_update'] = False
context['show_problem'] = self.show_problem
context['completed_problem_ids'] = user_completed_ids(
self.request.profile) if authenticated else []
context['authored_problem_ids'] = user_authored_ids(
self.request.profile) if authenticated else []
context['editable_problem_ids'] = user_editable_ids(
self.request.profile) if authenticated else []
context["dynamic_update"] = False
context["show_problem"] = self.show_problem
context["completed_problem_ids"] = (
user_completed_ids(self.request.profile) if authenticated else []
)
context["authored_problem_ids"] = (
user_authored_ids(self.request.profile) if authenticated else []
)
context["editable_problem_ids"] = (
user_editable_ids(self.request.profile) if authenticated else []
)
context['all_languages'] = Language.objects.all(
).values_list('key', 'name')
context['selected_languages'] = self.selected_languages
context["all_languages"] = Language.objects.all().values_list("key", "name")
context["selected_languages"] = self.selected_languages
context['all_statuses'] = self.get_searchable_status_codes()
context['selected_statuses'] = self.selected_statuses
context["all_statuses"] = self.get_searchable_status_codes()
context["selected_statuses"] = self.selected_statuses
context['results_json'] = mark_safe(json.dumps(self.get_result_data()))
context['results_colors_json'] = mark_safe(
json.dumps(settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS))
context["results_json"] = mark_safe(json.dumps(self.get_result_data()))
context["results_colors_json"] = mark_safe(
json.dumps(settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS)
)
context['page_suffix'] = suffix = (
'?' + self.request.GET.urlencode()) if self.request.GET else ''
context['first_page_href'] = (self.first_page_href or '.') + suffix
context['my_submissions_link'] = self.get_my_submissions_page()
context['all_submissions_link'] = self.get_all_submissions_page()
context['tab'] = self.tab
context["page_suffix"] = suffix = (
("?" + self.request.GET.urlencode()) if self.request.GET else ""
)
context["first_page_href"] = (self.first_page_href or ".") + suffix
context["my_submissions_link"] = self.get_my_submissions_page()
context["all_submissions_link"] = self.get_all_submissions_page()
context["tab"] = self.tab
return context
@ -370,10 +433,10 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
if check is not None:
return check
self.selected_languages = set(request.GET.getlist('language'))
self.selected_statuses = set(request.GET.getlist('status'))
self.selected_languages = set(request.GET.getlist("language"))
self.selected_statuses = set(request.GET.getlist("status"))
if 'results' in request.GET:
if "results" in request.GET:
return JsonResponse(self.get_result_data())
return super(SubmissionsListBase, self).get(request, *args, **kwargs)
@ -381,50 +444,57 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView):
class UserMixin(object):
def get(self, request, *args, **kwargs):
if 'user' not in kwargs:
raise ImproperlyConfigured('Must pass a user')
self.profile = get_object_or_404(
Profile, user__username=kwargs['user'])
self.username = kwargs['user']
if "user" not in kwargs:
raise ImproperlyConfigured("Must pass a user")
self.profile = get_object_or_404(Profile, user__username=kwargs["user"])
self.username = kwargs["user"]
return super(UserMixin, self).get(request, *args, **kwargs)
class ConditionalUserTabMixin(object):
def get_context_data(self, **kwargs):
context = super(ConditionalUserTabMixin,
self).get_context_data(**kwargs)
context = super(ConditionalUserTabMixin, self).get_context_data(**kwargs)
if self.request.user.is_authenticated and self.request.profile == self.profile:
context['tab'] = 'my_submissions_tab'
context["tab"] = "my_submissions_tab"
else:
context['tab'] = 'user_submissions_tab'
context['tab_username'] = self.profile.user.username
context["tab"] = "user_submissions_tab"
context["tab_username"] = self.profile.user.username
return context
class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase):
def get_queryset(self):
return super(AllUserSubmissions, self).get_queryset().filter(user_id=self.profile.id)
return (
super(AllUserSubmissions, self)
.get_queryset()
.filter(user_id=self.profile.id)
)
def get_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
return _('All my submissions')
return _('All submissions by %s') % self.username
return _("All my submissions")
return _("All submissions by %s") % self.username
def get_content_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
return format_html('All my submissions')
return format_html('All submissions by <a href="{1}">{0}</a>', self.username,
reverse('user_page', args=[self.username]))
return format_html("All my submissions")
return format_html(
'All submissions by <a href="{1}">{0}</a>',
self.username,
reverse("user_page", args=[self.username]),
)
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
return reverse('all_user_submissions', kwargs={'user': self.request.user.username})
return reverse(
"all_user_submissions", kwargs={"user": self.request.user.username}
)
def get_context_data(self, **kwargs):
context = super(AllUserSubmissions, self).get_context_data(**kwargs)
context['dynamic_update'] = context['page_obj'].number == 1
context['dynamic_user_id'] = self.profile.id
context['last_msg'] = event.last()
context["dynamic_update"] = context["page_obj"].number == 1
context["dynamic_user_id"] = self.profile.id
context["last_msg"] = event.last()
return context
@ -434,16 +504,28 @@ class ProblemSubmissionsBase(SubmissionsListBase):
check_contest_in_access_check = True
def get_queryset(self):
if self.in_contest and not self.contest.contest_problems.filter(problem_id=self.problem.id).exists():
if (
self.in_contest
and not self.contest.contest_problems.filter(
problem_id=self.problem.id
).exists()
):
raise Http404()
return super(ProblemSubmissionsBase, self)._get_queryset().filter(problem_id=self.problem.id)
return (
super(ProblemSubmissionsBase, self)
._get_queryset()
.filter(problem_id=self.problem.id)
)
def get_title(self):
return _('All submissions for %s') % self.problem_name
return _("All submissions for %s") % self.problem_name
def get_content_title(self):
return format_html('All submissions for <a href="{1}">{0}</a>', self.problem_name,
reverse('problem_detail', args=[self.problem.code]))
return format_html(
'All submissions for <a href="{1}">{0}</a>',
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
def access_check_contest(self, request):
if self.in_contest and not self.contest.can_see_own_scoreboard(request.user):
@ -457,33 +539,39 @@ class ProblemSubmissionsBase(SubmissionsListBase):
self.access_check_contest(request)
def get(self, request, *args, **kwargs):
if 'problem' not in kwargs:
raise ImproperlyConfigured(_('Must pass a problem'))
self.problem = get_object_or_404(Problem, code=kwargs['problem'])
self.problem_name = self.problem.translated_name(
self.request.LANGUAGE_CODE)
if "problem" not in kwargs:
raise ImproperlyConfigured(_("Must pass a problem"))
self.problem = get_object_or_404(Problem, code=kwargs["problem"])
self.problem_name = self.problem.translated_name(self.request.LANGUAGE_CODE)
return super(ProblemSubmissionsBase, self).get(request, *args, **kwargs)
def get_all_submissions_page(self):
return reverse('chronological_submissions', kwargs={'problem': self.problem.code})
return reverse(
"chronological_submissions", kwargs={"problem": self.problem.code}
)
def get_context_data(self, **kwargs):
context = super(ProblemSubmissionsBase,
self).get_context_data(**kwargs)
context = super(ProblemSubmissionsBase, self).get_context_data(**kwargs)
if self.dynamic_update:
context['dynamic_update'] = context['page_obj'].number == 1
context['dynamic_problem_id'] = self.problem.id
context['last_msg'] = event.last()
context['best_submissions_link'] = reverse('ranked_submissions', kwargs={
'problem': self.problem.code})
context["dynamic_update"] = context["page_obj"].number == 1
context["dynamic_problem_id"] = self.problem.id
context["last_msg"] = event.last()
context["best_submissions_link"] = reverse(
"ranked_submissions", kwargs={"problem": self.problem.code}
)
return context
class ProblemSubmissions(ProblemSubmissionsBase):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
return reverse('user_submissions', kwargs={'problem': self.problem.code,
'user': self.request.user.username})
return reverse(
"user_submissions",
kwargs={
"problem": self.problem.code,
"user": self.request.user.username,
},
)
class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissions):
@ -491,7 +579,9 @@ class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissi
@cached_property
def is_own(self):
return self.request.user.is_authenticated and self.request.profile == self.profile
return (
self.request.user.is_authenticated and self.request.profile == self.profile
)
def access_check(self, request):
super(UserProblemSubmissions, self).access_check(request)
@ -500,60 +590,84 @@ class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissi
self.access_check_contest(request)
def get_queryset(self):
return super(UserProblemSubmissions, self).get_queryset().filter(user_id=self.profile.id)
return (
super(UserProblemSubmissions, self)
.get_queryset()
.filter(user_id=self.profile.id)
)
def get_title(self):
if self.is_own:
return _("My submissions for %(problem)s") % {'problem': self.problem_name}
return _("%(user)s's submissions for %(problem)s") % {'user': self.username, 'problem': self.problem_name}
return _("My submissions for %(problem)s") % {"problem": self.problem_name}
return _("%(user)s's submissions for %(problem)s") % {
"user": self.username,
"problem": self.problem_name,
}
def get_content_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
return format_html('''My submissions for <a href="{3}">{2}</a>''',
self.username, reverse(
'user_page', args=[self.username]),
self.problem_name, reverse('problem_detail', args=[self.problem.code]))
return format_html('''<a href="{1}">{0}</a>'s submissions for <a href="{3}">{2}</a>''',
self.username, reverse(
'user_page', args=[self.username]),
self.problem_name, reverse('problem_detail', args=[self.problem.code]))
return format_html(
"""My submissions for <a href="{3}">{2}</a>""",
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
return format_html(
"""<a href="{1}">{0}</a>'s submissions for <a href="{3}">{2}</a>""",
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
)
def get_context_data(self, **kwargs):
context = super(UserProblemSubmissions,
self).get_context_data(**kwargs)
context['dynamic_user_id'] = self.profile.id
context = super(UserProblemSubmissions, self).get_context_data(**kwargs)
context["dynamic_user_id"] = self.profile.id
return context
def single_submission(request, submission_id, show_problem=True):
request.no_profile_update = True
authenticated = request.user.is_authenticated
submission = get_object_or_404(submission_related(
Submission.objects.all()), id=int(submission_id))
submission = get_object_or_404(
submission_related(Submission.objects.all()), id=int(submission_id)
)
if not submission.problem.is_accessible_by(request.user):
raise Http404()
return render(request, 'submission/row.html', {
'submission': submission,
'authored_problem_ids': user_authored_ids(request.profile) if authenticated else [],
'completed_problem_ids': user_completed_ids(request.profile) if authenticated else [],
'editable_problem_ids': user_editable_ids(request.profile) if authenticated else [],
'show_problem': show_problem,
'problem_name': show_problem and submission.problem.translated_name(request.LANGUAGE_CODE),
'profile_id': request.profile.id if authenticated else 0,
})
return render(
request,
"submission/row.html",
{
"submission": submission,
"authored_problem_ids": user_authored_ids(request.profile)
if authenticated
else [],
"completed_problem_ids": user_completed_ids(request.profile)
if authenticated
else [],
"editable_problem_ids": user_editable_ids(request.profile)
if authenticated
else [],
"show_problem": show_problem,
"problem_name": show_problem
and submission.problem.translated_name(request.LANGUAGE_CODE),
"profile_id": request.profile.id if authenticated else 0,
},
)
def single_submission_query(request):
request.no_profile_update = True
if 'id' not in request.GET or not request.GET['id'].isdigit():
if "id" not in request.GET or not request.GET["id"].isdigit():
return HttpResponseBadRequest()
try:
show_problem = int(request.GET.get('show_problem', '1'))
show_problem = int(request.GET.get("show_problem", "1"))
except ValueError:
return HttpResponseBadRequest()
return single_submission(request, int(request.GET['id']), bool(show_problem))
return single_submission(request, int(request.GET["id"]), bool(show_problem))
class AllSubmissions(SubmissionsListBase):
@ -561,20 +675,22 @@ class AllSubmissions(SubmissionsListBase):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
return reverse('all_user_submissions', kwargs={'user': self.request.user.username})
return reverse(
"all_user_submissions", kwargs={"user": self.request.user.username}
)
def get_context_data(self, **kwargs):
context = super(AllSubmissions, self).get_context_data(**kwargs)
context['dynamic_update'] = context['page_obj'].number == 1
context['last_msg'] = event.last()
context['stats_update_interval'] = self.stats_update_interval
context["dynamic_update"] = context["page_obj"].number == 1
context["last_msg"] = event.last()
context["stats_update_interval"] = self.stats_update_interval
return context
def _get_result_data(self):
if self.in_contest or self.selected_languages or self.selected_statuses:
return super(AllSubmissions, self)._get_result_data()
key = 'global_submission_result_data'
key = "global_submission_result_data"
result = cache.get(key)
if result:
return result
@ -595,28 +711,42 @@ class ForceContestMixin(object):
def access_check(self, request):
super(ForceContestMixin, self).access_check(request)
if not request.user.has_perm('judge.see_private_contest'):
if not request.user.has_perm("judge.see_private_contest"):
if not self.contest.is_visible:
raise Http404()
if self.contest.start_time is not None and self.contest.start_time > timezone.now():
if (
self.contest.start_time is not None
and self.contest.start_time > timezone.now()
):
raise Http404()
def get_problem_number(self, problem):
return self.contest.contest_problems.select_related('problem').get(problem=problem).order
return (
self.contest.contest_problems.select_related("problem")
.get(problem=problem)
.order
)
def get(self, request, *args, **kwargs):
if 'contest' not in kwargs:
raise ImproperlyConfigured(_('Must pass a contest'))
self._contest = get_object_or_404(Contest, key=kwargs['contest'])
if "contest" not in kwargs:
raise ImproperlyConfigured(_("Must pass a contest"))
self._contest = get_object_or_404(Contest, key=kwargs["contest"])
return super(ForceContestMixin, self).get(request, *args, **kwargs)
class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
def get_title(self):
if self.problem.is_accessible_by(self.request.user):
return "%s's submissions for %s in %s" % (self.username, self.problem_name, self.contest.name)
return "%s's submissions for %s in %s" % (
self.username,
self.problem_name,
self.contest.name,
)
return "%s's submissions for problem %s in %s" % (
self.username, self.get_problem_number(self.problem), self.contest.name)
self.username,
self.get_problem_number(self.problem),
self.contest.name,
)
def access_check(self, request):
super(UserContestSubmissions, self).access_check(request)
@ -625,16 +755,26 @@ class UserContestSubmissions(ForceContestMixin, UserProblemSubmissions):
def get_content_title(self):
if self.problem.is_accessible_by(self.request.user):
return format_html(_('<a href="{1}">{0}</a>\'s submissions for '
'<a href="{3}">{2}</a> in <a href="{5}">{4}</a>'),
self.username, reverse(
'user_page', args=[self.username]),
self.problem_name, reverse(
'problem_detail', args=[self.problem.code]),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
return format_html(_('<a href="{1}">{0}</a>\'s submissions for '
'problem {2} in <a href="{4}">{3}</a>'),
self.username, reverse(
'user_page', args=[self.username]),
self.get_problem_number(self.problem),
self.contest.name, reverse('contest_view', args=[self.contest.key]))
return format_html(
_(
'<a href="{1}">{0}</a>\'s submissions for '
'<a href="{3}">{2}</a> in <a href="{5}">{4}</a>'
),
self.username,
reverse("user_page", args=[self.username]),
self.problem_name,
reverse("problem_detail", args=[self.problem.code]),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)
return format_html(
_(
'<a href="{1}">{0}</a>\'s submissions for '
'problem {2} in <a href="{4}">{3}</a>'
),
self.username,
reverse("user_page", args=[self.username]),
self.get_problem_number(self.problem),
self.contest.name,
reverse("contest_view", args=[self.contest.key]),
)

View file

@ -4,7 +4,12 @@ from uuid import UUID
from celery.result import AsyncResult
from django.core.exceptions import PermissionDenied
from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
from django.http import (
Http404,
HttpResponseBadRequest,
HttpResponseRedirect,
JsonResponse,
)
from django.shortcuts import render
from django.urls import reverse
from django.utils.http import is_safe_url
@ -17,14 +22,19 @@ from judge.utils.views import short_circuit_middleware
def get_task_status(task_id):
result = AsyncResult(task_id)
info = result.result
if result.state == 'PROGRESS':
return {'code': 'PROGRESS', 'done': info['done'], 'total': info['total'], 'stage': info['stage']}
elif result.state == 'SUCCESS':
return {'code': 'SUCCESS'}
elif result.state == 'FAILURE':
return {'code': 'FAILURE', 'error': str(info)}
if result.state == "PROGRESS":
return {
"code": "PROGRESS",
"done": info["done"],
"total": info["total"],
"stage": info["stage"],
}
elif result.state == "SUCCESS":
return {"code": "SUCCESS"}
elif result.state == "FAILURE":
return {"code": "FAILURE", "error": str(info)}
else:
return {'code': 'WORKING'}
return {"code": "WORKING"}
def task_status(request, task_id):
@ -33,34 +43,48 @@ def task_status(request, task_id):
except ValueError:
raise Http404()
redirect = request.GET.get('redirect')
redirect = request.GET.get("redirect")
if not is_safe_url(redirect, allowed_hosts={request.get_host()}):
redirect = None
status = get_task_status(task_id)
if status['code'] == 'SUCCESS' and redirect:
if status["code"] == "SUCCESS" and redirect:
return HttpResponseRedirect(redirect)
return render(request, 'task_status.html', {
'task_id': task_id, 'task_status': json.dumps(status),
'message': request.GET.get('message', ''), 'redirect': redirect or '',
})
return render(
request,
"task_status.html",
{
"task_id": task_id,
"task_status": json.dumps(status),
"message": request.GET.get("message", ""),
"redirect": redirect or "",
},
)
@short_circuit_middleware
def task_status_ajax(request):
if 'id' not in request.GET:
return HttpResponseBadRequest('Need to pass GET parameter "id"', content_type='text/plain')
return JsonResponse(get_task_status(request.GET['id']))
if "id" not in request.GET:
return HttpResponseBadRequest(
'Need to pass GET parameter "id"', content_type="text/plain"
)
return JsonResponse(get_task_status(request.GET["id"]))
def demo_task(request, task, message):
if not request.user.is_superuser:
raise PermissionDenied()
result = task.delay()
return redirect_to_task_status(result, message=message, redirect=reverse('home'))
return redirect_to_task_status(result, message=message, redirect=reverse("home"))
demo_success = partial(demo_task, task=success, message='Running example task that succeeds...')
demo_failure = partial(demo_task, task=failure, message='Running example task that fails...')
demo_progress = partial(demo_task, task=progress, message='Running example task that waits 10 seconds...')
demo_success = partial(
demo_task, task=success, message="Running example task that succeeds..."
)
demo_failure = partial(
demo_task, task=failure, message="Running example task that fails..."
)
demo_progress = partial(
demo_task, task=progress, message="Running example task that waits 10 seconds..."
)

View file

@ -4,8 +4,18 @@ from itertools import chain
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ImproperlyConfigured, PermissionDenied, ValidationError
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
from django.core.exceptions import (
ImproperlyConfigured,
PermissionDenied,
ValidationError,
)
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
JsonResponse,
)
from django.shortcuts import get_object_or_404
from django.template.defaultfilters import truncatechars
from django.template.loader import get_template
@ -26,90 +36,107 @@ from judge.utils.views import SingleObjectFormView, TitleMixin, paginate_query_c
from judge.views.problem import ProblemMixin
from judge.widgets import HeavyPreviewPageDownWidget
ticket_widget = (forms.Textarea() if HeavyPreviewPageDownWidget is None else
HeavyPreviewPageDownWidget(preview=reverse_lazy('ticket_preview'),
preview_timeout=1000, hide_preview_button=True))
ticket_widget = (
forms.Textarea()
if HeavyPreviewPageDownWidget is None
else HeavyPreviewPageDownWidget(
preview=reverse_lazy("ticket_preview"),
preview_timeout=1000,
hide_preview_button=True,
)
)
def add_ticket_notifications(users, author, link, ticket):
html = f"<a href=\"{link}\">{ticket.linked_item}</a>"
html = f'<a href="{link}">{ticket.linked_item}</a>'
users = set(users)
if author in users:
users.remove(author)
for user in users:
notification = Notification(owner=user,
html_link=html,
category='Ticket',
author=author)
notification = Notification(
owner=user, html_link=html, category="Ticket", author=author
)
notification.save()
class TicketForm(forms.Form):
title = forms.CharField(max_length=100, label=gettext_lazy('Ticket title'))
title = forms.CharField(max_length=100, label=gettext_lazy("Ticket title"))
body = forms.CharField(widget=ticket_widget)
def __init__(self, request, *args, **kwargs):
self.request = request
super(TicketForm, self).__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'placeholder': _('Ticket title')})
self.fields['body'].widget.attrs.update({'placeholder': _('Issue description')})
self.fields["title"].widget.attrs.update({"placeholder": _("Ticket title")})
self.fields["body"].widget.attrs.update({"placeholder": _("Issue description")})
def clean(self):
if self.request is not None and self.request.user.is_authenticated:
profile = self.request.profile
if profile.mute:
raise ValidationError(_('Your part is silent, little toad.'))
raise ValidationError(_("Your part is silent, little toad."))
return super(TicketForm, self).clean()
class NewTicketView(LoginRequiredMixin, SingleObjectFormView):
form_class = TicketForm
template_name = 'ticket/new.html'
template_name = "ticket/new.html"
def get_assignees(self):
return []
def get_form_kwargs(self):
kwargs = super(NewTicketView, self).get_form_kwargs()
kwargs['request'] = self.request
kwargs["request"] = self.request
return kwargs
def form_valid(self, form):
ticket = Ticket(user=self.request.profile, title=form.cleaned_data['title'])
ticket = Ticket(user=self.request.profile, title=form.cleaned_data["title"])
ticket.linked_item = self.object
ticket.save()
message = TicketMessage(ticket=ticket, user=ticket.user, body=form.cleaned_data['body'])
message = TicketMessage(
ticket=ticket, user=ticket.user, body=form.cleaned_data["body"]
)
message.save()
ticket.assignees.set(self.get_assignees())
link = reverse('ticket', args=[ticket.id])
link = reverse("ticket", args=[ticket.id])
add_ticket_notifications(ticket.assignees.all(), ticket.user, link, ticket)
if event.real:
event.post('tickets', {
'type': 'new-ticket', 'id': ticket.id,
'message': message.id, 'user': ticket.user_id,
'assignees': list(ticket.assignees.values_list('id', flat=True)),
})
event.post(
"tickets",
{
"type": "new-ticket",
"id": ticket.id,
"message": message.id,
"user": ticket.user_id,
"assignees": list(ticket.assignees.values_list("id", flat=True)),
},
)
return HttpResponseRedirect(link)
class NewProblemTicketView(ProblemMixin, TitleMixin, NewTicketView):
template_name = 'ticket/new_problem.html'
template_name = "ticket/new_problem.html"
def get_assignees(self):
return self.object.authors.all()
def get_title(self):
return _('New ticket for %s') % self.object.name
return _("New ticket for %s") % self.object.name
def get_content_title(self):
return mark_safe(escape(_('New ticket for %s')) %
format_html('<a href="{0}">{1}</a>', reverse('problem_detail', args=[self.object.code]),
self.object.translated_name(self.request.LANGUAGE_CODE)))
return mark_safe(
escape(_("New ticket for %s"))
% format_html(
'<a href="{0}">{1}</a>',
reverse("problem_detail", args=[self.object.code]),
self.object.translated_name(self.request.LANGUAGE_CODE),
)
)
def form_valid(self, form):
if not self.object.is_accessible_by(self.request.user):
@ -127,7 +154,7 @@ class TicketMixin(object):
def get_object(self, queryset=None):
ticket = super(TicketMixin, self).get_object(queryset)
profile_id = self.request.profile.id
if self.request.user.has_perm('judge.change_ticket'):
if self.request.user.has_perm("judge.change_ticket"):
return ticket
if ticket.user_id == profile_id:
return ticket
@ -141,39 +168,55 @@ class TicketMixin(object):
class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormView):
form_class = TicketCommentForm
template_name = 'ticket/ticket.html'
context_object_name = 'ticket'
template_name = "ticket/ticket.html"
context_object_name = "ticket"
def form_valid(self, form):
message = TicketMessage(user=self.request.profile,
body=form.cleaned_data['body'],
ticket=self.object)
message = TicketMessage(
user=self.request.profile,
body=form.cleaned_data["body"],
ticket=self.object,
)
message.save()
link = '%s#message-%d' % (reverse('ticket', args=[self.object.id]), message.id)
link = "%s#message-%d" % (reverse("ticket", args=[self.object.id]), message.id)
notify_list = list(chain(self.object.assignees.all(), [self.object.user]))
add_ticket_notifications(notify_list, message.user, link, self.object)
if event.real:
event.post('tickets', {
'type': 'ticket-message', 'id': self.object.id,
'message': message.id, 'user': self.object.user_id,
'assignees': list(self.object.assignees.values_list('id', flat=True)),
})
event.post('ticket-%d' % self.object.id, {
'type': 'ticket-message', 'message': message.id,
})
event.post(
"tickets",
{
"type": "ticket-message",
"id": self.object.id,
"message": message.id,
"user": self.object.user_id,
"assignees": list(
self.object.assignees.values_list("id", flat=True)
),
},
)
event.post(
"ticket-%d" % self.object.id,
{
"type": "ticket-message",
"message": message.id,
},
)
return HttpResponseRedirect(link)
def get_title(self):
return _('%(title)s - Ticket %(id)d') % {'title': self.object.title, 'id': self.object.id}
return _("%(title)s - Ticket %(id)d") % {
"title": self.object.title,
"id": self.object.id,
}
def get_context_data(self, **kwargs):
context = super(TicketView, self).get_context_data(**kwargs)
context['ticket_messages'] = self.object.messages.select_related('user__user')
context['assignees'] = self.object.assignees.select_related('user')
context['last_msg'] = event.last()
context["ticket_messages"] = self.object.messages.select_related("user__user")
context["assignees"] = self.object.assignees.select_related("user")
context["last_msg"] = event.last()
return context
@ -182,21 +225,32 @@ class TicketStatusChangeView(LoginRequiredMixin, TicketMixin, SingleObjectMixin,
def post(self, request, *args, **kwargs):
if self.open is None:
raise ImproperlyConfigured('Need to define open')
raise ImproperlyConfigured("Need to define open")
ticket = self.get_object()
if ticket.is_open != self.open:
ticket.is_open = self.open
ticket.save()
if event.real:
event.post('tickets', {
'type': 'ticket-status', 'id': ticket.id,
'open': self.open, 'user': ticket.user_id,
'assignees': list(ticket.assignees.values_list('id', flat=True)),
'title': ticket.title,
})
event.post('ticket-%d' % ticket.id, {
'type': 'ticket-status', 'open': self.open,
})
event.post(
"tickets",
{
"type": "ticket-status",
"id": ticket.id,
"open": self.open,
"user": ticket.user_id,
"assignees": list(
ticket.assignees.values_list("id", flat=True)
),
"title": ticket.title,
},
)
event.post(
"ticket-%d" % ticket.id,
{
"type": "ticket-status",
"open": self.open,
},
)
return HttpResponse(status=204)
@ -205,16 +259,16 @@ class TicketNotesForm(forms.Form):
class TicketNotesEditView(LoginRequiredMixin, TicketMixin, SingleObjectFormView):
template_name = 'ticket/edit-notes.html'
template_name = "ticket/edit-notes.html"
form_class = TicketNotesForm
context_object_name = 'ticket'
context_object_name = "ticket"
def get_initial(self):
return {'notes': self.get_object().notes}
return {"notes": self.get_object().notes}
def form_valid(self, form):
ticket = self.get_object()
ticket.notes = notes = form.cleaned_data['notes']
ticket.notes = notes = form.cleaned_data["notes"]
ticket.save()
if notes:
return HttpResponse(linebreaks(notes, autoescape=True))
@ -227,8 +281,8 @@ class TicketNotesEditView(LoginRequiredMixin, TicketMixin, SingleObjectFormView)
class TicketList(LoginRequiredMixin, ListView):
model = Ticket
template_name = 'ticket/list.html'
context_object_name = 'tickets'
template_name = "ticket/list.html"
context_object_name = "tickets"
paginate_by = 50
paginator_class = DiggPaginator
@ -242,32 +296,38 @@ class TicketList(LoginRequiredMixin, ListView):
@cached_property
def can_edit_all(self):
return self.request.user.has_perm('judge.change_ticket')
return self.request.user.has_perm("judge.change_ticket")
@cached_property
def filter_users(self):
return self.request.GET.getlist('user')
return self.request.GET.getlist("user")
@cached_property
def filter_assignees(self):
return self.request.GET.getlist('assignee')
return self.request.GET.getlist("assignee")
def GET_with_session(self, key):
if not self.request.GET:
return self.request.session.get(key, False)
return self.request.GET.get(key, None) == '1'
return self.request.GET.get(key, None) == "1"
def _get_queryset(self):
return Ticket.objects.select_related('user__user').prefetch_related('assignees__user').order_by('-id')
return (
Ticket.objects.select_related("user__user")
.prefetch_related("assignees__user")
.order_by("-id")
)
def get_queryset(self):
queryset = self._get_queryset()
if self.GET_with_session('own'):
if self.GET_with_session("own"):
queryset = queryset.filter(own_ticket_filter(self.profile.id))
elif not self.can_edit_all:
queryset = filter_visible_tickets(queryset, self.user, self.profile)
if self.filter_assignees:
queryset = queryset.filter(assignees__user__username__in=self.filter_assignees)
queryset = queryset.filter(
assignees__user__username__in=self.filter_assignees
)
if self.filter_users:
queryset = queryset.filter(user__user__username__in=self.filter_users)
return queryset.distinct()
@ -275,29 +335,41 @@ class TicketList(LoginRequiredMixin, ListView):
def get_context_data(self, **kwargs):
context = super(TicketList, self).get_context_data(**kwargs)
page = context['page_obj']
context['title'] = _('Tickets - Page %(number)d of %(total)d') % {
'number': page.number,
'total': page.paginator.num_pages,
page = context["page_obj"]
context["title"] = _("Tickets - Page %(number)d of %(total)d") % {
"number": page.number,
"total": page.paginator.num_pages,
}
context['can_edit_all'] = self.can_edit_all
context['filter_status'] = {
'own': self.GET_with_session('own'), 'user': self.filter_users, 'assignee': self.filter_assignees,
'user_id': json.dumps(list(Profile.objects.filter(user__username__in=self.filter_users)
.values_list('id', flat=True))),
'assignee_id': json.dumps(list(Profile.objects.filter(user__username__in=self.filter_assignees)
.values_list('id', flat=True))),
'own_id': self.profile.id if self.GET_with_session('own') else 'null',
context["can_edit_all"] = self.can_edit_all
context["filter_status"] = {
"own": self.GET_with_session("own"),
"user": self.filter_users,
"assignee": self.filter_assignees,
"user_id": json.dumps(
list(
Profile.objects.filter(
user__username__in=self.filter_users
).values_list("id", flat=True)
)
),
"assignee_id": json.dumps(
list(
Profile.objects.filter(
user__username__in=self.filter_assignees
).values_list("id", flat=True)
)
),
"own_id": self.profile.id if self.GET_with_session("own") else "null",
}
context['last_msg'] = event.last()
context["last_msg"] = event.last()
context.update(paginate_query_context(self.request))
return context
def post(self, request, *args, **kwargs):
to_update = ('own',)
to_update = ("own",)
for key in to_update:
if key in request.GET:
val = request.GET.get(key) == '1'
val = request.GET.get(key) == "1"
request.session[key] = val
else:
request.session.pop(key, None)
@ -306,38 +378,56 @@ class TicketList(LoginRequiredMixin, ListView):
class ProblemTicketListView(TicketList):
def _get_queryset(self):
problem = get_object_or_404(Problem, code=self.kwargs.get('problem'))
problem = get_object_or_404(Problem, code=self.kwargs.get("problem"))
if problem.is_editable_by(self.request.user):
return problem.tickets.order_by('-id')
return problem.tickets.order_by("-id")
elif problem.is_accessible_by(self.request.user):
return problem.tickets.filter(own_ticket_filter(self.profile.id)).order_by('-id')
return problem.tickets.filter(own_ticket_filter(self.profile.id)).order_by(
"-id"
)
raise Http404()
class TicketListDataAjax(TicketMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
try:
self.kwargs['pk'] = request.GET['id']
self.kwargs["pk"] = request.GET["id"]
except KeyError:
return HttpResponseBadRequest()
ticket = self.get_object()
message = ticket.messages.first()
return JsonResponse({
'row': get_template('ticket/row.html').render({'ticket': ticket}, request),
'notification': {
'title': _('New Ticket: %s') % ticket.title,
'body': '%s\n%s' % (_('#%(id)d, assigned to: %(users)s') % {
'id': ticket.id,
'users': (_(', ').join(ticket.assignees.values_list('user__username', flat=True)) or _('no one')),
}, truncatechars(message.body, 200)),
},
})
return JsonResponse(
{
"row": get_template("ticket/row.html").render(
{"ticket": ticket}, request
),
"notification": {
"title": _("New Ticket: %s") % ticket.title,
"body": "%s\n%s"
% (
_("#%(id)d, assigned to: %(users)s")
% {
"id": ticket.id,
"users": (
_(", ").join(
ticket.assignees.values_list(
"user__username", flat=True
)
)
or _("no one")
),
},
truncatechars(message.body, 200),
),
},
}
)
class TicketMessageDataAjax(TicketMixin, SingleObjectMixin, View):
def get(self, request, *args, **kwargs):
try:
message_id = request.GET['message']
message_id = request.GET["message"]
except KeyError:
return HttpResponseBadRequest()
ticket = self.get_object()
@ -345,10 +435,14 @@ class TicketMessageDataAjax(TicketMixin, SingleObjectMixin, View):
message = ticket.messages.get(id=message_id)
except TicketMessage.DoesNotExist:
return HttpResponseBadRequest()
return JsonResponse({
'message': get_template('ticket/message.html').render({'message': message}, request),
'notification': {
'title': _('New Ticket Message For: %s') % ticket.title,
'body': truncatechars(message.body, 200),
},
})
return JsonResponse(
{
"message": get_template("ticket/message.html").render(
{"message": message}, request
),
"notification": {
"title": _("New Ticket Message For: %s") % ticket.title,
"body": truncatechars(message.body, 200),
},
}
)

View file

@ -21,7 +21,7 @@ class TOTPView(TitleMixin, LoginRequiredMixin, FormView):
def get_form_kwargs(self):
result = super(TOTPView, self).get_form_kwargs()
result['totp_key'] = self.profile.totp_key
result["totp_key"] = self.profile.totp_key
return result
def dispatch(self, request, *args, **kwargs):
@ -35,12 +35,12 @@ class TOTPView(TitleMixin, LoginRequiredMixin, FormView):
raise NotImplementedError()
def next_page(self):
return HttpResponseRedirect(reverse('user_edit_profile'))
return HttpResponseRedirect(reverse("user_edit_profile"))
class TOTPEnableView(TOTPView):
title = _('Enable Two Factor Authentication')
template_name = 'registration/totp_enable.html'
title = _("Enable Two Factor Authentication")
template_name = "registration/totp_enable.html"
def get(self, request, *args, **kwargs):
profile = self.profile
@ -54,20 +54,22 @@ class TOTPEnableView(TOTPView):
def post(self, request, *args, **kwargs):
if not self.profile.totp_key:
return HttpResponseBadRequest('No TOTP key generated on server side?')
return HttpResponseBadRequest("No TOTP key generated on server side?")
return super(TOTPEnableView, self).post(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(TOTPEnableView, self).get_context_data(**kwargs)
context['totp_key'] = self.profile.totp_key
context['qr_code'] = self.render_qr_code(self.request.user.username, self.profile.totp_key)
context["totp_key"] = self.profile.totp_key
context["qr_code"] = self.render_qr_code(
self.request.user.username, self.profile.totp_key
)
return context
def form_valid(self, form):
self.profile.is_totp_enabled = True
self.profile.save()
# Make sure users don't get prompted to enter code right after enabling:
self.request.session['2fa_passed'] = True
self.request.session["2fa_passed"] = True
return self.next_page()
@classmethod
@ -79,15 +81,17 @@ class TOTPEnableView(TOTPView):
qr.add_data(uri)
qr.make(fit=True)
image = qr.make_image(fill_color='black', back_color='white')
image = qr.make_image(fill_color="black", back_color="white")
buf = BytesIO()
image.save(buf, format='PNG')
return 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('ascii')
image.save(buf, format="PNG")
return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode(
"ascii"
)
class TOTPDisableView(TOTPView):
title = _('Disable Two Factor Authentication')
template_name = 'registration/totp_disable.html'
title = _("Disable Two Factor Authentication")
template_name = "registration/totp_disable.html"
def check_skip(self):
if not self.profile.is_totp_enabled:
@ -102,21 +106,25 @@ class TOTPDisableView(TOTPView):
class TOTPLoginView(SuccessURLAllowedHostsMixin, TOTPView):
title = _('Perform Two Factor Authentication')
template_name = 'registration/totp_auth.html'
title = _("Perform Two Factor Authentication")
template_name = "registration/totp_auth.html"
def check_skip(self):
return not self.profile.is_totp_enabled or self.request.session.get('2fa_passed', False)
return not self.profile.is_totp_enabled or self.request.session.get(
"2fa_passed", False
)
def next_page(self):
redirect_to = self.request.GET.get('next', '')
redirect_to = self.request.GET.get("next", "")
url_is_safe = is_safe_url(
url=redirect_to,
allowed_hosts=self.get_success_url_allowed_hosts(),
require_https=self.request.is_secure(),
)
return HttpResponseRedirect((redirect_to if url_is_safe else '') or reverse('user_page'))
return HttpResponseRedirect(
(redirect_to if url_is_safe else "") or reverse("user_page")
)
def form_valid(self, form):
self.request.session['2fa_passed'] = True
self.request.session["2fa_passed"] = True
return self.next_page()

View file

@ -14,7 +14,14 @@ from django.db.models import Count, Max, Min
from django.db.models.fields import DateField
from django.db.models.functions import Cast, ExtractYear
from django.forms import Form
from django.http import Http404, HttpResponseRedirect, JsonResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponse
from django.http import (
Http404,
HttpResponseRedirect,
JsonResponse,
HttpResponseForbidden,
HttpResponseBadRequest,
HttpResponse,
)
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils import timezone
@ -36,10 +43,16 @@ from judge.utils.problems import contest_completed_ids, user_completed_ids
from judge.utils.ranker import ranker
from judge.utils.subscription import Subscription
from judge.utils.unicode import utf8text
from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, generic_message, SingleObjectFormView
from judge.utils.views import (
DiggPaginatorMixin,
QueryStringSortMixin,
TitleMixin,
generic_message,
SingleObjectFormView,
)
from .contests import ContestRanking
__all__ = ['UserPage', 'UserAboutPage', 'UserProblemsPage', 'users', 'edit_profile']
__all__ = ["UserPage", "UserAboutPage", "UserProblemsPage", "users", "edit_profile"]
def remap_keys(iterable, mapping):
@ -48,16 +61,16 @@ def remap_keys(iterable, mapping):
class UserMixin(object):
model = Profile
slug_field = 'user__username'
slug_url_kwarg = 'user'
context_object_name = 'user'
slug_field = "user__username"
slug_url_kwarg = "user"
context_object_name = "user"
def render_to_response(self, context, **response_kwargs):
return super(UserMixin, self).render_to_response(context, **response_kwargs)
class UserPage(TitleMixin, UserMixin, DetailView):
template_name = 'user/user-base.html'
template_name = "user/user-base.html"
def get_object(self, queryset=None):
if self.kwargs.get(self.slug_url_kwarg, None) is None:
@ -71,12 +84,18 @@ class UserPage(TitleMixin, UserMixin, DetailView):
try:
return super(UserPage, self).dispatch(request, *args, **kwargs)
except Http404:
return generic_message(request, _('No such user'), _('No user handle "%s".') %
self.kwargs.get(self.slug_url_kwarg, None))
return generic_message(
request,
_("No such user"),
_('No user handle "%s".') % self.kwargs.get(self.slug_url_kwarg, None),
)
def get_title(self):
return (_('My account') if self.request.user == self.object.user else
_('User %s') % self.object.user.username)
return (
_("My account")
if self.request.user == self.object.user
else _("User %s") % self.object.user.username
)
def get_content_title(self):
username = self.object.user.username
@ -92,8 +111,11 @@ class UserPage(TitleMixin, UserMixin, DetailView):
@cached_property
def in_contest(self):
return self.profile is not None and self.profile.current_contest is not None \
return (
self.profile is not None
and self.profile.current_contest is not None
and self.request.in_contest_mode
)
def get_completed_problems(self):
if self.in_contest:
@ -104,29 +126,49 @@ class UserPage(TitleMixin, UserMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(UserPage, self).get_context_data(**kwargs)
context['followed'] = Friend.is_friend(self.request.profile, self.object)
context['hide_solved'] = int(self.hide_solved)
context['authored'] = self.object.authored_problems.filter(is_public=True, is_organization_private=False) \
.order_by('code')
context["followed"] = Friend.is_friend(self.request.profile, self.object)
context["hide_solved"] = int(self.hide_solved)
context["authored"] = self.object.authored_problems.filter(
is_public=True, is_organization_private=False
).order_by("code")
rating = self.object.ratings.order_by('-contest__end_time')[:1]
context['rating'] = rating[0] if rating else None
rating = self.object.ratings.order_by("-contest__end_time")[:1]
context["rating"] = rating[0] if rating else None
context['rank'] = Profile.objects.filter(
is_unlisted=False, performance_points__gt=self.object.performance_points,
).count() + 1
context["rank"] = (
Profile.objects.filter(
is_unlisted=False,
performance_points__gt=self.object.performance_points,
).count()
+ 1
)
if rating:
context['rating_rank'] = Profile.objects.filter(
is_unlisted=False, rating__gt=self.object.rating,
).count() + 1
context['rated_users'] = Profile.objects.filter(is_unlisted=False, rating__isnull=False).count()
context.update(self.object.ratings.aggregate(min_rating=Min('rating'), max_rating=Max('rating'),
contests=Count('contest')))
context["rating_rank"] = (
Profile.objects.filter(
is_unlisted=False,
rating__gt=self.object.rating,
).count()
+ 1
)
context["rated_users"] = Profile.objects.filter(
is_unlisted=False, rating__isnull=False
).count()
context.update(
self.object.ratings.aggregate(
min_rating=Min("rating"),
max_rating=Max("rating"),
contests=Count("contest"),
)
)
return context
def get(self, request, *args, **kwargs):
self.hide_solved = request.GET.get('hide_solved') == '1' if 'hide_solved' in request.GET else False
self.hide_solved = (
request.GET.get("hide_solved") == "1"
if "hide_solved" in request.GET
else False
)
return super(UserPage, self).get(request, *args, **kwargs)
@ -134,20 +176,27 @@ EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
class UserAboutPage(UserPage):
template_name = 'user/user-about.html'
template_name = "user/user-about.html"
def get_awards(self, ratings):
result = {}
sorted_ratings = sorted(ratings,
key=lambda x: (x.rank, -x.contest.end_time.timestamp()))
sorted_ratings = sorted(
ratings, key=lambda x: (x.rank, -x.contest.end_time.timestamp())
)
result['medals'] = [{
'label': rating.contest.name,
'ranking': rating.rank,
'link': reverse('contest_ranking', args=(rating.contest.key,)) + '#!' + self.object.username,
'date': date_format(rating.contest.end_time, _('M j, Y')),
} for rating in sorted_ratings if rating.rank <= 3]
result["medals"] = [
{
"label": rating.contest.name,
"ranking": rating.rank,
"link": reverse("contest_ranking", args=(rating.contest.key,))
+ "#!"
+ self.object.username,
"date": date_format(rating.contest.end_time, _("M j, Y")),
}
for rating in sorted_ratings
if rating.rank <= 3
]
num_awards = 0
for i in result:
@ -160,60 +209,86 @@ class UserAboutPage(UserPage):
def get_context_data(self, **kwargs):
context = super(UserAboutPage, self).get_context_data(**kwargs)
ratings = context['ratings'] = self.object.ratings.order_by('-contest__end_time').select_related('contest') \
.defer('contest__description')
context['rating_data'] = mark_safe(json.dumps([{
'label': rating.contest.name,
'rating': rating.rating,
'ranking': rating.rank,
'link': reverse('contest_ranking', args=(rating.contest.key,)),
'timestamp': (rating.contest.end_time - EPOCH).total_seconds() * 1000,
'date': date_format(timezone.localtime(rating.contest.end_time), _('M j, Y, G:i')),
'class': rating_class(rating.rating),
'height': '%.3fem' % rating_progress(rating.rating),
} for rating in ratings]))
context['awards'] = self.get_awards(ratings)
if ratings:
user_data = self.object.ratings.aggregate(Min('rating'), Max('rating'))
global_data = Rating.objects.aggregate(Min('rating'), Max('rating'))
min_ever, max_ever = global_data['rating__min'], global_data['rating__max']
min_user, max_user = user_data['rating__min'], user_data['rating__max']
delta = max_user - min_user
ratio = (max_ever - max_user) / (max_ever - min_ever) if max_ever != min_ever else 1.0
context['max_graph'] = max_user + ratio * delta
context['min_graph'] = min_user + ratio * delta - delta
submissions = (
self.object.submission_set
.annotate(date_only=Cast('date', DateField()))
.values('date_only').annotate(cnt=Count('id'))
ratings = context["ratings"] = (
self.object.ratings.order_by("-contest__end_time")
.select_related("contest")
.defer("contest__description")
)
context["rating_data"] = mark_safe(
json.dumps(
[
{
"label": rating.contest.name,
"rating": rating.rating,
"ranking": rating.rank,
"link": reverse("contest_ranking", args=(rating.contest.key,)),
"timestamp": (rating.contest.end_time - EPOCH).total_seconds()
* 1000,
"date": date_format(
timezone.localtime(rating.contest.end_time),
_("M j, Y, G:i"),
),
"class": rating_class(rating.rating),
"height": "%.3fem" % rating_progress(rating.rating),
}
for rating in ratings
]
)
)
context["awards"] = self.get_awards(ratings)
if ratings:
user_data = self.object.ratings.aggregate(Min("rating"), Max("rating"))
global_data = Rating.objects.aggregate(Min("rating"), Max("rating"))
min_ever, max_ever = global_data["rating__min"], global_data["rating__max"]
min_user, max_user = user_data["rating__min"], user_data["rating__max"]
delta = max_user - min_user
ratio = (
(max_ever - max_user) / (max_ever - min_ever)
if max_ever != min_ever
else 1.0
)
context["max_graph"] = max_user + ratio * delta
context["min_graph"] = min_user + ratio * delta - delta
submissions = (
self.object.submission_set.annotate(date_only=Cast("date", DateField()))
.values("date_only")
.annotate(cnt=Count("id"))
)
context["submission_data"] = mark_safe(
json.dumps(
{
date_counts["date_only"].isoformat(): date_counts["cnt"]
for date_counts in submissions
}
)
)
context["submission_metadata"] = mark_safe(
json.dumps(
{
"min_year": (
self.object.submission_set.annotate(
year_only=ExtractYear("date")
).aggregate(min_year=Min("year_only"))["min_year"]
),
}
)
)
context['submission_data'] = mark_safe(json.dumps({
date_counts['date_only'].isoformat(): date_counts['cnt'] for date_counts in submissions
}))
context['submission_metadata'] = mark_safe(json.dumps({
'min_year': (
self.object.submission_set
.annotate(year_only=ExtractYear('date'))
.aggregate(min_year=Min('year_only'))['min_year']
),
}))
return context
# follow/unfollow user
def post(self, request, user, *args, **kwargs):
try:
if not request.profile:
raise Exception('You have to login')
if (request.profile.username == user):
raise Exception('Cannot make friend with yourself')
raise Exception("You have to login")
if request.profile.username == user:
raise Exception("Cannot make friend with yourself")
following_profile = Profile.objects.get(user__username=user)
Friend.toggle_friend(request.profile, following_profile)
finally:
@ -221,60 +296,86 @@ class UserAboutPage(UserPage):
class UserProblemsPage(UserPage):
template_name = 'user/user-problems.html'
template_name = "user/user-problems.html"
def get_context_data(self, **kwargs):
context = super(UserProblemsPage, self).get_context_data(**kwargs)
result = Submission.objects.filter(user=self.object, points__gt=0, problem__is_public=True,
problem__is_organization_private=False) \
.exclude(problem__in=self.get_completed_problems() if self.hide_solved else []) \
.values('problem__id', 'problem__code', 'problem__name', 'problem__points', 'problem__group__full_name') \
.distinct().annotate(points=Max('points')).order_by('problem__group__full_name', 'problem__code')
result = (
Submission.objects.filter(
user=self.object,
points__gt=0,
problem__is_public=True,
problem__is_organization_private=False,
)
.exclude(
problem__in=self.get_completed_problems() if self.hide_solved else []
)
.values(
"problem__id",
"problem__code",
"problem__name",
"problem__points",
"problem__group__full_name",
)
.distinct()
.annotate(points=Max("points"))
.order_by("problem__group__full_name", "problem__code")
)
def process_group(group, problems_iter):
problems = list(problems_iter)
points = sum(map(itemgetter('points'), problems))
return {'name': group, 'problems': problems, 'points': points}
points = sum(map(itemgetter("points"), problems))
return {"name": group, "problems": problems, "points": points}
context['best_submissions'] = [
process_group(group, problems) for group, problems in itertools.groupby(
remap_keys(result, {
'problem__code': 'code', 'problem__name': 'name', 'problem__points': 'total',
'problem__group__full_name': 'group',
}), itemgetter('group'))
context["best_submissions"] = [
process_group(group, problems)
for group, problems in itertools.groupby(
remap_keys(
result,
{
"problem__code": "code",
"problem__name": "name",
"problem__points": "total",
"problem__group__full_name": "group",
},
),
itemgetter("group"),
)
]
breakdown, has_more = get_pp_breakdown(self.object, start=0, end=10)
context['pp_breakdown'] = breakdown
context['pp_has_more'] = has_more
context["pp_breakdown"] = breakdown
context["pp_has_more"] = has_more
return context
class UserPerformancePointsAjax(UserProblemsPage):
template_name = 'user/pp-table-body.html'
template_name = "user/pp-table-body.html"
def get_context_data(self, **kwargs):
context = super(UserPerformancePointsAjax, self).get_context_data(**kwargs)
try:
start = int(self.request.GET.get('start', 0))
end = int(self.request.GET.get('end', settings.DMOJ_PP_ENTRIES))
start = int(self.request.GET.get("start", 0))
end = int(self.request.GET.get("end", settings.DMOJ_PP_ENTRIES))
if start < 0 or end < 0 or start > end:
raise ValueError
except ValueError:
start, end = 0, 100
breakdown, self.has_more = get_pp_breakdown(self.object, start=start, end=end)
context['pp_breakdown'] = breakdown
context["pp_breakdown"] = breakdown
return context
def get(self, request, *args, **kwargs):
httpresp = super(UserPerformancePointsAjax, self).get(request, *args, **kwargs)
httpresp.render()
return JsonResponse({
'results': utf8text(httpresp.content),
'has_more': self.has_more,
})
return JsonResponse(
{
"results": utf8text(httpresp.content),
"has_more": self.has_more,
}
)
@login_required
@ -282,26 +383,39 @@ def edit_profile(request):
profile = Profile.objects.get(user=request.user)
if profile.mute:
raise Http404()
if request.method == 'POST':
if request.method == "POST":
form = ProfileForm(request.POST, instance=profile, user=request.user)
if form.is_valid():
with transaction.atomic(), revisions.create_revision():
form.save()
revisions.set_user(request.user)
revisions.set_comment(_('Updated on site'))
revisions.set_comment(_("Updated on site"))
if newsletter_id is not None:
try:
subscription = Subscription.objects.get(user=request.user, newsletter_id=newsletter_id)
subscription = Subscription.objects.get(
user=request.user, newsletter_id=newsletter_id
)
except Subscription.DoesNotExist:
if form.cleaned_data['newsletter']:
Subscription(user=request.user, newsletter_id=newsletter_id, subscribed=True).save()
if form.cleaned_data["newsletter"]:
Subscription(
user=request.user,
newsletter_id=newsletter_id,
subscribed=True,
).save()
else:
if subscription.subscribed != form.cleaned_data['newsletter']:
subscription.update(('unsubscribe', 'subscribe')[form.cleaned_data['newsletter']])
if subscription.subscribed != form.cleaned_data["newsletter"]:
subscription.update(
("unsubscribe", "subscribe")[
form.cleaned_data["newsletter"]
]
)
perm = Permission.objects.get(codename='test_site', content_type=ContentType.objects.get_for_model(Profile))
if form.cleaned_data['test_site']:
perm = Permission.objects.get(
codename="test_site",
content_type=ContentType.objects.get_for_model(Profile),
)
if form.cleaned_data["test_site"]:
request.user.user_permissions.add(perm)
else:
request.user.user_permissions.remove(perm)
@ -311,34 +425,42 @@ def edit_profile(request):
form = ProfileForm(instance=profile, user=request.user)
if newsletter_id is not None:
try:
subscription = Subscription.objects.get(user=request.user, newsletter_id=newsletter_id)
subscription = Subscription.objects.get(
user=request.user, newsletter_id=newsletter_id
)
except Subscription.DoesNotExist:
form.fields['newsletter'].initial = False
form.fields["newsletter"].initial = False
else:
form.fields['newsletter'].initial = subscription.subscribed
form.fields['test_site'].initial = request.user.has_perm('judge.test_site')
form.fields["newsletter"].initial = subscription.subscribed
form.fields["test_site"].initial = request.user.has_perm("judge.test_site")
tzmap = settings.TIMEZONE_MAP
print(settings.REGISTER_NAME_URL)
return render(request, 'user/edit-profile.html', {
'edit_name_url': settings.REGISTER_NAME_URL,
'require_staff_2fa': settings.DMOJ_REQUIRE_STAFF_2FA,
'form': form, 'title': _('Edit profile'), 'profile': profile,
'has_math_config': bool(settings.MATHOID_URL),
'TIMEZONE_MAP': tzmap or 'http://momentjs.com/static/img/world.png',
'TIMEZONE_BG': settings.TIMEZONE_BG if tzmap else '#4E7CAD',
})
return render(
request,
"user/edit-profile.html",
{
"edit_name_url": settings.REGISTER_NAME_URL,
"require_staff_2fa": settings.DMOJ_REQUIRE_STAFF_2FA,
"form": form,
"title": _("Edit profile"),
"profile": profile,
"has_math_config": bool(settings.MATHOID_URL),
"TIMEZONE_MAP": tzmap or "http://momentjs.com/static/img/world.png",
"TIMEZONE_BG": settings.TIMEZONE_BG if tzmap else "#4E7CAD",
},
)
class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
model = Profile
title = gettext_lazy('Leaderboard')
context_object_name = 'users'
template_name = 'user/list.html'
title = gettext_lazy("Leaderboard")
context_object_name = "users"
template_name = "user/list.html"
paginate_by = 100
all_sorts = frozenset(('points', 'problem_count', 'rating', 'performance_points'))
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
default_desc = all_sorts
default_sort = '-performance_points'
default_sort = "-performance_points"
def filter_friend_queryset(self, queryset):
friends = list(self.request.profile.get_friends())
@ -346,18 +468,30 @@ class UserList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ListView):
return ret
def get_queryset(self):
ret = Profile.objects.filter(is_unlisted=False).order_by(self.order, 'id').select_related('user') \
.only('display_rank', 'user__username', 'points', 'rating', 'performance_points',
'problem_count')
ret = (
Profile.objects.filter(is_unlisted=False)
.order_by(self.order, "id")
.select_related("user")
.only(
"display_rank",
"user__username",
"points",
"rating",
"performance_points",
"problem_count",
)
)
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)
return ret
def get_context_data(self, **kwargs):
context = super(UserList, self).get_context_data(**kwargs)
context['users'] = ranker(context['users'], rank=self.paginate_by * (context['page_obj'].number - 1))
context['first_page_href'] = '.'
context["users"] = ranker(
context["users"], rank=self.paginate_by * (context["page_obj"].number - 1)
)
context["first_page_href"] = "."
context.update(self.get_sort_context())
context.update(self.get_sort_paginate_context())
return context
@ -378,27 +512,36 @@ def users(request):
if request.in_contest_mode:
participation = request.profile.current_contest
contest = participation.contest
return FixedContestRanking.as_view(contest=contest)(request, contest=contest.key)
return FixedContestRanking.as_view(contest=contest)(
request, contest=contest.key
)
return user_list_view(request)
def user_ranking_redirect(request):
try:
username = request.GET['handle']
username = request.GET["handle"]
except KeyError:
raise Http404()
user = get_object_or_404(Profile, user__username=username)
rank = Profile.objects.filter(is_unlisted=False, performance_points__gt=user.performance_points).count()
rank = Profile.objects.filter(
is_unlisted=False, performance_points__gt=user.performance_points
).count()
rank += Profile.objects.filter(
is_unlisted=False, performance_points__exact=user.performance_points, id__lt=user.id,
is_unlisted=False,
performance_points__exact=user.performance_points,
id__lt=user.id,
).count()
page = rank // UserList.paginate_by
return HttpResponseRedirect('%s%s#!%s' % (reverse('user_list'), '?page=%d' % (page + 1) if page else '', username))
return HttpResponseRedirect(
"%s%s#!%s"
% (reverse("user_list"), "?page=%d" % (page + 1) if page else "", username)
)
class UserLogoutView(TitleMixin, TemplateView):
template_name = 'registration/logout.html'
title = 'You have been successfully logged out.'
template_name = "registration/logout.html"
title = "You have been successfully logged out."
def post(self, request, *args, **kwargs):
auth_logout(request)
@ -406,8 +549,8 @@ class UserLogoutView(TitleMixin, TemplateView):
class ImportUsersView(TitleMixin, TemplateView):
template_name = 'user/import/index.html'
title = _('Import Users')
template_name = "user/import/index.html"
title = _("Import Users")
def get(self, *args, **kwargs):
if self.request.user.is_superuser:
@ -416,43 +559,38 @@ class ImportUsersView(TitleMixin, TemplateView):
def import_users_post_file(request):
if not request.user.is_superuser or request.method != 'POST':
if not request.user.is_superuser or request.method != "POST":
return HttpResponseForbidden()
users = import_users.csv_to_dict(request.FILES['csv_file'])
users = import_users.csv_to_dict(request.FILES["csv_file"])
if not users:
return JsonResponse({
'done': False,
'msg': 'No valid row found. Make sure row containing username.'
})
table_html = render_to_string('user/import/table_csv.html', {
'data': users
})
return JsonResponse({
'done': True,
'html': table_html,
'data': users
})
return JsonResponse(
{
"done": False,
"msg": "No valid row found. Make sure row containing username.",
}
)
table_html = render_to_string("user/import/table_csv.html", {"data": users})
return JsonResponse({"done": True, "html": table_html, "data": users})
def import_users_submit(request):
import json
if not request.user.is_superuser or request.method != 'POST':
if not request.user.is_superuser or request.method != "POST":
return HttpResponseForbidden()
users = json.loads(request.body)['users']
users = json.loads(request.body)["users"]
log = import_users.import_users(users)
return JsonResponse({
'msg': log
})
return JsonResponse({"msg": log})
def sample_import_users(request):
if not request.user.is_superuser or request.method != 'GET':
if not request.user.is_superuser or request.method != "GET":
return HttpResponseForbidden()
filename = 'import_sample.csv'
content = ','.join(import_users.fields) + '\n' + ','.join(import_users.descriptions)
response = HttpResponse(content, content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename={0}'.format(filename)
return response
filename = "import_sample.csv"
content = ",".join(import_users.fields) + "\n" + ",".join(import_users.descriptions)
response = HttpResponse(content, content_type="text/plain")
response["Content-Disposition"] = "attachment; filename={0}".format(filename)
return response

View file

@ -5,17 +5,17 @@ from judge.models import VolunteerProblemVote, Problem, ProblemType
def vote_problem(request):
if not request.user or not request.user.has_perm('judge.suggest_problem_changes'):
if not request.user or not request.user.has_perm("judge.suggest_problem_changes"):
return HttpResponseBadRequest()
if not request.method == 'POST':
if not request.method == "POST":
return HttpResponseBadRequest()
try:
types_id = request.POST.getlist('types[]')
types_id = request.POST.getlist("types[]")
types = ProblemType.objects.filter(id__in=types_id)
problem = Problem.objects.get(code=request.POST['problem'])
knowledge_points = request.POST['knowledge_points']
thinking_points = request.POST['thinking_points']
feedback = request.POST['feedback']
problem = Problem.objects.get(code=request.POST["problem"])
knowledge_points = request.POST["knowledge_points"]
thinking_points = request.POST["thinking_points"]
feedback = request.POST["feedback"]
except Exception as e:
return HttpResponseBadRequest()
@ -23,7 +23,7 @@ def vote_problem(request):
vote, _ = VolunteerProblemVote.objects.get_or_create(
voter=request.profile,
problem=problem,
defaults={'knowledge_points': 0, 'thinking_points': 0},
defaults={"knowledge_points": 0, "thinking_points": 0},
)
vote.knowledge_points = knowledge_points
vote.thinking_points = thinking_points

View file

@ -2,60 +2,90 @@ import requests
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
HttpResponseRedirect,
)
from django.utils.translation import gettext as _
from django.views.generic import View
from judge.models import Submission
__all__ = ['rejudge_submission', 'DetectTimezone']
__all__ = ["rejudge_submission", "DetectTimezone"]
@login_required
def rejudge_submission(request):
if request.method != 'POST' or not request.user.has_perm('judge.rejudge_submission') or \
not request.user.has_perm('judge.edit_own_problem'):
if (
request.method != "POST"
or not request.user.has_perm("judge.rejudge_submission")
or not request.user.has_perm("judge.edit_own_problem")
):
return HttpResponseForbidden()
if 'id' not in request.POST or not request.POST['id'].isdigit():
if "id" not in request.POST or not request.POST["id"].isdigit():
return HttpResponseBadRequest()
try:
submission = Submission.objects.get(id=request.POST['id'])
submission = Submission.objects.get(id=request.POST["id"])
except Submission.DoesNotExist:
return HttpResponseBadRequest()
if not request.user.has_perm('judge.edit_all_problem') and \
not submission.problem.is_editor(request.profile):
if not request.user.has_perm(
"judge.edit_all_problem"
) and not submission.problem.is_editor(request.profile):
return HttpResponseForbidden()
submission.judge(rejudge=True)
redirect = request.POST.get('path', None)
redirect = request.POST.get("path", None)
return HttpResponseRedirect(redirect) if redirect else HttpResponse('success', content_type='text/plain')
return (
HttpResponseRedirect(redirect)
if redirect
else HttpResponse("success", content_type="text/plain")
)
class DetectTimezone(View):
def askgeo(self, lat, long):
if not hasattr(settings, 'ASKGEO_ACCOUNT_ID') or not hasattr(settings, 'ASKGEO_ACCOUNT_API_KEY'):
if not hasattr(settings, "ASKGEO_ACCOUNT_ID") or not hasattr(
settings, "ASKGEO_ACCOUNT_API_KEY"
):
raise ImproperlyConfigured()
data = requests.get('http://api.askgeo.com/v1/%s/%s/query.json?databases=TimeZone&points=%f,%f' %
(settings.ASKGEO_ACCOUNT_ID, settings.ASKGEO_ACCOUNT_API_KEY, lat, long)).json()
data = requests.get(
"http://api.askgeo.com/v1/%s/%s/query.json?databases=TimeZone&points=%f,%f"
% (settings.ASKGEO_ACCOUNT_ID, settings.ASKGEO_ACCOUNT_API_KEY, lat, long)
).json()
try:
return HttpResponse(data['data'][0]['TimeZone']['TimeZoneId'], content_type='text/plain')
return HttpResponse(
data["data"][0]["TimeZone"]["TimeZoneId"], content_type="text/plain"
)
except (IndexError, KeyError):
return HttpResponse(_('Invalid upstream data: %s') % data, content_type='text/plain', status=500)
return HttpResponse(
_("Invalid upstream data: %s") % data,
content_type="text/plain",
status=500,
)
def geonames(self, lat, long):
if not hasattr(settings, 'GEONAMES_USERNAME'):
if not hasattr(settings, "GEONAMES_USERNAME"):
raise ImproperlyConfigured()
data = requests.get('http://api.geonames.org/timezoneJSON?lat=%f&lng=%f&username=%s' %
(lat, long, settings.GEONAMES_USERNAME)).json()
data = requests.get(
"http://api.geonames.org/timezoneJSON?lat=%f&lng=%f&username=%s"
% (lat, long, settings.GEONAMES_USERNAME)
).json()
try:
return HttpResponse(data['timezoneId'], content_type='text/plain')
return HttpResponse(data["timezoneId"], content_type="text/plain")
except KeyError:
return HttpResponse(_('Invalid upstream data: %s') % data, content_type='text/plain', status=500)
return HttpResponse(
_("Invalid upstream data: %s") % data,
content_type="text/plain",
status=500,
)
def default(self, lat, long):
raise Http404()
@ -63,10 +93,11 @@ class DetectTimezone(View):
def get(self, request, *args, **kwargs):
backend = settings.TIMEZONE_DETECT_BACKEND
try:
lat, long = float(request.GET['lat']), float(request.GET['long'])
lat, long = float(request.GET["lat"]), float(request.GET["long"])
except (ValueError, KeyError):
return HttpResponse(_('Bad latitude or longitude'), content_type='text/plain', status=404)
return {
'askgeo': self.askgeo,
'geonames': self.geonames,
}.get(backend, self.default)(lat, long)
return HttpResponse(
_("Bad latitude or longitude"), content_type="text/plain", status=404
)
return {"askgeo": self.askgeo, "geonames": self.geonames,}.get(
backend, self.default
)(lat, long)