Add organization blogs

This commit is contained in:
cuom1999 2022-05-30 01:59:53 -05:00
parent 99fc3d1015
commit 5fff6b1510
27 changed files with 1119 additions and 630 deletions

View file

@ -617,6 +617,26 @@ urlpatterns = [
organization.KickUserWidgetView.as_view(), organization.KickUserWidgetView.as_view(),
name="organization_user_kick", name="organization_user_kick",
), ),
url(
r"^/add_member$",
organization.AddOrganizationMember.as_view(),
name="add_organization_member",
),
url(
r"^/blog/add$",
organization.AddOrganizationBlog.as_view(),
name="add_organization_blog",
),
url(
r"^/blog/edit/(?P<blog_pk>\d+)$",
organization.EditOrganizationBlog.as_view(),
name="edit_organization_blog",
),
url(
r"^/blog/pending$",
organization.PendingBlogs.as_view(),
name="organization_pending_blogs",
),
url( url(
r"^/request$", r"^/request$",
organization.RequestJoinOrganization.as_view(), organization.RequestJoinOrganization.as_view(),

View file

@ -4,12 +4,13 @@ import pyotp
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db.models import Q from django.db.models import Q
from django.forms import CharField, ChoiceField, Form, ModelForm from django.forms import CharField, ChoiceField, Form, ModelForm
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django_ace import AceWidget from django_ace import AceWidget
from judge.models import ( from judge.models import (
@ -21,6 +22,7 @@ from judge.models import (
ProblemPointsVote, ProblemPointsVote,
Profile, Profile,
Submission, Submission,
BlogPost,
) )
from judge.utils.subscription import newsletter_id from judge.utils.subscription import newsletter_id
from judge.widgets import ( from judge.widgets import (
@ -81,9 +83,9 @@ class ProfileForm(ModelForm):
if sum(org.is_open for org in organizations) > max_orgs: if sum(org.is_open for org in organizations) > max_orgs:
raise ValidationError( raise ValidationError(
_( _("You may not be part of more than {count} public groups.").format(
"You may not be part of more than {count} public organizations." count=max_orgs
).format(count=max_orgs) )
) )
return self.cleaned_data return self.cleaned_data
@ -125,7 +127,7 @@ class ProblemSubmitForm(ModelForm):
class EditOrganizationForm(ModelForm): class EditOrganizationForm(ModelForm):
class Meta: class Meta:
model = Organization model = Organization
fields = ["about", "logo_override_image", "admins"] fields = ["about", "logo_override_image", "admins", "is_open"]
widgets = {"admins": Select2MultipleWidget()} widgets = {"admins": Select2MultipleWidget()}
if HeavyPreviewPageDownWidget is not None: if HeavyPreviewPageDownWidget is not None:
widgets["about"] = HeavyPreviewPageDownWidget( widgets["about"] = HeavyPreviewPageDownWidget(
@ -133,6 +135,74 @@ class EditOrganizationForm(ModelForm):
) )
class AddOrganizationMemberForm(ModelForm):
new_users = CharField(
max_length=65536,
widget=forms.Textarea,
help_text=_("Enter usernames separating by space"),
)
def clean(self):
new_users = self.cleaned_data.get("new_users") or ""
usernames = new_users.split()
invalid_usernames = []
valid_usernames = []
for username in usernames:
try:
valid_usernames.append(Profile.objects.get(user__username=username))
except ObjectDoesNotExist:
invalid_usernames.append(username)
if invalid_usernames:
raise ValidationError(
_("These usernames don't exist: {usernames}").format(
usernames=str(invalid_usernames)
)
)
self.cleaned_data["new_users"] = valid_usernames
return self.cleaned_data
class Meta:
model = Organization
fields = ()
class OrganizationBlogForm(ModelForm):
class Meta:
model = BlogPost
fields = ("title", "content", "publish_on")
widgets = {
"publish_on": forms.HiddenInput,
}
if HeavyPreviewPageDownWidget is not None:
widgets["content"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("organization_preview")
)
def __init__(self, *args, **kwargs):
super(OrganizationBlogForm, self).__init__(*args, **kwargs)
self.fields["publish_on"].required = False
self.fields["publish_on"].is_hidden = True
def clean(self):
self.cleaned_data["publish_on"] = timezone.now()
return self.cleaned_data
class OrganizationAdminBlogForm(OrganizationBlogForm):
class Meta:
model = BlogPost
fields = ("visible", "sticky", "title", "content", "publish_on")
widgets = {
"publish_on": forms.HiddenInput,
}
if HeavyPreviewPageDownWidget is not None:
widgets["content"] = HeavyPreviewPageDownWidget(
preview=reverse_lazy("organization_preview")
)
class NewMessageForm(ModelForm): class NewMessageForm(ModelForm):
class Meta: class Meta:
model = PrivateMessage model = PrivateMessage

View file

@ -19,7 +19,14 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, gettext_lazy, ungettext from django.utils.translation import gettext as _, gettext_lazy, ungettext
from django.views.generic import DetailView, FormView, ListView, UpdateView, View from django.views.generic import (
DetailView,
FormView,
ListView,
UpdateView,
View,
CreateView,
)
from django.views.generic.detail import ( from django.views.generic.detail import (
SingleObjectMixin, SingleObjectMixin,
SingleObjectTemplateResponseMixin, SingleObjectTemplateResponseMixin,
@ -27,7 +34,12 @@ from django.views.generic.detail import (
from django.core.paginator import Paginator from django.core.paginator import Paginator
from reversion import revisions from reversion import revisions
from judge.forms import EditOrganizationForm from judge.forms import (
EditOrganizationForm,
AddOrganizationMemberForm,
OrganizationBlogForm,
OrganizationAdminBlogForm,
)
from judge.models import ( from judge.models import (
BlogPost, BlogPost,
Comment, Comment,
@ -44,6 +56,7 @@ from judge.utils.views import (
QueryStringSortMixin, QueryStringSortMixin,
DiggPaginatorMixin, DiggPaginatorMixin,
) )
from judge.utils.problems import user_attempted_ids
from judge.views.problem import ProblemList from judge.views.problem import ProblemList
from judge.views.contests import ContestList from judge.views.contests import ContestList
@ -73,7 +86,9 @@ class OrganizationBase(object):
return False return False
profile_id = self.request.profile.id profile_id = self.request.profile.id
return ( return (
org.admins.filter(id=profile_id).exists() or org.registrant_id == profile_id org.admins.filter(id=profile_id).exists()
or org.registrant_id == profile_id
or self.request.user.is_superuser
) )
def is_member(self, org=None): def is_member(self, org=None):
@ -92,17 +107,20 @@ class OrganizationBase(object):
class OrganizationMixin(OrganizationBase): class OrganizationMixin(OrganizationBase):
context_object_name = "organization"
model = Organization
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["logo_override_image"] = self.object.logo_override_image 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 return context
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
try: try:
return super(OrganizationMixin, self).dispatch(request, *args, **kwargs) self.organization_id = int(kwargs["pk"])
self.organization = Organization.objects.get(id=self.organization_id)
except Http404: except Http404:
key = kwargs.get(self.slug_url_kwarg, None) key = kwargs.get(self.slug_url_kwarg, None)
if key: if key:
@ -117,9 +135,72 @@ class OrganizationMixin(OrganizationBase):
_("No such organization"), _("No such organization"),
_("Could not find such organization."), _("Could not find such organization."),
) )
if self.organization.slug != kwargs["slug"]:
return HttpResponsePermanentRedirect(
request.get_full_path().replace(kwargs["slug"], self.object.slug)
)
return super(OrganizationMixin, self).dispatch(request, *args, **kwargs)
class OrganizationDetailView(OrganizationMixin, DetailView): 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
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if self.object.slug != kwargs["slug"]: if self.object.slug != kwargs["slug"]:
@ -140,7 +221,7 @@ class OrganizationList(TitleMixin, ListView, OrganizationBase):
model = Organization model = Organization
context_object_name = "organizations" context_object_name = "organizations"
template_name = "organization/list.html" template_name = "organization/list.html"
title = gettext_lazy("Organizations") title = gettext_lazy("Groups")
def get_queryset(self): def get_queryset(self):
return ( return (
@ -180,12 +261,6 @@ class OrganizationHome(OrganizationDetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(OrganizationHome, self).get_context_data(**kwargs) context = super(OrganizationHome, self).get_context_data(**kwargs)
context["title"] = self.object.name context["title"] = self.object.name
context["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"] = self.get_posts() context["posts"] = self.get_posts()
context["post_comment_counts"] = { context["post_comment_counts"] = {
int(page[2:]): count int(page[2:]): count
@ -196,9 +271,6 @@ class OrganizationHome(OrganizationDetailView):
.annotate(count=Count("page")) .annotate(count=Count("page"))
.order_by() .order_by()
} }
context["pending_count"] = OrganizationRequest.objects.filter(
state="P", organization=self.object
).count()
now = timezone.now() now = timezone.now()
visible_contests = ( visible_contests = (
@ -212,12 +284,6 @@ class OrganizationHome(OrganizationDetailView):
start_time__lte=now, end_time__gt=now start_time__lte=now, end_time__gt=now
) )
context["future_contests"] = visible_contests.filter(start_time__gt=now) context["future_contests"] = visible_contests.filter(start_time__gt=now)
context["top_rated"] = self.object.members.filter(is_unlisted=False).order_by(
"-rating"
)[:10]
context["top_scorer"] = self.object.members.filter(is_unlisted=False).order_by(
"-performance_points"
)[:10]
context["page_type"] = "home" context["page_type"] = "home"
return context return context
@ -232,7 +298,6 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
context = super(OrganizationUsers, self).get_context_data(**kwargs) context = super(OrganizationUsers, self).get_context_data(**kwargs)
context["title"] = _("%s Members") % self.object.name context["title"] = _("%s Members") % self.object.name
context["partial"] = True context["partial"] = True
context["can_edit"] = self.can_edit_organization()
context["kick_url"] = reverse( context["kick_url"] = reverse(
"organization_user_kick", args=[self.object.id, self.object.slug] "organization_user_kick", args=[self.object.id, self.object.slug]
) )
@ -257,31 +322,7 @@ class OrganizationUsers(QueryStringSortMixin, OrganizationDetailView):
return context return context
class OrganizationExternalMixin(OrganizationBase): class OrganizationProblems(LoginRequiredMixin, MemberOrganizationMixin, ProblemList):
organization_id = None
organization = None
def get_organization_from_url(self, request, *args, **kwargs):
try:
self.organization_id = int(kwargs["pk"])
self.organization = Organization.objects.get(id=self.organization_id)
except:
return HttpResponseBadRequest()
if self.organization.slug != kwargs["slug"]:
return HttpResponsePermanentRedirect(
request.get_full_path().replace(kwargs["slug"], self.object.slug)
)
return None
def edit_context_data(self, context):
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
context.pop("organizations")
class OrganizationProblems(ProblemList, OrganizationExternalMixin):
template_name = "organization/problems.html" template_name = "organization/problems.html"
def get_queryset(self): def get_queryset(self):
@ -289,39 +330,35 @@ class OrganizationProblems(ProblemList, OrganizationExternalMixin):
return super().get_normal_queryset() return super().get_normal_queryset()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
ret = super().get_organization_from_url(request, *args, **kwargs)
if ret:
return ret
if not self.can_access(self.organization):
return HttpResponseBadRequest()
self.setup_problem_list(request) self.setup_problem_list(request)
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_latest_attempted_problems(self, limit=None):
if 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): def get_context_data(self, **kwargs):
context = super(OrganizationProblems, self).get_context_data(**kwargs) context = super(OrganizationProblems, self).get_context_data(**kwargs)
self.edit_context_data(context)
context["page_type"] = "problems" context["page_type"] = "problems"
return context return context
class OrganizationContests(ContestList, OrganizationExternalMixin): class OrganizationContests(LoginRequiredMixin, MemberOrganizationMixin, ContestList):
template_name = "organization/contests.html" template_name = "organization/contests.html"
def get_queryset(self): def get_queryset(self):
self.org_query = [self.organization_id] self.org_query = [self.organization_id]
return super().get_queryset() return super().get_queryset()
def get(self, request, *args, **kwargs):
ret = super().get_organization_from_url(request, *args, **kwargs)
if ret:
return ret
if not self.can_access(self.organization):
return HttpResponseBadRequest()
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(OrganizationContests, self).get_context_data(**kwargs) context = super(OrganizationContests, self).get_context_data(**kwargs)
self.edit_context_data(context)
context["page_type"] = "contests" context["page_type"] = "contests"
return context return context
@ -329,6 +366,9 @@ class OrganizationContests(ContestList, OrganizationExternalMixin):
class OrganizationMembershipChange( class OrganizationMembershipChange(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
): ):
model = Organization
context_object_name = "organization"
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
org = self.get_object() org = self.get_object()
response = self.handle(request, org, request.profile) response = self.handle(request, org, request.profile)
@ -345,23 +385,23 @@ class JoinOrganization(OrganizationMembershipChange):
if profile.organizations.filter(id=org.id).exists(): if profile.organizations.filter(id=org.id).exists():
return generic_message( return generic_message(
request, request,
_("Joining organization"), _("Joining group"),
_("You are already in the organization."), _("You are already in the group."),
) )
if not org.is_open: if not org.is_open:
return generic_message( return generic_message(
request, _("Joining organization"), _("This organization is not open.") request, _("Joining group"), _("This group is not open.")
) )
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if profile.organizations.filter(is_open=True).count() >= max_orgs: if profile.organizations.filter(is_open=True).count() >= max_orgs:
return generic_message( return generic_message(
request, request,
_("Joining organization"), _("Joining group"),
_( _("You may not be part of more than {count} public groups.").format(
"You may not be part of more than {count} public organizations." count=max_orgs
).format(count=max_orgs), ),
) )
profile.organizations.add(org) profile.organizations.add(org)
@ -374,7 +414,7 @@ class LeaveOrganization(OrganizationMembershipChange):
if not profile.organizations.filter(id=org.id).exists(): if not profile.organizations.filter(id=org.id).exists():
return generic_message( return generic_message(
request, request,
_("Leaving organization"), _("Leaving group"),
_('You are not in "%s".') % org.short_name, _('You are not in "%s".') % org.short_name,
) )
profile.organizations.remove(org) profile.organizations.remove(org)
@ -422,7 +462,13 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
) )
class OrganizationRequestDetail(LoginRequiredMixin, TitleMixin, DetailView): class OrganizationRequestDetail(
LoginRequiredMixin,
TitleMixin,
OrganizationMixin,
OrganizationHomeViewContext,
DetailView,
):
model = OrganizationRequest model = OrganizationRequest
template_name = "organization/requests/detail.html" template_name = "organization/requests/detail.html"
title = gettext_lazy("Join request detail") title = gettext_lazy("Join request detail")
@ -445,11 +491,11 @@ OrganizationRequestFormSet = modelformset_factory(
class OrganizationRequestBaseView( class OrganizationRequestBaseView(
OrganizationDetailView,
TitleMixin, TitleMixin,
LoginRequiredMixin, LoginRequiredMixin,
SingleObjectTemplateResponseMixin, SingleObjectTemplateResponseMixin,
SingleObjectMixin, SingleObjectMixin,
View,
): ):
model = Organization model = Organization
slug_field = "key" slug_field = "key"
@ -562,58 +608,43 @@ class OrganizationRequestLog(OrganizationRequestBaseView):
return context return context
class EditOrganization(LoginRequiredMixin, TitleMixin, OrganizationMixin, UpdateView): class AddOrganizationMember(
template_name = "organization/edit.html" LoginRequiredMixin,
TitleMixin,
AdminOrganizationMixin,
OrganizationDetailView,
UpdateView,
):
template_name = "organization/add-member.html"
model = Organization model = Organization
form_class = EditOrganizationForm form_class = AddOrganizationMemberForm
def get_title(self): def get_title(self):
return _("Editing %s") % self.object.name return _("Add member for %s") % self.object.name
def get_object(self, queryset=None): def get_object(self, queryset=None):
object = super(EditOrganization, self).get_object() object = super(AddOrganizationMember, self).get_object()
if not self.can_edit_organization(object): if not self.can_edit_organization(object):
raise PermissionDenied() raise PermissionDenied()
return object 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): def form_valid(self, form):
new_users = form.cleaned_data["new_users"]
self.object.members.add(*new_users)
with transaction.atomic(), revisions.create_revision(): with transaction.atomic(), revisions.create_revision():
revisions.set_comment(_("Edited from site")) revisions.set_comment(_("Added members from site"))
revisions.set_user(self.request.user) revisions.set_user(self.request.user)
return super(EditOrganization, self).form_valid(form) return super(AddOrganizationMember, self).form_valid(form)
def dispatch(self, request, *args, **kwargs): def get_success_url(self):
try: return reverse("organization_users", args=[self.object.id, self.object.slug])
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,
)
class KickUserWidgetView( class KickUserWidgetView(
LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View LoginRequiredMixin, AdminOrganizationMixin, SingleObjectMixin, View
): ):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
organization = self.get_object() 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,
)
try: try:
user = Profile.objects.get(id=request.POST.get("user", None)) user = Profile.objects.get(id=request.POST.get("user", None))
except Profile.DoesNotExist: except Profile.DoesNotExist:
@ -635,3 +666,147 @@ class KickUserWidgetView(
organization.members.remove(user) organization.members.remove(user)
return HttpResponseRedirect(organization.get_users_url()) 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 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

View file

@ -568,6 +568,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
] ]
def get_org_query(self, query): def get_org_query(self, query):
if not self.profile:
return None
return [ return [
i i
for i in query for i in query

View file

@ -46,7 +46,7 @@ class CustomRegistrationForm(RegistrationForm):
) )
organizations = SortedMultipleChoiceField( organizations = SortedMultipleChoiceField(
queryset=Organization.objects.filter(is_open=True), queryset=Organization.objects.filter(is_open=True),
label=_("Organizations"), label=_("Groups"),
required=False, required=False,
widget=Select2MultipleWidget(attrs={"style": "width:100%"}), widget=Select2MultipleWidget(attrs={"style": "width:100%"}),
) )
@ -65,9 +65,9 @@ class CustomRegistrationForm(RegistrationForm):
if sum(org.is_open for org in organizations) > max_orgs: if sum(org.is_open for org in organizations) > max_orgs:
raise forms.ValidationError( raise forms.ValidationError(
_( _("You may not be part of more than {count} public groups.").format(
"You may not be part of more than {count} public organizations." count=max_orgs
).format(count=max_orgs) )
) )
return self.cleaned_data["organizations"] return self.cleaned_data["organizations"]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
.leave-organization, .leave-organization:hover {
color: red;
}
#control-list li {
border-bottom: 1px solid black;
}
#pending-count-box {
float: right;
text-align: center;
background: red;
color: white;
border-radius: 3px;
padding-left: 0.3em;
padding-right: 0.3em;
}
.org-field-wrapper {
input[type=text], textarea {
width: 100%;
}
.select2 {
width: 100% !important;
}
}
.org-field-wrapper {
margin-top: 0.4em;
}

View file

@ -13,3 +13,4 @@
@import "contest"; @import "contest";
@import "misc"; @import "misc";
@import "chatbox"; @import "chatbox";
@import "organization";

View file

@ -106,7 +106,7 @@
makeToggleBtn($("#ongoing-btn"), $("#ongoing-table")); makeToggleBtn($("#ongoing-btn"), $("#ongoing-table"));
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'}) $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'})
.css({'visibility': 'visible'}); .css({'visibility': 'visible'});
// var tooltip_classes = 'tooltipped tooltipped-e'; // var tooltip_classes = 'tooltipped tooltipped-e';

