diff --git a/judge/jinja2/reference.py b/judge/jinja2/reference.py index 4b5a99914..0700d7a45 100644 --- a/judge/jinja2/reference.py +++ b/judge/jinja2/reference.py @@ -144,11 +144,13 @@ def item_title(item): @registry.function def link_user(user): + is_contest_ranking_profile = False if isinstance(user, Profile): user, profile = user.user, user elif isinstance(user, AbstractUser): profile = user.profile elif type(user).__name__ == 'ContestRankingProfile': + is_contest_ranking_profile = True user, profile = user.user, user else: raise ValueError('Expected profile or user, got %s' % (type(user),)) @@ -160,7 +162,7 @@ def link_user(user): else: display_badge_img = '' - return mark_safe(f'' + return mark_safe(f'' f'' f'{escape(profile.display_name)}{display_badge_img}') diff --git a/judge/management/commands/batch_add_icpc_team.py b/judge/management/commands/batch_add_icpc_team.py old mode 100644 new mode 100755 index 0683361fa..43b6c0353 --- a/judge/management/commands/batch_add_icpc_team.py +++ b/judge/management/commands/batch_add_icpc_team.py @@ -18,7 +18,7 @@ def generate_password(): return ''.join(secrets.choice(ALPHABET) for _ in range(8)) -def add_user(username, teamname, password, org, internalid): +def add_user(username, teamname, password, org, org_group, internalid): usr = User(username=username, is_active=True) usr.set_password(password) usr.save() @@ -28,6 +28,7 @@ def add_user(username, teamname, password, org, internalid): profile.language = Language.objects.get(key=settings.DEFAULT_USER_LANGUAGE) profile.site_theme = 'light' profile.notes = internalid # save the internal id for later use. + profile.group = org_group profile.save() profile.organizations.set([org]) @@ -68,7 +69,7 @@ def handle(self, *args, **options): prefix = options['prefix'] reader = csv.DictReader(fin) - writer = csv.DictWriter(fout, fieldnames=['username', 'teamname', 'password']) + writer = csv.DictWriter(fout, fieldnames=['username', 'teamname', 'password', 'group']) writer.writeheader() done_team_ids = set() @@ -79,16 +80,18 @@ def handle(self, *args, **options): org = get_org(row['instName']) password = generate_password() internalid = row['id'] + org_group = row['group'] if internalid in done_team_ids: continue done_team_ids.add(internalid) - add_user(username, teamname, password, org, internalid) + add_user(username, teamname, password, org, org_group, internalid) writer.writerow({ 'username': username, 'teamname': teamname, 'password': password, + 'group': org_group }) fin.close() diff --git a/judge/migrations/0199_add_user_group.py b/judge/migrations/0199_add_user_group.py new file mode 100644 index 000000000..5c89dd808 --- /dev/null +++ b/judge/migrations/0199_add_user_group.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.22 on 2023-10-18 07:02 + +from django.db import migrations, models +import judge.models.problem + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0198_add_ranking_stop_last_minutes'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='group', + field=models.TextField(blank=True, null=True, verbose_name='uni group'), + ), + ] diff --git a/judge/models/profile.py b/judge/models/profile.py index d30c98f51..fe25d4371 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -194,6 +194,8 @@ class Profile(models.Model): data_last_downloaded = models.DateTimeField(verbose_name=_('last data download time'), null=True, blank=True) username_display_override = models.CharField(max_length=100, blank=True, verbose_name=_('display name override'), help_text=_('Name displayed in place of username.')) + # For ICPC only + group = models.TextField(verbose_name=_('uni group'), null=True, blank=True) @cached_property def organization(self): diff --git a/judge/views/contests.py b/judge/views/contests.py index 5b1ec5d86..0666a0720 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -838,7 +838,7 @@ def get_context_data(self, **kwargs): ContestRankingProfile = namedtuple( 'ContestRankingProfile', - 'id user css_class username points cumtime tiebreaker organization participation ' + 'id user css_class username points cumtime tiebreaker organization participation group ' 'participation_rating problem_cells result_cell virtual display_name', ) @@ -870,6 +870,7 @@ def display_user_problem(contest_problem): participation=participation, virtual=participation.virtual, display_name=user.display_name, + group=user.group, ) diff --git a/resources/contest.scss b/resources/contest.scss index c8840910e..e624c1d1f 100644 --- a/resources/contest.scss +++ b/resources/contest.scss @@ -318,7 +318,7 @@ form.contest-join-pseudotab { } #org-dropdown-check-list { - display: inline-block; + display: flex; } #org-check-list-wrapper { diff --git a/templates/contest/media-icpc-css.html b/templates/contest/media-icpc-css.html index d15fc30d2..315189f6a 100644 --- a/templates/contest/media-icpc-css.html +++ b/templates/contest/media-icpc-css.html @@ -30,4 +30,14 @@ font-size: 1.25em; padding-bottom: -0.75em; } + + span.rating.top-team::after { + content: attr(data-user-tag); + font-size: 0.75em; + padding: 0.25em; + margin-left: 0.5em; + background-color: #FFC008; + border-radius: 2px; + color: black; + } diff --git a/templates/contest/ranking.html b/templates/contest/ranking.html index f6c4f79ba..61a0b8246 100644 --- a/templates/contest/ranking.html +++ b/templates/contest/ranking.html @@ -39,6 +39,11 @@ .filter-checklist-button { float: right; } + + .select-container { + display: flex; + gap: 2em; + } {% endblock %} @@ -81,6 +86,7 @@ window.enableAdminOperations(); window.loadOrgLogo(); window.applyFavoriteUsers(); + window.getTagNamesAndSetTopTag(); }).always(function () { ranking_outdated = false; setTimeout(update_ranking, 10000); @@ -225,12 +231,58 @@ }); }); + $(function() { + $('#tag-check-list').select2({ + theme: '{{ DMOJ_SELECT2_THEME }}', + multiple: true, + closeOnSelect: false, + placeholder: '{{ _('Filter by tag') }}', + }); + + const selection = $('#tag-check-list').data().select2.selection; + const results = $('#tag-check-list').data().select2.results; + + $('#tag-check-list').on('select2:selecting', function(e) { + let id = e.params.args.data.id; + let val = $(e.target).val().concat([id]); + $(e.target).val(val).trigger('change'); + + if (selection.$search.val() != '') { + selection.$search.val(''); + selection.trigger('query', {}); + } else { + results.setClasses(); + } + + return false; + }); + + $('#tag-check-list').on('select2:unselecting', function(e) { + const id = e.params.args.data.id; + const val = $(e.target).val().filter(function(v) { return v != id; }); + $(e.target).val(val).trigger('change'); + + if (selection.$search.val() != '') { + selection.$search.val(''); + selection.trigger('query', {}); + } else { + results.setClasses(); + } + + return false; + }); + }) + if (localStorage.getItem(`filter-cleared-${contest_key}`) === null) { localStorage.setItem(`filter-cleared-${contest_key}`, 'true'); } if (localStorage.getItem(`filter-selected-orgs-${contest_key}`) === null) { - localStorage.setItem(`filter-selected-orgs-${contest_key}`, []); + localStorage.setItem(`filter-selected-orgs-${contest_key}`, JSON.stringify([])); + } + + if (localStorage.getItem(`filter-selected-tags-${contest_key}`) === null) { + localStorage.setItem(`filter-selected-tags-${contest_key}`, JSON.stringify([])); } $('#apply-organization-filter').click(function () { @@ -240,12 +292,15 @@ $('#org-check-list').val().forEach(function (el) { selected_orgs.push(el.trim()); }); + let selected_tags = $('#tag-check-list').val().map(x => x.trim()); localStorage.setItem(`filter-selected-orgs-${contest_key}`, JSON.stringify(selected_orgs)); + localStorage.setItem(`filter-selected-tags-${contest_key}`, JSON.stringify(selected_tags)); window.applyRankingFilter(); }); $('#clear-organization-filter').click(function () { $('#org-check-list').val(null).trigger('change'); + $('#tag-check-list').val(null).trigger('change'); $('#apply-organization-filter').click(); }); @@ -259,7 +314,7 @@ return; } - if ($('#select2-org-check-list-results').has(e.target).length !== 0) { + if ($('#select2-org-check-list-results').has(e.target).length !== 0 || $('#select2-tag-check-list-results').has(e.target).length !== 0) { return; } @@ -267,6 +322,10 @@ return; } + if ($(e.target).attr('id') && $(e.target).attr('id').startsWith('select2-tag-check-list-result')) { + return; + } + // check if the clicked area is the checklist or not if ($('#org-dropdown-check-list').has(e.target).length === 0) { $('#apply-organization-filter').click(); @@ -301,6 +360,34 @@ window.getOrganizationCodes(); + window.getTagNamesAndSetTopTag = function () { + const tags = []; + $('#ranking-table > tbody > tr[id] > td span').each(function () { + let row = $(this); + + const user_tag = row.data('user-tag'); + + if (user_tag == undefined || user_tag == 'None') { + return; + } + + if (tags.findIndex(x => x == user_tag) == -1) { + row.addClass('top-team'); + tags.push(user_tag); + } + }); + + let tag_options = $('#tag-check-list'); + tag_options.empty(); + tags.sort(); + tags.forEach(function (tag) { + tag_options.append( + `` + ); + }); + } + window.getTagNamesAndSetTopTag(); + function extractCurrentAbsRank(row_text) { let paren_surround_text = row_text.match(/\(([^)]+)\)/); let current_abs_rank @@ -327,8 +414,9 @@ let counter = 0; let previous_abs_rank = -1; let selected_orgs = JSON.parse(localStorage.getItem(`filter-selected-orgs-${contest_key}`)); + let selected_tags = JSON.parse(localStorage.getItem(`filter-selected-tags-${contest_key}`)); - if (!selected_orgs.length) { + if (!selected_orgs.length && !selected_tags.length) { window.clearRankingFilter(); return; } @@ -338,15 +426,24 @@ let org_anchor = row.find("div > div > .personal-info > .organization > a")[0]; let org = org_anchor ? org_anchor.text : 'Other'; + let tag_anchor = row.find("span")[0]; + let tag = tag_anchor ? tag_anchor.dataset.userTag : 'Other' - let should_show_org = false; + let should_show_org = selected_orgs.length == 0; selected_orgs.forEach(function (selected_org) { if (selected_org === org.trim()) { should_show_org = true; } }); + let should_show_tag = selected_tags.length == 0; + selected_tags.forEach(function (selected_tag) { + if (selected_tag === tag.trim()) { + should_show_tag = true; + } + }); - if (!should_show_org) { + + if (!should_show_org || !should_show_tag) { row.hide(); return; } @@ -370,8 +467,10 @@ window.applyRankingFilter(); window.restoreChecklistOptions = function () { - let selected_orgs = localStorage.getItem(`filter-selected-orgs-${contest_key}`).split(','); + let selected_orgs = JSON.parse(localStorage.getItem(`filter-selected-orgs-${contest_key}`)); $('#org-check-list').val(selected_orgs).trigger('change'); + let selected_tags = JSON.parse(localStorage.getItem(`filter-selected-tags-${contest_key}`)); + $('#tag-check-list').val(selected_tags).trigger('change'); }; window.restoreChecklistOptions(); @@ -566,11 +665,14 @@
-
+
- +
+ + +
{% endif %}