diff --git a/rv_voting_calc/templates/rv_voting_calc/combined_steps.html b/rv_voting_calc/templates/rv_voting_calc/combined_steps.html new file mode 100644 index 0000000000000000000000000000000000000000..394215e538e4840986e5b5e7302a7771bbcaacbe --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/combined_steps.html @@ -0,0 +1,15 @@ +{% comment %} + It's fine to pass in |safe here, as the template variables have + already been sanitized once, as each step got rendered. +{% endcomment %} + +{{ html_steps|safe }} + +<div id="md-steps" class="hidden"> + {% for md_step in md_steps %} +{{ md_step }}{% if not forloop.last %} + +--- + +{% endif %}{% endfor %} +</div> diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/found_popularity_winner.html b/rv_voting_calc/templates/rv_voting_calc/html_steps/found_popularity_winner.html similarity index 100% rename from rv_voting_calc/templates/rv_voting_calc/steps/found_popularity_winner.html rename to rv_voting_calc/templates/rv_voting_calc/html_steps/found_popularity_winner.html diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/initial_sort.html b/rv_voting_calc/templates/rv_voting_calc/html_steps/initial_sort.html similarity index 100% rename from rv_voting_calc/templates/rv_voting_calc/steps/initial_sort.html rename to rv_voting_calc/templates/rv_voting_calc/html_steps/initial_sort.html diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/no_winner.html b/rv_voting_calc/templates/rv_voting_calc/html_steps/no_winner.html similarity index 100% rename from rv_voting_calc/templates/rv_voting_calc/steps/no_winner.html rename to rv_voting_calc/templates/rv_voting_calc/html_steps/no_winner.html diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html b/rv_voting_calc/templates/rv_voting_calc/html_steps/removed_option.html similarity index 100% rename from rv_voting_calc/templates/rv_voting_calc/steps/removed_option.html rename to rv_voting_calc/templates/rv_voting_calc/html_steps/removed_option.html diff --git a/rv_voting_calc/templates/rv_voting_calc/steps/with_support.html b/rv_voting_calc/templates/rv_voting_calc/html_steps/with_support.html similarity index 73% rename from rv_voting_calc/templates/rv_voting_calc/steps/with_support.html rename to rv_voting_calc/templates/rv_voting_calc/html_steps/with_support.html index f40b5dfee2e03d8f9fac30fe51fdbee34ee2fdab..6f3367a1c992d8cb47f9a8b1a1181d980756930b 100644 --- a/rv_voting_calc/templates/rv_voting_calc/steps/with_support.html +++ b/rv_voting_calc/templates/rv_voting_calc/html_steps/with_support.html @@ -3,9 +3,9 @@ <h2 class="text-2xl font-semibold font-bebas">Po vyřazení možností bez nadpoloviční podpory</h2> </div> - {% if options|length != 0 %} + {% if options_with_support_count != 0 %} {% include "rv_voting_calc/includes/option_list.html" with options=options options_by_member=options_by_member %} {% else %} - <span class="text-gray-800 mt-1">Žádné možnosti nemají nadpoloviční podporu.</span> + <p class="text-gray-800 mt-3">Žádné možnosti nemají nadpoloviční podporu.</p> {% endif %} </li> diff --git a/rv_voting_calc/templates/rv_voting_calc/includes/option_list.md b/rv_voting_calc/templates/rv_voting_calc/includes/option_list.md new file mode 100644 index 0000000000000000000000000000000000000000..d605fed05c80226347bbae7a1512f567d7af5f88 --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/includes/option_list.md @@ -0,0 +1,6 @@ +{% load index %} + +{% for option_key, option in options.items %}{% if not option.marked_for_deletion or show_marked_for_deletion %}{% if option.has_support or show_has_support %} +- **Možnost {{ option_key }}** - {{ option.ticket_count }} lístků, {{ option.vote_count }} hlasů.{% if show_has_support and not option.has_support %} *(Nemá nadpoloviční podporu)*{% endif %}{% if option.marked_for_deletion or not option.has_support %} *(K odstranění)*{% endif %}{% if option.ticket_votes|length != 0 %} Volby: {% for vote in option.ticket_votes %}{% if not vote.hidden %} + - **{{ rv_members|index:vote.member|index:"displayName" }}** - přijatelné možnosti:{% for option in options_by_member|index:vote.member %} + - {{ option }}{% endfor %}{% endif %}{% endfor %}{% endif %}{% endif %}{% endif %}{% endfor %} diff --git a/rv_voting_calc/templates/rv_voting_calc/index.html b/rv_voting_calc/templates/rv_voting_calc/index.html index 12bb2a7964b9793ea7c63bf0bd22e12966c43d09..b08953579cda5387d16add0d4fba2f0f8de6f34a 100644 --- a/rv_voting_calc/templates/rv_voting_calc/index.html +++ b/rv_voting_calc/templates/rv_voting_calc/index.html @@ -33,7 +33,7 @@ <button class="btn disabled:cursor-progress" id="count-votes" - disabled + {% if not options_by_member %}disabled{% endif %} > <div class="btn__body"> Vypočítat @@ -66,12 +66,18 @@ > </ul> - <div class="mt-3"> + <div class="mt-4 flex gap-4 justify-end"> <a - class="hidden underline" + class="hidden btn" id="permalink" href="" - >Permalink</a> + ><div class="btn__body">Permalink</div></a> + <a + class="hidden btn" + id="download-log" + download="kroky.md" + href="" + ><div class="btn__body">Stáhnout log</div></a> </div> </div> </div> diff --git a/rv_voting_calc/templates/rv_voting_calc/md_steps/found_popularity_winner.md b/rv_voting_calc/templates/rv_voting_calc/md_steps/found_popularity_winner.md new file mode 100644 index 0000000000000000000000000000000000000000..c01793dd7d57f33b8f4d238823146cdefaba9edb --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/md_steps/found_popularity_winner.md @@ -0,0 +1 @@ +**Možnost {{ option_key }} vyhrává s {{ option.vote_count }} hlasy.** diff --git a/rv_voting_calc/templates/rv_voting_calc/md_steps/initial_sort.md b/rv_voting_calc/templates/rv_voting_calc/md_steps/initial_sort.md new file mode 100644 index 0000000000000000000000000000000000000000..f4403f8f5a5c4ba829604b9bdf8b388a26b27858 --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/md_steps/initial_sort.md @@ -0,0 +1,5 @@ +# Počáteční rozdělení + +**{{ total_ticket_count }}** lístků, hranice pro nadpoloviční podporu **{{ has_support_treshold }}**. + +{% include "rv_voting_calc/includes/option_list.md" with options=options options_by_member=options_by_member show_proposers=True show_has_support=True %} diff --git a/rv_voting_calc/templates/rv_voting_calc/md_steps/no_winner.md b/rv_voting_calc/templates/rv_voting_calc/md_steps/no_winner.md new file mode 100644 index 0000000000000000000000000000000000000000..01483b3565635af8f55dcc7fa1c9c4b4c4f4235d --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/md_steps/no_winner.md @@ -0,0 +1 @@ +**Ani jedna možnost nevyhrává.** diff --git a/rv_voting_calc/templates/rv_voting_calc/md_steps/removed_option.md b/rv_voting_calc/templates/rv_voting_calc/md_steps/removed_option.md new file mode 100644 index 0000000000000000000000000000000000000000..1b22831c74424fafa07e7a60065047f1945e5edb --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/md_steps/removed_option.md @@ -0,0 +1,7 @@ +{% load index %} + +# {{ iteration|add:1 }}. vyřazovací kolo + +**Možnost {{ option_key }} bude {% if randomly %}losováním{% else %}kvůli nepopularitě{% endif %} odstraněna.** + +{% include "rv_voting_calc/includes/option_list.md" with options=options options_by_member=options_by_member show_proposers=True show_marked_for_deletion=True %} diff --git a/rv_voting_calc/templates/rv_voting_calc/md_steps/with_support.md b/rv_voting_calc/templates/rv_voting_calc/md_steps/with_support.md new file mode 100644 index 0000000000000000000000000000000000000000..b55458fd1b96fd9aa6633dbcdda15694ffd5c4df --- /dev/null +++ b/rv_voting_calc/templates/rv_voting_calc/md_steps/with_support.md @@ -0,0 +1,7 @@ +# Po vyřazení možností bez nadpoloviční podpory + +{% if options_with_support_count != 0 %} + {% include "rv_voting_calc/includes/option_list.md" with options=options options_by_member=options_by_member %} +{% else %} + *Žádné možnosti nemají nadpoloviční podporu.* +{% endif %} diff --git a/rv_voting_calc/views.py b/rv_voting_calc/views.py index 4810e7c1d72ece439321ec3427877d6cc105b97f..38091e416a283bde2817216daedfaf52318a0b84 100644 --- a/rv_voting_calc/views.py +++ b/rv_voting_calc/views.py @@ -149,7 +149,8 @@ def convert_rv_members_to_dict(source: list) -> dict: def do_step_b_through_d( request, iteration: int, - steps: list, + html_steps: list, + md_steps: list, options: dict, options_without_support: list, options_by_member: dict, @@ -203,14 +204,31 @@ def do_step_b_through_d( # If we are on the first iteration, show the list of options with those that # don't have support removed. if iteration == 0: - steps.append( + # Get the number of options that have support, for displaying. + options_with_support_count = 0 + + for option in options.values(): + if option["has_support"]: + options_with_support_count += 1 + + context = { + "options": options, + "options_by_member": options_by_member, + "options_with_support_count": options_with_support_count, + "rv_members": rv_members + } + + html_steps.append( render_to_string( - "rv_voting_calc/steps/with_support.html", - { - "options": options, - "options_by_member": options_by_member, - "rv_members": rv_members - }, + "rv_voting_calc/html_steps/with_support.html", + context, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/with_support.md", + context, request=request, ) ) @@ -221,9 +239,16 @@ def do_step_b_through_d( # If no options remain, show that there are no winners and end everything. if len(options) == 0: - steps.append( + html_steps.append( + render_to_string( + "rv_voting_calc/html_steps/no_winner.html", + {}, + request=request, + ) + ) + md_steps.append( render_to_string( - "rv_voting_calc/steps/no_winner.html", + "rv_voting_calc/md_steps/no_winner.md", {}, request=request, ) @@ -248,47 +273,69 @@ def do_step_b_through_d( # Find winners. winner_vote_count = options[next(iter(options))]["vote_count"] + ticket_count_same = True winner_count = 0 winning_option_keys = [] for option_key, option in options.items(): if option["vote_count"] == winner_vote_count: + if option["ticket_count"] != options[next(iter(options))]["ticket_count"]: + ticket_count_same = False + winning_option_keys.append(option_key) winner_count += 1 # If there is exactly 1 winner, show the winner and end everything. if winner_count == 1: - steps.append( + context = { + "option_key": winning_option_keys[0], + "option": options[winning_option_keys[0]], + } + + html_steps.append( render_to_string( - "rv_voting_calc/steps/found_popularity_winner.html", - { - "option_key": winning_option_keys[0], - "option": options[winning_option_keys[0]], - }, + "rv_voting_calc/html_steps/found_popularity_winner.html", + context, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/found_popularity_winner.md", + context, request=request, ) ) return options, True - # If all options are winners, randomly eliminate one and continue - # to the next iteration. - elif winner_count == len(options): + # If all options are winners and have the same ticket vote count, + # randomly eliminate one and continue to the next iteration. + elif winner_count == len(options) and ticket_count_same: eliminated_winner = random.choice(winning_option_keys) options[eliminated_winner]["marked_for_deletion"] = True - steps.append( + context = { + "iteration": iteration, + "option_key": eliminated_winner, + "option": options[eliminated_winner], + "options": options, + "options_by_member": options_by_member, + "rv_members": rv_members, + "randomly": True + } + + html_steps.append( render_to_string( - "rv_voting_calc/steps/removed_option.html", - { - "iteration": iteration, - "option_key": eliminated_winner, - "option": options[eliminated_winner], - "options": options, - "options_by_member": options_by_member, - "rv_members": rv_members, - "randomly": True - }, + "rv_voting_calc/html_steps/removed_option.html", + context, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/removed_option.md", + context, request=request, ) ) @@ -326,17 +373,26 @@ def do_step_b_through_d( options[loser_key]["marked_for_deletion"] = True - steps.append( + context = { + "iteration": iteration, + "option_key": loser_key, + "option": options[loser_key], + "options": options, + "options_by_member": options_by_member, + "rv_members": rv_members, + } + + html_steps.append( render_to_string( - "rv_voting_calc/steps/removed_option.html", - { - "iteration": iteration, - "option_key": loser_key, - "option": options[loser_key], - "options": options, - "options_by_member": options_by_member, - "rv_members": rv_members, - }, + "rv_voting_calc/html_steps/removed_option.html", + context, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/removed_option.md", + context, request=request, ) ) @@ -349,21 +405,30 @@ def do_step_b_through_d( key=lambda key: options[key]["ticket_count"] ) - options[losing_option_keys[0]]["marked_for_deletion"] = True - # Delete the vote with the least ticket votes. There maybe more, but # those should be taken care of in the next iteration. - steps.append( + options[losing_option_keys[0]]["marked_for_deletion"] = True + + context = { + "iteration": iteration, + "option_key": losing_option_keys[0], + "option": options[losing_option_keys[0]], + "options": options, + "options_by_member": options_by_member, + "rv_members": rv_members, + } + + html_steps.append( render_to_string( - "rv_voting_calc/steps/removed_option.html", - { - "iteration": iteration, - "option_key": losing_option_keys[0], - "option": options[losing_option_keys[0]], - "options": options, - "options_by_member": options_by_member, - "rv_members": rv_members, - }, + "rv_voting_calc/html_steps/removed_option.html", + context, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/removed_option.md", + context, request=request, ) ) @@ -377,9 +442,16 @@ def do_step_b_through_d( # If there are no winners, show that and end everything. if len(options) == 0: - steps.append( + html_steps.append( render_to_string( - "rv_voting_calc/steps/no_winner.html", + "rv_voting_calc/html_steps/no_winner.html", + {}, + request=request, + ) + ) + md_steps.append( + render_to_string( + "rv_voting_calc/md_steps/no_winner.md", {}, request=request, ) @@ -411,7 +483,8 @@ def get_calculated_votes(request): ## BEGIN Step A # Sorting - steps = [] + html_steps = [] + md_steps = [] options = {} total_ticket_count = len(options_by_member) # 1 member = 1 ticket @@ -485,16 +558,25 @@ def get_calculated_votes(request): ) } - steps.append( + context = { + "options": options, + "options_by_member": options_by_member, + "rv_members": rv_members, + "total_ticket_count": total_ticket_count, + "has_support_treshold": has_support_treshold + } + + html_steps.append( + render_to_string( + "rv_voting_calc/html_steps/initial_sort.html", + context, + request=request, + ) + ) + md_steps.append( render_to_string( - "rv_voting_calc/steps/initial_sort.html", - { - "options": options, - "options_by_member": options_by_member, - "rv_members": rv_members, - "total_ticket_count": total_ticket_count, - "has_support_treshold": has_support_treshold - }, + "rv_voting_calc/md_steps/initial_sort.md", + context, request=request, ) ) @@ -524,7 +606,8 @@ def get_calculated_votes(request): options, ended = do_step_b_through_d( request, iteration, - steps, + html_steps, + md_steps, options, options_without_support, options_by_member, @@ -532,7 +615,14 @@ def get_calculated_votes(request): ) if ended: - response = HttpResponse("\n".join(steps)) + response = render( + request, + "rv_voting_calc/combined_steps.html", + { + "html_steps": "\n".join(html_steps), + "md_steps": md_steps + } + ) # Set the random seed cookie to the one used in the calculation. response.set_cookie("seed", seed) # Do the same for RV's GID. diff --git a/static_src/rv_voting_calc.js b/static_src/rv_voting_calc.js index 136238f903b1ae67e2a9c685be75edc190ded1e6..69ba73f8742c06a6b0ddcffc70378f3d95022ef2 100644 --- a/static_src/rv_voting_calc.js +++ b/static_src/rv_voting_calc.js @@ -108,7 +108,7 @@ $(window).ready( $("#result").html(await response.text()); - $("#permalink").show(); + $("#permalink,#download-log").show(); $("#permalink").attr( "href", ( @@ -119,6 +119,13 @@ $(window).ready( + `&seed=${Cookies.get("seed")}` ) ); + $("#download-log").attr( + "href", + ( + "data:text/plain;charset=utf-8," + + encodeURIComponent($("#md-steps").html()) + ) + ); $(event.currentTarget).removeClass("btn--loading").prop("disabled", false); }