Charts changed to highcharts

This commit is contained in:
ix 2021-09-08 03:59:35 +02:00
parent c5713d3887
commit c5dedbfc26
5 changed files with 396 additions and 182 deletions

View File

@ -1,6 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
{% load i18n %} {% load i18n %}
{% load key_value %}
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-9"> <div class="col-sm-12 col-md-9">
<div> <div>
@ -8,8 +9,8 @@
{% if solves%} {% if solves%}
<div class="table table-dark"> <div class="table table-dark">
<div class="card-body" style="padding-left:10px;"> <div class="card-body">
<canvas id="time-chart" height="100"></canvas> <div id="time-chart"></div>
</div> </div>
</div> </div>
<table class="table table-dark"> <table class="table table-dark">
@ -50,79 +51,273 @@
{% endif %} {% endif %}
<li class="list-group-item">{% trans "Member since" %} {{ user.date_joined|date:"Y-m-d" }}</li> <li class="list-group-item">{% trans "Member since" %} {{ user.date_joined|date:"Y-m-d" }}</li>
</ul> </ul>
<div class="right-sidebar">
<ul class="list-group">
<li class="list-group-item">{% trans "Category Stats" %}</li>
<li class="list-group-item">
<canvas id="global-chart"></canvas>
</li>
</div>
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script> <script src="https://code.highcharts.com/highcharts.src.js"></script>
<script>
var pie_conf= { <script>
type: 'pie', Highcharts.theme = {
data: { colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066',
datasets: [{ '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
data: {{ globalDatas|safe }}, chart: {
backgroundColor: [ backgroundColor: {
'#696969', '#808080', '#A9A9A9', '#C0C0C0', '#D3D3D3' linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
], stops: [
label: 'Solved by category' [0, '#1D1D1D'],
}], [1, '#1D1D1D']
labels: {{ globalLabels|safe }} ]
}, },
options: { style: {
legend: {display: false}, fontFamily: '\'Unica One\', sans-serif'
responsive: true },
} plotBorderColor: '#606063'
}; },
var time_conf = { title: {
type: 'line', style: {
data: { color: '#E0E0E3',
datasets: [{ textTransform: 'uppercase',
data: {{ timeDatas|safe }}, fontSize: '20px'
fill: false, }
borderColor: 'rgb(75, 192, 192)', },
pointBackgroundColor: '#fff', subtitle: {
pointBorderColor: '#fff', style: {
tension: 0.1, color: '#E0E0E3',
label: 'Challenge solved' textTransform: 'uppercase'
}, { }
}], },
labels: {{ timeLabels|safe }} xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: '#A0A0A3'
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
tickWidth: 1,
title: {
style: {
color: '#A0A0A3'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: '#F0F0F0'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#F0F0F3',
style: {
fontSize: '13px'
}
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
}
},
legend: {
backgroundColor: '#1D1D1D',
itemStyle: {
color: '#E0E0E3'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
},
title: {
style: {
color: '#C0C0C0'
}
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: '#F0F0F3'
},
activeDataLabelStyle: {
color: '#F0F0F3'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#CCC'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: '#000003',
stroke: '#000000',
style: {
color: 'white'
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(255,255,255,0.1)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
}
};
// Apply the theme
Highcharts.setOptions(Highcharts.theme);
Highcharts.chart('time-chart', {
title: {
text: 'Points earned for each category'
}, },
options: { yAxis: {
legend: { title: {
display: false, text: 'Points earned'
}, }
scales: { },
yAxes: [{ xAxis: {
ticks: { type: 'datetime',
fontColor: "#D9D9D9", // this here // Use the date format in the
}, // labels property of the chart
}], labels: {
xAxes: [{ formatter: function() {
ticks: { return Highcharts.dateFormat('%d.%b %Y',
fontColor: "#D9D9D9", // this here this.value);
}, }
}], }
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle'
},
plotOptions: {
pointStart: {{ user.date_joined|timestamp_fromdate }},
series: {
label: {
connectorAllowed: false
},
allowPointSelect: true,
marker: {
enabled: true
}
}
},
series: [
{
name: 'Total',
data: {{ solved|safe }}
}, },
responsive: true, {% for cat in cats %}
} {
}; name: '{{ cat.name }}',
data: {{ pointDatas|keyvalue:cat.name|safe }},
window.onload = function() { visible: false,
var globalchart = document.getElementById('global-chart').getContext('2d'); },
var timechart = document.getElementById('time-chart').getContext('2d'); {% endfor %}
window.globalPie = new Chart(globalchart, pie_conf); ],
window.timePie = new Chart(timechart, time_conf); responsive: {
}; rules: [{
condition: {
</script> maxWidth: 500
},
chartOptions: {
legend: {
layout: 'horizontal',
align: 'center',
verticalAlign: 'bottom'
}
}
}]
}
});
</script>
{% endblock %} {% endblock %}

View File

View File

@ -0,0 +1,11 @@
from django import template
register = template.Library()
@register.filter
def keyvalue(dict, key):
return dict[key]
@register.filter
def timestamp_fromdate(date):
return str(date.timestamp() * 1000).replace(',','.')

View File

@ -18,130 +18,139 @@ from accounts.models import UserProfileInfo
from . import connection from . import connection
def signin(request): def signin(request):
if not request.user.is_authenticated: if not request.user.is_authenticated:
if request.method == 'POST': if request.method == 'POST':
username = request.POST.get('username') username = request.POST.get('username')
password = request.POST.get('password') password = request.POST.get('password')
user = authenticate(username=username, password=password) user = authenticate(username=username, password=password)
if user: if user:
if user.is_active: if user.is_active:
login(request,user) login(request,user)
return HttpResponseRedirect(reverse('home')) return HttpResponseRedirect(reverse('home'))
else: else:
return HttpResponse(_("Your account was inactive.")) return HttpResponse(_("Your account was inactive."))
else: else:
return render(request, 'accounts/login.html', {'error': True}) return render(request, 'accounts/login.html', {'error': True})
else: else:
return render(request, 'accounts/login.html', {}) return render(request, 'accounts/login.html', {})
else: else:
return HttpResponseRedirect(reverse('home')) return HttpResponseRedirect(reverse('home'))
def signup(request): def signup(request):
if not request.user.is_authenticated: if not request.user.is_authenticated:
user_form = UserForm() user_form = UserForm()
profile_form = UserProfileInfoForm() profile_form = UserProfileInfoForm()
registered = False registered = False
if request.method == 'POST': if request.method == 'POST':
pass1 = request.POST.get('password') pass1 = request.POST.get('password')
if len(pass1) < 8: if len(pass1) < 8:
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':"The new password must be at least %d characters long." % 8}) return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':"The new password must be at least %d characters long." % 8})
first_isalpha = pass1[0].isalpha() first_isalpha = pass1[0].isalpha()
if not any(c.isdigit() for c in pass1) or not any(c.isalpha() for c in pass1): if not any(c.isdigit() for c in pass1) or not any(c.isalpha() for c in pass1):
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("The password must contain at least one letter and at least one digit or punctuation character.")}) return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("The password must contain at least one letter and at least one digit or punctuation character.")})
if User.objects.filter(email=request.POST.get('email')).exists(): if User.objects.filter(email=request.POST.get('email')).exists():
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that email already exists.")}) return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that email already exists.")})
user_form = UserForm(data=request.POST) user_form = UserForm(data=request.POST)
profile_form = UserProfileInfoForm(data=request.POST) profile_form = UserProfileInfoForm(data=request.POST)
if user_form.is_valid() and profile_form.is_valid(): if user_form.is_valid() and profile_form.is_valid():
user = user_form.save() user = user_form.save()
user.set_password(user.password) user.set_password(user.password)
user.save() user.save()
profile = profile_form.save(commit=False) profile = profile_form.save(commit=False)
profile.user = user profile.user = user
profile.token = token_hex(16) profile.token = token_hex(16)
profile.save() profile.save()
registered = True registered = True
else: else:
return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that username already exists.")}) return render(request,'accounts/register.html', {'user_form':user_form, 'profile_form':profile_form, 'registered_failed':_("A user with that username already exists.")})
return render(request,'accounts/register.html', return render(request,'accounts/register.html',
{'user_form':user_form, {'user_form':user_form,
'profile_form':profile_form, 'profile_form':profile_form,
'registered':registered}) 'registered':registered})
else: else:
return HttpResponseRedirect(reverse('home')) return HttpResponseRedirect(reverse('home'))
@login_required @login_required
def out(request): def out(request):
logout(request) logout(request)
return HttpResponseRedirect(reverse('home')) return HttpResponseRedirect(reverse('home'))
@login_required @login_required
def edit(request): def edit(request):
if request.method == 'POST': if request.method == 'POST':
umail = request.user.email umail = request.user.email
uuser = request.user.username uuser = request.user.username
p_form = UserInfosUpdateForm(request.POST, instance=request.user.userprofileinfo) p_form = UserInfosUpdateForm(request.POST, instance=request.user.userprofileinfo)
u_form = UserUpdateForm(request.POST, instance=request.user) u_form = UserUpdateForm(request.POST, instance=request.user)
error = None error = None
success = None success = None
if p_form.is_valid() and u_form.is_valid(): if p_form.is_valid() and u_form.is_valid():
pmail = u_form.cleaned_data['email'] pmail = u_form.cleaned_data['email']
if pmail == umail: if pmail == umail:
pass pass
else: else:
if User.objects.filter(email=pmail).exists(): if User.objects.filter(email=pmail).exists():
error = _("Email already taken.") error = _("Email already taken.")
puser = u_form.cleaned_data['username'] puser = u_form.cleaned_data['username']
if puser == uuser: if puser == uuser:
pass pass
else: else:
if User.objects.filter(username=puser).exists(): if User.objects.filter(username=puser).exists():
error = _("Username already taken.") error = _("Username already taken.")
if error is None: if error is None:
u_form.save() u_form.save()
p_form.save() p_form.save()
success = _("Updated.") success = _("Updated.")
request.user.username = uuser request.user.username = uuser
context={'p_form': p_form, 'u_form': u_form, 'error':error, 'success' : success} context={'p_form': p_form, 'u_form': u_form, 'error':error, 'success' : success}
return render(request, 'accounts/edit.html', context) return render(request, 'accounts/edit.html', context)
else: else:
p_form = UserInfosUpdateForm(instance=request.user.userprofileinfo) p_form = UserInfosUpdateForm(instance=request.user.userprofileinfo)
u_form = UserUpdateForm(instance=request.user) u_form = UserUpdateForm(instance=request.user)
context={'p_form': p_form, 'u_form': u_form, 'token': request.user.userprofileinfo.token} context={'p_form': p_form, 'u_form': u_form, 'token': request.user.userprofileinfo.token}
return render(request, 'accounts/edit.html',context ) return render(request, 'accounts/edit.html',context )
@login_required @login_required
def profile(request, user_name): def profile(request, user_name):
globalLabels= [] globalLabels= []
globalDatas = [] globalDatas = []
timeLabels = []
timeDatas= []
user_obj = get_object_or_404(User, username=user_name) user_obj = get_object_or_404(User, username=user_name)
cats = Category.objects.all() cats = Category.objects.all()
for cat in cats: pointDatas = {}
globalLabels.append(cat.name)
solved_count = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat).order_by('-flag_date').count()
globalDatas.append(solved_count)
solves = CTF_flags.objects.filter(user=user_obj).order_by('flag_date') for cat in cats:
somme = 0 # prepare categories
for flag in solves: globalLabels.append(cat.name)
timeLabels.append(flag.flag_date.strftime('%Y-%m-%d')) solved_count = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name).order_by('-flag_date').count()
somme += flag.ctf.points globalDatas.append(solved_count)
timeDatas.append(somme) # get datas
solves = CTF_flags.objects.filter(user=user_obj).order_by('-flag_date') somme = 0
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'globalLabels': globalLabels, 'globalDatas': globalDatas, 'timeLabels': timeLabels, 'timeDatas': timeDatas}) solved = CTF_flags.objects.filter(user=user_obj, ctf__category__name=cat.name).order_by('flag_date')
pointDatas[cat.name] = []
pointDatas[cat.name].append([user_obj.date_joined.timestamp() * 1000, 0])
for flag in solved:
somme += flag.ctf.points
pointDatas[cat.name].append([flag.flag_date.timestamp() * 1000, somme])
solves = CTF_flags.objects.filter(user=user_obj).order_by('-flag_date')
solved = []
somme = 0
solved.append([user_obj.date_joined.timestamp() * 1000, 0])
for s in solves.reverse():
somme += s.ctf.points
solved.append([s.flag_date.timestamp() * 1000,somme])
return render(request,'accounts/profile.html', {'user':user_obj, 'solves':solves,'solved':solved,'globalLabels': globalLabels, 'globalDatas': globalDatas, 'pointDatas': pointDatas})
# Create your views here. # Create your views here.
def rank(request, token): def rank(request, token):
all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username') all_users = UserProfileInfo.objects.filter(score__gt=0).select_related().order_by('-score', 'last_submission_date', 'user__username')
rank = 1 rank = 1
for elem in all_users: for elem in all_users:
if elem.token == token: if elem.token == token:
break break
rank += 1 rank += 1
data = {"rank": rank} data = {"rank": rank}
return JsonResponse(data) return JsonResponse(data)

View File

@ -106,7 +106,6 @@
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script>
</body> </body>
</html> </html>