2020-01-21 06:35:58 +00:00
|
|
|
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
|
2020-12-28 05:45:58 +00:00
|
|
|
from django.db.models import Count, Q, Value, BooleanField
|
2022-05-28 04:28:22 +00:00
|
|
|
from django.db.utils import ProgrammingError
|
2020-01-21 06:35:58 +00:00
|
|
|
from django.forms import Form, modelformset_factory
|
2022-05-28 04:28:22 +00:00
|
|
|
from django.http import (
|
|
|
|
Http404,
|
|
|
|
HttpResponsePermanentRedirect,
|
|
|
|
HttpResponseRedirect,
|
|
|
|
HttpResponseBadRequest,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
from django.urls import reverse
|
2020-12-28 05:45:58 +00:00
|
|
|
from django.utils import timezone
|
2021-10-10 22:37:15 +00:00
|
|
|
from django.utils.safestring import mark_safe
|
2020-01-21 06:35:58 +00:00
|
|
|
from django.utils.translation import gettext as _, gettext_lazy, ungettext
|
2022-05-30 06:59:53 +00:00
|
|
|
from django.views.generic import (
|
|
|
|
DetailView,
|
|
|
|
FormView,
|
|
|
|
ListView,
|
|
|
|
UpdateView,
|
|
|
|
View,
|
|
|
|
CreateView,
|
|
|
|
)
|
2022-05-14 17:57:27 +00:00
|
|
|
from django.views.generic.detail import (
|
|
|
|
SingleObjectMixin,
|
|
|
|
SingleObjectTemplateResponseMixin,
|
|
|
|
)
|
2022-05-28 04:28:22 +00:00
|
|
|
from django.core.paginator import Paginator
|
2020-01-21 06:35:58 +00:00
|
|
|
from reversion import revisions
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
from judge.forms import (
|
|
|
|
EditOrganizationForm,
|
|
|
|
AddOrganizationMemberForm,
|
|
|
|
OrganizationBlogForm,
|
|
|
|
OrganizationAdminBlogForm,
|
|
|
|
)
|
2022-05-14 17:57:27 +00:00
|
|
|
from judge.models import (
|
|
|
|
BlogPost,
|
|
|
|
Comment,
|
|
|
|
Organization,
|
|
|
|
OrganizationRequest,
|
|
|
|
Problem,
|
|
|
|
Profile,
|
|
|
|
Contest,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
from judge.utils.ranker import ranker
|
2022-05-14 17:57:27 +00:00
|
|
|
from judge.utils.views import (
|
|
|
|
TitleMixin,
|
|
|
|
generic_message,
|
|
|
|
QueryStringSortMixin,
|
|
|
|
DiggPaginatorMixin,
|
|
|
|
)
|
2022-05-30 06:59:53 +00:00
|
|
|
from judge.utils.problems import user_attempted_ids
|
2022-05-28 04:28:22 +00:00
|
|
|
from judge.views.problem import ProblemList
|
|
|
|
from judge.views.contests import ContestList
|
2022-05-14 17:57:27 +00:00
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
"OrganizationList",
|
|
|
|
"OrganizationHome",
|
|
|
|
"OrganizationUsers",
|
2022-05-28 04:28:22 +00:00
|
|
|
"OrganizationProblems",
|
|
|
|
"OrganizationContests",
|
2022-05-14 17:57:27 +00:00
|
|
|
"OrganizationMembershipChange",
|
|
|
|
"JoinOrganization",
|
|
|
|
"LeaveOrganization",
|
|
|
|
"EditOrganization",
|
|
|
|
"RequestJoinOrganization",
|
|
|
|
"OrganizationRequestDetail",
|
|
|
|
"OrganizationRequestView",
|
|
|
|
"OrganizationRequestLog",
|
|
|
|
"KickUserWidgetView",
|
|
|
|
]
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2020-12-28 05:45:58 +00:00
|
|
|
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
|
2022-05-14 17:57:27 +00:00
|
|
|
return (
|
2022-05-30 06:59:53 +00:00
|
|
|
org.admins.filter(id=profile_id).exists()
|
|
|
|
or org.registrant_id == profile_id
|
|
|
|
or self.request.user.is_superuser
|
2022-05-14 17:57:27 +00:00
|
|
|
)
|
2020-12-28 05:45:58 +00:00
|
|
|
|
|
|
|
def is_member(self, org=None):
|
|
|
|
if org is None:
|
|
|
|
org = self.object
|
2022-05-14 17:57:27 +00:00
|
|
|
return (
|
|
|
|
self.request.profile in org if self.request.user.is_authenticated else False
|
|
|
|
)
|
|
|
|
|
2022-05-28 04:33:00 +00:00
|
|
|
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)
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-12-28 05:45:58 +00:00
|
|
|
class OrganizationMixin(OrganizationBase):
|
2020-01-21 06:35:58 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super().get_context_data(**kwargs)
|
2022-05-30 06:59:53 +00:00
|
|
|
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")
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
try:
|
2022-05-30 06:59:53 +00:00
|
|
|
self.organization_id = int(kwargs["pk"])
|
|
|
|
self.organization = Organization.objects.get(id=self.organization_id)
|
2020-01-21 06:35:58 +00:00
|
|
|
except Http404:
|
|
|
|
key = kwargs.get(self.slug_url_kwarg, None)
|
|
|
|
if key:
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
|
|
|
_("No such organization"),
|
|
|
|
_('Could not find an organization with the key "%s".') % key,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
else:
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
|
|
|
_("No such organization"),
|
|
|
|
_("Could not find such organization."),
|
|
|
|
)
|
2022-05-30 06:59:53 +00:00
|
|
|
if self.organization.slug != kwargs["slug"]:
|
|
|
|
return HttpResponsePermanentRedirect(
|
2022-05-30 10:54:21 +00:00
|
|
|
request.get_full_path().replace(kwargs["slug"], self.organization.slug)
|
2022-05-30 06:59:53 +00:00
|
|
|
)
|
|
|
|
return super(OrganizationMixin, self).dispatch(request, *args, **kwargs)
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
class AdminOrganizationMixin(OrganizationMixin):
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
|
|
res = super(AdminOrganizationMixin, self).dispatch(request, *args, **kwargs)
|
|
|
|
if 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 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
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
2022-05-14 17:57:27 +00:00
|
|
|
if self.object.slug != kwargs["slug"]:
|
|
|
|
return HttpResponsePermanentRedirect(
|
|
|
|
request.get_full_path().replace(kwargs["slug"], self.object.slug)
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
context = self.get_context_data(object=self.object)
|
|
|
|
return self.render_to_response(context)
|
|
|
|
|
2022-05-28 04:28:22 +00:00
|
|
|
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
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-12-28 05:45:58 +00:00
|
|
|
class OrganizationList(TitleMixin, ListView, OrganizationBase):
|
2020-01-21 06:35:58 +00:00
|
|
|
model = Organization
|
2022-05-14 17:57:27 +00:00
|
|
|
context_object_name = "organizations"
|
|
|
|
template_name = "organization/list.html"
|
2022-05-30 06:59:53 +00:00
|
|
|
title = gettext_lazy("Groups")
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_queryset(self):
|
2022-05-14 17:57:27 +00:00
|
|
|
return (
|
|
|
|
super(OrganizationList, self)
|
|
|
|
.get_queryset()
|
|
|
|
.annotate(member_count=Count("member"))
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2020-12-28 05:45:58 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationList, self).get_context_data(**kwargs)
|
2022-05-28 19:20:41 +00:00
|
|
|
context["my_organizations"] = []
|
2022-05-28 07:29:25 +00:00
|
|
|
if self.request.profile:
|
|
|
|
context["my_organizations"] = self.request.profile.organizations.all()
|
2020-12-28 05:45:58 +00:00
|
|
|
|
|
|
|
return context
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
class OrganizationHome(OrganizationDetailView):
|
2022-05-14 17:57:27 +00:00
|
|
|
template_name = "organization/home.html"
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-05-28 04:28:22 +00:00
|
|
|
def get_posts(self):
|
|
|
|
posts = (
|
2022-05-14 17:57:27 +00:00
|
|
|
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")
|
|
|
|
)
|
2022-05-28 04:28:22 +00:00
|
|
|
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()
|
2022-05-14 17:57:27 +00:00
|
|
|
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()
|
2020-12-28 05:45:58 +00:00
|
|
|
}
|
2022-05-28 04:28:22 +00:00
|
|
|
|
|
|
|
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"
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
2020-12-30 02:29:50 +00:00
|
|
|
class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
|
2022-05-14 17:57:27 +00:00
|
|
|
template_name = "organization/users.html"
|
|
|
|
all_sorts = frozenset(("points", "problem_count", "rating", "performance_points"))
|
2020-12-30 02:29:50 +00:00
|
|
|
default_desc = all_sorts
|
2022-05-14 17:57:27 +00:00
|
|
|
default_sort = "-performance_points"
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationUsers, self).get_context_data(**kwargs)
|
2022-05-14 17:57:27 +00:00
|
|
|
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",
|
2020-12-30 02:29:50 +00:00
|
|
|
)
|
2022-05-14 17:57:27 +00:00
|
|
|
)
|
|
|
|
context["first_page_href"] = "."
|
2022-05-28 04:28:22 +00:00
|
|
|
context["page_type"] = "users"
|
2020-12-30 02:29:50 +00:00
|
|
|
context.update(self.get_sort_context())
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
|
2022-05-28 04:28:22 +00:00
|
|
|
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)
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
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
|
|
|
|
|
2022-05-28 04:28:22 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationProblems, self).get_context_data(**kwargs)
|
|
|
|
context["page_type"] = "problems"
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
class OrganizationContests(LoginRequiredMixin, MemberOrganizationMixin, ContestList):
|
2022-05-28 04:28:22 +00:00
|
|
|
template_name = "organization/contests.html"
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
self.org_query = [self.organization_id]
|
|
|
|
return super().get_queryset()
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationContests, self).get_context_data(**kwargs)
|
|
|
|
context["page_type"] = "contests"
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
class OrganizationMembershipChange(
|
|
|
|
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
|
|
|
|
):
|
2022-05-30 06:59:53 +00:00
|
|
|
model = Organization
|
|
|
|
context_object_name = "organization"
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
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():
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
2022-05-30 06:59:53 +00:00
|
|
|
_("Joining group"),
|
|
|
|
_("You are already in the group."),
|
2022-05-14 17:57:27 +00:00
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if not org.is_open:
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
2022-05-30 06:59:53 +00:00
|
|
|
request, _("Joining group"), _("This group is not open.")
|
2022-05-14 17:57:27 +00:00
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
|
|
|
|
if profile.organizations.filter(is_open=True).count() >= max_orgs:
|
|
|
|
return generic_message(
|
2022-05-14 17:57:27 +00:00
|
|
|
request,
|
2022-05-30 06:59:53 +00:00
|
|
|
_("Joining group"),
|
|
|
|
_("You may not be part of more than {count} public groups.").format(
|
|
|
|
count=max_orgs
|
|
|
|
),
|
2020-01-21 06:35:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
profile.organizations.add(org)
|
|
|
|
profile.save()
|
2022-05-14 17:57:27 +00:00
|
|
|
cache.delete(make_template_fragment_key("org_member_count", (org.id,)))
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LeaveOrganization(OrganizationMembershipChange):
|
|
|
|
def handle(self, request, org, profile):
|
|
|
|
if not profile.organizations.filter(id=org.id).exists():
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
2022-05-30 06:59:53 +00:00
|
|
|
_("Leaving group"),
|
2022-05-14 17:57:27 +00:00
|
|
|
_('You are not in "%s".') % org.short_name,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
profile.organizations.remove(org)
|
2022-05-14 17:57:27 +00:00
|
|
|
cache.delete(make_template_fragment_key("org_member_count", (org.id,)))
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
class OrganizationRequestForm(Form):
|
|
|
|
reason = forms.CharField(widget=forms.Textarea)
|
|
|
|
|
|
|
|
|
|
|
|
class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
|
|
|
|
model = Organization
|
2022-05-14 17:57:27 +00:00
|
|
|
slug_field = "key"
|
|
|
|
slug_url_kwarg = "key"
|
|
|
|
template_name = "organization/requests/request.html"
|
2020-01-21 06:35:58 +00:00
|
|
|
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()
|
2022-05-14 17:57:27 +00:00
|
|
|
context["title"] = _("Request to join %s") % self.object.name
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
|
|
|
request = OrganizationRequest()
|
|
|
|
request.organization = self.get_object()
|
|
|
|
request.user = self.request.profile
|
2022-05-14 17:57:27 +00:00
|
|
|
request.reason = form.cleaned_data["reason"]
|
|
|
|
request.state = "P"
|
2020-01-21 06:35:58 +00:00
|
|
|
request.save()
|
2022-05-14 17:57:27 +00:00
|
|
|
return HttpResponseRedirect(
|
|
|
|
reverse(
|
|
|
|
"request_organization_detail",
|
|
|
|
args=(
|
|
|
|
request.organization.id,
|
|
|
|
request.organization.slug,
|
|
|
|
request.id,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
class OrganizationRequestDetail(
|
|
|
|
LoginRequiredMixin,
|
|
|
|
TitleMixin,
|
|
|
|
OrganizationMixin,
|
|
|
|
OrganizationHomeViewContext,
|
|
|
|
DetailView,
|
|
|
|
):
|
2020-01-21 06:35:58 +00:00
|
|
|
model = OrganizationRequest
|
2022-05-14 17:57:27 +00:00
|
|
|
template_name = "organization/requests/detail.html"
|
|
|
|
title = gettext_lazy("Join request detail")
|
|
|
|
pk_url_kwarg = "rpk"
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
object = super(OrganizationRequestDetail, self).get_object(queryset)
|
|
|
|
profile = self.request.profile
|
2022-05-14 17:57:27 +00:00
|
|
|
if (
|
|
|
|
object.user_id != profile.id
|
|
|
|
and not object.organization.admins.filter(id=profile.id).exists()
|
|
|
|
):
|
2020-01-21 06:35:58 +00:00
|
|
|
raise PermissionDenied()
|
|
|
|
return object
|
|
|
|
|
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
OrganizationRequestFormSet = modelformset_factory(
|
|
|
|
OrganizationRequest, extra=0, fields=("state",), can_delete=True
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
class OrganizationRequestBaseView(
|
2022-05-30 06:59:53 +00:00
|
|
|
OrganizationDetailView,
|
2022-05-14 17:57:27 +00:00
|
|
|
TitleMixin,
|
|
|
|
LoginRequiredMixin,
|
|
|
|
SingleObjectTemplateResponseMixin,
|
|
|
|
SingleObjectMixin,
|
|
|
|
):
|
2020-01-21 06:35:58 +00:00
|
|
|
model = Organization
|
2022-05-14 17:57:27 +00:00
|
|
|
slug_field = "key"
|
|
|
|
slug_url_kwarg = "key"
|
2020-01-21 06:35:58 +00:00
|
|
|
tab = None
|
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
|
|
|
organization = super(OrganizationRequestBaseView, self).get_object(queryset)
|
2022-05-14 17:57:27 +00:00
|
|
|
if not (
|
|
|
|
organization.admins.filter(id=self.request.profile.id).exists()
|
|
|
|
or organization.registrant_id == self.request.profile.id
|
|
|
|
):
|
2020-01-21 06:35:58 +00:00
|
|
|
raise PermissionDenied()
|
|
|
|
return organization
|
|
|
|
|
2021-10-10 22:37:15 +00:00
|
|
|
def get_content_title(self):
|
2022-05-14 17:57:27 +00:00
|
|
|
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>'
|
|
|
|
)
|
|
|
|
|
2020-01-21 06:35:58 +00:00
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationRequestBaseView, self).get_context_data(**kwargs)
|
2022-05-14 17:57:27 +00:00
|
|
|
context["title"] = _("Managing join requests for %s") % self.object.name
|
|
|
|
context["tab"] = self.tab
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class OrganizationRequestView(OrganizationRequestBaseView):
|
2022-05-14 17:57:27 +00:00
|
|
|
template_name = "organization/requests/pending.html"
|
|
|
|
tab = "pending"
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
context = super(OrganizationRequestView, self).get_context_data(**kwargs)
|
2022-05-14 17:57:27 +00:00
|
|
|
context["formset"] = self.formset
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
|
|
self.object = self.get_object()
|
|
|
|
self.formset = OrganizationRequestFormSet(
|
2022-05-14 17:57:27 +00:00
|
|
|
queryset=OrganizationRequest.objects.filter(
|
|
|
|
state="P", organization=self.object
|
|
|
|
),
|
2020-01-21 06:35:58 +00:00
|
|
|
)
|
|
|
|
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)
|
2022-05-14 17:57:27 +00:00
|
|
|
to_approve = sum(
|
|
|
|
form.cleaned_data["state"] == "A"
|
|
|
|
for form in formset.forms
|
|
|
|
if form not in deleted_set
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
can_add = organization.slots - organization.members.count()
|
|
|
|
if to_approve > can_add:
|
2022-05-14 17:57:27 +00:00
|
|
|
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)
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
approved, rejected = 0, 0
|
|
|
|
for obj in formset.save():
|
2022-05-14 17:57:27 +00:00
|
|
|
if obj.state == "A":
|
2020-01-21 06:35:58 +00:00
|
|
|
obj.user.organizations.add(obj.organization)
|
|
|
|
approved += 1
|
2022-05-14 17:57:27 +00:00
|
|
|
elif obj.state == "R":
|
2020-01-21 06:35:58 +00:00
|
|
|
rejected += 1
|
2022-05-14 17:57:27 +00:00
|
|
|
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,))
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
return HttpResponseRedirect(request.get_full_path())
|
|
|
|
return self.render_to_response(self.get_context_data(object=organization))
|
|
|
|
|
|
|
|
put = post
|
|
|
|
|
|
|
|
|
|
|
|
class OrganizationRequestLog(OrganizationRequestBaseView):
|
2022-05-14 17:57:27 +00:00
|
|
|
states = ("A", "R")
|
|
|
|
tab = "log"
|
|
|
|
template_name = "organization/requests/log.html"
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
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)
|
2022-05-14 17:57:27 +00:00
|
|
|
context["requests"] = self.object.requests.filter(state__in=self.states)
|
2020-01-21 06:35:58 +00:00
|
|
|
return context
|
|
|
|
|
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
class AddOrganizationMember(
|
|
|
|
LoginRequiredMixin,
|
|
|
|
TitleMixin,
|
|
|
|
AdminOrganizationMixin,
|
|
|
|
OrganizationDetailView,
|
|
|
|
UpdateView,
|
|
|
|
):
|
|
|
|
template_name = "organization/add-member.html"
|
2020-01-21 06:35:58 +00:00
|
|
|
model = Organization
|
2022-05-30 06:59:53 +00:00
|
|
|
form_class = AddOrganizationMemberForm
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_title(self):
|
2022-05-30 06:59:53 +00:00
|
|
|
return _("Add member for %s") % self.object.name
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
def get_object(self, queryset=None):
|
2022-05-30 06:59:53 +00:00
|
|
|
object = super(AddOrganizationMember, self).get_object()
|
2020-01-21 06:35:58 +00:00
|
|
|
if not self.can_edit_organization(object):
|
|
|
|
raise PermissionDenied()
|
|
|
|
return object
|
|
|
|
|
|
|
|
def form_valid(self, form):
|
2022-05-30 06:59:53 +00:00
|
|
|
new_users = form.cleaned_data["new_users"]
|
|
|
|
self.object.members.add(*new_users)
|
2020-01-21 06:35:58 +00:00
|
|
|
with transaction.atomic(), revisions.create_revision():
|
2022-05-30 06:59:53 +00:00
|
|
|
revisions.set_comment(_("Added members from site"))
|
2020-01-21 06:35:58 +00:00
|
|
|
revisions.set_user(self.request.user)
|
2022-05-30 06:59:53 +00:00
|
|
|
return super(AddOrganizationMember, self).form_valid(form)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
2022-05-30 06:59:53 +00:00
|
|
|
def get_success_url(self):
|
|
|
|
return reverse("organization_users", args=[self.object.id, self.object.slug])
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
|
2022-05-14 17:57:27 +00:00
|
|
|
class KickUserWidgetView(
|
2022-05-30 06:59:53 +00:00
|
|
|
LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
|
2022-05-14 17:57:27 +00:00
|
|
|
):
|
2022-05-30 08:14:02 +00:00
|
|
|
model = Organization
|
2020-01-21 06:35:58 +00:00
|
|
|
def post(self, request, *args, **kwargs):
|
|
|
|
organization = self.get_object()
|
|
|
|
try:
|
2022-05-14 17:57:27 +00:00
|
|
|
user = Profile.objects.get(id=request.POST.get("user", None))
|
2020-01-21 06:35:58 +00:00
|
|
|
except Profile.DoesNotExist:
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
|
|
|
_("Can't kick user"),
|
|
|
|
_("The user you are trying to kick does not exist!"),
|
|
|
|
status=400,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
if not organization.members.filter(id=user.id).exists():
|
2022-05-14 17:57:27 +00:00
|
|
|
return generic_message(
|
|
|
|
request,
|
|
|
|
_("Can't kick user"),
|
|
|
|
_("The user you are trying to kick is not in organization: %s.")
|
|
|
|
% organization.name,
|
|
|
|
status=400,
|
|
|
|
)
|
2020-01-21 06:35:58 +00:00
|
|
|
|
|
|
|
organization.members.remove(user)
|
|
|
|
return HttpResponseRedirect(organization.get_users_url())
|
2022-05-30 06:59:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
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 AddOrganizationBlog(
|
|
|
|
LoginRequiredMixin,
|
|
|
|
TitleMixin,
|
|
|
|
OrganizationHomeViewContext,
|
|
|
|
MemberOrganizationMixin,
|
|
|
|
CreateView,
|
|
|
|
):
|
|
|
|
template_name = "organization/blog/add.html"
|
|
|
|
model = BlogPost
|
|
|
|
form_class = 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)
|
|
|
|
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)
|
|
|
|
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
|