diff --git a/decide/base/migrations/0001_initial.py b/decide/base/migrations/0001_initial.py index de3adce07d..f4f711d4c9 100644 --- a/decide/base/migrations/0001_initial.py +++ b/decide/base/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2021-12-11 12:30 +# Generated by Django 2.2.5 on 2021-12-11 16:57 import base.models from django.db import migrations, models diff --git a/decide/census/migrations/0001_initial.py b/decide/census/migrations/0001_initial.py index 26b9d3a1c6..7d7ca249fd 100644 --- a/decide/census/migrations/0001_initial.py +++ b/decide/census/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2021-12-11 12:30 +# Generated by Django 2.2.5 on 2021-12-11 16:57 from django.db import migrations, models diff --git a/decide/decide/settings.py b/decide/decide/settings.py index 005681e78d..db1f621df5 100644 --- a/decide/decide/settings.py +++ b/decide/decide/settings.py @@ -22,6 +22,9 @@ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '^##ydkswfu0+=ofw0l#$kv^8n)0$i(qd&d&ol#p9!b$8*5%j1+' +#will change on production +TELEGRAM_TOKEN = '2111051748:AAH1R736I0_HsZEW6_22Tf0r-OqihtF5x88' + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -151,7 +154,7 @@ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Europe/Madrid' USE_I18N = True @@ -167,7 +170,7 @@ STATIC_URL = '/static/' -#temporary link to visualizer page for bots (until hosted) +#temporary link to visualizer page for bots VISUALIZER_VIEW="https://decide-full-tortuga-2.herokuapp.com/visualizer/" # number of bits for the key, all auths should use the same number of bits @@ -191,4 +194,4 @@ vars()[k] = v -INSTALLED_APPS = INSTALLED_APPS + MODULES +INSTALLED_APPS = INSTALLED_APPS + MODULES \ No newline at end of file diff --git a/decide/mixnet/migrations/0001_initial.py b/decide/mixnet/migrations/0001_initial.py index b1b66b6ed0..edcf17dda1 100644 --- a/decide/mixnet/migrations/0001_initial.py +++ b/decide/mixnet/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2021-12-11 12:30 +# Generated by Django 2.2.5 on 2021-12-11 16:57 from django.db import migrations, models import django.db.models.deletion diff --git a/decide/store/migrations/0001_initial.py b/decide/store/migrations/0001_initial.py index 5921089fd6..e6e11443ba 100644 --- a/decide/store/migrations/0001_initial.py +++ b/decide/store/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2021-12-11 12:30 +# Generated by Django 2.2.5 on 2021-12-11 16:57 import base.models from django.db import migrations, models diff --git a/decide/visualizer/models.py b/decide/visualizer/models.py index 1c622f6e5a..392b583b01 100644 --- a/decide/visualizer/models.py +++ b/decide/visualizer/models.py @@ -8,4 +8,14 @@ def __str__(self): return '{}'.format(self.auto_msg) class Meta: - verbose_name = 'Telegram user' \ No newline at end of file + verbose_name = 'Telegram user' + + + +class Graphs(models.Model): + voting_id=models.BigIntegerField() + voting_type=models.CharField(max_length=30, default='V') + graphs_url=models.TextField(null=True, blank=True) + + class Meta: + unique_together = ('voting_id', 'voting_type',) \ No newline at end of file diff --git a/decide/visualizer/static/scripts/get_graphs.js b/decide/visualizer/static/scripts/get_graphs.js new file mode 100644 index 0000000000..b9881afb85 --- /dev/null +++ b/decide/visualizer/static/scripts/get_graphs.js @@ -0,0 +1,52 @@ +//ajax function to send graphs to backend +$(document).ready(async function(){ + await new Promise(r => setTimeout(r, 1500)); + var canvas_elements=document.getElementsByClassName("chartjs-render-monitor") + if (canvas_elements.length==2) { + $('#bar-chart').addClass(function(){ + const csrf_cookie=getCookie('csrftoken'); + $.ajax({ + url: "graphs/", + type: "POST", + dataType:"json", + data:{ + type:$('#vot_type').val(), + graphs:graphs_images(), + }, + beforeSend: function(xhr) { + xhr.setRequestHeader("X-CSRFToken", csrf_cookie) + }, + failure: function(data){ + console.log(error) + } + }); + }); + } +}); + +//retrieve canvas graphs as base64 encoded image +function graphs_images(){ + var graphs=document.getElementsByClassName("chartjs-render-monitor"); + var images=[]; + for (var i=0; i< graphs.length; i++){ + images.push(graphs[i].toDataURL()); + } + return images +} + +//function to retrieve csrf_cookie, copied from Django Docs +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; +} \ No newline at end of file diff --git a/decide/visualizer/static/visualizer.css b/decide/visualizer/static/visualizer.css index 135d61f328..afffc46dbe 100644 --- a/decide/visualizer/static/visualizer.css +++ b/decide/visualizer/static/visualizer.css @@ -2,16 +2,16 @@ position: fixed; width: 100%; bottom : 0px; + margin-top: 10px; height : 60px; text-align: center; vertical-align: middle; padding-top: 0.5 rem; - padding-bottom: 0.5 rem; background-color: rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity)); color: #fff; } -#start-tlg { +#start_tlg { border: #79aec8; border-radius: 12px; font-size: 14px; @@ -26,6 +26,9 @@ -o-border-radius: 12px; } -#start-tlg:hover{ +#start_tlg:hover{ background-color: #417690; +} +.chartjs-render-monitor{ + margin: 77px 10px; } \ No newline at end of file diff --git a/decide/visualizer/telegramBot.py b/decide/visualizer/telegramBot.py index a1e2db1e2c..a390b42e53 100644 --- a/decide/visualizer/telegramBot.py +++ b/decide/visualizer/telegramBot.py @@ -5,25 +5,23 @@ from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton from telegram.inline.inlinekeyboardmarkup import InlineKeyboardMarkup -import logging, os, sys -from .website_scrapping import get_graphs -from .models import TelegramBot +import os, sys, base64 +from .models import TelegramBot, Graphs from threading import Thread +from selenium import webdriver +from functools import partial #This is token is temporary and won't be the same in final version, so security won't be compromised #auth and front-end for '@VotitosBot' -UPDATER = Updater('2111051748:AAH1R736I0_HsZEW6_22Tf0r-OqihtF5x88', - use_context=True) +UPDATER = Updater(settings.TELEGRAM_TOKEN, + use_context=True) -BOT=Bot(token='2111051748:AAH1R736I0_HsZEW6_22Tf0r-OqihtF5x88') +BOT=Bot(token=settings.TELEGRAM_TOKEN) #configures and activate '@VotitosBot' to receive any messages from users def init_bot(): - - #logging - logging.basicConfig(level=logging.ERROR, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + setup_commands(UPDATER) updates_setting() #starts the bot @@ -34,15 +32,16 @@ def setup_commands(votitos): dp=votitos.dispatcher dp.add_handler(CommandHandler('start', start)) + dp.add_handler(CommandHandler('help', help)) dp.add_handler(CommandHandler('relaunch', relaunch, Filters.user(user_id=1931864468))) dp.add_handler(CommandHandler('stop', stop, Filters.user(user_id=1931864468))) - dp.add_handler(CommandHandler('results', show_results)) - dp.add_handler(CommandHandler('details', show_details)) + dp.add_handler(CommandHandler('results', partial(voting_selection_menu, command_name='results'))) + dp.add_handler(CommandHandler('details', partial(voting_selection_menu, command_name='details'))) dp.add_handler(CommandHandler('auto', change_auto_status)) - dp.add_handler(CommandHandler('help', help)) dp.add_handler(MessageHandler(Filters.command, unknown_command)) + dp.add_handler(CallbackQueryHandler(voting_selection_query_handler, pattern="^v_[a-zA-Z]+_[a-zA-Z]+$")) dp.add_handler(CallbackQueryHandler(results_query_handler, pattern="^[1-9][0-9]*$")) - dp.add_handler(CallbackQueryHandler(details_query_handler, pattern="^d[1-9][0-9]*$")) + dp.add_handler(CallbackQueryHandler(details_query_handler, pattern="^d[1-9][0-9]*_[a-zA-Z]+$")) dp.add_handler(CallbackQueryHandler(auto_query_handler, pattern="(^True$|^False$)")) #set bot configuration not to reply to old messages @@ -58,7 +57,7 @@ def start(update, context): id=update.message.chat.id context.bot.send_message(chat_id=id, text="Hola {}, a las buenas tardes. ¿En qué puedo ayudarte?".format(name)) TelegramBot.objects.get_or_create(user_id=id) - help(update) + help(update, context) # relaunch the bot and also the whole project (limited to admin) def relaunch(update, context): @@ -74,7 +73,7 @@ def stop(update, context): UPDATER.stop() #list of available commands -def help(update): +def help(update, context): update.message.reply_text("""Esta es mi lista de comandos: /start - Inicia la interacción conmigo @@ -88,42 +87,49 @@ def unknown_command(update, context): update.message.reply_text("Lo siento, no sé qué es '%s'. Revisa que has escrito bien el comando o bien revisa mi lista de comandos, puedes hacerlo con\n/help" % update.message.text) #allows you to select a closed voting and show its results -def show_results(update, context): - update.message.reply_text("Aquí tienes la lista de votaciones finalizadas.") - finished_votings=models.Voting.objects.exclude(start_date__isnull=True).exclude(end_date__isnull=True) - keyboard_buttons=[] - for v in finished_votings: - keyboard_buttons.append(InlineKeyboardButton(text=str(v.name), callback_data=str(v.id))) - keyboard=InlineKeyboardMarkup(build_keyboard_menu(keyboard_buttons,2)) - context.bot.send_message(chat_id=update.message.chat.id, text= "Elige por favor:", reply_markup=keyboard) +def show_results(update, context, chat_identifier, vot_type): + votings=get_voting_objects(vot_type) + finished_votings=votings.exclude(start_date__isnull=True).exclude(end_date__isnull=True) + if finished_votings is not None: + keyboard_buttons=[] + for v in finished_votings: + keyboard_buttons.append(InlineKeyboardButton(text=str(v.name), callback_data=str(v.id))) + keyboard=InlineKeyboardMarkup(build_keyboard_menu(keyboard_buttons,2)) + context.bot.send_message(chat_id=chat_identifier, text= "Aquí tienes la lista de votaciones finalizadas. Elige por favor:", reply_markup=keyboard) + else: + context.bot.send_message(chat_id=chat_identifier, text= "Vaya...no hay ninguna votación de este tipo que haya finalizado.\nInténtalo de nuevo en otro momemnto") #handler for '/results' command def results_query_handler(update, context): query=update.callback_query query.answer("¡A la orden!") - results_graph(query.data, update.callback_query.message.chat_id, context) + results_graph(query.data, query.message.chat_id, context) #allows you to select an active or closed voting and show its details -def show_details(update, context): - update.message.reply_text("Selecciona la votación de la que desea ver sus detalles") - votings=models.Voting.objects.exclude(start_date__isnull=True) - keyboard_buttons=[InlineKeyboardButton(text=str(v.name), callback_data="d"+str(v.id)) for v in votings] - keyboard=InlineKeyboardMarkup(build_keyboard_menu(keyboard_buttons,2)) - context.bot.send_message(chat_id=update.message.chat.id, text="Seleccione una por favor:", reply_markup=keyboard) - -#constructs menu for inline buttons -def build_keyboard_menu(buttons, n_cols): - return [buttons[b:(b + n_cols)] for b in range(0, len(buttons), n_cols)] - +def show_details(update, context, chat_identifier,vot_type): + + votings=get_voting_objects(vot_type) + started_votings=votings.exclude(start_date__isnull=True) + type=vot_type.split("_")[1] + if started_votings is not None: + keyboard_buttons=[InlineKeyboardButton(text=str(v.name), callback_data="d"+str(v.id)+"_"+type) for v in started_votings] + keyboard=InlineKeyboardMarkup(build_keyboard_menu(keyboard_buttons,2)) + context.bot.send_message(chat_id=chat_identifier, text="Selecciona la votación de la que desea ver sus detalles", reply_markup=keyboard) + else: + context.bot.send_message(chat_id=chat_identifier, text= "Vaya...no hay ninguna votación de este tipo ahora mismo.\nInténtalo de nuevo en otro momemnto") + #handler for '/details' command def details_query_handler(update, context): - query=update.callback_query query.answer("¡A la orden!") - vot_id=query.data[1] - voting=models.Voting.objects.exclude(start_date__isnull=True).get(id=vot_id) - msg=aux_message_builder(voting) + response_array=query.data.split("_") + vot_id=response_array[0][1] + vot_type=response_array[1] + + voting=get_voting_objects(vot_type).exclude(start_date__isnull=True).get(id=vot_id) + v_type=translate_type(vot_type) + msg=aux_message_builder(voting,v_type) context.bot.send_message(chat_id=query.message.chat_id,text=msg, parse_mode="HTML") #opt-in and opt-out for auto notifications @@ -132,34 +138,88 @@ def change_auto_status(update, context): status_user=TelegramBot.objects.get(user_id=id) if status_user.auto_msg: choose_msg="Actualmente las notificaciones automáticas se encuentran activadas.\n¿Desea desactivarlas?" - keyboard_buttons=[[InlineKeyboardButton(text="Sí", callback_data="False")], - [InlineKeyboardButton(text="No", callback_data="True")]] + keyboard_buttons=build_keyboard_menu([InlineKeyboardButton(text="Sí", callback_data="False"), + InlineKeyboardButton(text="No", callback_data="True")], 2) + else: choose_msg="Actualmente las notificaciones automáticas se encuentran desactivadas.\n¿Desea activarlas?" - keyboard_buttons=[[InlineKeyboardButton(text="Sí", callback_data="True")], - [InlineKeyboardButton(text="No", callback_data="False")]] + keyboard_buttons=build_keyboard_menu([InlineKeyboardButton(text="Sí", callback_data="True"), + InlineKeyboardButton(text="No", callback_data="False")], 2) keyboard=InlineKeyboardMarkup(keyboard_buttons) context.bot.send_message(chat_id=id, text=choose_msg, reply_markup=keyboard) #handler for '/auto' command def auto_query_handler(update, context): query=update.callback_query - u_id=update.callback_query.message.chat_id - msg_id=update.callback_query.message.message_id - for id in range(msg_id-2, msg_id+1): + u_id=query.message.chat_id + msg_id=query.message.message_id + for id in range(msg_id, msg_id+1): context.bot.delete_message(chat_id=u_id, message_id=id) TelegramBot.objects.filter(user_id=u_id).update(auto_msg=query.data) query.answer("¡Listo! He actualizado tus preferencias") + -# =================== -# AUXILIARY METHODS -# =================== +# ===================== +# AUXILIARY METHODS +# ===================== +#auxiliary keyboard to select voting type and get command name +def voting_selection_menu(update, context, command_name): + keyboard_buttons = [InlineKeyboardButton('Votación simple', callback_data='v_simple_'+command_name), + InlineKeyboardButton('Votación binaria', callback_data='v_binary_'+command_name), + InlineKeyboardButton('Votación múltiple', callback_data='v_multiple_'+command_name), + InlineKeyboardButton('Votación por puntuación', callback_data='v_score_'+command_name)] + context.bot.send_message(chat_id=update.message.chat.id, text="Elige el tipo de votación:", reply_markup=InlineKeyboardMarkup(build_keyboard_menu(keyboard_buttons,2))) + + +#handler for voting selection +def voting_selection_query_handler(update, context): + query=update.callback_query + vot_type_command=query.data + if 'details' in vot_type_command: + show_details(update, context, query.message.chat_id, vot_type_command) + elif 'results' in vot_type_command: + show_results(update, context, query.message.chat_id, vot_type_command) + query.answer() + +#gets voting type objects +def get_voting_objects(vot_type): + res=None + if 'simple' in vot_type: + res=models.Voting.objects + elif 'binary' in vot_type: + res=models.BinaryVoting.objects + elif 'multiple' in vot_type: + res=models.MultipleVoting.objects + elif 'score' in vot_type: + res=models.ScoreVoting.objects + return res + +#translate vot_type var to actual voting type name +def translate_type(vot_type): + res=None + if 'simple' in vot_type: + res=('V', 'Voting') + elif 'binary' in vot_type: + res=('BV', 'BinaryVoting') + elif 'multiple' in vot_type: + res=('MV', 'MultipleVoting') + elif 'score' in vot_type: + res=('SV', 'ScoreVoting') + return res + +#constructs menu for inline buttons +def build_keyboard_menu(buttons, n_cols): + return [buttons[b:(b + n_cols)] for b in range(0, len(buttons), n_cols)] + #auxiliary message to print details from votings -def aux_message_builder(voting): +def aux_message_builder(voting, vot_type): - options=list(voting.question.options.values_list('option', flat=True)) - tally=stmodels.Vote.objects.filter(voting_id=voting.id).values('voter_id').distinct().count() #gets unique votes for a voting + options=list(voting.question.options.values_list('option', flat=True)) + if stmodels.Vote.objects.filter(voting_id=voting.id, type=vot_type).exists(): + tally=stmodels.Vote.objects.filter(voting_id=voting.id, type=vot_type).values('voter_id').distinct().count() + else: + tally=0 start_d=voting.start_date.strftime('%d-%m-%Y %H:%M:%S')+"\n" end_d="Por decidir\n" @@ -178,21 +238,46 @@ def aux_message_builder(voting): return msg + #extracts graph's images from website selected voting and sends them to the user def results_graph(id, chat_identifier, context): - url=settings.VISUALIZER_VIEW+ str(id) - images=get_graphs(url) - if images: - media_group=[InputMediaPhoto(media=i) for i in images] - context.bot.sendMediaGroup(chat_id=chat_identifier, media=media_group) + open_graphs_generator_view(id) + if Graphs.objects.filter(voting_id=id).exists(): + graphs_base64=Graphs.objects.filter(voting_id=id).values('graphs_url') + try: + base64_url_list=eval(graphs_base64[0]['graphs_url']) + b64_images=[] + media_group=[] + for i in range(0,len(base64_url_list)): + b64_images.append(base64_url_list[i].split(",")[1]) + path="graph_"+str(id)+"_"+str(i)+".png" + with open(path,"wb") as f: + f.write(base64.b64decode(b64_images[i])) + media_group.append(InputMediaPhoto(media=open(path, 'rb'))) + os.remove(path) + context.bot.sendMediaGroup(chat_id=chat_identifier, media=media_group) + except: + context.bot.send_message(chat_id=chat_identifier, + text="Vaya...no hay gráficas disponibles para mostrar.\nInténtalo de nuevo más tarde.") + else: context.bot.send_message(chat_id=chat_identifier, - text="Upss! Parece que aún no hay ningún gráfico asociado a esta votación.\nInténtalo de nuevo en otro momento.") - + text="Upss! Parece que aún no hay ninguna gráfica asociada a esta votación.\nInténtalo de nuevo en otro momento.") + + +#uses selenium to call view which generates voting graphs +def open_graphs_generator_view(id): + options = webdriver.ChromeOptions() + options.add_argument("--headless") + driver=webdriver.Chrome(options=options) + driver.get('http://127.0.0.1:8000/visualizer/' + str(id)) #VISUALIZER_VIEW will be taken from setting in production + + + #sends notifications when a new voting is created def auto_notifications(voting): users_id_enabled=list(TelegramBot.objects.values_list('user_id', flat=True).exclude(auto_msg=False)) - msg=aux_message_builder(voting) + msg=aux_message_builder(voting, voting.type) for id in users_id_enabled: BOT.send_message(chat_id=id, text=msg, parse_mode="HTML") diff --git a/decide/visualizer/templates/visualizer/telegram_admin.html b/decide/visualizer/templates/visualizer/telegram_admin.html index 661aa1b85e..ba17088d08 100644 --- a/decide/visualizer/templates/visualizer/telegram_admin.html +++ b/decide/visualizer/templates/visualizer/telegram_admin.html @@ -7,7 +7,8 @@ {% block content %}
- Start Telegram Bot + Start Telegram Bot

