From 6faf7a10bd7e9b4874416ddd222a0b40387edfda Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 28 Jul 2021 17:58:42 -0500 Subject: [PATCH] Add import users --- dmoj/urls.py | 9 ++- judge/tasks/experiment.py | 56 +------------- judge/tasks/import_users.py | 98 +++++++++++++++++++++++ judge/views/user.py | 61 ++++++++++++++- templates/user/import/index.html | 111 +++++++++++++++++++++++++++ templates/user/import/table_csv.html | 24 ++++++ templates/user/user-about.html | 4 +- templates/user/user-list-tabs.html | 3 + 8 files changed, 307 insertions(+), 59 deletions(-) create mode 100644 judge/tasks/import_users.py create mode 100644 templates/user/import/index.html create mode 100644 templates/user/import/table_csv.html diff --git a/dmoj/urls.py b/dmoj/urls.py index 0a73d5e..f53b373 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -382,7 +382,14 @@ urlpatterns = [ url(r'^notifications/', login_required(notification.NotificationList.as_view()), - name='notification') + name='notification'), + + url(r'^import_users/', include([ + url(r'^$', user.ImportUsersView.as_view(), name='import_users'), + url(r'post_file/$', user.import_users_post_file, name='import_users_post_file'), + url(r'submit/$', user.import_users_submit, name='import_users_submit'), + url(r'sample/$', user.sample_import_users, name='import_users_sample') + ])), ] favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png', diff --git a/judge/tasks/experiment.py b/judge/tasks/experiment.py index 2028fbe..0e991c4 100644 --- a/judge/tasks/experiment.py +++ b/judge/tasks/experiment.py @@ -1,10 +1,6 @@ -from django.contrib.auth.models import User -from django.conf import settings - -from judge.models import SubmissionTestCase, Problem, Profile, Language, Organization +from judge.models import SubmissionTestCase, Problem from collections import defaultdict -import csv def generate_report(problem): testcases = SubmissionTestCase.objects.filter(submission__problem=problem).all() @@ -21,52 +17,4 @@ def generate_report(problem): rate[i] = score[i] / total[i] for i, _ in sorted(rate.items(), key=lambda x: x[1], reverse=True): - print(i, score[i], total[i], rate[i]) - - -def import_users(csv_file): - # 1st row: username, password, name, organization - # ... row: a_username, passhere, my_name, organ - try: - f = open(csv_file, 'r') - except OSError: - print("Could not open csv file", csv_file) - return - - with f: - reader = csv.DictReader(f) - - for row in reader: - try: - username = row['username'] - pwd = row['password'] - except Exception: - print('username and/or password column missing') - print('Make sure your columns are: username, password, name, organization') - - user, created = User.objects.get_or_create(username=username, defaults={ - 'is_active': True, - }) - - profile, _ = Profile.objects.get_or_create(user=user, defaults={ - 'language': Language.get_python3(), - 'timezone': settings.DEFAULT_USER_TIME_ZONE, - }) - if created: - print('Created user', username) - - if pwd: - user.set_password(pwd) - elif created: - user.set_password('lqdoj') - print('User', username, 'missing password, default=lqdoj') - - if 'name' in row.keys() and row['name']: - user.first_name = row['name'] - - if 'organization' in row.keys() and row['organization']: - org = Organization.objects.get(name=row['organization']) - profile.organizations.add(org) - user.email = row['email'] - user.save() - profile.save() + print(i, score[i], total[i], rate[i]) \ No newline at end of file diff --git a/judge/tasks/import_users.py b/judge/tasks/import_users.py new file mode 100644 index 0000000..8e297b7 --- /dev/null +++ b/judge/tasks/import_users.py @@ -0,0 +1,98 @@ +import csv +from tempfile import mktemp + +from django.conf import settings +from django.contrib.auth.models import User + +from judge.models import Profile, Language, Organization + + +fields = ['username', 'password', 'name', 'school', 'email', 'organizations'] +descriptions = ['my_username(edit old one if exist)', + '123456 (must have)', + 'Le Van A (can be empty)', + 'Le Quy Don (can be empty)', + 'email@email.com (can be empty)', + 'org1&org2&org3&... (can be empty - org slug in URL)'] + +def csv_to_dict(csv_file): + rows = csv.reader(csv_file.read().decode().split('\n')) + header = next(rows) + header = [i.lower() for i in header] + + if 'username' not in header: + return [] + + res = [] + + for row in rows: + if len(row) != len(header): + continue + cur_dict = {i: '' for i in fields} + for i in range(len(header)): + if header[i] not in fields: + continue + cur_dict[header[i]] = row[i] + if cur_dict['username']: + res.append(cur_dict) + return res + + +# return result log +def import_users(users): + log = '' + for i, row in enumerate(users): + cur_log = str(i + 1) + '. ' + + username = row['username'] + cur_log += username + ': ' + + pwd = row['password'] + + user, created = User.objects.get_or_create(username=username, defaults={ + 'is_active': True, + }) + + profile, _ = Profile.objects.get_or_create(user=user, defaults={ + 'language': Language.get_python3(), + 'timezone': settings.DEFAULT_USER_TIME_ZONE, + }) + + if created: + cur_log += 'Create new - ' + else: + cur_log += 'Edit - ' + + if pwd: + user.set_password(pwd) + elif created: + user.set_password('lqdoj') + cur_log += 'Missing password, set password = lqdoj - ' + + if 'name' in row.keys() and row['name']: + user.first_name = row['name'] + + if 'school' in row.keys() and row['school']: + user.last_name = row['school'] + + if row['organizations']: + orgs = row['organizations'].split('&') + added_orgs = [] + for o in orgs: + try: + org = Organization.objects.get(slug=o) + profile.organizations.add(org) + added_orgs.append(org.name) + except Organization.DoesNotExist: + continue + if added_orgs: + cur_log += 'Added to ' + ', '.join(added_orgs) + ' - ' + + user.email = row['email'] + user.save() + profile.save() + cur_log += 'Saved\n' + log += cur_log + log += 'FINISH' + + return log \ No newline at end of file diff --git a/judge/views/user.py b/judge/views/user.py index 8e971f1..fc68fd2 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -13,7 +13,8 @@ from django.db import transaction from django.db.models import Count, Max, Min from django.db.models.fields import DateField from django.db.models.functions import Cast, ExtractYear -from django.http import Http404, HttpResponseRedirect, JsonResponse +from django.forms import Form +from django.http import Http404, HttpResponseRedirect, JsonResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponse from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils import timezone @@ -21,18 +22,21 @@ from django.utils.formats import date_format from django.utils.functional import cached_property from django.utils.safestring import mark_safe from django.utils.translation import gettext as _, gettext_lazy +from django.views import View from django.views.generic import DetailView, ListView, TemplateView +from django.template.loader import render_to_string from reversion import revisions from judge.forms import ProfileForm, newsletter_id from judge.models import Profile, Rating, Submission, Friend from judge.performance_points import get_pp_breakdown from judge.ratings import rating_class, rating_progress +from judge.tasks import import_users from judge.utils.problems import contest_completed_ids, user_completed_ids from judge.utils.ranker import ranker from judge.utils.subscription import Subscription from judge.utils.unicode import utf8text -from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, generic_message +from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, generic_message, SingleObjectFormView from .contests import ContestRanking __all__ = ['UserPage', 'UserAboutPage', 'UserProblemsPage', 'users', 'edit_profile'] @@ -396,3 +400,56 @@ class UserLogoutView(TitleMixin, TemplateView): def post(self, request, *args, **kwargs): auth_logout(request) return HttpResponseRedirect(request.get_full_path()) + + +class ImportUsersView(TitleMixin, TemplateView): + template_name = 'user/import/index.html' + title = _('Import Users') + + def get(self, *args, **kwargs): + if self.request.user.is_superuser: + return super().get(self, *args, **kwargs) + return HttpResponseForbidden() + + +def import_users_post_file(request): + if not request.user.is_superuser or request.method != 'POST': + return HttpResponseForbidden() + users = import_users.csv_to_dict(request.FILES['csv_file']) + + if not users: + return JsonResponse({ + 'done': False, + 'msg': 'No valid row found. Make sure row containing username.' + }) + + table_html = render_to_string('user/import/table_csv.html', { + 'data': users + }) + return JsonResponse({ + 'done': True, + 'html': table_html, + 'data': users + }) + + +def import_users_submit(request): + import json + if not request.user.is_superuser or request.method != 'POST': + return HttpResponseForbidden() + + users = json.loads(request.body)['users'] + log = import_users.import_users(users) + return JsonResponse({ + 'msg': log + }) + + +def sample_import_users(request): + if not request.user.is_superuser or request.method != 'GET': + return HttpResponseForbidden() + filename = 'import_sample.csv' + content = ','.join(import_users.fields) + '\n' + ','.join(import_users.descriptions) + response = HttpResponse(content, content_type='text/plain') + response['Content-Disposition'] = 'attachment; filename={0}'.format(filename) + return response \ No newline at end of file diff --git a/templates/user/import/index.html b/templates/user/import/index.html new file mode 100644 index 0000000..635b4fd --- /dev/null +++ b/templates/user/import/index.html @@ -0,0 +1,111 @@ +{% extends "user/user-base.html" %} +{% block js_media %} + +{% endblock %} + +{% block body %} +{% csrf_token %} +
+ + + {{_('Sample')}} +
+ + +
+
+
+
+

