Add import users

This commit is contained in:
cuom1999 2021-07-28 17:58:42 -05:00
parent 850076b444
commit 6faf7a10bd
8 changed files with 307 additions and 59 deletions

View file

@ -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',

View file

@ -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()
@ -22,51 +18,3 @@ def generate_report(problem):
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()

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,111 @@
{% extends "user/user-base.html" %}
{% block js_media %}
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
$(function() {
$('#load_button').on('click', function(e) {
e.preventDefault();
var files = $('#csv_file').prop('files');
if (files.length == 1) {
$('#load_button').addClass('disabled');
var file = files[0];
if (file.type != 'text/csv') {
alert("{{_('Upload CSV only')}}");
return;
}
var form_data = new FormData();
form_data.append('csv_file', file, file.name);
var xhr = new XMLHttpRequest();
xhr.open('POST', "{{url('import_users_post_file')}}", true);
xhr.setRequestHeader('X-CSRFToken', csrftoken);
xhr.onload = function () {
if (xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
$('#load_button').removeClass('disabled');
if (json.done) {
window.import_users = json.data
$('#table_csv').html(json.html);
$('#confirm_button').removeClass('disabled');
}
else {
window.import_users = []
$('#table_csv').html(json.msg);
$('#confirm_button').addClass('disabled');
}
} else {
alert('Fail to read file.');
}
};
xhr.send(form_data);
}
})
$('#confirm_button').on('click', function() {
$(this).addClass('disabled');
var data = {
'users': window.import_users
};
if (!data.users || data.users.length == 0) {
alert('No valid users');
return;
}
$('#table_csv').html('');
$('#log').html('Working...');
$.post({
url: "{{url('import_users_submit')}}",
data: JSON.stringify(data),
contentType:"application/json; charset=utf-8",
dataType:"text",
fail: function() {alert('Fail to import')},
success: function(data) {
data = JSON.parse(data);
var msg = data.msg.split('\n');
$('#log').html('')
for (var i of msg) {
$('#log').append(`<p>${i}</p>`);
}
}
})
})
});
</script>
{% endblock %}
{% block body %}
{% csrf_token %}
<center>
<label for="csv_file">{{_('User File')}}:</label>
<input type="file" accept=".csv" id="csv_file">
<a href="{{url('import_users_sample')}}">{{_('Sample')}}</a>
<div style="display: inline-flex">
<button id="load_button" style="margin-left: 1em">{{_('Load')}}</button>
<button id="confirm_button" style="margin-left: 1em" class="disabled">{{_('Import')}}</button>
</div>
</center>
<br>
<table id="table_csv" class="table"></table>
<p style="margin-left: 2em" id="log"></p>
{% endblock %}

View file

@ -0,0 +1,24 @@
<thead>
<tr>
<th>{{_('ID')}}</th>
<th>{{_('Username')}}</th>
<th>{{_('Password')}}</th>
<th>{{_('Name')}}</th>
<th>{{_('School')}}</th>
<th>{{_('Email')}}</th>
<th>{{_('Organizations')}}</th>
</tr>
</thead>
<tbody>
{% for i in data %}
<tr>
<td>{{loop.index}}</td>
<td>{{i.username}}</td>
<td>{{i.password}}</td>
<td>{{i.name}}</td>
<td>{{i.school}}</td>
<td>{{i.email}}</td>
<td>{{i.organizations}}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -13,7 +13,7 @@
<div class="user-info-card">
<div class="user-info">
<div class="user-info-header"><i class="fa fa-star {{user.css_class}}"></i> {{_('Rating')}}</div>
<div class="user-info-body {{user.css_class}}">{{user.rating}}</div>
<div class="user-info-body {{user.css_class}}">{{user.rating if user.rating else '-'}}</div>
</div>
</div>
<div class="user-info-card">
@ -43,7 +43,7 @@
<div class="user-info-card">
<div class="user-info">
<div class="user-info-header" title="{{_('Rank by rating')}}"><i style="color: peru" class="fa fa-globe" ></i> {{_('Rating')}}</div>
<div class="user-info-body">{{rating_rank}}</div>
<div class="user-info-body">{{rating_rank if rating_rank else '-'}}</div>
</div>
</div>
{% endif %}

View file

@ -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 %}