{{ block.super }} + {% endblock %} \ No newline at end of file diff --git a/decide/visualizer/templates/visualizer/visualizer.html b/decide/visualizer/templates/visualizer/visualizer.html index 0ebf4fa94b..1aa9681c70 100644 --- a/decide/visualizer/templates/visualizer/visualizer.html +++ b/decide/visualizer/templates/visualizer/visualizer.html @@ -11,6 +11,7 @@ {% endblock %} {% block content %} + {% csrf_token %}
@@ -18,6 +19,7 @@
+

[[ voting[0]["id"] ]] - [[ voting[0]["name"] ]]

Votación no comenzada

@@ -57,6 +59,10 @@

Resultados:

{% block extrabody %} + + + + @@ -156,7 +162,7 @@

Resultados:

} }) - - + + {% endblock %} \ No newline at end of file diff --git a/decide/visualizer/templates/visualizer/visualizer_binary.html b/decide/visualizer/templates/visualizer/visualizer_binary.html index c01846cde5..be5309ec1d 100644 --- a/decide/visualizer/templates/visualizer/visualizer_binary.html +++ b/decide/visualizer/templates/visualizer/visualizer_binary.html @@ -7,10 +7,12 @@ + {% endblock %} {% block content %} + {% csrf_token %}
@@ -18,6 +20,7 @@
+