View file

@ -0,0 +1,9 @@
{% extends "organization/home-base.html" %}
{% block org_js %}
{{ form.media.js }}
{% endblock %}
{% block middle_content %}
{% include "organization/form.html" %}
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "organization/home-base.html" %}
{% block three_col_js %}
{{ form.media.js }}
{% include "organization/home-js.html" %}
{% endblock %}
{% block three_col_media %}
{{ form.media.css }}
<style>
#org-field-wrapper-visible, #org-field-wrapper-sticky {
display: contents;
}
</style>
{% endblock %}
{% block middle_content %}
{% include "organization/form.html" %}
{% endblock %}

View file

@ -0,0 +1,28 @@
{% extends "organization/home-base.html" %}
{% block middle_content %}
<table class="table">
<thead>
<tr>
<th>
{{_('Blog')}}
</th>
<th>
{{_('Author')}}
</th>
<th>
{{_('Post time')}}
</th>
</tr>
</thead>
<tbody>
{% for blog in blogs %}
<tr>
<td><a href="{{url('edit_organization_blog', organization.id, organization.slug, blog.id)}}">{{blog.title}}</a></td>
<td>{{link_users(blog.authors.all())}}</td>
<td>{{- blog.publish_on|date(_("N j, Y, g:i a")) -}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "organization/home-base.html" %}
{% block js_media %} {% block org_js %}
{{ form.media.js }} {{ form.media.js }}
<script type="text/javascript"> <script type="text/javascript">
window.django = {jQuery: $}; window.django = {jQuery: $};
@ -27,22 +27,15 @@
</script> </script>
{% endblock %} {% endblock %}
{% block media %} {% block three_col_media %}
{{ form.media.css }} {{ form.media.css }}
<link rel="stylesheet" href="{{ static('admin/css/widgets.css') }}" type="text/css">
<link rel="stylesheet" href="{{ static('admin/css/pagedown.css') }}" type="text/css">
<link rel="stylesheet" href="{{ static('problem_edit.css') }}" type="text/css">
<style> <style>
#id_about { #org-field-wrapper-is_open {
width: 500px; display: contents;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block body %} {% block middle_content %}
<form action="" method="post" class="form-area" style="width:100%"> {% include "organization/form.html" %}
{% csrf_token %} {% endblock %}
<table border="0" style="text-align:left">{{ form.as_table() }}</table>
<button type="submit">{{ _('Update') }}</button>
</form>
{% endblock %}

