NDOJ/judge/views/organization.py
2022-09-24 23:50:26 -05:00

1129 lines
38 KiB
Python

from itertools import chain
from django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Count, Q, Value, BooleanField
from django.db.utils import ProgrammingError
from django.forms import Form, modelformset_factory
from django.http import (
Http404,
HttpResponsePermanentRedirect,
HttpResponseRedirect,
HttpResponseBadRequest,
)
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils import timezone
from django.utils.html import format_html
from django.utils.functional import cached_property
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,
CreateView,
)
from django.views.generic.detail import (
SingleObjectMixin,
SingleObjectTemplateResponseMixin,
)
from django.core.paginator import Paginator
from reversion import revisions
from judge.forms import (
EditOrganizationForm,
AddOrganizationForm,
AddOrganizationMemberForm,
OrganizationBlogForm,
OrganizationAdminBlogForm,
EditOrganizationContestForm,
ContestProblemFormSet,
AddOrganizationContestForm,
)
from judge.models import (
BlogPost,
Comment,
Organization,
OrganizationRequest,
Problem,
Profile,
Contest,
Notification,
ContestProblem,
)
from judge import event_poster as event
from judge.utils.ranker import ranker
from judge.utils.views import (
TitleMixin,
generic_message,
QueryStringSortMixin,
DiggPaginatorMixin,
)
from judge.utils.problems import user_attempted_ids
from judge.views.problem import ProblemList
from judge.views.contests import ContestList
from judge.views.submission import AllSubmissions, SubmissionsListBase
__all__ = [
"OrganizationList",
"OrganizationHome",
"OrganizationUsers",
"OrganizationProblems",
"OrganizationContests",
"OrganizationMembershipChange",
"JoinOrganization",
"LeaveOrganization",
"EditOrganization",
"RequestJoinOrganization",
"OrganizationRequestDetail",
"OrganizationRequestView",
"OrganizationRequestLog",
"KickUserWidgetView",
]
class OrganizationBase(object):
def can_edit_organization(self, org=None):
if org is None:
org = self.object
if not self.request.user.is_authenticated:
return False
profile_id = self.request.profile.id
return (
org.admins.filter(id=profile_id).exists()
or org.registrant_id == profile_id
or self.request.user.is_superuser
)
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
)
def can_access(self, org):
if self.request.user.is_superuser:
return True
if org is None:
org = self.object
return self.is_member(org) or self.can_edit_organization(org)
class OrganizationMixin(OrganizationBase):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["is_member"] = self.is_member(self.organization)
context["can_edit"] = self.can_edit_organization(self.organization)
context["organization"] = self.organization
context["logo_override_image"] = self.organization.logo_override_image
if "organizations" in context:
context.pop("organizations")
return context
def dispatch(self, request, *args, **kwargs):
try:
self.organization_id = int(kwargs["pk"])
self.organization = get_object_or_404(Organization, id=self.organization_id)
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,
)
else:
return generic_message(
request,
_("No such organization"),
_("Could not find such organization."),
)
if self.organization.slug != kwargs["slug"]:
return HttpResponsePermanentRedirect(
request.get_full_path().replace(kwargs["slug"], self.organization.slug)
)
return super(OrganizationMixin, self).dispatch(request, *args, **kwargs)
class AdminOrganizationMixin(OrganizationMixin):
def dispatch(self, request, *args, **kwargs):
res = super(AdminOrganizationMixin, self).dispatch(request, *args, **kwargs)
if not hasattr(self, "organization") or self.can_edit_organization(
self.organization
):
return res
return generic_message(
request,
_("Can't edit organization"),
_("You are not allowed to edit this organization."),
status=403,
)
class MemberOrganizationMixin(OrganizationMixin):
def dispatch(self, request, *args, **kwargs):
res = super(MemberOrganizationMixin, self).dispatch(request, *args, **kwargs)
if not hasattr(self, "organization") or self.can_access(self.organization):
return res
return generic_message(
request,
_("Can't access organization"),
_("You are not allowed to access this organization."),
status=403,
)
class OrganizationHomeViewContext:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if not hasattr(self, "organization"):
self.organization = self.object
if self.can_edit_organization(self.organization):
context["pending_count"] = OrganizationRequest.objects.filter(
state="P", organization=self.organization
).count()
context["pending_blog_count"] = BlogPost.objects.filter(
visible=False, organizations=self.organization
).count()
else:
context["pending_blog_count"] = BlogPost.objects.filter(
visible=False,
organizations=self.organization,
authors=self.request.profile,
).count()
context["top_rated"] = self.organization.members.filter(
is_unlisted=False
).order_by("-rating")[:10]
context["top_scorer"] = self.organization.members.filter(
is_unlisted=False
).order_by("-performance_points")[:10]
return context
class OrganizationDetailView(
OrganizationMixin, OrganizationHomeViewContext, DetailView
):
context_object_name = "organization"
model = Organization
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)
)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["can_edit"] = self.can_edit_organization()
context["is_member"] = self.is_member()
return context
class OrganizationList(TitleMixin, ListView, OrganizationBase):
model = Organization
context_object_name = "organizations"
template_name = "organization/list.html"
title = gettext_lazy("Groups")
def get_queryset(self):
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"] = []
context["page_type"] = "organizations"
if self.request.profile:
context["my_organizations"] = context["organizations"].filter(
id__in=self.request.profile.organizations.values("id")
)
other_organizations = context["organizations"].exclude(
id__in=context["my_organizations"]
)
context["open_organizations"] = other_organizations.filter(is_open=True)
context["private_organizations"] = other_organizations.filter(is_open=False)
return context
class OrganizationHome(OrganizationDetailView):
template_name = "organization/home.html"
def get_posts(self):
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")
)
paginator = Paginator(posts, 10)
page_number = self.request.GET.get("page", 1)
posts = paginator.get_page(page_number)
return posts
def get_context_data(self, **kwargs):
context = super(OrganizationHome, self).get_context_data(**kwargs)
context["title"] = self.object.name
context["posts"] = self.get_posts()
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()
}
now = timezone.now()
visible_contests = (
Contest.get_visible_contests(self.request.user)
.filter(
is_visible=True, is_organization_private=True, organizations=self.object
)
.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)
context["page_type"] = "home"
return context
class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
template_name = "organization/users.html"
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
default_desc = all_sorts
default_sort = "-performance_points"
def dispatch(self, request, *args, **kwargs):
res = super(OrganizationUsers, self).dispatch(request, *args, **kwargs)
if self.can_access(self.organization) or self.organization.is_open:
return res
return generic_message(
request,
_("Can't access organization"),
_("You are not allowed to access this organization."),
status=403,
)
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["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["first_page_href"] = "."
context["page_type"] = "users"
context.update(self.get_sort_context())
return context
class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
template_name = "organization/problems.html"
def get_queryset(self):
self.org_query = [self.organization_id]
return super().get_normal_queryset()
def get(self, request, *args, **kwargs):
self.setup_problem_list(request)
return super().get(request, *args, **kwargs)
def get_latest_attempted_problems(self, limit=None):
if self.in_contest or not self.profile:
return ()
problems = set(self.get_queryset().values_list("code", flat=True))
result = list(user_attempted_ids(self.profile).values())
result = [i for i in result if i["code"] in problems]
result = sorted(result, key=lambda d: -d["last_submission"])
if limit:
result = result[:limit]
return result
def get_context_data(self, **kwargs):
context = super(OrganizationProblems, self).get_context_data(**kwargs)
context["page_type"] = "problems"
return context
class OrganizationContestMixin(
LoginRequiredMixin,
TitleMixin,
OrganizationMixin,
OrganizationHomeViewContext,
):
model = Contest
def is_contest_editable(self, request, contest):
return request.profile in contest.authors.all() or self.can_edit_organization(
self.organization
)
class OrganizationContests(
OrganizationContestMixin, MemberOrganizationMixin, ContestList
):
template_name = "organization/contests.html"
def get_queryset(self):
self.org_query = [self.organization_id]
return super().get_queryset()
def set_editable_contest(self, contest):
if not contest:
return False
contest.is_editable = self.is_contest_editable(self.request, contest)
def get_context_data(self, **kwargs):
context = super(OrganizationContests, self).get_context_data(**kwargs)
context["page_type"] = "contests"
context["hide_contest_orgs"] = True
context.pop("organizations")
context["create_url"] = reverse(
"organization_contest_add",
args=[self.organization.id, self.organization.slug],
)
for participation in context["active_participations"]:
self.set_editable_contest(participation.contest)
for contest in context["past_contests"]:
self.set_editable_contest(contest)
for contest in context["current_contests"]:
self.set_editable_contest(contest)
for contest in context["future_contests"]:
self.set_editable_contest(contest)
return context
class OrganizationSubmissions(
LoginRequiredMixin, MemberOrganizationMixin, SubmissionsListBase
):
template_name = "organization/submissions.html"
@cached_property
def in_contest(self):
return False
@cached_property
def contest(self):
return None
def get_queryset(self):
return (
super()
._get_queryset()
.filter(contest_object__organizations=self.organization)
)
def get_context_data(self, **kwargs):
context = super(OrganizationSubmissions, self).get_context_data(**kwargs)
# context["dynamic_update"] = context["page_obj"].number == 1
# context["last_msg"] = event.last()
context["stats_update_interval"] = 3600
context["page_type"] = "submissions"
context["page_prefix"] = None
context["page_suffix"] = suffix = (
("?" + self.request.GET.urlencode()) if self.request.GET else ""
)
context["first_page_href"] = (self.first_page_href or ".") + suffix
return context
def get_content_title(self):
return format_html(
_('All submissions in <a href="{1}">{0}</a>'),
self.organization,
reverse(
"organization_home", args=[self.organization.id, self.organization.slug]
),
)
class OrganizationMembershipChange(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
):
model = Organization
context_object_name = "organization"
def post(self, request, *args, **kwargs):
org = self.get_object()
response = self.handle(request, org, request.profile)
if response is not None:
return response
return HttpResponseRedirect(org.get_absolute_url())
def handle(self, request, org, profile):
raise NotImplementedError()
class JoinOrganization(OrganizationMembershipChange):
def handle(self, request, org, profile):
if profile.organizations.filter(id=org.id).exists():
return generic_message(
request,
_("Joining group"),
_("You are already in the group."),
)
if not org.is_open:
return generic_message(
request, _("Joining group"), _("This group 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 group"),
_("You may not be part of more than {count} public groups.").format(
count=max_orgs
),
)
profile.organizations.add(org)
profile.save()
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 group"),
_('You are not in "%s".') % org.short_name,
)
profile.organizations.remove(org)
cache.delete(make_template_fragment_key("org_member_count", (org.id,)))
class OrganizationRequestForm(Form):
reason = forms.CharField(widget=forms.Textarea)
class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
model = Organization
slug_field = "key"
slug_url_kwarg = "key"
template_name = "organization/requests/request.html"
form_class = OrganizationRequestForm
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(RequestJoinOrganization, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(RequestJoinOrganization, self).get_context_data(**kwargs)
if self.object.is_open:
raise Http404()
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.save()
return HttpResponseRedirect(
reverse(
"request_organization_detail",
args=(
request.organization.id,
request.organization.slug,
request.id,
),
)
)
class OrganizationRequestDetail(
LoginRequiredMixin,
TitleMixin,
OrganizationMixin,
OrganizationHomeViewContext,
DetailView,
):
model = OrganizationRequest
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()
):
raise PermissionDenied()
return object
OrganizationRequestFormSet = modelformset_factory(
OrganizationRequest, extra=0, fields=("state",), can_delete=True
)
class OrganizationRequestBaseView(
OrganizationDetailView,
TitleMixin,
LoginRequiredMixin,
SingleObjectTemplateResponseMixin,
SingleObjectMixin,
):
model = Organization
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
):
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>'
)
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
return context
class OrganizationRequestView(OrganizationRequestBaseView):
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
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
),
)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.object = organization = self.get_object()
self.formset = formset = OrganizationRequestFormSet(request.POST, request.FILES)
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
)
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)
)
approved, rejected = 0, 0
for obj in formset.save():
if obj.state == "A":
obj.user.organizations.add(obj.organization)
approved += 1
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,))
)
return HttpResponseRedirect(request.get_full_path())
return self.render_to_response(self.get_context_data(object=organization))
put = post
class OrganizationRequestLog(OrganizationRequestBaseView):
states = ("A", "R")
tab = "log"
template_name = "organization/requests/log.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def get_context_data(self, **kwargs):
context = super(OrganizationRequestLog, self).get_context_data(**kwargs)
context["requests"] = self.object.requests.filter(state__in=self.states)
return context
class AddOrganizationMember(
LoginRequiredMixin,
TitleMixin,
AdminOrganizationMixin,
OrganizationDetailView,
UpdateView,
):
template_name = "organization/add-member.html"
model = Organization
form_class = AddOrganizationMemberForm
def get_title(self):
return _("Add member for %s") % self.object.name
def get_object(self, queryset=None):
object = super(AddOrganizationMember, self).get_object()
if not self.can_edit_organization(object):
raise PermissionDenied()
return object
def form_valid(self, form):
new_users = form.cleaned_data["new_users"]
self.object.members.add(*new_users)
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Added members from site"))
revisions.set_user(self.request.user)
return super(AddOrganizationMember, self).form_valid(form)
def get_success_url(self):
return reverse("organization_users", args=[self.object.id, self.object.slug])
class KickUserWidgetView(
LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
):
model = Organization
def post(self, request, *args, **kwargs):
organization = self.get_object()
try:
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,
)
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,
)
organization.members.remove(user)
return HttpResponseRedirect(organization.get_users_url())
class EditOrganization(
LoginRequiredMixin,
TitleMixin,
AdminOrganizationMixin,
OrganizationDetailView,
UpdateView,
):
template_name = "organization/edit.html"
model = Organization
form_class = EditOrganizationForm
def get_title(self):
return _("Edit %s") % self.object.name
def get_object(self, queryset=None):
object = super(EditOrganization, self).get_object()
if not self.can_edit_organization(object):
raise PermissionDenied()
return object
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()
return form
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Edited from site"))
revisions.set_user(self.request.user)
return super(EditOrganization, self).form_valid(form)
class AddOrganization(LoginRequiredMixin, TitleMixin, CreateView):
template_name = "organization/add.html"
model = Organization
form_class = AddOrganizationForm
def get_title(self):
return _("Create group")
def get_form_kwargs(self):
kwargs = super(AddOrganization, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def form_valid(self, form):
if (
not self.request.user.is_staff
and Organization.objects.filter(registrant=self.request.profile).count()
>= settings.DMOJ_USER_MAX_ORGANIZATION_ADD
):
return generic_message(
self.request,
_("Exceeded limit"),
_("You created too many groups. You can only create at most %d groups")
% settings.DMOJ_USER_MAX_ORGANIZATION_ADD,
status=400,
)
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Added from site"))
revisions.set_user(self.request.user)
res = super(AddOrganization, self).form_valid(form)
self.object.admins.add(self.request.profile)
self.object.members.add(self.request.profile)
self.object.save()
return res
class AddOrganizationContest(
AdminOrganizationMixin, OrganizationContestMixin, CreateView
):
template_name = "organization/contest/add.html"
form_class = AddOrganizationContestForm
def get_title(self):
return _("Add contest")
def get_form_kwargs(self):
kwargs = super(AddOrganizationContest, self).get_form_kwargs()
kwargs["request"] = self.request
return kwargs
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Added from site"))
revisions.set_user(self.request.user)
res = super(AddOrganizationContest, self).form_valid(form)
self.object.organizations.add(self.organization)
self.object.is_organization_private = True
self.object.authors.add(self.request.profile)
self.object.save()
return res
def get_success_url(self):
return reverse(
"organization_contest_edit",
args=[self.organization.id, self.organization.slug, self.object.key],
)
class EditOrganizationContest(
OrganizationContestMixin, MemberOrganizationMixin, UpdateView
):
template_name = "organization/contest/edit.html"
form_class = EditOrganizationContestForm
def setup_contest(self, request, *args, **kwargs):
contest_key = kwargs.get("contest", None)
if not contest_key:
raise Http404()
self.contest = get_object_or_404(Contest, key=contest_key)
if self.organization not in self.contest.organizations.all():
raise Http404()
if not self.is_contest_editable(request, self.contest):
return generic_message(
self.request,
_("Permission denied"),
_("You are not allowed to edit this contest"),
status=400,
)
def get_form_kwargs(self):
kwargs = super(EditOrganizationContest, self).get_form_kwargs()
kwargs["org_id"] = self.organization.id
return kwargs
def get(self, request, *args, **kwargs):
res = self.setup_contest(request, *args, **kwargs)
if res:
return res
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
res = self.setup_contest(request, *args, **kwargs)
if res:
return res
problem_formset = self.get_problem_formset(True)
if problem_formset.is_valid():
for problem_form in problem_formset.save(commit=False):
if problem_form:
problem_form.contest = self.contest
problem_form.save()
for problem_form in problem_formset.deleted_objects:
problem_form.delete()
return super().post(request, *args, **kwargs)
self.object = self.contest
return self.render_to_response(
self.get_context_data(
problems_form=problem_formset,
)
)
def get_title(self):
return _("Edit %s") % self.contest.key
def get_content_title(self):
href = reverse("contest_view", args=[self.contest.key])
return mark_safe(f'Edit <a href="{href}">{self.contest.key}</a>')
def get_object(self):
return self.contest
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Edited from site"))
revisions.set_user(self.request.user)
res = super(EditOrganizationContest, self).form_valid(form)
self.object.organizations.add(self.organization)
self.object.is_organization_private = True
self.object.save()
return res
def get_problem_formset(self, post=False):
return ContestProblemFormSet(
data=self.request.POST if post else None,
prefix="problems",
queryset=ContestProblem.objects.filter(contest=self.contest).order_by(
"order"
),
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "problems_form" not in context:
context["problems_form"] = self.get_problem_formset()
return context
def get_success_url(self):
return self.request.path
class AddOrganizationBlog(
LoginRequiredMixin,
TitleMixin,
OrganizationHomeViewContext,
MemberOrganizationMixin,
CreateView,
):
template_name = "organization/blog/add.html"
model = BlogPost
form_class = OrganizationBlogForm
def get_form_class(self):
if self.can_edit_organization(self.organization):
return OrganizationAdminBlogForm
return OrganizationBlogForm
def get_title(self):
return _("Add blog for %s") % self.organization.name
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
res = super(AddOrganizationBlog, self).form_valid(form)
self.object.is_organization_private = True
self.object.authors.add(self.request.profile)
self.object.slug = self.organization.slug + "-" + self.request.user.username
self.object.organizations.add(self.organization)
self.object.save()
revisions.set_comment(_("Added from site"))
revisions.set_user(self.request.user)
link = reverse(
"edit_organization_blog",
args=[self.organization.id, self.organization.slug, self.object.id],
)
html = (
f'<a href="{link}">{self.object.title} - {self.organization.name}</a>'
)
for user in self.organization.admins.all():
if user.id == self.request.profile.id:
continue
notification = Notification(
owner=user,
author=self.request.profile,
category="Add blog",
html_link=html,
)
notification.save()
return res
class EditOrganizationBlog(
LoginRequiredMixin,
TitleMixin,
OrganizationHomeViewContext,
MemberOrganizationMixin,
UpdateView,
):
template_name = "organization/blog/add.html"
model = BlogPost
def get_form_class(self):
if self.can_edit_organization(self.organization):
return OrganizationAdminBlogForm
return OrganizationBlogForm
def setup_blog(self, request, *args, **kwargs):
try:
self.blog_id = kwargs["blog_pk"]
self.blog = BlogPost.objects.get(id=self.blog_id)
if self.organization not in self.blog.organizations.all():
raise Exception("This blog does not belong to this organization")
if (
self.request.profile not in self.blog.authors.all()
and not self.can_edit_organization(self.organization)
):
raise Exception("Not allowed to edit this blog")
except:
return generic_message(
request,
_("Permission denied"),
_("Not allowed to edit this blog"),
)
def get(self, request, *args, **kwargs):
res = self.setup_blog(request, *args, **kwargs)
if res:
return res
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
res = self.setup_blog(request, *args, **kwargs)
if res:
return res
return super().post(request, *args, **kwargs)
def get_object(self):
return self.blog
def get_title(self):
return _("Edit blog %s") % self.object.title
def form_valid(self, form):
with transaction.atomic(), revisions.create_revision():
res = super(EditOrganizationBlog, self).form_valid(form)
revisions.set_comment(_("Edited from site"))
revisions.set_user(self.request.user)
link = reverse(
"edit_organization_blog",
args=[self.organization.id, self.organization.slug, self.object.id],
)
html = (
f'<a href="{link}">{self.object.title} - {self.organization.name}</a>'
)
for user in self.organization.admins.all():
if user.id == self.request.profile.id:
continue
notification = Notification(
owner=user,
author=self.request.profile,
category="Edit blog",
html_link=html,
)
notification.save()
return res
class PendingBlogs(
LoginRequiredMixin,
TitleMixin,
MemberOrganizationMixin,
OrganizationHomeViewContext,
ListView,
):
model = BlogPost
template_name = "organization/blog/pending.html"
context_object_name = "blogs"
def get_queryset(self):
queryset = BlogPost.objects.filter(
organizations=self.organization, visible=False
)
if not self.can_edit_organization(self.organization):
queryset = queryset.filter(authors=self.request.profile)
return queryset.order_by("publish_on")
def get_title(self):
return _("Pending blogs in %s") % self.organization.name