+{% endblock %} \ No newline at end of file diff --git a/templates/user/import/table_csv.html b/templates/user/import/table_csv.html new file mode 100644 index 0000000..5ab87ef --- /dev/null +++ b/templates/user/import/table_csv.html @@ -0,0 +1,24 @@ + + + {{_('ID')}} + {{_('Username')}} + {{_('Password')}} + {{_('Name')}} + {{_('School')}} + {{_('Email')}} + {{_('Organizations')}} + + + + {% for i in data %} + + {{loop.index}} + {{i.username}} + {{i.password}} + {{i.name}} + {{i.school}} + {{i.email}} + {{i.organizations}} + + {% endfor %} + diff --git a/templates/user/user-about.html b/templates/user/user-about.html index 30b1ab2..d390028 100644 --- a/templates/user/user-about.html +++ b/templates/user/user-about.html @@ -13,7 +13,7 @@
@@ -43,7 +43,7 @@ {% endif %} diff --git a/templates/user/user-list-tabs.html b/templates/user/user-list-tabs.html index 8288c53..9acd5d4 100644 --- a/templates/user/user-list-tabs.html +++ b/templates/user/user-list-tabs.html @@ -4,4 +4,7 @@ {{ make_tab('list', 'fa-trophy', url('user_list'), _('Leaderboard')) }} {{ make_tab('friends', 'fa-users', url('user_list') + '?friend=true', _('Friends')) }} {{ make_tab('organizations', 'fa-university', url('organization_list'), _('Organizations')) }} + {% if request.user.is_superuser %} + {{ make_tab('import', 'fa-table', url('import_users'), _('Import')) }} + {% endif %} {% endblock %}