[[ voting.id ]] - [[ voting.name ]]

Votación no comenzada

@@ -47,13 +50,20 @@

Resultados:

- - +
+ +
{% endblock %} {% block extrabody %} + + + @@ -146,7 +156,6 @@

Resultados:

- + {% endblock %} \ No newline at end of file diff --git a/decide/visualizer/templates/visualizer/visualizer_multiple.html b/decide/visualizer/templates/visualizer/visualizer_multiple.html index a05f0b329d..9ceeba3f33 100644 --- a/decide/visualizer/templates/visualizer/visualizer_multiple.html +++ b/decide/visualizer/templates/visualizer/visualizer_multiple.html @@ -7,10 +7,12 @@ + {% endblock %} {% block content %} + {% csrf_token %}
@@ -18,6 +20,7 @@
+

[[ voting.id ]] - [[ voting.name ]]

Votación no comenzada

@@ -47,13 +50,21 @@

Resultados:

- +
+ +
{% endblock %} {% block extrabody %} + + + @@ -146,7 +157,6 @@

Resultados:

- + {% endblock %} \ No newline at end of file diff --git a/decide/visualizer/templates/visualizer/visualizer_scoring.html b/decide/visualizer/templates/visualizer/visualizer_scoring.html index c53b3dabda..48600613cb 100644 --- a/decide/visualizer/templates/visualizer/visualizer_scoring.html +++ b/decide/visualizer/templates/visualizer/visualizer_scoring.html @@ -7,10 +7,12 @@ + {% endblock %} {% block content %} + {% csrf_token %}
@@ -18,6 +20,7 @@
+

[[ voting.id ]] - [[ voting.name ]]

Votación no comenzada

@@ -42,18 +45,25 @@

Resultados:

Descargar datos en CSV - + +
-
- - + {% endblock %} {% block extrabody %} + + + @@ -146,7 +156,6 @@

Resultados:

- - + + {% endblock %} \ No newline at end of file diff --git a/decide/visualizer/urls.py b/decide/visualizer/urls.py index b5255b1119..4621a7212d 100644 --- a/decide/visualizer/urls.py +++ b/decide/visualizer/urls.py @@ -1,5 +1,6 @@ from django.urls import path -from .views import VisualizerView, VisualizerViewScoring, Votes_csv, VisualizerViewBinary, VotesBinary_csv, VotesScoring_csv, VisualizerViewMultiple, VotesMultiple_csv +from .views import VisualizerView, VisualizerViewScoring, Votes_csv, VisualizerViewBinary, VotesBinary_csv, VotesScoring_csv, VisualizerViewMultiple, VotesMultiple_csv, initialize, graphs_requests + app_name= 'visualizer' urlpatterns = [ @@ -10,5 +11,11 @@ path('scoringVoting//', VisualizerViewScoring.as_view()), path('votes/scoryVoting//', VotesScoring_csv.as_view()), path('multipleVoting//', VisualizerViewMultiple.as_view()), - path('votes/multipleVoting//', VotesMultiple_csv.as_view()) -] + path('votes/multipleVoting//', VotesMultiple_csv.as_view()), + path('startTelegram/', initialize, name="start_telegram"), + path('/graphs/', graphs_requests), + path('binaryVoting//graphs/', graphs_requests), + path('scoringVoting//graphs/', graphs_requests), + path('multipleVoting//graphs/', graphs_requests), + +] \ No newline at end of file diff --git a/decide/visualizer/views.py b/decide/visualizer/views.py index 6e60d778fd..f82fc0c17a 100644 --- a/decide/visualizer/views.py +++ b/decide/visualizer/views.py @@ -1,4 +1,3 @@ -import json from django.http.response import HttpResponse, HttpResponseRedirect from django.views.generic import TemplateView from django.conf import settings @@ -11,11 +10,11 @@ from django.shortcuts import get_object_or_404 from voting.models import BinaryVoting, MultipleVoting, ScoreVoting -import json -import csv -import os - +import json, csv +from .telegramBot import init_bot +from .models import Graphs +TELEGRAM_BOT_STATUS=False #Generate a CSV File class Votes_csv(View): @@ -33,7 +32,6 @@ def get(self,request,*args,**kwargs): res = HttpResponse(content_type="text/csv") res['Content-Disposition'] = 'attachment; filename=' + str(r[0]["id"]) + '.csv' - csv_file = csv.writer(res) csv_file.writerow(["Opcion", "Puntuacion", "Votos"]) for vote in voting: @@ -43,6 +41,7 @@ def get(self,request,*args,**kwargs): class VisualizerView(TemplateView): template_name = 'visualizer/visualizer.html' + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) vid = kwargs.get('voting_id', 0) @@ -150,4 +149,41 @@ def get(self,request,voting_id,*args,**kwargs): for vote in voting.postproc: csv_file.writerow([vote["option"], vote["postproc"], vote["votes"]]) - return res \ No newline at end of file + return res + +def initialize(request): + #call to initalize telegram bot + global TELEGRAM_BOT_STATUS + if not TELEGRAM_BOT_STATUS: + init_bot() + TELEGRAM_BOT_STATUS=True + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + +def graphs_requests(request, voting_id): + if request.method == 'POST': + vot_type=request.POST.get('type') + urls=request.POST.getlist('graphs[]') + if Graphs.objects.filter(voting_id=voting_id, voting_type=vot_type).exists(): + to_update=Graphs.objects.get(voting_id=voting_id, voting_type=vot_type) + to_update.voting_type=vot_type + to_update.graphs_url=urls + to_update.save() + else: + Graphs.objects.create(voting_id=voting_id, voting_type=vot_type, graphs_url=urls) + return HttpResponse() + + if request.method == 'GET': + vot_type=translate_type(request.path_info) + data=list(Graphs.objects.filter(voting_id=voting_id, voting_type=vot_type).values('voting_id', 'voting_type','graphs_url')) + return HttpResponse(json.dumps(data), content_type="application/json") + +#translate path to vot_type +def translate_type(path_url): + vot_type='V' + if 'binaryVoting' in path_url: + vot_type='BV' + elif 'multiple' in path_url: + vot_type='MV' + elif 'score' in path_url: + vot_type='SV' + return vot_type \ No newline at end of file diff --git a/decide/visualizer/website_scrapping.py b/decide/visualizer/website_scrapping.py deleted file mode 100644 index 4fb5f62c7c..0000000000 --- a/decide/visualizer/website_scrapping.py +++ /dev/null @@ -1,17 +0,0 @@ -from bs4 import BeautifulSoup as bs -import urllib.request as request -from urllib.parse import urlparse - -#returns images of a voting -def get_graphs(link): - file=request.urlopen(link) - s=bs(file, "lxml") - images=s.find_all("img") - urls=[] - for img in images: - img_url=img.attrs.get("src") - if not img_url: - continue - if len(urls) == 0: - urls=False - return urls \ No newline at end of file diff --git a/decide/voting/admin.py b/decide/voting/admin.py index e745b01761..978e519a95 100644 --- a/decide/voting/admin.py +++ b/decide/voting/admin.py @@ -1,25 +1,26 @@ from django.contrib import admin from django.db.models.base import Model from django.utils import timezone - - from .models import MultipleQuestion, MultipleQuestionOption, MultipleVoting, QuestionOption, BinaryQuestionOption, ScoreQuestionOption from .models import Question, BinaryQuestion, ScoreQuestion from .models import Voting, BinaryVoting, ScoreVoting from .filters import StartedFilter - +from visualizer.telegramBot import auto_notifications +import visualizer.views def start(modeladmin, request, queryset): for v in queryset.all(): v.create_pubkey() v.start_date = timezone.now() v.save() + send_notifications(v) def stop(ModelAdmin, request, queryset): for v in queryset.all(): v.end_date = timezone.now() v.save() + send_notifications(v) def tally(ModelAdmin, request, queryset): @@ -27,6 +28,10 @@ def tally(ModelAdmin, request, queryset): token = request.session.get('auth-token', '') v.tally_votes(token) +#for users who have auto notifications enabled +def send_notifications(v): + if visualizer.views.TELEGRAM_BOT_STATUS: + auto_notifications(v) class QuestionOptionInline(admin.TabularInline): model = QuestionOption diff --git a/decide/voting/migrations/0001_initial.py b/decide/voting/migrations/0001_initial.py index 75415cf40f..d05db5ab78 100644 --- a/decide/voting/migrations/0001_initial.py +++ b/decide/voting/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.5 on 2021-12-11 12:30 +# Generated by Django 2.2.5 on 2021-12-11 16:57 from django.db import migrations, models import django.db.models.deletion @@ -14,7 +14,6 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='BinaryQuestion', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), @@ -22,7 +21,6 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Question', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), @@ -53,7 +51,6 @@ class Migration(migrations.Migration): ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='voting.Question')), ], ), - migrations.CreateModel( name='BinaryVoting', fields=[ diff --git a/decide/voting/views.py b/decide/voting/views.py index a174bab130..e583977430 100644 --- a/decide/voting/views.py +++ b/decide/voting/views.py @@ -6,8 +6,8 @@ from rest_framework import generics, status from rest_framework.response import Response -from .models import BinaryQuestion, BinaryQuestionOption, BinaryVoting, MultipleQuestion, MultipleQuestionOption, MultipleVoting, Question, QuestionOption, ScoreVoting, Voting -from .serializers import BinaryVotingSerializer, MultipleVotingSerializer, SimpleBinaryVotingSerializer, SimpleMultipleVotingSerializer, SimpleVotingSerializer, VotingSerializer +from .models import BinaryQuestion, BinaryQuestionOption, BinaryVoting, MultipleQuestion, MultipleQuestionOption, MultipleVoting, Question, QuestionOption, ScoreQuestion, ScoreQuestionOption, ScoreVoting, Voting +from .serializers import BinaryVotingSerializer, MultipleVotingSerializer, SimpleBinaryVotingSerializer, SimpleMultipleVotingSerializer, SimpleVotingSerializer, VotingSerializer, ScoreVotingSerializer from base.perms import UserIsStaff from base.models import Auth diff --git a/requirements.txt b/requirements.txt index 2d003ab537..d4eb9951c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,6 @@ pymongo==3.12.1 six==1.16.0 sqlparse==0.2.4 python-telegram-bot==13.9.0 +selenium==3.9.0 + +