View file

@ -0,0 +1,24 @@
<form action="" method="post">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger alert-dismissable">
<a href="#" class="close">x</a>
{{ form.non_field_errors() }}
</div>
{% endif %}
{% for field in form %}
{% if not field.is_hidden %}
<div style="margin-bottom: 1em;">
{{ field.errors }}
<label for="{{field.id_for_label }}"><b>{{ field.label }}:</b></label>
<div class="org-field-wrapper" id="org-field-wrapper-{{field.html_name }}">
{{ field }}
</div>
{% if field.help_text %}
<i>{{ field.help_text|safe }}</i>
{% endif %}
</div>
{% endif %}
{% endfor %}
<button type="submit">{{ _('Save') }}</button>
</form>

View file

@ -0,0 +1,21 @@
{% extends "three-column-content.html" %}
{% block three_col_js %}
{% include "organization/home-js.html" %}
{% block org_js %}{% endblock %}
{% endblock %}
{% block left_sidebar %}
{% include "organization/org-left-sidebar.html" %}
{% endblock %}
{% block right_sidebar %}
{% include "organization/org-right-sidebar.html" %}
{% endblock %}
{% block middle_title %}
{% if title %}
<center><h2>{{ title }}</h2></center>
<br/>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,25 @@
<script type="text/javascript">
$(function () {
$('.time-remaining').each(function () {
count_down($(this));
});
$('.leave-organization').click(function () {
if (confirm('{{ _('Are you sure you want to leave this organization?') }}\n' +
{% if organization.is_open %}
'{{ _('You will have to rejoin to show up on the organization leaderboard.') }}'
{% else %}
'{{ _('You will have to request membership in order to join again.') }}'
{% endif %}
)) {
$(this).parent().submit();
}
});
$(document).ready(function () {
$('.control-button').click(function(e) {
e.preventDefault();
$('#control-panel').toggle("fast");
})
});
});
</script>

View file

@ -1,79 +1,31 @@
{% extends "three-column-content.html" %} {% extends "organization/home-base.html" %}
{% block three_col_media %}
<style>
.leave-organization, .leave-organization:hover {
color: red;
}
#control-list li {
border-bottom: 1px solid black;
}
#pending-count-box {
float: right;
text-align: center;
background: red;
color: white;
border-radius: 3px;
padding-left: 0.3em;
padding-right: 0.3em;
}
</style>
{% endblock %}
{% block three_col_js %}
<script type="text/javascript">
$(function () {
$('.time-remaining').each(function () {
count_down($(this));
});
$('.leave-organization').click(function () {
if (confirm('{{ _('Are you sure you want to leave this organization?') }}\n' +
{% if organization.is_open %}
'{{ _('You will have to rejoin to show up on the organization leaderboard.') }}'
{% else %}
'{{ _('You will have to request membership in order to join again.') }}'
{% endif %}
)) {
$(this).parent().submit();
}
});
$(document).ready(function () {
$('.control-button').click(function(e) {
e.preventDefault();
$('#control-panel').toggle("fast");
})
});
});
</script>
{% endblock %}
{% block left_sidebar %}
{% include "organization/org-left-sidebar.html" %}
{% endblock %}
{% block title_ruler %}{% endblock %} {% block title_ruler %}{% endblock %}
{% block middle_content %}
{% block before_posts %}{% endblock %} {% block middle_title %}
<div class="page-title"> <div class="page-title">
<div class="tabs"> <div class="tabs">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<span class="spacer"></span> <span class="spacer"></span>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
{% if is_member or can_edit %} {% if is_member or can_edit %}
{% elif organization.is_open or can_edit %}
{% elif organization.is_open or can_edit %} <form method="post" action="{{ url('join_organization', organization.id, organization.slug) }}">
<form method="post" action="{{ url('join_organization', organization.id, organization.slug) }}"> {% csrf_token %}
{% csrf_token %} <input type="submit" class="unselectable button" value="{{ _('Join') }}" style="margin-right: 1em">
<input type="submit" class="unselectable button" value="{{ _('Join organization') }}" style="margin-right: 1em"> </form>
</form> {% else %}
{% else %} <a href="{{ url('request_organization', organization.id, organization.slug) }}"
<a href="{{ url('request_organization', organization.id, organization.slug) }}" class="unselectable button">{{ _('Request membership') }}</a>
class="unselectable button">{{ _('Request membership') }}</a>
{% endif %}
{% endif %} {% endif %}
</div> {% endif %}
</div> </div>
</div>
{% endblock %}
{% block middle_content %}
{% block before_posts %}{% endblock %}
{% if is_member or can_edit %} {% if is_member or can_edit %}
{% for post in posts %} {% for post in posts %}
{% include "blog/content.html" %} {% include "blog/content.html" %}
@ -95,56 +47,3 @@
{% endif %} {% endif %}
{% block after_posts %}{% endblock %} {% block after_posts %}{% endblock %}
{% endblock %} {% endblock %}
{% block right_sidebar %}
<div class="right-sidebar">
{% if (is_member or can_edit) %}
{% include 'contests-countdown.html' %}
{% endif %}
{% if is_member or can_edit %}
<div class="blog-sidebox sidebox">
<h3>{{ _('About') }}<i class="fa fa-info-circle"></i></h3>
<div class="sidebox-content">
<div style="margin: 0.3em;">
{% cache 3600 'organization_html' organization.id MATH_ENGINE %}
{{ organization.about|markdown('organization-about', MATH_ENGINE)|reference|str|safe }}
{% endcache %}
</div>
</div>
</div>
{% endif %}
{% if can_edit %}
<div id="control-panel" class="blog-sidebox sidebox">
<h3>{{ _('Controls') }} <i class="fa fa-cog"></i></h3>
<ul id="control-list" class="sidebox-content" style="padding: 1em;">
<li>
<div>
<a href="{{ url('edit_organization', organization.id, organization.slug) }}">{{ _('Edit organization') }}</a>
</div>
</li>
{% if not organization.is_open %}
<li>
<div>
<a href="{{ url('organization_requests_pending', organization.id, organization.slug) }}">{{ _('View requests') }}</a>
{% if pending_count > 0 %}
<span id="pending-count-box">
{{pending_count}}
</span>
{% endif %}
</div>
</li>
{% endif %}
{% if is_member and not can_edit %}
<li>
<form method="post" action="{{ url('leave_organization', organization.id, organization.slug) }}">
{% csrf_token %}
<a href="#" class="leave-organization">{{ _('Leave organization') }}</a>
</form>
</li>
{% endif %}
</ul>
</div>
{% endif %}
{% include 'top-users.html' %}
</div>
{% endblock %}

View file

@ -22,14 +22,13 @@
{% endif %} {% endif %}
}); });
</script> </script>
{% endblock %} {% endblock %}
{% block title_ruler %}{% endblock %} {% block title_ruler %}{% endblock %}
{% block title_row %} {% block title_row %}
{% set tab = 'organizations' %} {% set tab = 'organizations' %}
{% set title = _('Organizations') %} {% set title = _('Group') %}
{% include "user/user-list-tabs.html" %} {% include "user/user-list-tabs.html" %}
{% endblock %} {% endblock %}
@ -37,7 +36,7 @@
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div style="margin-bottom: 0.5em"> <div style="margin-bottom: 0.5em">
<input id="show-my-org-checkbox" type="checkbox" style="vertical-align: bottom;"> <input id="show-my-org-checkbox" type="checkbox" style="vertical-align: bottom;">
<label for="show-my-org-checkbox" style="vertical-align: bottom; margin-right: 1em;">{{ _('Show my organizations only') }}</label> <label for="show-my-org-checkbox" style="vertical-align: bottom; margin-right: 1em;">{{ _('Show my groups only') }}</label>
</div> </div>
{% endif %} {% endif %}

View file

@ -0,0 +1,76 @@
<div class="right-sidebar">
{% if (is_member or can_edit) %}
{% include 'contests-countdown.html' %}
{% endif %}
{% if is_member or can_edit %}
<div class="blog-sidebox sidebox">
<h3>{{ _('About') }}<i class="fa fa-info-circle"></i></h3>
<div class="sidebox-content">
<div style="margin: 0.3em;">
{% cache 3600 'organization_html' organization.id MATH_ENGINE %}
{{ organization.about|markdown('organization-about', MATH_ENGINE)|reference|str|safe }}
{% endcache %}
</div>
</div>
</div>
{% endif %}
{% if can_edit or is_member %}
<div id="control-panel" class="blog-sidebox sidebox">
<h3>{{ _('Controls') }} <i class="fa fa-cog"></i></h3>
<ul id="control-list" class="sidebox-content" style="padding: 1em;">
{% if can_edit %}
<li>
<div>
<a href="{{ url('edit_organization', organization.id, organization.slug) }}">{{ _('Edit organization') }}</a>
</div>
</li>
{% endif %}
{% if can_edit and not organization.is_open %}
<li>
<div>
<a href="{{ url('organization_requests_pending', organization.id, organization.slug) }}">{{ _('View requests') }}</a>
{% if pending_count > 0 %}
<span id="pending-count-box">
{{pending_count}}
</span>
{% endif %}
</div>
</li>
{% endif %}
{% if can_edit %}
<li>
<div>
<a href="{{ url('add_organization_member', organization.id, organization.slug) }}">{{ _('Add members') }}</a>
</div>
</li>
{% endif %}
{% if is_member %}
<li>
<div>
<a href="{{ url('add_organization_blog', organization.id, organization.slug) }}">{{ _('Add blog') }}</a>
</div>
</li>
{% endif %}
<li>
<div>
<a href="{{ url('organization_pending_blogs', organization.id, organization.slug) }}">{{ _('Pending blogs') }}</a>
{% if pending_blog_count > 0 %}
<span id="pending-count-box">
{{pending_blog_count}}
</span>
{% endif %}
</div>
</li>
{% if is_member and not can_edit %}
<li>
<form method="post" action="{{ url('leave_organization', organization.id, organization.slug) }}">
{% csrf_token %}
<a href="#" class="leave-organization">{{ _('Leave group') }}</a>
</form>
</li>
{% endif %}
</ul>
</div>
{% endif %}
{% include 'top-users.html' %}
</div>

