Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added <desired bridge log path>
Empty file.
1 change: 1 addition & 0 deletions dmoj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def paged_list_view(view, name):

path('organizations/', organization.OrganizationList.as_view(), name='organization_list'),
path('organizations/create', organization.CreateOrganization.as_view(), name='organization_create'),
path('organization/id/<int:pk>', organization.OrganizationHomeById.as_view(), name='organization_home_by_id'),
path('organization/<int:pk>-<path:suffix>',
lambda _, pk, suffix: HttpResponsePermanentRedirect('/organization/%s' % suffix)),
path('organization/<slug:slug>', include([
Expand Down
9 changes: 8 additions & 1 deletion judge/admin/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ class OrganizationAdmin(VersionAdmin):
readonly_fields = ('creation_date', 'current_consumed_credit')
fields = ('name', 'slug', 'short_name', 'is_open', 'is_unlisted', 'paid_credit', 'current_consumed_credit',
'about', 'logo_override_image', 'slots', 'creation_date', 'admins')
list_display = ('name', 'short_name', 'is_open', 'is_unlisted', 'slots', 'show_public')
list_display = ('id', 'name', 'short_name', 'is_open', 'is_unlisted', 'slots', 'show_public')
prepopulated_fields = {'slug': ('name',)}
actions = ('recalculate_points',)
actions_on_top = True
actions_on_bottom = True
form = OrganizationForm
search_fields = ('name', 'short_name')

def id(self, obj):
return format_html('<a href="/organization/{}" target="_blank">{}</a>', obj.id, obj.id)
id.short_description = 'ID'
id.admin_order_field = 'id'
list_filter = ('is_open', 'is_unlisted')

@admin.display(description='')
def show_public(self, obj):
Expand Down
91 changes: 89 additions & 2 deletions judge/views/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,49 @@ def is_in_organization_subdomain(self):


# Use this mixin to mark a view is public for all users, including non-members
class OrganizationByIdMixin(object):
model = Organization

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['organization'] = self.organization
context['logo_override_image'] = self.organization.logo_override_image
context['meta_description'] = self.organization.about[:settings.DESCRIPTION_MAX_LENGTH]
return context

@cached_property
def organization(self):
return get_object_or_404(Organization, id=self.kwargs['pk'])

def dispatch(self, request, *args, **kwargs):
if 'pk' not in kwargs:
raise ImproperlyConfigured('Must pass an id')

try:
self.object = self.organization

# block the user from viewing other orgs in the subdomain
if self.is_in_organization_subdomain() and self.organization.pk != self.request.organization.pk:
return generic_message(request, _('Cannot view other organizations'),
_('You cannot view other organizations'), status=403)

return super(OrganizationByIdMixin, self).dispatch(request, *args, **kwargs)
except Http404:
pk = kwargs.get('pk', None)
return generic_message(request, _('No such organization'),
_('Could not find an organization with ID "%s".') % pk)

def can_edit_organization(self, org=None):
if org is None:
org = self.organization
if not self.request.user.is_authenticated:
return False
return org.is_admin(self.request.profile) or self.request.user.has_perm('judge.edit_all_organization')

def is_in_organization_subdomain(self):
return hasattr(self.request, 'organization')


class PublicOrganizationMixin(OrganizationMixin):
pass

Expand All @@ -96,13 +139,18 @@ class PublicOrganizationMixin(OrganizationMixin):
class PrivateOrganizationMixin(OrganizationMixin):
# If the user has at least one of the following permissions,
# they can access the private data even if they are not in the org
permission_bypass = []
permission_bypass = ['judge.edit_organization']

# Override this method to customize the permission check
def can_access_this_view(self):
if self.request.user.is_authenticated:
# Allow superusers and staff to access any organization
if self.request.user.is_superuser or self.request.user.is_staff:
return True
Comment on lines +147 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this check is redundant, if user is superuser, they already have the judge.edit_organization, also i think better to leave the PrivateOrganizationMixin alone, don't change it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i understand

# Allow organization members
if self.request.profile in self.organization:
return True
# Allow users with specific permissions
if any(self.request.user.has_perm(perm) for perm in self.permission_bypass):
return True
return False
Expand Down Expand Up @@ -146,7 +194,14 @@ class OrganizationList(TitleMixin, ListView):
title = gettext_lazy('Organizations')

def get_queryset(self):
return Organization.objects.filter(is_unlisted=False)
queryset = Organization.objects.filter(is_unlisted=False)
search_query = self.request.GET.get('search', '').strip()
if search_query:
queryset = queryset.filter(
Q(name__icontains=search_query) |
Q(short_name__icontains=search_query)
)
return queryset


class OrganizationUsers(QueryStringSortMixin, DiggPaginatorMixin, BaseOrganizationListView):
Expand Down Expand Up @@ -471,6 +526,38 @@ def post(self, request, *args, **kwargs):
# using PublicOrganizationMixin to allow user to view org's public information
# like name, request join org, ...
# However, they cannot see the organization private blog
class OrganizationHomeById(TitleMixin, OrganizationByIdMixin, PostListBase):
template_name = 'organization/home.html'

def get_title(self):
return self.organization.name

def get_queryset(self):
queryset = BlogPost.objects.filter(organization=self.organization)

if not self.request.user.has_perm('judge.edit_all_post'):
if not self.can_edit_organization():
if self.request.profile in self.organization:
# Normal user can only view public posts
queryset = queryset.filter(publish_on__lte=timezone.now(), visible=True)
else:
# User cannot view organization blog
# if they are not in the org
# even if the org is public
queryset = BlogPost.objects.none()
else:
# Org admin can view public posts & their own posts
queryset = queryset.filter(Q(visible=True) | Q(authors=self.request.profile))

if self.request.user.is_authenticated:
profile = self.request.profile
queryset = queryset.annotate(
my_vote=FilteredRelation('votes', condition=Q(votes__voter_id=profile.id)),
).annotate(vote_score=Coalesce(F('my_vote__score'), Value(0)))

return queryset.order_by('-sticky', '-publish_on').prefetch_related('authors__user')
Comment on lines +529 to +558
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is ok but it's make the code duplicated, is there a better way? Perhaps we could inherit the OrganizationHome, and override the get_object/get_query_set?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I’ll switch to overriding get_object() or get_queryset() instead that way I can reuse the logic from OrganizationHome and avoid code duplication.



class OrganizationHome(TitleMixin, PublicOrganizationMixin, PostListBase):
template_name = 'organization/home.html'

Expand Down
74 changes: 42 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"dependencies": {
"@commander-js/extra-typings": "11.0.0",
"commander": "11.0.0",
"ws": "8.14.0"
"ws": "^8.18.3"
},
"devDependencies": {
"@types/ws": "8.5.5",
"autoprefixer": "10.4.15",
"postcss": "8.4.29",
"postcss": "^8.5.6",
"postcss-cli": "10.1.0",
"prettier": "3.0.3",
"sass": "1.66.1"
Expand Down
40 changes: 40 additions & 0 deletions resources/organization.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.search-container {
margin: 1em 0;
padding: 1em;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.search-form {
display: flex;
max-width: 600px;
margin: 0 auto;
}

.search-input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 1em;
outline: none;
}

.search-input:focus {
border-color: #5b80b2;
}

.search-submit {
padding: 8px 16px;
background: #5b80b2;
color: white;
border: 1px solid #5b80b2;
border-radius: 0 4px 4px 0;
cursor: pointer;
transition: background-color 0.2s;
}

.search-submit:hover {
background: #466a9f;
}
Loading