diff --git a/.gitignore b/.gitignore index b823e313b4..970c89f17b 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ wheels/ *.egg-info/ .installed.cfg *.egg - +decide-enviroment/ # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. @@ -45,6 +45,8 @@ nosetests.xml coverage.xml *.cover .hypothesis/ +loadtest/gen_census.py +loadtest/locustfile.py # Translations *.mo diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..e191e8ed8c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "name": "Python: Django", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/decide/manage.py", + "args": [ + "runserver", + "0.0.0.0:8000" + ], + "django": true, + "justMyCode": false, + } + ] +} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..f53429927d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,84 @@ +pipeline { + agent { docker { image 'python:3.7.2' } } + + stages { + stage('Build Master') { + when { + branch 'master' + } + steps { + sh ''' + pip install -r requirements.txt + + cd decide + ./manage.py test -v 2 + ''' + } + } + + stage('Deploy Master') { + when { + branch 'master' + } + steps { + /* Llamamos a Heroku y a darle caña */ + } + } + + stage('Build Develop') { + when { + branch 'develop' + } + steps { + sh ''' + pip install -r requirements.txt + + cd decide + ./manage.py test -v 2 + ''' + } + } + + stage('Build G1') { + when { + branch 'G1' + } + steps { + sh ''' + pip install -r requirements.txt + + cd decide + ./manage.py test authentication -v 2 + ''' + } + } + + stage('Build G2') { + when { + branch 'G2' + } + steps { + sh ''' + pip install -r requirements.txt + + cd decide + ./manage.py test booth -v 2 + ''' + } + } + + stage('Build G3') { + when { + branch 'G3' + } + steps { + sh ''' + pip install -r requirements.txt + + cd decide + ./manage.py test voting -v 2 + ''' + } + } + } +} diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..818fef7fe3 --- /dev/null +++ b/Procfile @@ -0,0 +1,4 @@ +% prepara el repositorio para su despliegue. +release: sh -c 'cd decide && python manage.py migrate' +% especifica el comando para lanzar Decide +web: sh -c 'cd decide && gunicorn decide.wsgi --log-file -' diff --git a/README.md b/README.md index 83d0a57e27..3cbb53640d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Build Status](https://travis-ci.com/wadobo/decide.svg?branch=master)](https://travis-ci.com/wadobo/decide) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/94a85eaa0e974c71af6899ea3b0d27e0)](https://www.codacy.com/app/Wadobo/decide?utm_source=github.com&utm_medium=referral&utm_content=wadobo/decide&utm_campaign=Badge_Grade) [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/94a85eaa0e974c71af6899ea3b0d27e0)](https://www.codacy.com/app/Wadobo/decide?utm_source=github.com&utm_medium=referral&utm_content=wadobo/decide&utm_campaign=Badge_Coverage) - +Prueba de commit 7 Plataforma voto electrónico educativa -===================================== El objetivo de este proyecto es implementar una plataforma de voto electrónico seguro, que cumpla una serie de garantías básicas, como la diff --git a/decide/decide/settings.py b/decide/decide/settings.py index 1d22b67324..399527ea8a 100644 --- a/decide/decide/settings.py +++ b/decide/decide/settings.py @@ -1,11 +1,8 @@ """ Django settings for decide project. - Generated by 'django-admin startproject' using Django 2.0. - For more information on this file, see https://docs.djangoproject.com/en/2.0/topics/settings/ - For the full list of settings and their values, see https://docs.djangoproject.com/en/2.0/ref/settings/ """ @@ -179,4 +176,6 @@ vars()[k] = v + INSTALLED_APPS = INSTALLED_APPS + MODULES + diff --git a/decide/local_settings.example.py b/decide/local_settings.example.py deleted file mode 100644 index 41db563a5a..0000000000 --- a/decide/local_settings.example.py +++ /dev/null @@ -1,41 +0,0 @@ -ALLOWED_HOSTS = ["*"] - -# Modules in use, commented modules that you won't use -MODULES = [ - 'authentication', - 'base', - 'booth', - 'census', - 'mixnet', - 'postproc', - 'store', - 'visualizer', - 'voting', -] - -APIS = { - 'authentication': 'http://10.5.0.1:8000', - 'base': 'http://10.5.0.1:8000', - 'booth': 'http://10.5.0.1:8000', - 'census': 'http://10.5.0.1:8000', - 'mixnet': 'http://10.5.0.1:8000', - 'postproc': 'http://10.5.0.1:8000', - 'store': 'http://10.5.0.1:8000', - 'visualizer': 'http://10.5.0.1:8000', - 'voting': 'http://10.5.0.1:8000', -} - -BASEURL = 'http://10.5.0.1:8000' - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'postgres', - 'USER': 'postgres', - 'HOST': 'db', - 'PORT': '5432', - } -} - -# number of bits for the key, all auths should use the same number of bits -KEYBITS = 256 diff --git a/decide/postproc/tests.py b/decide/postproc/tests.py index 1a6a27e86c..3eef928f17 100644 --- a/decide/postproc/tests.py +++ b/decide/postproc/tests.py @@ -15,6 +15,7 @@ def setUp(self): def tearDown(self): self.client = None + def test_identity(self): data = { 'type': 'IDENTITY', @@ -42,3 +43,302 @@ def test_identity(self): values = response.json() self.assertEqual(values, expected_result) + + + +#test de la función de postproc Bipartisanship, +#comprueba que las dos opciones mayoritarias obtienen 25 y 15 escaños cada una + def test_bipartitanship(self): + data = { + 'type': 'BIPARTISHANSHIP', + 'numEscanyos': 40, + 'options': [ + { 'option': 'Option 1', 'number': 1, 'votes': 50 }, + { 'option': 'Option 2', 'number': 2, 'votes': 0 }, + { 'option': 'Option 3', 'number': 3, 'votes': 30 }, + { 'option': 'Option 4', 'number': 4, 'votes': 20 }, + ] + } + + expected_result = [ + { 'option': 'Option 1', 'number': 1, 'votes': 50, 'postproc': 25 }, + { 'option': 'Option 3', 'number': 3, 'votes': 30, 'postproc': 15 }, + { 'option': 'Option 4', 'number': 4, 'votes': 20, 'postproc': 0 }, + { 'option': 'Option 2', 'number': 2, 'votes': 0, 'postproc': 0 }, + ] + + + def test_hamilton(self): + data = { + 'type': 'HAMILTON', + 'options': [ + {'option':'A','number':1,'votes': 100000}, + {'option':'B', 'number':2,'votes': 80000}, + {'option':'C', 'number':3,'votes': 30000}, + {'option':'D', 'number':4,'votes': 20000} + ], 'numEscanyos': 10 + + } + + expected_result = [ + {'option':'A','number':1,'votes': 100000, 'postproc': 4}, + {'option':'B', 'number':2,'votes': 80000, 'postproc': 4}, + {'option':'C', 'number':3,'votes': 30000, 'postproc': 1}, + {'option':'D', 'number':4,'votes': 20000, 'postproc': 1} + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + def testHuntington(self): + data = { + 'type': 'HUNTINGTONHILL', + 'options': [ + {'option':'A','number':1,'votes': 95000}, + {'option':'B', 'number':2,'votes': 75000}, + {'option':'C', 'number':3,'votes': 25000}, + {'option':'D', 'number':4,'votes': 15000} + ], 'numEscanyos': 8 + + } + + expected_result = [ + {'option':'A','number':1,'votes': 95000,'postproc':4}, + {'option':'B', 'number':2,'votes': 75000,'postproc':3}, + {'option':'C', 'number':3,'votes': 25000,'postproc':1}, + {'option':'D', 'number':4,'votes': 15000,'postproc':0} +] + response = self.client.post('/postproc/', data, format='json') + + self.assertEqual(response.status_code, 200) + values = response.json() + + + self.assertEqual(values, expected_result) + + + def testDHont1(self): #Fácil de comprobar manualmente + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 12000}, + {'option':'Option 2','number':2,'votes': 140000}, + {'option':'Option 3','number':3,'votes': 110000}, + {'option':'Option 4','number':4,'votes': 205000}, + {'option':'Option 5','number':5,'votes': 150000}, + {'option':'Option 6','number':6,'votes': 16000} + ], + 'numEscanyos': 10 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 12000, 'postproc': 0}, + {'option':'Option 2','number':2,'votes': 140000, 'postproc': 2}, + {'option':'Option 3','number':3,'votes': 110000, 'postproc': 2}, + {'option':'Option 4','number':4,'votes': 205000, 'postproc': 4}, + {'option':'Option 5','number':5,'votes': 150000, 'postproc': 2}, + {'option':'Option 6','number':6,'votes': 16000, 'postproc': 0} + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def testDHont2(self): #Votos muy igualados + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 65000}, + {'option':'Option 2','number':2,'votes': 60000}, + {'option':'Option 3','number':3,'votes': 50000}, + {'option':'Option 4','number':4,'votes': 55000}, + {'option':'Option 5','number':5,'votes': 62000}, + {'option':'Option 6','number':6,'votes': 57000}, + ], + 'numEscanyos': 10 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 65000,'postproc': 2}, + {'option':'Option 2','number':2,'votes': 60000,'postproc': 2}, + {'option':'Option 3','number':3,'votes': 50000,'postproc': 1}, + {'option':'Option 4','number':4,'votes': 55000,'postproc': 1}, + {'option':'Option 5','number':5,'votes': 62000,'postproc': 2}, + {'option':'Option 6','number':6,'votes': 57000,'postproc': 2}, + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def testDHont3(self): #Votos muy desiguales + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 65000}, + {'option':'Option 2','number':2,'votes': 30000}, + {'option':'Option 3','number':3,'votes': 1500}, + {'option':'Option 4','number':4,'votes': 4500}, + {'option':'Option 5','number':5,'votes': 2000}, + ], + 'numEscanyos': 100 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 65000,'postproc': 65}, + {'option':'Option 2','number':2,'votes': 30000,'postproc': 29}, + {'option':'Option 3','number':3,'votes': 1500,'postproc': 1}, + {'option':'Option 4','number':4,'votes': 4500,'postproc': 4}, + {'option':'Option 5','number':5,'votes': 2000,'postproc': 1}, + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def testDHont4(self): #Votos iguales + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 50000}, + {'option':'Option 2','number':2,'votes': 50000}, + {'option':'Option 3','number':3,'votes': 50000} + ], + 'numEscanyos': 300 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 50000,'postproc': 100}, + {'option':'Option 2','number':2,'votes': 50000,'postproc': 100}, + {'option':'Option 3','number':3,'votes': 50000,'postproc': 100} + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def testDHont5(self): #Votos muy elevados + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 150150150150150}, + {'option':'Option 2','number':2,'votes': 300300300300300}, + {'option':'Option 3','number':3,'votes': 200200200200200} + ], + 'numEscanyos': 100 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 150150150150150,'postproc': 23}, + {'option':'Option 2','number':2,'votes': 300300300300300,'postproc': 46}, + {'option':'Option 3','number':3,'votes': 200200200200200,'postproc': 31} + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def testDHont6(self): #Escaños elevados + data = { + 'type': 'DHONT', + 'options': [ + {'option':'Option 1','number':1,'votes': 15000}, + {'option':'Option 2','number':2,'votes': 75000}, + {'option':'Option 3','number':3,'votes': 10000}, + {'option':'Option 4','number':4,'votes': 5000}, + {'option':'Option 5','number':5,'votes': 2500}, + ], + 'numEscanyos': 900 + } + + expected_result = [ + {'option':'Option 1','number':1,'votes': 15000,'postproc': 126}, + {'option':'Option 2','number':2,'votes': 75000,'postproc': 630}, + {'option':'Option 3','number':3,'votes': 10000,'postproc': 83}, + {'option':'Option 4','number':4,'votes': 5000,'postproc': 41}, + {'option':'Option 5','number':5,'votes': 2500,'postproc': 20} + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + def test_imperiali(self): + data={ + 'type':'IMPERIALI', + 'options':[ + {'option':'A', 'number':1, 'votes':391000}, + {'option':'B', 'number':2, 'votes':311000}, + {'option':'C', 'number':2, 'votes':184000}, + {'option':'D', 'number':4, 'votes':73000}, + {'option':'E', 'number':5, 'votes':27000}, + {'option':'F', 'number':6, 'votes':12000}, + {'option':'G', 'number':7, 'votes':2000}, + ], 'escanyosTotales':21 + } + + expected_result=[ + {'option':'A', 'number':1, 'votes':391000, 'postproc':0}, + {'option':'B', 'number':2, 'votes':311000, 'postproc':0}, + {'option':'C', 'number':2, 'votes':184000, 'postproc':0}, + {'option':'D', 'number':4, 'votes':73000, 'postproc':0}, + {'option':'E', 'number':5, 'votes':27000, 'postproc':0}, + {'option':'F', 'number':6, 'votes':12000, 'postproc':0}, + {'option':'G', 'number':7, 'votes':2000, 'postproc':0}, + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + + values = response.json() + self.assertEqual(values, expected_result) + + + def test_saintelague3(self): + data = { + 'type': 'SAINTELAGUE', + 'numEscanyos': 4, + 'options': [ + { 'option': 'Option 1', 'number': 1, 'votes': 1200 },{ 'option': 'Option 2', 'number': 2, 'votes': 700 }, + { 'option': 'Option 3', 'number': 3, 'votes': 650 },{ 'option': 'Option 4', 'number': 4, 'votes': 400 }, + { 'option': 'Option 5', 'number': 5, 'votes': 200 }, + ] + } + + expected_result = [ + { 'option': 'Option 1', 'number': 1, 'votes': 1200, 'postproc': 2 }, + { 'option': 'Option 2', 'number': 2, 'votes': 700, 'postproc': 1 },{ 'option': 'Option 3', 'number': 3, 'votes': 650, 'postproc': 1 }, + { 'option': 'Option 4', 'number': 4, 'votes': 400, 'postproc': 0 },{ 'option': 'Option 5', 'number': 5, 'votes': 200, 'postproc': 0 } + ] + + response = self.client.post('/postproc/', data, format='json') + self.assertEqual(response.status_code, 200) + values = response.json() + self.assertEqual(values, expected_result) + diff --git a/decide/postproc/views.py b/decide/postproc/views.py index f4c5de1e5f..427eb322e1 100644 --- a/decide/postproc/views.py +++ b/decide/postproc/views.py @@ -1,5 +1,8 @@ +from django.db import models from rest_framework.views import APIView from rest_framework.response import Response +import math +#import numpy as np class PostProcView(APIView): @@ -15,10 +18,240 @@ def identity(self, options): out.sort(key=lambda x: -x['postproc']) return Response(out) + + + def hamilton(self, options, numEscanyos): + #Definimos votos totales y el numero de escanyos asignados + votos = 0 + numEscanyosAsignados=0 + #Hacemos recuento de votos totales y le añadimos a cada opción otro valor llamado postproc en el que almacenaremos el numero de escanyos que le asignamos + for option in options: + option['postproc']=0 + votos += option['votes'] + #Creamos una lista vacia para introducir el resto de cada partido al anyadir los escanyos + lista=[] + if votos>0 and numEscanyos>0: + participantes = len(options) + #Recorremos las opciones y al atributo postproc que habiamos creado anteriormente le asignamos un numero de escanyos mediante + #el siguiente calculo: (NumVotosPartido*NumEscanyos)//VotosTotales. El resultado sera una division exacta + #A su vez rellenamos la lista vacia con un diccionario en el que ponemos el nombre de la opcion y el resto de la division + #Tambien vamos incrementando el numero de escanyos asignados + for option in options: + option['postproc']=(option['votes']*numEscanyos)//votos + lista.append({'number':option['number'],'votes':(option['votes']*numEscanyos)%votos}) + numEscanyosAsignados+=(option['votes']*numEscanyos)//votos + + + lista.sort(key=lambda o: o['votes'],reverse=True) + for option in lista: + i = option['number']-1 + if(numEscanyosAsignados 0 and numEscanyos > 0: + + limit = votosTotales/numEscanyos + + #Vamos a aplicar la regla rounding + rounding = limit*0.001 + lower = limit-rounding + upper = limit+rounding + + numEscanyosAsig = 0 + + while(numEscanyosAsig != numEscanyos): + + #si llegamos a aplicar rounding rule y no llegamos al numero igual de escanos, + #reseteamos de nuevo el numero de escanos asig y empezamos de nuevo + ##si no se cumple la regla reseteamos cero + + numEscanyosAsig = 0 + + for x in options: + + if(x['votes'] mediaG): + x['postproc']=(lQ+1) + else: + x['postproc']=lQ + + numEscanyosAsig += x['postproc'] + + #Rounding Rule: + + + #For a quota q, let L denote it's lower quota, U its upper quota, and G the + #geometric mean of L and U. If then round q down to L, otherwise + #round q up to U. + + if(numEscanyosAsig < numEscanyos): + + limit = lower + lower = limit-rounding + upper = limit+rounding + + else: + limit = upper + lower = limit-rounding + upper = limit+rounding + else: + for x in options: + x.update({'postproc' : 0}) + return Response(options) + + return Response(options) + + + def imperiali(self, options,numEscanyos): + votosTotales = 0 + + for i in options: + votosTotales= votosTotales+ i['votes'] + + if votosTotales>0 and numEscanyos>0: + + q=round(votosTotales/(numEscanyos+2),0) + + escanyosAsigandos=0 + for i in options: + votos= i['votes'] + escanyos=math.floor(votos/q) + i.update({'postproc': escanyos}) + escanyosAsigandos=escanyosAsigandos+i['postproc'] + + #Mientras queden escaños libre + + while(escanyosAsigandos