View file

@ -1,14 +1,7 @@
{% extends "base.html" %} {% extends "organization/home-base.html" %}
{% block media %}
<style>
th {
text-align: left
}
</style>
{% endblock %}
{% block body %} {% block middle_content %}
<table> <table class="table">
<tr> <tr>
<th>{{ _('User:') }}</th> <th>{{ _('User:') }}</th>
<td>{{ link_user(object.user) }}</td> <td>{{ link_user(object.user) }}</td>
@ -26,10 +19,8 @@
<td>{{ object.time|date(_("N j, Y, g:i a")) }}</td> <td>{{ object.time|date(_("N j, Y, g:i a")) }}</td>
</tr> </tr>
<tr> <tr>
<th colspan="2">{{ _('Reason:') }}</th> <th>{{ _('Reason:') }}</th>
</tr> <td>{{ object.reason }}</td>
<tr>
<td colspan="2" style="padding-left: 2em">{{ object.reason }}</td>
</tr> </tr>
</table> </table>
{% endblock %} {% endblock %}

View file

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "organization/home-base.html" %}
{% block body %} {% block middle_content %}
{% include "organization/requests/tabs.html" %} {% include "organization/requests/tabs.html" %}
{% if requests %} {% if requests %}
@ -16,7 +16,7 @@
<td>{{ link_user(r.user) }}</td> <td>{{ link_user(r.user) }}</td>
<td> <td>
<a href="{{ url('request_organization_detail', object.id, object.slug, r.id) }}"> <a href="{{ url('request_organization_detail', object.id, object.slug, r.id) }}">
{{- r.time|date(_("N j, Y, H:i")) -}} {{- r.time|date(_("N j, Y, g:i a")) -}}
</a> </a>
</td> </td>
<td>{{ r.state }}</td> <td>{{ r.state }}</td>

View file

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "organization/home-base.html" %}
{% block body %} {% block middle_content %}
{% include "messages.html" %} {% include "messages.html" %}
{% include "organization/requests/tabs.html" %} {% include "organization/requests/tabs.html" %}
@ -21,7 +21,7 @@
<tr id="request-{{ form.instance.id }}"> <tr id="request-{{ form.instance.id }}">
<td>{{ form.id }}{{ link_user(form.instance.user) }}</td> <td>{{ form.id }}{{ link_user(form.instance.user) }}</td>
<td><a href="{{ url('request_organization_detail', object.id, object.slug, form.instance.id) }}"> <td><a href="{{ url('request_organization_detail', object.id, object.slug, form.instance.id) }}">
{{ form.instance.time|date(_("N j, Y, H:i")) }} {{ form.instance.time|date(_("N j, Y, g:i a")) }}
</a></td> </a></td>
<td>{{ form.state }}</td> <td>{{ form.state }}</td>
<td>{{ form.instance.reason|truncatechars(50) }}</td> <td>{{ form.instance.reason|truncatechars(50) }}</td>

View file

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "organization/home-base.html" %}
{% block js_media %} {% block org_js %}
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
$('#id_reason').keydown(function (e) { $('#id_reason').keydown(function (e) {
@ -12,7 +12,7 @@
</script> </script>
{% endblock %} {% endblock %}
{% block body %} {% block middle_content %}
<form action="" method="post" class="form-area"> <form action="" method="post" class="form-area">
{% csrf_token %} {% csrf_token %}
<p><label for="{{ form.reason.id_for_label }}"><b>{{ _('Your reason for joining:') }}</b></label></p> <p><label for="{{ form.reason.id_for_label }}"><b>{{ _('Your reason for joining:') }}</b></label></p>

View file

@ -92,7 +92,7 @@
$category.select2().css({'visibility': 'visible'}).change(clean_submit); $category.select2().css({'visibility': 'visible'}).change(clean_submit);
$('#types').select2({multiple: 1, placeholder: '{{ _('Filter by type...') }}'}) $('#types').select2({multiple: 1, placeholder: '{{ _('Filter by type...') }}'})
.css({'visibility': 'visible'}); .css({'visibility': 'visible'});
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'}) $('#search-org').select2({multiple: 1, placeholder: '{{ _('Groups') }}...'})
.css({'visibility': 'visible'}); .css({'visibility': 'visible'});
$('#search-author').select2({multiple: 1, placeholder: '{{ _('Authors') }}...'}) $('#search-author').select2({multiple: 1, placeholder: '{{ _('Authors') }}...'})
.css({'visibility': 'visible'}); .css({'visibility': 'visible'});

View file

@ -35,7 +35,7 @@
{% endif %} {% endif %}
{% if organizations %} {% if organizations %}
<div class="filter-form-group"> <div class="filter-form-group">
<label for="type"><i>{{ _('Organization') }}</i></label> <label for="type"><i>{{ _('Group') }}</i></label>
<select id="search-org" name="orgs" multiple> <select id="search-org" name="orgs" multiple>
{% for org in organizations %} {% for org in organizations %}
<option value="{{ org.id }}"{% if org.id in org_query %} selected{% endif %}> <option value="{{ org.id }}"{% if org.id in org_query %} selected{% endif %}>

View file

@ -53,6 +53,7 @@
<div id="three-col-container"> <div id="three-col-container">
{% block left_sidebar %}{% endblock %} {% block left_sidebar %}{% endblock %}
<div class="middle-content"> <div class="middle-content">
{% block middle_title %}{% endblock %}
{% block middle_content %}{% endblock %} {% block middle_content %}{% endblock %}
</div> </div>
{% block right_sidebar %}{% endblock %} {% block right_sidebar %}{% endblock %}

View file

@ -3,7 +3,7 @@
{% block tabs %} {% block tabs %}
{{ make_tab('list', 'fa-trophy', url('user_list'), _('Leaderboard')) }} {{ make_tab('list', 'fa-trophy', url('user_list'), _('Leaderboard')) }}
{{ make_tab('friends', 'fa-users', url('user_list') + '?friend=true', _('Friends')) }} {{ make_tab('friends', 'fa-users', url('user_list') + '?friend=true', _('Friends')) }}
{{ make_tab('organizations', 'fa-university', url('organization_list'), _('Organizations')) }} {{ make_tab('organizations', 'fa-university', url('organization_list'), _('Group')) }}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
{{ make_tab('import', 'fa-table', url('import_users'), _('Import')) }} {{ make_tab('import', 'fa-table', url('import_users'), _('Import')) }}
{% endif %} {% endif %}