Skip to content
Snippets Groups Projects
Commit 0d456b18 authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

initial vote evaluation

parent 0f65537e
No related branches found
No related tags found
1 merge request!1Test release
Pipeline #11237 failed
......@@ -43,13 +43,15 @@
{% endfor %}
</ul>
<div>
<div id="result">
</div>
</div>
</main>
{{ keyed_rv_members|json_script:"rv-members" }}
<script>
const RV_MEMBERS = {{ json_rv_members|safe }};
const VOTE_CALCULATION_ENDPOINT = "{% url "rv_voting_calc:get_calculated_votes" %}"
</script>
{% endblock %}
CIRNO CHIRUMIRU
{{ options }}
CIRNO CHIRUMIRU
{{ options }}
......@@ -5,4 +5,9 @@ from . import views
app_name = "rv_voting_calc"
urlpatterns = [
path("", views.index, name="index"),
path(
"calculated-votes",
views.get_calculated_votes,
name="get_calculated_votes"
)
]
import json
import typing
import math
import gql
import requests
......@@ -7,9 +7,9 @@ import requests
from gql.transport.requests import RequestsHTTPTransport
from django.conf import settings
from django.http import HttpResponseBadRequest
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.shortcuts import render
from django.views.decorators.http import require_POST
from django.template.loader import render_to_string
def get_rv_members() -> list:
......@@ -64,45 +64,200 @@ def convert_rv_members_to_dict(source: list) -> dict:
def index(request):
rv_members = get_rv_members()
json_rv_members = json.dumps(convert_rv_members_to_dict(rv_members))
keyed_rv_members = convert_rv_members_to_dict(rv_members)
return render(
request,
"rv_voting_calc/index.html",
{
"rv_members": rv_members,
# JS-Readable format
"json_rv_members": json.dumps(json_rv_members),
"keyed_rv_members": keyed_rv_members,
}
)
@require_POST
def get_calculated_votes(request):
votes = request.GET.get("votes")
options_by_member = request.GET.get("votes")
if votes is None:
# BEGIN Input validation
if options_by_member is None:
return HttpResponseBadRequest("Nebyly zadány žádné hlasy")
try:
votes = json.loads(votes)
options_by_member = json.loads(options_by_member)
if not isinstance(votes, dict):
if not isinstance(options_by_member, dict):
raise ValueError
except ValueError:
return HttpResponseBadRequest("Formát hlasů není validní")
rv_members = convert_rv_members_to_dict(get_rv_members())
rv_member_names_to_clear = rv_members.keys()
integrity_check_failed = False
for key, value in votes.items():
if key not in rv_member_names_to_clear:
for member, options in options_by_member.items():
# `member` is not in the list of known members
if member not in options_by_member:
integrity_check_failed = True
break
# `options` is a list
if not isinstance(options, list):
integrity_check_failed = True
break
if not isinstance(
# all items in `options` are strings
for item in options:
if not isinstance(item, str):
integrity_check_failed = True
break
if integrity_check_failed:
return HttpResponseBadRequest("Formát hlasů není validní")
# END Input validation
# BEGIN Sorting
steps = []
options = {}
total_ticket_count = len(options_by_member) # 1 member = 1 ticket
max_vote_position = 0
for member, selected_options in options_by_member.items():
option_is_ticket = True
for position, option in enumerate(selected_options):
if position > max_vote_position:
max_vote_position = position
if option not in options:
options[option] = {
"ticket_count": 0,
"vote_count": 0,
"points": 0,
"votes": [],
"has_support": False
}
options[option]["votes"].append({
"position": position,
"member": member
})
if option_is_ticket:
options[option]["ticket_count"] += 1
else:
options[option]["vote_count"] += 1
option_is_ticket = False
steps.append(
render_to_string(
"rv_voting_calc/steps/initial_sort.html",
{
"options": options,
"options_by_member": options_by_member,
"rv_members": rv_members
},
request=request,
)
)
# END Sorting
# BEGIN Filtering out options without enough support and figuring out
# the most popular options
options_to_remove = []
for option_key, option in options.items():
if (
(option["vote_count"] + option["ticket_count"])
>= math.ceil(total_ticket_count * 0.5)
):
option["has_support"] = True
if not option["has_support"]:
options_to_remove.append(option_key)
continue
option["points"] += option["ticket_count"] * (max_vote_position + 1)
for vote in option["votes"]:
option["points"] += (
max_vote_position
- vote["position"]
+ 1
)
for option_key in options_to_remove:
options.pop(option_key)
options = {
key: value
for key, value
in sorted(options.items(), key=lambda option: option[1]["points"])
}
steps.append(
render_to_string(
"rv_voting_calc/steps/with_support.html",
{
"options": options,
"options_by_member": options_by_member,
"rv_members": rv_members
},
request=request,
)
)
# END Filtering out options without enough support and figuring out
# the most popular options
return HttpResponse("\n".join(steps))
#acceptable_options = []
#voting_member_count = len(options_by_member)
#for option, members in members_by_option.items():
#if len(members) >= math.floor(voting_member_count * 0.5):
#acceptable_options.append(option)
#steps = []
#if len(acceptable_options) == 0:
#result = {
#"type": "final:no_acceptable_options",
#"voting_member_count": voting_member_count,
#"options": [],
#}
#for option, members in members_by_option.items():
#result["options"].append({
#"name": option,
#"vote_count": len(members),
#})
#steps.append(result)
#return JsonResponse(steps, safe=False)
#acceptable_options_step = {
#"type": "progress:acceptable_options",
#"voting_member_count": voting_member_count,
#"options": []
#}
#for option, members in members_by_option.items():
#if option in acceptable_options:
#acceptable_options_step["options"].append({
#"name": option,
#"vote_count": len(members),
#})
#steps.append(acceptable_options_step)
rv_member_names_to_clear.pop(key)
# END Acceptable options
......@@ -16,7 +16,7 @@ Including another URLconf
from django.urls import include, path
urlpatterns = [
path("vypocet-skupiny-clenu", include("member_group_size_calc.urls")),
path("hlasovani-rv", include("rv_voting_calc.urls")),
path("vypocet-skupiny-clenu/", include("member_group_size_calc.urls")),
path("hlasovani-rv/", include("rv_voting_calc.urls")),
path("", include("shared.urls")),
]
......@@ -8,6 +8,8 @@ import "select2/dist/css/select2.min.css";
$(window).ready(
() => {
const RV_MEMBERS = JSON.parse($("#rv-members")[0].textContent);
$(".__vote-selection").select2({
tags: true,
tokenSeparators: [",", " "],
......@@ -34,39 +36,47 @@ $(window).ready(
$(".__vote-selection").on(
"select2:select",
event => {
//// Sync the tag option with other selectors.
// If the tag isn't new for this selection do nothing.
if (!event.params.data.isNew) {
return;
}
const tagName = event.params.data.id;
const addedTag = new Option(
tagName,
tagName,
false,
false
);
// Get all other selections.
const unfilteredSelections = $(".__vote-selection").not(event.target);
let filteredSelections = [];
// Check if they contain the tag. If they do, ignore them.
for (const selection of unfilteredSelections) {
if (
$(selection).
children(`option[value=${$.escapeSelector(tagName)}]`).
length === 0
) {
filteredSelections.push(selection);
}
}
// Add the new tag to all selections that don't have it yet.
$(filteredSelections).append(addedTag).trigger("change");
// //// Sync the tag option with other selectors.
//
// // If the tag isn't new for this selection, do nothing.
// if (!event.params.data.isNew) {
// return;
// }
//
// const tagName = event.params.data.id;
//
// const addedTag = new Option(
// tagName,
// tagName,
// false,
// false
// );
//
// // Get all other selections.
// const unfilteredSelections = $(".__vote-selection").not(event.target);
// let filteredSelections = [];
//
// // Check if they contain the tag. If they do, ignore them.
// for (const selection of unfilteredSelections) {
// if (
// $(selection).
// children(`option[value=${$.escapeSelector(tagName)}]`).
// length === 0
// ) {
// filteredSelections.push(selection);
// }
// }
//
// // Add the new tag to all selections that don't have it yet.
// $(filteredSelections).append(addedTag).trigger("change");
// Keep user-defined ordering. This is imperative!
// http://github.com/select2/select2/issues/3106 - Thanks to kevin-brown!
const element = $(event.params.data.element);
element.detach();
$(this).append(element);
$(this).trigger("change");
}
);
......@@ -75,50 +85,65 @@ $(window).ready(
event => {
//// Remove the tag option if it's not selected anywhere.
const tagName = event.params.data.id;
// Get all other selections.
const selections = $(".__vote-selection");
// Check if any of them have the tag selected. If they do, end the function.
for (const selection of selections) {
for (const data of $(selection).select2("data")) {
if (data.id == tagName) {
// TODO - Don't remove the tag from this select.
// I'm not sure select2 has a good way of doing this.
return;
}
}
}
// If the function has not ended by now, we can remove the tag.
for (const selection of selections) {
(
$(selection).
children(`option[value=${$.escapeSelector(tagName)}]`).
remove()
);
$(selection).trigger("change");
}
// const tagName = event.params.data.id;
//
// // Get all other selections.
// const selections = $(".__vote-selection");
//
// // Check if any of them have the tag selected. If they do, end the function.
// for (const selection of selections) {
// for (const data of $(selection).select2("data")) {
// if (data.id == tagName) {
// // TODO - Don't remove the tag from this select.
// // I'm not sure select2 has a good way of doing this.
//
// return;
// }
// }
// }
//
// // If the function has not ended by now, we can remove the tag.
// for (const selection of selections) {
// (
// $(selection).
// children(`option[value=${$.escapeSelector(tagName)}]`).
// remove()
// );
//
// $(selection).trigger("change");
// }
}
);
$("#count-votes").on(
"click",
event => {
async (event) => {
let votes = {};
for (const username of Object.keys(RV_MEMBERS)) {
const selectedOptions = $(`#${$.escapeSelector(username)}-selection`).select2("data");
if (selectedOptions.length === 0) {
continue;
}
votes[username] = [];
for (const data of $(`#${$.escapeSelector(username)}-selection`).select2("data")) {
votes[username].push(data.id);
for (const option of selectedOptions) {
votes[username].push(option.id);
}
}
console.log(votes);
let response = await fetch(
`${VOTE_CALCULATION_ENDPOINT}?votes=${encodeURIComponent(JSON.stringify(votes))}`
);
if (!response.ok) {
alert("Chyba při získávání výsledků hlasování.");
return;
}
$("#result").html(await response.text());
}
);
}
......
const defaultTheme = require('tailwindcss/defaultTheme')
const defaultTheme = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */
module.exports = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment