diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 4df6af83ac..ca5bc7dd22 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -3,7 +3,7 @@ name: Python application on: push: branches: - - '*' + - '**' workflow_call: secrets: CODACY_PROJECT_TOKEN: @@ -11,7 +11,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: pyversion: ['3.6.15', '3.8'] @@ -44,10 +44,6 @@ jobs: pip install -r requirements.txt pip install codacy-coverage cp decide/local_settings.gactions.py decide/local_settings.py - - name: Run migrations (unnecessary) - run: | - cd decide - python manage.py migrate - name: Generate translation files run: | sudo apt-get install gettext diff --git a/.github/workflows/releases_workflow.yml b/.github/workflows/releases_workflow.yml index dc19c2ec10..c75f3a8417 100644 --- a/.github/workflows/releases_workflow.yml +++ b/.github/workflows/releases_workflow.yml @@ -3,7 +3,7 @@ name: release on: push: tags: - - '*' + - '**' jobs: buildTest: @@ -13,7 +13,7 @@ jobs: release: needs: buildTest - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout uses: actions/checkout@v2 diff --git a/decide/authentication/locale/en_US/LC_MESSAGES/django.po b/decide/authentication/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000000..0ab66ea744 --- /dev/null +++ b/decide/authentication/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# decide auth translation +# This file is distributed under the same license as the main project + +# Translators: Francisco Javier Migueles , 2022 + +msgid "" +msgstr "" + +"Language: en\n" +"Last-Translator: Francisco Javier Migueles\n" +"POT-Creation-Date: 2022-12-01 18:45+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "registrar" +msgstr "Registration" + +msgid "infoRegistrar" +msgstr "Please fill in this form to create an account." + +msgid "first_name" +msgstr "First Name" + +msgid "last_name" +msgstr "Last Name" + +msgid "username" +msgstr "Username" + +msgid "email" +msgstr "Email" + +msgid "password" +msgstr "Password" + +msgid "acceptRegister" +msgstr "By registering, you accept the" + +msgid "terms" +msgstr "Terms & Privacy" + +msgid "alreadyAccount" +msgstr "Already have an account?" + +msgid "sign" +msgstr "Sign In" \ No newline at end of file diff --git a/decide/authentication/locale/es_ES/LC_MESSAGES/django.po b/decide/authentication/locale/es_ES/LC_MESSAGES/django.po index fdb9742279..76a2a61161 100644 --- a/decide/authentication/locale/es_ES/LC_MESSAGES/django.po +++ b/decide/authentication/locale/es_ES/LC_MESSAGES/django.po @@ -12,4 +12,41 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" \ No newline at end of file +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + + +msgid "registrar" +msgstr "Registro" + +msgid "infoRegistrar" +msgstr "Por favor, rellene este formulario para crear una cuenta." + +msgid "first_name" +msgstr "Nombre" + +msgid "last_name" +msgstr "Apellido" + +msgid "username" +msgstr "Nombre de usuario" + +msgid "email" +msgstr "Email" + +msgid "password" +msgstr "Contraseña" + +msgid "register" +msgstr "Registrarse" + +msgid "sign" +msgstr "Identificarse" + +msgid "acceptRegister" +msgstr "Al registrarse, acepta" + +msgid "terms" +msgstr "Terminos y Privacidad" + +msgid "alreadyAccount" +msgstr "¿Ya estas registrado?" \ No newline at end of file diff --git a/decide/authentication/templates/authentication/register.html b/decide/authentication/templates/authentication/register.html index edd6aa6abb..c872e904ea 100644 --- a/decide/authentication/templates/authentication/register.html +++ b/decide/authentication/templates/authentication/register.html @@ -5,48 +5,48 @@
{% csrf_token %}
-

Register

-

Please fill in this form to create an account.

+

{% trans "registrar" %}:

+

{% trans "infoRegistrar" %}


- +

- +

- +

- +

- +
-

By registering, you accept the Terms & Privacy.

+

{% trans "acceptRegister" %}{% trans "terms" %}.

- +
diff --git a/decide/authentication/templates/template_activate_account.html b/decide/authentication/templates/template_activate_account.html index 1500a6f72f..be34a555ad 100644 --- a/decide/authentication/templates/template_activate_account.html +++ b/decide/authentication/templates/template_activate_account.html @@ -1,7 +1,12 @@ {% autoescape off %} Hi {{ user.username }}, +Welcome to Decide Villanueva del Trabuco. +Before you can vote in our system or use Decide, we need you to activate your account. Please click on the link below to confirm your registration: {{protocol}}://{{domain}}{% url 'activate' uidb64=uid token=token %} + +We hope you enjoy your time using Decide Villanueva del Trbuco. +If you have any suggestion to improve our web, send an e-mail to this account with the subject: BetterDecide!. {% endautoescape %} \ No newline at end of file diff --git a/decide/authentication/tests.py b/decide/authentication/tests.py index 2eb24d1ddd..4dab85a625 100644 --- a/decide/authentication/tests.py +++ b/decide/authentication/tests.py @@ -14,6 +14,7 @@ def setUp(self): self.client = APIClient() mods.mock_query(self.client) u = User(username='voter1') + u.email = 'voter1@gmail.com' u.set_password('123') u.save() @@ -38,6 +39,19 @@ def test_login_fail(self): response = self.client.post('/authentication/login/', data, format='json') self.assertEqual(response.status_code, 400) + def test_login_email(self): + data = {'username': 'voter1@gmail.com', 'password': '123'} + response = self.client.post('/authentication/login/', data, format='json') + self.assertEqual(response.status_code, 200) + + token = response.json() + self.assertTrue(token.get('token')) + + def test_login_fail_email(self): + data = {'username': 'voter1@gmail.com', 'password': '321'} + response = self.client.post('/authentication/login/', data, format='json') + self.assertEqual(response.status_code, 400) + def test_getuser(self): data = {'username': 'voter1', 'password': '123'} response = self.client.post('/authentication/login/', data, format='json') diff --git a/decide/base/locale/en_US/LC_MESSAGES/django.po b/decide/base/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000000..45e265b140 --- /dev/null +++ b/decide/base/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,15 @@ +# decide base translation +# This file is distributed under the same license as the main project + +# Translators: Francisco Javier Migueles Dominguez , 2022 + +msgid "" +msgstr "" + +"Language: es\n" +"Last-Translator: Francisco Javier Migueles Dominguez\n" +"POT-Creation-Date: 2022-12-04 11:00+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" \ No newline at end of file diff --git a/decide/base/templates/base.html b/decide/base/templates/base.html index 606f6ad7d4..89b8fb45c0 100644 --- a/decide/base/templates/base.html +++ b/decide/base/templates/base.html @@ -8,8 +8,9 @@ {% block content %} - {% endblock %} + {% endblock %} + {% block extrabody %}{% endblock %} diff --git a/decide/booth/locale/en_US/LC_MESSAGES/django.po b/decide/booth/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000000..d5d36623dc --- /dev/null +++ b/decide/booth/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,76 @@ +# decide booth translation +# This file is distributed under the same license as the main project + +# Translators: Francisco Javier Migueles , 2022 + +msgid "" +msgstr "" + +"Language: en\n" +"Last-Translator: Francisco Javier Migueles\n" +"POT-Creation-Date: 2022-12-01 18:00+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +# Booth + +msgid "decide" +msgstr "Decide" + +msgid "logout" +msgstr "Logout" + +msgid "vID" +msgstr "ID of Vote" + +msgid "vName" +msgstr "Name of voting" + +msgid "Login" +msgstr "Login" + +msgid "LoginGitHub" +msgstr "Login with GitHub" + +msgid "LoginFacebook" +msgstr "Login with Facebook" + +msgid "Vote" +msgstr "Vote" + +msgid "Error" +msgstr "Error" + +msgid "Conglatulations. Your vote has been sent" +msgstr "Conglatulations. Your vote has been sent" + +msgid "registered_as_1" +msgstr "You are registered " + +msgid "registered_as_2" +msgstr " as " + +msgid "vote_with" +msgstr "You have voted with" + +# Votings + +msgid "voting" +msgstr "Voting" + +msgid "link_voting" +msgstr "Link to voting" + +msgid "link_census" +msgstr "Link to census" + +msgid "booth_voting" +msgstr "Booth to voting" + +msgid "closed_voting" +msgstr "Voting closed" + +msgid "voting_census" +msgstr "Census of voting" \ No newline at end of file diff --git a/decide/booth/locale/es_ES/LC_MESSAGES/django.po b/decide/booth/locale/es_ES/LC_MESSAGES/django.po index 33fc079c3b..5e4e6200e1 100644 --- a/decide/booth/locale/es_ES/LC_MESSAGES/django.po +++ b/decide/booth/locale/es_ES/LC_MESSAGES/django.po @@ -14,6 +14,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +# Booth + msgid "decide" msgstr "Decide" @@ -42,4 +44,33 @@ msgid "Error" msgstr "Error" msgid "Conglatulations. Your vote has been sent" -msgstr "¡Enhorabuena! Tu voto se ha enviado" \ No newline at end of file +msgstr "¡Enhorabuena! Tu voto se ha enviado" + +msgid "registered_as_1" +msgstr "Registrado en " + +msgid "registered_as_2" +msgstr " como " + +msgid "vote_with" +msgstr "Votar como" + +# Votings + +msgid "voting" +msgstr "Votación" + +msgid "link_voting" +msgstr "Enlace a la votación" + +msgid "link_census" +msgstr "Enlace al censo" + +msgid "booth_voting" +msgstr "Booth votación " + +msgid "closed_voting" +msgstr "Votación cerrada" + +msgid "voting_census" +msgstr "Censo votación " \ No newline at end of file diff --git a/decide/booth/templates/booth/booth.html b/decide/booth/templates/booth/booth.html index 2fd34d4242..8802dd2288 100644 --- a/decide/booth/templates/booth/booth.html +++ b/decide/booth/templates/booth/booth.html @@ -55,31 +55,43 @@

{% trans "vName" %}: [[ voting.name ]]

{% if not user.is_authenticated and not user.is_staff %} - - Login with GitHub + + {% trans "LoginGitHub" %} {% elif not user.is_staff %} -

Registrado en {{user.social_auth.get.provider}} como {{user}}

+

{% trans "register_as_1" %}{{user.social_auth.get.provider}} {% trans "register_as_2" %} {{user}}

- Vote with {{user.social_auth.get.provider}} + {% trans "vote_with" %} {{user.social_auth.get.provider}} {% endif %}
-

[[ voting.question.desc ]]

- - - [[ opt.option ]] - - - - {% trans "Vote" %} - +
+

[[ voting.question.desc ]]

+ + + [[ opt.option ]] + + + + {% trans "Vote" %} + +
+
+

[[ voting.question.desc ]]

+ + {% trans "Question number " %}[[i+1]] : [[opt.option]] + + + + {% trans "Vote" %} + +
@@ -201,6 +213,11 @@

[[ voting.question.desc ]]

var cipher = ElGamal.encrypt(this.bigpk, bigmsg); return cipher; }, + decideEncryptMultioption() { + var bigmsg = BigInt.fromJSONObject(this.order.toString()); + var cipher = ElGamal.encrypt(this.bigpk, bigmsg); + return cipher; + }, decideSend(evt) { evt.preventDefault(); var v = this.decideEncrypt(); @@ -218,6 +235,46 @@

[[ voting.question.desc ]]

this.showAlert("danger", '{% trans "Error: " %}' + error); }); }, + decideSendMultioption(evt) { + if (this.order.toString().length < this.voting.question.options.length || this.order.toString().length == null) { + this.showAlert("danger", '{% trans "Please input the order of your selection (remember that the input must be a sequence of all question numbers at your preferred order" %}'); + return; + } else if (this.order.toString().length > this.voting.question.options.length) { + this.showAlert("danger", '{% trans "Please input the order of your selection (You introduce too much values" %}'); + return; + } + + function checkIfDuplicateExists(arr) { + return new Set(arr).size !== arr.length + } + + for (var i = 0; i < this.order.toString().length; i++) { + if (this.order.toString().charAt(i) > this.voting.question.options.length || this.order.toString().charAt(i) <= 0) { + this.showAlert("danger", '{% trans "Invalid Values (You introduced selection that contain invalid question number)" %}'); + return; + } + } + var duplicates = checkIfDuplicateExists(this.order.toString()) + if (duplicates) { + this.showAlert("danger", '{% trans "Duplicated Values (the preferred order must not use duplicated question numbers)" %}'); + return; + } + evt.preventDefault(); + var v = this.decideEncryptMultioption(); + var data = { + vote: {a: v.alpha.toString(), b: v.beta.toString()}, + voting: this.voting.id, + voter: this.user.id, + token: this.token + } + this.postData("{% url "gateway" "store" "/" %}", data) + .then(data => { + this.showAlert("success", '{% trans "Conglatulations. Your selection has been sent" %}'); + }) + .catch(error => { + this.showAlert("danger", '{% trans "Error: " %}' + error); + }); + }, showAlert(lvl, msg) { this.alertLvl = lvl; this.alertMsg = msg; diff --git a/decide/booth/templates/booth/votings.html b/decide/booth/templates/booth/votings.html new file mode 100644 index 0000000000..9b1fd54466 --- /dev/null +++ b/decide/booth/templates/booth/votings.html @@ -0,0 +1,51 @@ +{% extends "base.html" %} +{% load i18n static %} + + +{% block extrahead %} + + + +{% endblock %} + +{% block content %} +
+ + + + + + + + + + + {% for voting in votings %} + + + + {% if voting.end_date == None %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
#{% trans "voting" %}{% trans "link_voting" %}{% trans "link_census" %}
{{voting.id}}{{voting.name}}

{% trans "booth_voting" %} {{voting.id}}

{% trans "closed_voting" %}

{% trans "voting_census" %} {{voting.id}}

+ +
+ + +{% endblock %} + +{% block extrabody %} + + + + + +{% endblock %} diff --git a/decide/booth/tests.py b/decide/booth/tests.py index 7ce503c2dd..fefb5420b4 100644 --- a/decide/booth/tests.py +++ b/decide/booth/tests.py @@ -1,3 +1,174 @@ from django.test import TestCase +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from voting.models import Voting, Question +from django.utils import timezone +from mixnet.models import Auth +from django.conf import settings -# Create your tests here. +from selenium import webdriver +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import NoSuchElementException +from unittest import mock + +from django.contrib.auth import get_user_model, authenticate, login +from django.contrib.auth.models import AbstractBaseUser +from django.test import override_settings +from base.tests import BaseTestCase +from django.urls import reverse +from voting.models import Voting, Question, QuestionOption +from django.utils import timezone +from mixnet.models import Auth +from django.conf import settings + +from social_django.models import UserSocialAuth +from social_django.views import get_session_timeout + +from base.tests import BaseTestCase + + +class BoothTranslationTestCase(StaticLiveServerTestCase): + + def setUp(self): + self.base = BaseTestCase() + self.base.setUp() + super().setUp() + + options = webdriver.ChromeOptions() + options.headless = True + self.driver = webdriver.Chrome(options=options) + + + def tearDown(self): + super().tearDown() + self.driver.quit() + self.base.tearDown() + + def crear_votacion(self): + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + a, _ = Auth.objects.get_or_create(url=settings.BASEURL, + defaults={'me': True, 'name': 'test auth'}) + a.save() + v.auths.add(a) + v.create_pubkey() + v.start_date = timezone.now() + v.save() + + self.v_id = v.id + return v.id + + def testCheckIDTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/booth/'+str(self.v_id)) + title = self.driver.find_elements(By.TAG_NAME, 'h1')[1].text + title = title.split(": ")[0] + return self.assertEqual(str(title),'ID de la votación') + + def testCheckNombreTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/booth/'+str(self.v_id)) + sub_title = self.driver.find_element(By.TAG_NAME, 'h3').text + sub_title = sub_title.split(": ")[0] + return self.assertEqual(str(sub_title),'Nombre de la votación') + + def testCheckInputsTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/booth/'+str(self.v_id)) + inputs = [element.text for element in self.driver.find_elements(By.TAG_NAME, 'label')] + + username, password = inputs[0], inputs[1] + local_check = self.assertEqual(username,"Nombre de usuario") + local_check = local_check and self.assertEqual(password,"Contraseña") + return local_check + + def testCheckLoginTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/booth/'+str(self.v_id)) + login = self.driver.find_element(By.TAG_NAME, 'button').text + + return self.assertEqual(login,"Identificarse") + + def testCheckGitHubTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/booth/'+str(self.v_id)) + login = self.driver.find_element(By.CLASS_NAME,'btn-secondary').text + return self.assertEqual(login,"Identificarse con GitHub") + + +@override_settings(SOCIAL_AUTH_GITHUB_KEY='1', + SOCIAL_AUTH_GITHUB_SECRET='2') +class TestViews(StaticLiveServerTestCase): + + def setUp(self): + self.base = BaseTestCase() + self.base.setUp() + super().setUp() + + options = webdriver.ChromeOptions() + options.headless = True + self.driver = webdriver.Chrome(options=options) + + def tearDown(self): + super().tearDown() + self.voting = None + + def login_admin(self): + self.driver.get(f'{self.live_server_url}/admin/login/?next=/admin/') + self.driver.find_element(By.ID, "id_username").click() + self.driver.find_element(By.ID, "id_username").send_keys("admin") + self.driver.find_element(By.ID, "id_password").click() + self.driver.find_element(By.ID, "id_password").send_keys("qwerty") + self.driver.find_element(By.CSS_SELECTOR, ".submit-row > input").click() + + def create_voting(self): + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + a, _ = Auth.objects.get_or_create(url=settings.BASEURL, + defaults={'me': True, 'name': 'test auth'}) + a.save() + v.auths.add(a) + v.create_pubkey() + v.start_date = timezone.now() + v.save() + + self.v_id = v.id + return v.id + + def test_backend_exists(self): + response = self.client.get(reverse('social:begin', kwargs={'backend': 'github'})) + self.assertEqual(response.status_code, 302) + + def test_backend_not_exists(self): + url = reverse('social:begin', kwargs={'backend': 'blabla'}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_not_logged_booth_view(self): + v = self.create_voting() + self.driver.get(f'{self.live_server_url}/booth/'+str(v)) + try: + githubButton = self.driver.find_element_by_xpath("//a[@id='githubButton']") + assert True + except NoSuchElementException: + assert False + + def test_admin_logged_booth_view(self): + v = self.create_voting() + self.login_admin() + self.driver.get(f'{self.live_server_url}/booth/'+str(v)) + try: + githubButton = self.driver.find_element_by_xpath("//a[@id='githubButton']") + assert False + except NoSuchElementException: + assert True \ No newline at end of file diff --git a/decide/booth/urls.py b/decide/booth/urls.py index 787b518786..d614dc49d3 100644 --- a/decide/booth/urls.py +++ b/decide/booth/urls.py @@ -1,7 +1,9 @@ from django.urls import path from .views import BoothView +from . import views urlpatterns = [ path('/', BoothView.as_view()), + path('all/', views.list_votings), ] diff --git a/decide/booth/views.py b/decide/booth/views.py index 9b1b513b8d..186985ee67 100644 --- a/decide/booth/views.py +++ b/decide/booth/views.py @@ -3,8 +3,11 @@ from django.conf import settings from django.http import Http404 from django.contrib.auth.models import User +from django.http import HttpResponse +from django.template import loader from base import mods +from voting.models import Voting # TODO: check permissions and census @@ -18,7 +21,6 @@ def get_context_data(self, **kwargs): try: r = mods.get('voting', params={'id': vid}) - # Casting numbers to string to manage in javascript with BigInt # and avoid problems with js and big number conversion for k, v in r[0]['pub_key'].items(): @@ -36,5 +38,13 @@ def get_context_data(self, **kwargs): user.set_password(password) user.save() - return context + +def list_votings(request): + template = loader.get_template('booth/votings.html') + votings = Voting.objects.all() + context = { + 'votings': votings + } + return HttpResponse(template.render(context, request)) + diff --git a/decide/census/census_utils.py b/decide/census/census_utils.py new file mode 100644 index 0000000000..33c8cafe13 --- /dev/null +++ b/decide/census/census_utils.py @@ -0,0 +1,49 @@ +from django.contrib.auth.models import User +from .models import Census + +def get_user_atributes(): + user = User.objects.all().values()[0] + atributes_list = [] + + counter = 0 + for atribute in user.keys(): + if not atribute == 'id' and not atribute == 'password': + atributes_list.append((counter, atribute)) + counter += 1 + + return atributes_list + +# csvtext -> Header1,Header2,Header3/value1,value2,value3/value1,value2,value3/ +def get_csvtext_and_data(form_values, census): + atributes_list = get_user_atributes() + voters_data = [] + headers = [] + + # Header + census_text = 'id,' + headers.append('id') + for index in form_values: + atribute = str(atributes_list[int(index)][1]) + headers.append(atribute) + census_text += atribute + if not form_values[-1] == index: + census_text += ',' + else: + census_text += '/' + + # CSV values + for c in census: + voter = User.objects.filter(id=c['voter_id']).values()[0] + values_list = [] + for atr in headers: + census_text += str(voter[atr]) + values_list.append(str(voter[atr])) + if not headers[-1] == atr: + census_text += ',' + else: + census_text += '/' + voters_data.append(values_list) + + return (census_text, headers, voters_data) + + diff --git a/decide/census/forms.py b/decide/census/forms.py new file mode 100644 index 0000000000..2a4aa70750 --- /dev/null +++ b/decide/census/forms.py @@ -0,0 +1,14 @@ +from django import forms +from django.contrib.auth.models import User + +class AtributosUser(forms.Form): + user = User.objects.all().values()[0] + atributes_list = [] + + counter = 0 + for atribute in user.keys(): + if not atribute == 'id' and not atribute == 'password': + atributes_list.append((counter, atribute)) + counter += 1 + + user_atributes = forms.MultipleChoiceField(label="", choices=atributes_list, widget=forms.CheckboxSelectMultiple) \ No newline at end of file diff --git a/decide/census/locale/en_US/LC_MESSAGES/django.po b/decide/census/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000000..13fca071a0 --- /dev/null +++ b/decide/census/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,72 @@ +# decide census translation +# This file is distributed under the same license as the main project + +# Translators: Francisco Javier Migueles , 2022 + +msgid "" +msgstr "" + +"Language: es\n" +"Last-Translator: Francisco Javier Migueles\n" +"POT-Creation-Date: 2022-12-04 11:14+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "decide" +msgstr "Decide" + +# Census + +msgid "administration_census" +msgstr "Administration census" + +msgid "add_census_question" +msgstr "Which vote do you want to add the census?" + +msgid "select_file" +msgstr "Select the file you want to import your census from" + +msgid "input_user_admin" +msgstr "Insert the administrator user that will import the census" + +msgid "input_user_admin_password" +msgstr "Insert the password of the administrator user" + +# Export census + +msgid "census" +msgstr "Census:" + +msgid "download_csv" +msgstr "Download CSV" + +msgid "vName" +msgstr "Name of the vote" + +msgid "vDesc" +msgstr "Description of the vote" + +msgid "username" +msgstr "Username" + +msgid "first_name" +msgstr "First name" + +msgid "last_name" +msgstr "Last name" + +msgid "voter_id" +msgstr "Voter ID" + +msgid "select_atr" +msgstr "Select the attribute:" + +msgid "goBack" +msgstr "Go back to the voting list" + + + + + diff --git a/decide/census/locale/es_ES/LC_MESSAGES/django.po b/decide/census/locale/es_ES/LC_MESSAGES/django.po index 67c664e63f..84350f4e55 100644 --- a/decide/census/locale/es_ES/LC_MESSAGES/django.po +++ b/decide/census/locale/es_ES/LC_MESSAGES/django.po @@ -9,12 +9,33 @@ msgstr "" "Language: es\n" "Last-Translator: Pablo Marin Gomez\n" "POT-Creation-Date: 2022-11-19 16:14+0100\n" -"PO-Revision-Date: 2022-11-23 17:08+0100\n" +"PO-Revision-Date: 2022-12-08 10:08+0100\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "decide" +msgstr "Decide" + +# Census + +msgid "administration_census" +msgstr "Administración de censos en votaciones" + +msgid "add_census_question" +msgstr "¿A qué votación quieres añadir el censo?" + +msgid "select_file" +msgstr "Selecciona el archivo desde el que deseas importar tu censo" + +msgid "input_user_admin" +msgstr "Introduzca el usuario administrador que importará el censo" + +msgid "input_user_admin_password" +msgstr "Introduzca la contraseña del usuario administrador" + +# Export census msgid "census" msgstr "Censo:" @@ -31,13 +52,18 @@ msgid "username" msgstr "Nombre de usuario" msgid "first_name" -msgstr "Primer Apellido" +msgstr "Nombre" msgid "last_name" -msgstr "Segundo Apellido" +msgstr "Apellido" msgid "voter_id" msgstr "ID del votannte" +# Errors + +msgid "goBack" +msgstr "Volver a la lista de votaciones" + diff --git a/decide/census/templates/census/census.html b/decide/census/templates/census/census.html index 644267f29c..77471d6ac5 100644 --- a/decide/census/templates/census/census.html +++ b/decide/census/templates/census/census.html @@ -13,30 +13,30 @@
- Decide + {% trans "decide" %}
-

Administración de censos en votaciones

+

{% trans "administration_census" %}

- +
- +
- +
- +
diff --git a/decide/census/templates/errors.html b/decide/census/templates/errors.html new file mode 100644 index 0000000000..790c531fad --- /dev/null +++ b/decide/census/templates/errors.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% load i18n static %} +{% load index %} + +{% block extrahead %} + + +{% endblock %} + +{% block content %} +
+
+ +
+
+{% endblock %} diff --git a/decide/census/templates/export_census.html b/decide/census/templates/export_census.html index ec5b5610cd..1f03b8eb42 100644 --- a/decide/census/templates/export_census.html +++ b/decide/census/templates/export_census.html @@ -15,6 +15,13 @@

{% trans "vName" %}: {{voting.name}}

{% trans "vDesc" %}: {{ voting.desc }}

+

{% trans "select_atr" %}

+ {% csrf_token %} +
+ {{formulario}} + +
+

{% trans "census" %}

@@ -23,47 +30,38 @@

{% trans "census" %}

- - - - - - - - - - - - {% for i in index %} - - - {% with census|index:i as c%} - {% with voters|index:i as voter%} - - {% if voter.first_name%} - - {% else %} + +
#{% trans "username" %}{% trans "first_name" %}{% trans "last_name" %}{% trans "voter_id" %}
{{i}}{{voter.username}}{{voter.first_name}}
+ + + {% for header in headers %} + + {% endfor %} + + + + {% for i in index %} + + {% with voters_data|index:i as voter_data %} + {% for j in header_index %} + {% with voter_data|index:j as data %} + {% if data == '' %} - {% endif %} - {% if voter.first_name%} - {% else %} - + {% endif %} {% endwith %} - - {% endwith %} - - {% endfor %} - -
{{header}}
-{{voter.last_name}}-{{data}}{{c.voter_id}}
-
+ {% endfor %} + {% endwith %} + + {% endfor %} + +
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/decide/visualizer/tests.py b/decide/visualizer/tests.py index 94bb6fb10b..0c9bd12b7b 100644 --- a/decide/visualizer/tests.py +++ b/decide/visualizer/tests.py @@ -1,13 +1,12 @@ -from django.test import TestCase from voting.models import Voting, Question from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from django.utils import timezone from selenium import webdriver -from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys - +from mixnet.models import Auth +from django.conf import settings from base.tests import BaseTestCase @@ -29,17 +28,6 @@ def tearDown(self): self.driver.quit() self.base.tearDown() - - def test_simpleVisualizer(self): - q = Question(desc = 'test question') - q.save() - - v = Voting(name='test voting', question=q) - v.save() - - response = self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') - vstate = self.driver.find_element(By.TAG_NAME,'h2').text - self.assertEqual(vstate,'Votación no comenzada') def test_simpleCorrectLogin(self): self.driver.get(f'{self.live_server_url}/admin') @@ -58,5 +46,268 @@ def test_simpleWrongLogin(self): self.assertTrue(len(self.driver.find_elements(By.CLASS_NAME,'errornote'))==1) + def test_graphs_title_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + title_text = self.driver.find_element(By.ID,'graphs_title').text + self.assertEqual(title_text,'Gráficas de la votación:') + + def test_graph_title_1_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + title_text = self.driver.find_element(By.ID,'graph_title_1').text + self.assertEqual(title_text,'Gráfica de votos') + + def test_graph_title_2_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + title_text = self.driver.find_element(By.ID,'graph_title_2').text + self.assertEqual(title_text,'Gráfica de escaños') + + def test_graph_title_3_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + title_text = self.driver.find_element(By.ID,'graph_title_3').text + self.assertEqual(title_text,'Gráfica de porcentaje de representación') + + def test_graph_canvas_1_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + canvas_is_displayed = self.driver.find_element(By.ID,'Graph1').is_displayed() + self.assertTrue(canvas_is_displayed) + + def test_graph_canvas_2_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + canvas_is_displayed = self.driver.find_element(By.ID,'Graph2').is_displayed() + self.assertTrue(canvas_is_displayed) + + def test_graph_canvas_3_exist(self): + + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + + self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}') + canvas_is_displayed = self.driver.find_element(By.ID,'Graph3').is_displayed() + self.assertTrue(canvas_is_displayed) + +class VotingVisualizerTestCase(StaticLiveServerTestCase): + + def setUp(self): + #Load base test functionality for decide + self.base = BaseTestCase() + self.base.setUp() + + options = webdriver.ChromeOptions() + options.headless = True + self.driver = webdriver.Chrome(options=options) + + super().setUp() + + def tearDown(self): + super().tearDown() + self.driver.quit() -# Create your tests here. + self.base.tearDown() + + def test_noStartVoting_visualizer(self): + q = Question(desc='test question 1') + q.save() + v = Voting(name='test voting 1', question=q, desc='test voting 1') + v.save() + + response =self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}/') + vState= self.driver.find_element(By.TAG_NAME,"h2").text + self.assertTrue(vState=="Votación no comenzada") + + vDesc= self.driver.find_element(By.TAG_NAME,"h3").text + textoDesc="Descripción de la votación: {}".format(v.desc) + self.assertTrue(vDesc==textoDesc) + + def test_startVoting_visualizer(self): + q = Question(desc='test question 2') + q.save() + date = timezone.now() + v = Voting(name='test voting 2', question=q, desc='test voting 2',start_date=date) + v.save() + + response =self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}/') + vState= self.driver.find_element(By.TAG_NAME,"h2").text + self.assertTrue(vState=="Votación en curso") + + vDesc= self.driver.find_element(By.TAG_NAME,"h3").text + textoDesc="Descripción de la votación: {}".format(v.desc) + self.assertTrue(vDesc==textoDesc) + + vDate= self.driver.find_element(By.CLASS_NAME,"inicio").text + textoFechaInicio="Fecha de inicio de la votación: {}".format(v.start_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vDate==textoFechaInicio) + + def test_finishVoting_visualizer(self): + q = Question(desc='test question 3') + q.save() + date = timezone.now() + v = Voting(name='test voting 3', question=q, desc='test voting 3', start_date=date, end_date=date) + v.save() + + response =self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}/') + vDesc= self.driver.find_element(By.TAG_NAME,"h3").text + textoDesc="Descripción de la votación: {}".format(v.desc) + self.assertTrue(vDesc==textoDesc) + + vStartDate= self.driver.find_element(By.CLASS_NAME,"inicio").text + textoFechaInicio="Fecha de inicio de la votación: {}".format(v.start_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vStartDate==textoFechaInicio) + + vEndDate= self.driver.find_element(By.CLASS_NAME,"fin").text + textoFechaFin="Fecha de fin de la votación: {}".format(v.end_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vEndDate==textoFechaFin) + + def test_postProcVoting_noSeats_visualizer(self): + q = Question(desc='test question 4') + q.save() + date = timezone.now() + v = Voting(name='test voting 4', question=q, desc='test voting 4', start_date=date, end_date=date) + v.save() + + response =self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}/') + vDesc= self.driver.find_element(By.TAG_NAME,"h3").text + textoDesc="Descripción de la votación: {}".format(v.desc) + + self.assertTrue(vDesc==textoDesc) + vStartDate= self.driver.find_element(By.CLASS_NAME,"inicio").text + textoFechaInicio="Fecha de inicio de la votación: {}".format(v.start_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vStartDate==textoFechaInicio) + + vEndDate= self.driver.find_element(By.CLASS_NAME,"fin").text + textoFechaFin="Fecha de fin de la votación: {}".format(v.end_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vEndDate==textoFechaFin) + + tipoResultado = self.driver.find_element(By.XPATH,"//table/thead/tr/th[2]").text + self.assertTrue(tipoResultado=="Puntuación") + + def test_postProcVoting_seats_visualizer(self): + q = Question(desc='test question 5') + q.save() + date = timezone.now() + v = Voting(name='test voting 5', question=q, desc='test voting 5', seats=5, min_percentage_representation=10, start_date=date, end_date=date) + v.save() + + response =self.driver.get(f'{self.live_server_url}/visualizer/{v.pk}/') + vDesc= self.driver.find_element(By.TAG_NAME,"h3").text + textoDesc="Descripción de la votación: {}".format(v.desc) + + self.assertTrue(vDesc==textoDesc) + vStartDate= self.driver.find_element(By.CLASS_NAME,"inicio").text + textoFechaInicio="Fecha de inicio de la votación: {}".format(v.start_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vStartDate==textoFechaInicio) + + vEndDate= self.driver.find_element(By.CLASS_NAME,"fin").text + textoFechaFin="Fecha de fin de la votación: {}".format(v.end_date.strftime("%d/%m/%Y, %H:%M:%S")) + self.assertTrue(vEndDate==textoFechaFin) + + tipoResultado = self.driver.find_element(By.XPATH,"//table/thead/tr/th[2]").text + self.assertTrue(tipoResultado=="Escaños") + + minPercentage = self.driver.find_element(By.CLASS_NAME,"minPercentage").text + textoMinPercentage="Porcentaje mínimo para tener representación: {}% de los votos totales".format(v.min_percentage_representation) + self.assertTrue(minPercentage==textoMinPercentage) + +class VotingVisualizerTransalationTestCase(StaticLiveServerTestCase): + def setUp(self): + self.base = BaseTestCase() + self.base.setUp() + + options = webdriver.ChromeOptions() + options.headless = True + self.driver = webdriver.Chrome(options=options) + super().setUp() + + def tearDown(self): + super().tearDown() + self.driver.quit() + + self.base.tearDown() + + def crear_votacion(self): + q = Question(desc = 'test question') + q.save() + + v = Voting(name='test voting', question=q) + v.save() + a, _ = Auth.objects.get_or_create(url=settings.BASEURL, + defaults={'me': True, 'name': 'test auth'}) + a.save() + v.auths.add(a) + v.create_pubkey() + v.start_date = timezone.now() + v.save() + + self.v_id = v.id + return v.id + + def detener_votacion(self): + v = Voting.objects.get(id=self.v_id) + v.end_date = timezone.now() + v.save() + + def testCheckIDTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/visualizer/'+str(self.v_id)) + ID_text= self.driver.find_elements(By.TAG_NAME, 'h1')[1].text + ID_text = ID_text.split(":")[0] + return self.assertEqual(str(ID_text),'ID de la votación') + + def testCheckNombreTransES(self): + self.crear_votacion() + self.driver.get(f'{self.live_server_url}/visualizer/'+str(self.v_id)) + Nombre_text= self.driver.find_elements(By.TAG_NAME, 'h1')[2].text + Nombre_text = Nombre_text.split(":")[0] + return self.assertEqual(str(Nombre_text),'Nombre de la votación') + + def testCheckResultadosTransES(self): + self.crear_votacion() + self.detener_votacion() + self.driver.get(f'{self.live_server_url}/visualizer/'+str(self.v_id)) + Resultados_text= self.driver.find_elements(By.TAG_NAME, 'h2')[0].text + return self.assertEqual(str(Resultados_text),'Resultados:') + diff --git a/decide/visualizer/views.py b/decide/visualizer/views.py index 8fea64ecb2..9fe27cebd1 100644 --- a/decide/visualizer/views.py +++ b/decide/visualizer/views.py @@ -16,6 +16,23 @@ def get_context_data(self, **kwargs): try: r = mods.get('voting', params={'id': vid}) context['voting'] = json.dumps(r[0]) + + if r[0]['question']['multioption']: + postpro = r[0]['postproc'] + position_votes = {} + for i,votes in enumerate(postpro): + position_votes[i] = votes['votes'] + + sorted_votes = dict(sorted(position_votes.items(), key=lambda item:item[1])) + order = list(sorted_votes.values()) + + for index,key in enumerate(sorted_votes.keys()): + order[key] = index+1 + context['order'] = order + for index in range(0,len(r[0]['postproc'])): + r[0]['postproc'][index]['order'] = order[index] + context['voting'] = json.dumps(r[0]) + except: raise Http404 diff --git a/decide/voting/locale/en_US/LC_MESSAGES/django.po b/decide/voting/locale/en_US/LC_MESSAGES/django.po new file mode 100644 index 0000000000..45e265b140 --- /dev/null +++ b/decide/voting/locale/en_US/LC_MESSAGES/django.po @@ -0,0 +1,15 @@ +# decide base translation +# This file is distributed under the same license as the main project + +# Translators: Francisco Javier Migueles Dominguez , 2022 + +msgid "" +msgstr "" + +"Language: es\n" +"Last-Translator: Francisco Javier Migueles Dominguez\n" +"POT-Creation-Date: 2022-12-04 11:00+0100\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" \ No newline at end of file diff --git a/decide/voting/migrations/0004_question_classic.py b/decide/voting/migrations/0004_question_classic.py new file mode 100644 index 0000000000..80537b2fe4 --- /dev/null +++ b/decide/voting/migrations/0004_question_classic.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0 on 2022-11-20 13:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('voting', '0003_auto_20180605_0842'), + ] + + operations = [ + migrations.AddField( + model_name='question', + name='classic', + field=models.BooleanField(default=True), + ), + ] diff --git a/decide/voting/migrations/0005_auto_20221120_1354.py b/decide/voting/migrations/0005_auto_20221120_1354.py new file mode 100644 index 0000000000..34930af139 --- /dev/null +++ b/decide/voting/migrations/0005_auto_20221120_1354.py @@ -0,0 +1,22 @@ +# Generated by Django 2.0 on 2022-11-20 13:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('voting', '0004_question_classic'), + ] + + operations = [ + migrations.RemoveField( + model_name='question', + name='classic', + ), + migrations.AddField( + model_name='question', + name='multioption', + field=models.BooleanField(default=False), + ), + ] diff --git a/decide/voting/migrations/0009_merge_20221206_1713.py b/decide/voting/migrations/0009_merge_20221206_1713.py new file mode 100644 index 0000000000..903162910c --- /dev/null +++ b/decide/voting/migrations/0009_merge_20221206_1713.py @@ -0,0 +1,14 @@ +# Generated by Django 2.0.12 on 2022-12-06 17:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('voting', '0005_auto_20221120_1354'), + ('voting', '0008_auto_20221119_1128'), + ] + + operations = [ + ] diff --git a/decide/voting/models.py b/decide/voting/models.py index 62c61a7772..a40acb4e9e 100644 --- a/decide/voting/models.py +++ b/decide/voting/models.py @@ -25,6 +25,7 @@ def __init__(self, *args, **kwargs): class Question(models.Model): desc = models.TextField() + multioption = models.BooleanField(default=False) def __str__(self): return self.desc @@ -190,17 +191,22 @@ def do_postproc(self): seats = self.seats min_percentage_representation = self.min_percentage_representation - #Si hay 0 escaños por repartir, el procesado de los datos se hace de forma normal - if seats==0: + if self.question.multioption: opts = [] - for opt in options: - if isinstance(tally, list): - votes = tally.count(opt.number) + for optIndex in enumerate(options): + votes = 0 + if isinstance(tally,list): + for vote in tally: + vote = [int(x) for x in str(vote)] + if votes == 0: + votes = vote.index(optIndex[0]+1) + else: + votes = votes + vote.index(optIndex[0]+1) else: votes = 0 opts.append({ - 'option': opt.option, - 'number': opt.number, + 'option': optIndex[1].option, + 'number': optIndex[1].number, 'votes': votes }) @@ -209,37 +215,57 @@ def do_postproc(self): self.postproc = postp self.save() - - #Si hay escaños por repartir, el procesado de los datos se hace con la ley d'Hont else: - #Hacemos el recuento de votos de todas las opciones - opts = [] - for opt in options: - if isinstance(tally, list): - votes = tally.count(opt.number) - else: - votes = 0 - opts.append({ - 'option': opt.option, - 'number': opt.number, - 'votes': votes - }) - - #Ahora con todos los votos contados hacemos el reparto de escaños - dicc_options_votes={} - for opt in opts: - dicc_options_votes[opt['option']]=opt['votes'] - escaños_partidos=Voting.hont(dicc_options_votes, seats, min_percentage_representation) + #Si hay 0 escaños por repartir, el procesado de los datos se hace de forma normal + if seats==0: + opts = [] + for opt in options: + if isinstance(tally, list): + votes = tally.count(opt.number) + else: + votes = 0 + opts.append({ + 'option': opt.option, + 'number': opt.number, + 'votes': votes + }) + + data = { 'type': 'IDENTITY', 'options': opts } + postp = mods.post('postproc', json=data) + + self.postproc = postp + self.save() + + #Si hay escaños por repartir, el procesado de los datos se hace con la ley d'Hont + else: + #Hacemos el recuento de votos de todas las opciones + opts = [] + for opt in options: + if isinstance(tally, list): + votes = tally.count(opt.number) + else: + votes = 0 + opts.append({ + 'option': opt.option, + 'number': opt.number, + 'votes': votes + }) + + #Ahora con todos los votos contados hacemos el reparto de escaños + dicc_options_votes={} + for opt in opts: + dicc_options_votes[opt['option']]=opt['votes'] + escaños_partidos=Voting.hont(dicc_options_votes, seats, min_percentage_representation) + + #Una vez calculados los escaños de cada partido, se añaden a la lista de opciones + for opt in opts: + opt['seats']=escaños_partidos[opt['option']] + + data = { 'type': 'IDENTITY', 'options': opts } + postp = mods.post('postproc', json=data) + print(postp) + self.postproc = postp + self.save() - #Una vez calculados los escaños de cada partido, se añaden a la lista de opciones - for opt in opts: - opt['seats']=escaños_partidos[opt['option']] - - data = { 'type': 'IDENTITY', 'options': opts } - postp = mods.post('postproc', json=data) - print(postp) - self.postproc = postp - self.save() - def __str__(self): - return self.name + return self.name \ No newline at end of file diff --git a/decide/voting/serializers.py b/decide/voting/serializers.py index 37af63376d..8c754613e0 100644 --- a/decide/voting/serializers.py +++ b/decide/voting/serializers.py @@ -14,7 +14,7 @@ class QuestionSerializer(serializers.HyperlinkedModelSerializer): options = QuestionOptionSerializer(many=True) class Meta: model = Question - fields = ('desc', 'options') + fields = ('desc', 'multioption', 'options') class VotingSerializer(serializers.HyperlinkedModelSerializer): diff --git a/decide/voting/tests.py b/decide/voting/tests.py index 94b7f6e799..39c313293e 100644 --- a/decide/voting/tests.py +++ b/decide/voting/tests.py @@ -1,12 +1,8 @@ import random import itertools -from click import option from django.utils import timezone from django.conf import settings from django.contrib.auth.models import User -from django.test import TestCase -from rest_framework.test import APIClient -from rest_framework.test import APITestCase from base import mods from base.tests import BaseTestCase @@ -353,4 +349,4 @@ def test_update_voting(self): data = {'action': 'tally'} response = self.client.put('/voting/{}/'.format(voting.pk), data, format='json') self.assertEqual(response.status_code, 400) - self.assertEqual(response.json(), 'Voting already tallied') + self.assertEqual(response.json(), 'Voting already tallied') \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index e8024c5e7c..b92e334831 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ from python:3.7-alpine RUN apk add --no-cache git postgresql-dev gcc libc-dev -RUN apk add --no-cache gcc g++ make libffi-dev python3-dev build-base +RUN apk add --no-cache gcc g++ make libffi-dev python3-dev build-base gettext RUN pip install gunicorn RUN pip install psycopg2 @@ -18,6 +18,9 @@ WORKDIR /app/decide # local settings.py ADD docker-settings.py /app/decide/local_settings.py -RUN ./manage.py collectstatic +RUN django-admin makemessages +RUN django-admin compilemessages -#CMD ["gunicorn", "-w 5", "decide.wsgi", "--timeout=500", "-b 0.0.0.0:5000"] +RUN ./manage.py collectstatic --noinput + +#CMD ["gunicorn", "-w 5", "decide.wsgi", "--timeout=500", "-b 0.0.0.0:5000"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index baf0085b33..a515a8e67b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,5 @@ click locust python-social-auth social-auth-app-django +pandas +xlrd