Reformat using black
This commit is contained in:
parent
efee4ad081
commit
a87fb49918
221 changed files with 19127 additions and 7310 deletions
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
@ -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)
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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"),
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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]),
|
||||
)
|
||||
|
|
|
@ -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..."
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue