Add organization blogs
This commit is contained in:
parent
99fc3d1015
commit
5fff6b1510
27 changed files with 1119 additions and 630 deletions
20
dmoj/urls.py
20
dmoj/urls.py
|
@ -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(),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
26
resources/organization.scss
Normal file
26
resources/organization.scss
Normal 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;
|
||||||
|
}
|
|
@ -13,3 +13,4 @@
|
||||||
@import "contest";
|
@import "contest";
|
||||||
@import "misc";
|
@import "misc";
|
||||||
@import "chatbox";
|
@import "chatbox";
|
||||||
|
@import "organization";
|
||||||
|
|
|
@ -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';
|
||||||
|
|
9
templates/organization/add-member.html
Normal file
9
templates/organization/add-member.html
Normal 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 %}
|
19
templates/organization/blog/add.html
Normal file
19
templates/organization/blog/add.html
Normal 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 %}
|
28
templates/organization/blog/pending.html
Normal file
28
templates/organization/blog/pending.html
Normal 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 %}
|
|
@ -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 %}
|
|
||||||
<table border="0" style="text-align:left">{{ form.as_table() }}</table>
|
|
||||||
<button type="submit">{{ _('Update') }}</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
24
templates/organization/form.html
Normal file
24
templates/organization/form.html
Normal 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>
|
21
templates/organization/home-base.html
Normal file
21
templates/organization/home-base.html
Normal 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 %}
|
25
templates/organization/home-js.html
Normal file
25
templates/organization/home-js.html
Normal 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>
|
|
@ -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 %}
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
76
templates/organization/org-right-sidebar.html
Normal file
76
templates/organization/org-right-sidebar.html
Normal 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>
|
|
@ -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 %}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'});
|
||||||
|
|
|
@ -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 %}>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue