diff --git a/.gitignore b/.gitignore
index b823e313b4..0ab4853464 100644
--- a/.gitignore
+++ b/.gitignore
@@ -101,3 +101,5 @@ ENV/
.mypy_cache/
.vagrant
+.vscode/settings.json
+.vscode/settings.json
diff --git a/.project b/.project
new file mode 100644
index 0000000000..7f0df834a7
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ decide
+
+
+
+
+
+ org.python.pydev.PyDevBuilder
+
+
+
+
+
+ org.python.pydev.pythonNature
+
+
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000000..2b045655f8
--- /dev/null
+++ b/.pydevproject
@@ -0,0 +1,5 @@
+
+
+ Default
+ python interpreter
+
diff --git a/.travis.yml b/.travis.yml
index b72f88a6e0..b78b9d2d0e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,21 +1,27 @@
dist: xenial
-
services:
- - postgresql
+- postgresql
addons:
- postgresql: "9.4"
+ postgresql: '9.4'
before_script:
- - psql -U postgres -c "create user decide password 'decide'"
- - psql -U postgres -c "create database test_decide owner decide"
- - psql -U postgres -c "ALTER USER decide CREATEDB"
+- psql -U postgres -c "create user decide password 'decide'"
+- psql -U postgres -c "create database decidedb owner decide"
+- psql -U postgres -c "ALTER USER decide CREATEDB"
language: python
python:
- - "3.6"
+- '3.6'
install:
- - pip install -r requirements.txt
- - pip install codacy-coverage
+- pip install -r requirements.txt
+- pip install codacy-coverage
script:
- - cd decide
- - coverage run --branch --source=. ./manage.py test --keepdb --with-xunit
- - coverage xml
- - python-codacy-coverage -r coverage.xml
+- cd decide
+- cp travis_local_settings.py local_settings.py
+- coverage run --branch --source=. ./manage.py test mixnet --keepdb
+- coverage xml
+- python-codacy-coverage -r coverage.xml
+deploy:
+ provider: heroku
+ app: picaro-decide
+ strategy: git
+ api_key:
+ secure: SDt7FSvAfUp9J0aA0vhQGKN7ZUfHvbyX/GMr/gVQmlkobFNWu9Wk8f5O2jzxEcHJfP9DKRPwzK5CF2d422bd/toKXEJldIoZL57YQnomlKElqetaLBoETyydhn/oZkkF+aTv/zZF92m2dQGetTlG6Zp1sqFSW9BwXFKBrMIh9XjqFton6AFDZ5DPCy6Gn9303OAkxtWmqG4EGTEzJ2VV9ambCEMs30ZSeAGn4eVbWC92CvSaef32aOBvKcHKSKIrBkN5Y0olYGHRj00s+tr0iBlVfxKyVb1lI6vwuuh85+8w2UGVXp+NCAEy+Dm1RLz3lhDi3hpIyXK1V9JCqw6arbuNgvB0vHvRuldAj8cw3lmpD9kLlmDfstyw5MJd8UAf22rwsI1nUA6Ga/qycAD1kOIOgPwF6oWLW16M+MOGE/+loZIj4NmQTs3wiYAemqwVqEHc9enViEAaSD2M2zUWPI7L2m9gd2iOll6UleKosqz9f8hQFBLKcaTuaoH5qRHAIPeSVIAZA+GmJhbj9lM++yVhZW5aJ0SVzx+RhGAXcgweVNtUIH8F2DU1Es001zNML90k+G47MpnGqfIHuZcxSVtttLS6raoJIHEQSNUzfwXsy4mBjnUzZvy4YeOf/DId8kb/Wrk0+8P/MOoaDVUEePae7rz4jRcHfHj1UXMPpd0=
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..615aafb035
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.pythonPath": "/usr/bin/python3"
+}
\ No newline at end of file
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..56ed957699 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
[![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:
+pabfrasan
+abrgarvil
Plataforma voto electrónico educativa
=====================================
diff --git a/decide/authentication/fixtures/loadTestUsers.json b/decide/authentication/fixtures/loadTestUsers.json
new file mode 100644
index 0000000000..67b647f58c
--- /dev/null
+++ b/decide/authentication/fixtures/loadTestUsers.json
@@ -0,0 +1,364 @@
+[
+
+ {
+ "model": "auth.User",
+ "pk": 21,
+ "fields": {
+ "username": "acc1",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 22,
+ "fields": {
+ "username": "acc2",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 23,
+ "fields": {
+ "username": "acc3",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 24,
+ "fields": {
+ "username": "acc4",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 25,
+ "fields": {
+ "username": "acc5",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 26,
+ "fields": {
+ "username": "acc6",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 27,
+ "fields": {
+ "username": "acc7",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 28,
+ "fields": {
+ "username": "acc8",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 29,
+ "fields": {
+ "username": "acc9",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 30,
+ "fields": {
+ "username": "acc10",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 31,
+ "fields": {
+ "username": "acc11",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 32,
+ "fields": {
+ "username": "acc12",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 33,
+ "fields": {
+ "username": "acc13",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 34,
+ "fields": {
+ "username": "acc14",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 35,
+ "fields": {
+ "username": "acc15",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 36,
+ "fields": {
+ "username": "acc16",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 37,
+ "fields": {
+ "username": "acc17",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 38,
+ "fields": {
+ "username": "acc18",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 39,
+ "fields": {
+ "username": "acc19",
+ "password": "password123"
+ }
+ },
+ {
+ "model": "auth.User",
+ "pk": 40,
+ "fields": {
+ "username": "acc20",
+ "password": "password123"
+ }
+ },
+
+ {
+ "model": "authentication.Profile",
+ "pk": 1,
+ "fields": {
+ "user":"21",
+ "first_name": "acc1",
+ "last_name": "acc1",
+ "email": "acc1@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 2,
+ "fields": {
+ "user": "22",
+ "first_name": "acc2",
+ "last_name": "acc2",
+ "email": "acc2@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 3,
+ "fields": {
+ "user": "23",
+ "first_name": "acc3",
+ "last_name": "acc3",
+ "email": "acc3@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 4,
+ "fields": {
+ "user": "24",
+ "first_name": "acc4",
+ "last_name": "acc4",
+ "email": "acc4@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 5,
+ "fields": {
+ "user": "25",
+ "first_name": "acc5",
+ "last_name": "acc5",
+ "email": "acc5@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 6,
+ "fields": {
+ "user": "26",
+ "first_name": "acc6",
+ "last_name": "acc6",
+ "email": "acc6@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 7,
+ "fields": {
+ "user": "27",
+ "first_name": "acc7",
+ "last_name": "acc7",
+ "email": "acc7@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 8,
+ "fields": {
+ "user": "28",
+ "first_name": "acc8",
+ "last_name": "acc8",
+ "email": "acc8@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 9,
+ "fields": {
+ "user": "29",
+ "first_name": "acc9",
+ "last_name": "acc9",
+ "email": "acc9@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 10,
+ "fields": {
+ "user": "30",
+ "first_name": "acc10",
+ "last_name": "acc10",
+ "email": "acc10@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 11,
+ "fields": {
+ "user": "31",
+ "first_name": "acc11",
+ "last_name": "acc11",
+ "email": "acc11@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 12,
+ "fields": {
+ "user": "32",
+ "first_name": "acc12",
+ "last_name": "acc12",
+ "email": "acc12@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 13,
+ "fields": {
+ "user": "33",
+ "first_name": "acc13",
+ "last_name": "acc13",
+ "email": "acc13@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 14,
+ "fields": {
+ "user": "34",
+ "first_name": "acc14",
+ "last_name": "acc14",
+ "email": "acc14@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 15,
+ "fields": {
+ "user": "35",
+ "first_name": "acc15",
+ "last_name": "acc15",
+ "email": "acc15@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 16,
+ "fields": {
+ "user": "36",
+ "first_name": "acc16",
+ "last_name": "acc16",
+ "email": "acc16@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 17,
+ "fields": {
+ "user": "37",
+ "first_name": "acc17",
+ "last_name": "acc17",
+ "email": "acc17@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 18,
+ "fields": {
+ "user": "38",
+ "first_name": "acc18",
+ "last_name": "acc18",
+ "email": "acc18@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 19,
+ "fields": {
+ "user": "39",
+ "first_name": "acc19",
+ "last_name": "acc19",
+ "email": "acc19@gmail.com"
+ }
+ },
+ {
+ "model": "authentication.Profile",
+ "pk": 20,
+ "fields": {
+ "user": "40",
+ "first_name": "acc20",
+ "last_name": "acc20",
+ "email": "acc20@gmail.com"
+ }
+ }
+ ]
\ No newline at end of file
diff --git a/decide/authentication/forms.py b/decide/authentication/forms.py
new file mode 100644
index 0000000000..10e2a5155b
--- /dev/null
+++ b/decide/authentication/forms.py
@@ -0,0 +1,23 @@
+from django import forms
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.contrib.auth.models import User
+
+#Register Form used in the register process, it adds the fields specified in the "Profile" model (Except for email status which is automatic)
+class RegisterForm(UserCreationForm):
+ first_name = forms.CharField(max_length=30)
+ last_name = forms.CharField(max_length=100)
+ email = forms.EmailField(max_length=254, help_text='Mandatory, must be fulfilled')
+
+ class Meta:
+ model = User
+ fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
+
+class UpdateProfile(forms.ModelForm):
+ username = forms.CharField(required=True)
+ email = forms.EmailField(required=True, max_length=254)
+ first_name = forms.CharField(required=False, max_length=30)
+ last_name = forms.CharField(required=False, max_length=100)
+
+ class Meta:
+ model = User
+ fields = ('username', 'email', 'first_name', 'last_name')
diff --git a/decide/authentication/migrations/0001_initial.py b/decide/authentication/migrations/0001_initial.py
new file mode 100644
index 0000000000..e34bfe0d09
--- /dev/null
+++ b/decide/authentication/migrations/0001_initial.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.0 on 2021-01-03 16:28
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Profile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('first_name', models.CharField(max_length=30)),
+ ('last_name', models.CharField(max_length=100)),
+ ('email', models.EmailField(help_text='Mandatory, must be fulfilled', max_length=254)),
+ ('email_confirmed', models.BooleanField(default=False)),
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/decide/authentication/models.py b/decide/authentication/models.py
index 71a8362390..7520d88971 100644
--- a/decide/authentication/models.py
+++ b/decide/authentication/models.py
@@ -1,3 +1,20 @@
from django.db import models
+from django.contrib.auth.models import User
+from django.dispatch import receiver
+from django.db.models.signals import post_save
-# Create your models here.
+
+#Model for Profile
+#Adds email, first name, last name and check for email verification
+class Profile(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=100)
+ email = models.EmailField(max_length=254, help_text='Mandatory, must be fulfilled')
+ email_confirmed = models.BooleanField(default=False)
+
+@receiver(post_save, sender=User)
+def update_user_profile(sender, instance, created, **kwargs):
+ if created:
+ Profile.objects.create(user=instance)
+ instance.profile.save()
\ No newline at end of file
diff --git a/decide/authentication/templates/account_activation_email.html b/decide/authentication/templates/account_activation_email.html
new file mode 100644
index 0000000000..2300dcab18
--- /dev/null
+++ b/decide/authentication/templates/account_activation_email.html
@@ -0,0 +1,7 @@
+{% autoescape off %}
+ Hi {{ user.username }},
+
+ Please, click below to activate your Decide account:
+
+ http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
+{% endautoescape %}
\ No newline at end of file
diff --git a/decide/authentication/templates/account_activation_invalid.html b/decide/authentication/templates/account_activation_invalid.html
new file mode 100644
index 0000000000..f1371865ee
--- /dev/null
+++ b/decide/authentication/templates/account_activation_invalid.html
@@ -0,0 +1,13 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+
+
+
+
+ The confirmation link was invalid, possibly because it has already been used. Please try again later.
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/account_activation_sent.html b/decide/authentication/templates/account_activation_sent.html
new file mode 100644
index 0000000000..b4f200b7b0
--- /dev/null
+++ b/decide/authentication/templates/account_activation_sent.html
@@ -0,0 +1,5 @@
+Email sent
+Check your email for confirmation
+
+
+
diff --git a/decide/authentication/templates/delete_profile.html b/decide/authentication/templates/delete_profile.html
new file mode 100644
index 0000000000..c2c1a618bc
--- /dev/null
+++ b/decide/authentication/templates/delete_profile.html
@@ -0,0 +1,32 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+Esta acción no es reversible
+Para confirmar, escribar BORRAR a continuación
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/edit_user_profile.html b/decide/authentication/templates/edit_user_profile.html
new file mode 100644
index 0000000000..9604a6576f
--- /dev/null
+++ b/decide/authentication/templates/edit_user_profile.html
@@ -0,0 +1,53 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+
+
Datos de {{username}}
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/home.html b/decide/authentication/templates/home.html
new file mode 100644
index 0000000000..a11e042906
--- /dev/null
+++ b/decide/authentication/templates/home.html
@@ -0,0 +1,23 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+
Bienvenido a Decide-Pícaro
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/login.html b/decide/authentication/templates/login.html
new file mode 100644
index 0000000000..573661e808
--- /dev/null
+++ b/decide/authentication/templates/login.html
@@ -0,0 +1,42 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/register.html b/decide/authentication/templates/register.html
new file mode 100644
index 0000000000..eba00f5416
--- /dev/null
+++ b/decide/authentication/templates/register.html
@@ -0,0 +1,144 @@
+{% extends 'base.html' %}
+
+
+{% block content %}
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/templates/user_profile.html b/decide/authentication/templates/user_profile.html
new file mode 100644
index 0000000000..a30ad3974a
--- /dev/null
+++ b/decide/authentication/templates/user_profile.html
@@ -0,0 +1,46 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+
+
Datos de {{username}}
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/authentication/tests.py b/decide/authentication/tests.py
index dfd09df191..ea7f2adc5f 100644
--- a/decide/authentication/tests.py
+++ b/decide/authentication/tests.py
@@ -1,4 +1,4 @@
-from django.test import TestCase
+from django.test import TestCase, override_settings
from rest_framework.test import APIClient
from rest_framework.test import APITestCase
@@ -7,6 +7,10 @@
from base import mods
+from urllib.parse import urlparse
+
+from .models import Profile
+
class AuthTestCase(APITestCase):
@@ -128,3 +132,276 @@ def test_register(self):
sorted(list(response.json().keys())),
['token', 'user_pk']
)
+class RegisterGuiTests(TestCase):
+#Tests for the Register Form
+
+ #Load up some data for easy access
+ def setUp(self) -> None:
+ self.username = 'epicTestUser'
+ self.email = 'epicTestUser@gmail.com'
+ self.first_name = 'Epic'
+ self.last_name = 'Test User'
+
+ #Testing the Get Access for the Register Form
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_page_access(self):
+ response = self.client.get("/authentication/registergui/")
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Testing the Get Access for the Register Form
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_page_access_logged(self):
+ usertest = User.objects.create_user(username='testuser')
+ usertest.set_password('ganma231')
+ usertest.save()
+ login = self.client.force_login(usertest)
+
+ response = self.client.get("/authentication/registergui/")
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(urlparse(response.url).path, "/")
+ self.client.logout()
+
+ #Testing a post request with an invalid password
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_pass(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : '123',
+ 'password2' : '123'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with no username given
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_no_user(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : '',
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : '123',
+ 'password2' : '123'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Testing a succesful post request
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #When the form is properly fulfilled it will redirect the users towards the page that ask checking their email
+ self.assertEqual(response.status_code, 302)
+
+ #We check if the user has been registered to the database
+ user = User.objects.get(username=self.username)
+ #We use asserts different to the username and check if they correspond to what we have registered with the post request
+ self.assertEqual(user.email, self.email)
+ self.assertEqual(user.first_name, self.first_name)
+
+ #Testing a post request with bad pass confirmation (not the same password1 that password2)
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_pass_confirmation(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma422'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with empty mail
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_no_email(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : '',
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with bad email format
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_email(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : 'email$dominio.net',
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with empty passwords
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_no_passwords(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : 'email$dominio.net',
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : '',
+ 'password2' : ''
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request without password confirmation
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_no_pass_confirmation(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : ''
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with bad first_name characters length
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_firstname_length(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': 'TESTNAMETESTNAMETESTNAMETESTNAMETESTNAME',
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with bad lastname characters length
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_lastname_length(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : self.email,
+ 'first_name': self.first_name,
+ 'last_name' : 'TESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAMETESTNAME',
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+ #Testing a post request with bad email characters length
+ @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
+ def test_register_form_bad_email_length(self):
+ response = self.client.post("/authentication/registergui/", data={
+ 'username' : self.username,
+ 'email' : 'TESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAILTESTEMAIL@gmail.com',
+ 'first_name': self.first_name,
+ 'last_name' : self.last_name,
+ 'password1' : 'ganma421',
+ 'password2' : 'ganma421'
+ })
+ #The form returns the user to the form in case of a failure
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, template_name = 'register.html')
+
+ #Checking that the user hasn't been created
+ self.assertFalse(User.objects.filter(username=self.username).exists())
+
+class ProfileModelTest(TestCase):
+ #Test for the Profile model
+ def setUp(self):
+ super().setUp()
+
+ def tearDown(self):
+ super().tearDown()
+
+
+ def test_create_and_delete_profile(self):
+ #El modelo Profile se crea junto al usuario debido al metodo "update_user_profile" presente en models.py
+ #Es una relación one-to-one, basicamente uno extiende del otro
+ usertest = User(username="testnewuser", password="ganma231", first_name= "TestNew", last_name= "User", email="TestUser@gmail.com")
+ usertest.save()
+
+ dbuser = User.objects.get(username = "testnewuser")
+ #Comprobamos si se ha añadido uno de los campos correctamente
+ self.assertEqual(dbuser.email , usertest.email)
+ #Comprobamos el campo de Email Confirmed que es exclusivo de Profile
+ self.assertEqual(dbuser.profile.email_confirmed, False)
+
+ prof = Profile.objects.get(user = usertest)
+ #Comprobamos si existe un modelo Profile en el usuario
+ self.assertTrue(Profile.objects.filter(user = dbuser).exists())
+ us = User.objects.get(username = "testnewuser")
+ #Borramos el Profile del usuario
+ prof.delete()
+ #Comprobamos si se ha borrado el Profile del usuario correctamente
+ self.assertFalse(Profile.objects.filter(user = dbuser).exists())
+ us.delete()
+
+ def test_profile_tostring(self):
+ usertest = User(username="testnewuser", password="ganma231", first_name= "TestNew", last_name= "User", email="TestUser@gmail.com")
+ usertest.save()
+
+ dbuser = User.objects.get(username = "testnewuser")
+ #Probamos los strings de profile
+ self.assertEqual(str(dbuser.email), "TestUser@gmail.com")
+ self.assertEqual(str(dbuser.first_name), "TestNew")
+ self.assertEqual(str(dbuser.last_name), "User")
+ self.assertEqual(str(dbuser.profile.email_confirmed), "False")
+
+ prof = Profile.objects.get(user = usertest)
+ us = User.objects.get(username = "testnewuser")
+ prof.delete()
+ us.delete()
+
diff --git a/decide/authentication/tokens.py b/decide/authentication/tokens.py
new file mode 100644
index 0000000000..50f927d64c
--- /dev/null
+++ b/decide/authentication/tokens.py
@@ -0,0 +1,13 @@
+from django.contrib.auth.tokens import PasswordResetTokenGenerator
+from django.utils import six
+
+#Secure Token Generation by using the project SECRET KEY
+
+class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
+ def _make_hash_value(self, user, timestamp):
+ return (
+ six.text_type(user.pk) + six.text_type(timestamp) +
+ six.text_type(user.profile.email_confirmed)
+ )
+
+account_activation_token = AccountActivationTokenGenerator()
\ No newline at end of file
diff --git a/decide/authentication/urls.py b/decide/authentication/urls.py
index d05bfed6fc..8f45f166ff 100644
--- a/decide/authentication/urls.py
+++ b/decide/authentication/urls.py
@@ -1,7 +1,10 @@
from django.urls import include, path
from rest_framework.authtoken.views import obtain_auth_token
-from .views import GetUserView, LogoutView, RegisterView
+from django.urls import include, path
+from django.contrib.auth import views as auth_views
+
+from .views import GetUserView, LogoutView, RegisterView, RegisterGUI, LogOutTestView, AccountActivation, ProfileView, UserProfile, EditUserProfile, EditProfileView, DeleteProfile, DeleteProfileView
urlpatterns = [
@@ -9,4 +12,28 @@
path('logout/', LogoutView.as_view()),
path('getuser/', GetUserView.as_view()),
path('register/', RegisterView.as_view()),
+ path('profile/', ProfileView.as_view()),
+ path('editprofile/', EditProfileView.as_view()),
+ path('deleteprofile/', DeleteProfileView.as_view()),
+ #Register URL
+ path('registergui/', RegisterGUI.register, name='account_activation_sent'),
+ #Login URL Built In
+ path('logingui/', auth_views.login,{'template_name': 'login.html'}, name='login2'),
+ #Logout URL
+ path('logoutgui/', LogOutTestView.logout, name='logout'),
+ #Account activation sent view
+ path(r'^account_activation_sent/$', AccountActivation.account_activation_sent, name='account_activation_sent'),
+ #Activation URL
+ path('activate///', AccountActivation.activate, name='activate'),
+ #OAuth Social Login URL
+ path('oauth/', include('social_django.urls', namespace='social')),
+ #User Profile
+ path('profile/', UserProfile.user_profile, name='user_profile'),
+ #Edit Profile
+ path('editprofile/', EditUserProfile.edit_user_profile, name='edit_user_profile'),
+ #Delete user
+ path('deleteprofile/', DeleteProfile.delete, name='delete_profile'),
+
+
+
]
diff --git a/decide/authentication/views.py b/decide/authentication/views.py
index 7825be67c2..4924a42c57 100644
--- a/decide/authentication/views.py
+++ b/decide/authentication/views.py
@@ -13,6 +13,28 @@
from .serializers import UserSerializer
+from django.shortcuts import render, redirect
+from django.utils.encoding import force_text, force_bytes
+from django.utils.http import urlsafe_base64_encode
+from django.contrib.sites.shortcuts import get_current_site
+from django.utils.http import urlsafe_base64_decode
+from django.template.loader import render_to_string
+from django.http import HttpResponse
+from django.http import HttpResponseRedirect
+from django.urls import reverse
+from django.contrib import messages
+
+from .forms import RegisterForm
+from django.contrib.auth import logout, login, authenticate
+from django.contrib import messages
+
+from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
+
+from .tokens import account_activation_token
+
+from .models import Profile
+from .forms import UpdateProfile
+
class GetUserView(APIView):
def post(self, request):
@@ -53,3 +75,179 @@ def post(self, request):
except IntegrityError:
return Response({}, status=HTTP_400_BAD_REQUEST)
return Response({'user_pk': user.pk, 'token': token.key}, HTTP_201_CREATED)
+
+
+#Sign Up View
+class RegisterGUI:
+ def register(request):
+ if request.user.is_authenticated:
+ return redirect('/')
+ if request.method == 'POST':
+ form = RegisterForm(request.POST)
+ if form.is_valid():
+ #Setting the additional fields on the BBDD
+ user = form.save()
+ user.refresh_from_db()
+ user.profile.email = form.cleaned_data.get('email')
+ user.profile.first_name = form.cleaned_data.get('first_name')
+ user.profile.last_name = form.cleaned_data.get('last_name')
+ user.is_active = False
+ user.save()
+ #Email verification handler
+ current_site = get_current_site(request)
+ subject = 'Your Decide account needs activation'
+ message = render_to_string('account_activation_email.html', {
+ 'user': user,
+ 'domain': current_site.domain,
+ 'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
+ 'token': account_activation_token.make_token(user),
+ })
+ user.email_user(subject, message)
+
+ return redirect('account_activation_sent')
+ else:
+ return render(request, 'register.html', {'form': form})
+ else:
+ form = RegisterForm()
+ return render(request, 'register.html', {'form': form})
+
+#Simple logout, could be improved
+
+class LogOutTestView:
+ def logout(request):
+ logout(request)
+ return redirect('/')
+
+#Activation View
+class AccountActivation:
+ #Activation method
+ def activate(request, uidb64, token):
+ try:
+ #Extracting the token
+ uid = force_text(urlsafe_base64_decode(uidb64))
+ user = User.objects.get(pk=uid)
+ except (TypeError, ValueError, OverflowError, User.DoesNotExist):
+ user = None
+ #Token verification and account activation
+ if user is not None and account_activation_token.check_token(user, token):
+ user.is_active = True
+ user.profile.email_confirmed = True
+ user.save()
+ login(request, user, backend = 'base.backends.AuthBackend')
+ return HttpResponse('Thank you for verifying your email, you can login your account now')
+ else:
+ return render(request, 'account_activation_invalid.html')
+
+ #Account activation sent view
+ def account_activation_sent(request):
+ return render(request, 'account_activation_sent.html')
+
+#User Profile
+class UserProfile:
+ def user_profile(request, username):
+ user = User.objects.get(username=username)
+ username = user.username
+ first_name = user.first_name
+ last_name = user.last_name
+ email = user.email
+ context = {
+ "user": user
+ }
+ selfusername = request.user.username
+ if not username == selfusername:
+ return HttpResponse('You are not authorized to see this page.')
+
+ return render(request, 'user_profile.html', context={'username': username,'first_name': first_name, 'last_name': last_name, 'email': email})
+
+class ProfileView(APIView):
+ def post(self, request):
+ key = request.data.get('token', '')
+ tk = get_object_or_404(Token, key=key)
+ if not tk.user.is_superuser:
+ return Response({}, status=HTTP_401_UNAUTHORIZED)
+
+ username = user.profile.username
+ if not username:
+ return Response({}, status=HTTP_400_BAD_REQUEST)
+
+ return Response(request, 'user_profile.html', context)
+
+#User Profile
+class EditUserProfile:
+ def edit_user_profile(request, username):
+ user = User.objects.get(username=username)
+ username = user.username
+ first_name = user.first_name
+ last_name = user.last_name
+ email = user.email
+ context = {
+ "user": user
+ }
+ selfusername = request.user.username
+ if not username == selfusername:
+ return HttpResponse('You are not authorized to see this page.')
+ if request.method == 'POST':
+ form = UpdateProfile(request.POST, instance=request.user)
+ form.actual_user = request.user
+ if form.is_valid():
+ form.save()
+ username = form.actual_user.username
+ first_name = form.actual_user.first_name
+ last_name = form.actual_user.last_name
+ email = form.actual_user.email
+ return render(request, 'user_profile.html', context={'username': username,'first_name': first_name, 'last_name': last_name, 'email': email})
+ elif request.POST.get('username') == '':
+ messages.error(request, 'El nombre de usuario de puede estar vacío.')
+ elif User.objects.get(username=request.POST.get('username')).DoesNotExist:
+ messages.error(request, 'El nombre de usuario ya está en uso.')
+ else:
+ form = UpdateProfile()
+ return render(request, 'edit_user_profile.html', context={'username': username,'first_name': first_name, 'last_name': last_name, 'email': email})
+
+class EditProfileView(APIView):
+ def post(self, request):
+ key = request.data.get('token', '')
+ tk = get_object_or_404(Token, key=key)
+ if not tk.user.is_superuser:
+ return Response({}, status=HTTP_401_UNAUTHORIZED)
+
+ username = user.profile.username
+ if not username:
+ return Response({}, status=HTTP_400_BAD_REQUEST)
+
+ return Response(request, 'user_profile.html', context)
+
+#Creamos los controladores para el borrado de usuario y su conveniente redireción
+class DeleteProfile:
+ def delete(request, username):
+ user = User.objects.get(username=username)
+ username = user.username
+ selfusername = request.user.username
+ if not username == selfusername:
+ return HttpResponse('You are not authorized to see this page.')
+ if request.method == 'POST':
+ form = UpdateProfile(request.POST, instance=request.user)
+ form.actual_user = request.user
+ if request.POST.get('deleteprofile') == 'BORRAR':
+ try:
+ user = User.objects.get(username=username)
+ user.delete()
+ return redirect('../../')
+ except User.DoesNotExist:
+ messages.error(request, "El usuario no existe")
+ elif request.POST.get('deleteprofile') != 'BORRAR':
+ messages.error(request, 'Por favor, escriba la parabra solicitada.')
+ return render(request, 'delete_profile.html')
+
+class DeleteProfileView(APIView):
+ def post(self, request):
+ key = request.data.get('token', '')
+ tk = get_object_or_404(Token, key=key)
+ if not tk.user.is_superuser:
+ return Response({}, status=HTTP_401_UNAUTHORIZED)
+
+ username = user.profile.username
+ if not username:
+ return Response({}, status=HTTP_400_BAD_REQUEST)
+
+ return Response(request, 'logingui.html')
diff --git a/decide/base/templates/base.html b/decide/base/templates/base.html
index fa9cf4aa8a..16008356fd 100644
--- a/decide/base/templates/base.html
+++ b/decide/base/templates/base.html
@@ -1,14 +1,52 @@
+{% load i18n static %}
- {% block title %}Decide!{% endblock %}
+ Decide Pícaro{% block title %}{% endblock %}
+
+
{% block extrahead %}{% endblock %}
{% block content %}
{% endblock %}
-
+
+
{% block extrabody %}{% endblock %}
diff --git a/decide/base/urls.py b/decide/base/urls.py
index 637600f58a..918c70cf9f 100644
--- a/decide/base/urls.py
+++ b/decide/base/urls.py
@@ -1 +1,6 @@
-urlpatterns = []
+from django.conf.urls import url
+from django.urls import include
+
+urlpatterns = [
+ url('i18n/', include('django.conf.urls.i18n')),
+]
diff --git a/decide/booth/templates/booth/booth.html b/decide/booth/templates/booth/booth.html
index 164f547a31..bc8e2449ca 100644
--- a/decide/booth/templates/booth/booth.html
+++ b/decide/booth/templates/booth/booth.html
@@ -16,7 +16,7 @@
Decide
- {% trans "logout" %}
+ {% trans "Logout" %}
diff --git a/decide/decide/settings.py b/decide/decide/settings.py
index 1d22b67324..a00b563070 100644
--- a/decide/decide/settings.py
+++ b/decide/decide/settings.py
@@ -27,6 +27,7 @@
ALLOWED_HOSTS = []
+from django.utils.translation import ugettext_lazy as _
# Application definition
@@ -46,6 +47,12 @@
'gateway',
]
+#Authentication Apps
+
+AUTH_APPS = [
+ 'social_django',
+]
+
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
@@ -54,10 +61,33 @@
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning'
}
+#Authentication Backends
+
AUTHENTICATION_BACKENDS = [
'base.backends.AuthBackend',
+ 'social_core.backends.facebook.FacebookOAuth2',
+ 'social_core.backends.twitter.TwitterOAuth',
]
+#Social Login Keys
+import environ
+#Facebook App Keys
+SOCIAL_AUTH_FACEBOOK_KEY = os.environ.get('SOCIAL_AUTH_FACEBOOK_KEY')
+SOCIAL_AUTH_FACEBOOK_SECRET = os.environ.get('SOCIAL_AUTH_FACEBOOK_SECRET')
+
+#Twitter App keys
+SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY')
+SOCIAL_AUTH_TWITTER_SECRET = os.environ.get('SOCIAL_AUTH_TWITTER_SECRET')
+
+#Email Auth Backend (For testing and debugging purposes, not yet ready for production)
+#If emails doesn't show up in the command prompt when performing an email dependant operation (Such as email verification)
+#Try running the following line is a new command prompt: python3 -m smtpd -n -c DebuggingServer localhost:1025 > mail.log
+#This command will run a dummy smtpd server in the port 1025 of your machine, note that this server may already been enabled
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+#Url to redirect after successfull login
+LOGIN_REDIRECT_URL = '/'
+
MODULES = [
'authentication',
'base',
@@ -70,16 +100,31 @@
'voting',
]
-BASEURL = 'http://localhost:8000'
+BASEURL = 'https://picaro-decide.herokuapp.com'
+
+APIS = {
+ 'authentication': BASEURL ,
+ 'base': BASEURL ,
+ 'booth': BASEURL ,
+ 'census': BASEURL ,
+ 'mixnet': BASEURL ,
+ 'postproc': BASEURL ,
+ 'store': BASEURL ,
+ 'visualizer': BASEURL ,
+ 'voting': BASEURL ,
+ }
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+ 'social_django.middleware.SocialAuthExceptionMiddleware', #Authentication Social Middleware
]
ROOT_URLCONF = 'decide.urls'
@@ -87,7 +132,7 @@
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
+ 'DIRS': ["decide/templates"],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -140,7 +185,14 @@
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
-LANGUAGE_CODE = 'en-us'
+#LANGUAGE_CODE = 'es'
+LANGUAGE_CODE = 'en'
+_ = lambda s: s
+
+LANGUAGES = [
+ ('en', _('English')),
+ ('es', _('Spanish')),
+]
TIME_ZONE = 'UTC'
@@ -150,6 +202,10 @@
USE_TZ = True
+LOCALE_PATHS = [
+ os.path.join(BASE_DIR, 'locale')
+]
+
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
@@ -157,6 +213,21 @@
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = '/static/'
+STATIC_ROOT = ''
+STATIC_TMP = os.path.join(BASE_DIR, 'static')
+
+os.makedirs(STATIC_TMP, exist_ok=True)
+
+STATICFILES_DIRS = (
+ 'static',
+)
+
+MEDIA_URL = '/media/'
+MEDIA_ROOT = ''
+
+MEDIAFILES_DIRS = (
+ 'media',
+)
# number of bits for the key, all auths should use the same number of bits
KEYBITS = 256
@@ -178,5 +249,9 @@
for k, v in config.items():
vars()[k] = v
+#All the apps are loaded from here, it's better to divide the apps of each team in different arrays so we can identify
+#which are the newly added ones, for example: All the needed Apps for Authentication are under the array: AUTH_APPS
+INSTALLED_APPS = INSTALLED_APPS + MODULES + AUTH_APPS
-INSTALLED_APPS = INSTALLED_APPS + MODULES
+import django_heroku
+django_heroku.settings(locals())
diff --git a/decide/decide/templates/home.html b/decide/decide/templates/home.html
new file mode 100644
index 0000000000..a11e042906
--- /dev/null
+++ b/decide/decide/templates/home.html
@@ -0,0 +1,23 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+
Bienvenido a Decide-Pícaro
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/decide/templates/home_auth.html b/decide/decide/templates/home_auth.html
new file mode 100644
index 0000000000..1bf855c57a
--- /dev/null
+++ b/decide/decide/templates/home_auth.html
@@ -0,0 +1,24 @@
+
+
+
+{% extends 'base.html' %}
+{% block content %}
+
+
+
+
Bienvenido a Decide-Pícaro
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/decide/decide/urls.py b/decide/decide/urls.py
index d73f3cdb5d..f2546ac98d 100644
--- a/decide/decide/urls.py
+++ b/decide/decide/urls.py
@@ -17,7 +17,7 @@
from django.contrib import admin
from django.urls import path, include
from rest_framework_swagger.views import get_swagger_view
-
+from .views import Home
schema_view = get_swagger_view(title='Decide API')
@@ -25,6 +25,9 @@
path('admin/', admin.site.urls),
path('doc/', schema_view),
path('gateway/', include('gateway.urls')),
+ #Agregamos las dos rutas 'default' para que el sistema cargue en home
+ path('', Home.home),
+ path('home/', Home.home)
]
for module in settings.MODULES:
diff --git a/decide/decide/views.py b/decide/decide/views.py
new file mode 100644
index 0000000000..7ad5e64fc3
--- /dev/null
+++ b/decide/decide/views.py
@@ -0,0 +1,14 @@
+from django.shortcuts import render
+
+# Clase que renderizara el home
+class Home:
+ def home(request):
+
+ context = {}
+ context['profileUrl'] = "/authentication/profile/" + request.user.username
+ #Si el usuario está autenticado, cargamos un home adecuado para ello
+ if request.user.is_authenticated:
+ return render(request, 'home_auth.html', context)
+ #Si no lo está, se carga el home por defecto con los botones de Login y Registro
+ else:
+ return render(request, 'home.html')
\ No newline at end of file
diff --git a/decide/locale/es/LC_MESSAGES/django.mo b/decide/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..3751a37d28
Binary files /dev/null and b/decide/locale/es/LC_MESSAGES/django.mo differ
diff --git a/decide/locale/es/LC_MESSAGES/django.po b/decide/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..ecf2e922bf
--- /dev/null
+++ b/decide/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,92 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-12-21 11:16+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \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/templates/booth/booth.html:19
+#, fuzzy
+#| msgid "logout"
+msgid "Logout"
+msgstr "Cerrar sesión"
+
+#: booth/templates/booth/booth.html:33
+msgid "Username"
+msgstr "Nombre de usuario"
+
+#: booth/templates/booth/booth.html:41
+msgid "Password"
+msgstr "Contraseña"
+
+#: booth/templates/booth/booth.html:49
+msgid "Login"
+msgstr "Iniciar sesión"
+
+#: booth/templates/booth/booth.html:64
+msgid "Vote"
+msgstr "Voto"
+
+#: booth/templates/booth/booth.html:160 booth/templates/booth/booth.html:170
+#: booth/templates/booth/booth.html:201
+msgid "Error: "
+msgstr "Error: "
+
+#: booth/templates/booth/booth.html:198
+msgid "Conglatulations. Your vote has been sent"
+msgstr "Enhorabuena. Tu voto ha sido enviado"
+
+#: visualizer/templates/visualizer/visualizer.html:21
+msgid "Decide Visualization"
+msgstr "Decide Visualización"
+
+#: visualizer/templates/visualizer/visualizer.html:26
+msgid "Light mode"
+msgstr "Modo claro"
+
+#: visualizer/templates/visualizer/visualizer.html:28
+msgid "Dark mode"
+msgstr "Modo oscuro"
+
+#: visualizer/templates/visualizer/visualizer.html:33
+msgid "Voting not started"
+msgstr "Votación no comenzada"
+
+#: visualizer/templates/visualizer/visualizer.html:34
+msgid "Voting in progress"
+msgstr "Votación en curso"
+
+#: visualizer/templates/visualizer/visualizer.html:36
+msgid "Results"
+msgstr "Resultados"
+
+#: visualizer/templates/visualizer/visualizer.html:41
+msgid "Option"
+msgstr "Opción"
+
+#: visualizer/templates/visualizer/visualizer.html:42
+msgid "Punctuation"
+msgstr "Puntuación"
+
+#: visualizer/templates/visualizer/visualizer.html:43
+#, fuzzy
+#| msgid "Vote"
+msgid "Votes"
+msgstr "Voto"
+
+#: visualizer/templates/visualizer/visualizer.html:62
+msgid "Evolution and Configuration Management"
+msgstr "Evolución y Gestión de la Configuración"
diff --git a/decide/postproc/views.py b/decide/postproc/views.py
index f4c5de1e5f..47871478a6 100644
--- a/decide/postproc/views.py
+++ b/decide/postproc/views.py
@@ -11,7 +11,7 @@ def identity(self, options):
out.append({
**opt,
'postproc': opt['votes'],
- });
+ })
out.sort(key=lambda x: -x['postproc'])
return Response(out)
diff --git a/decide/booth/static/booth/style.css b/decide/static/booth/style.css
similarity index 100%
rename from decide/booth/static/booth/style.css
rename to decide/static/booth/style.css
diff --git a/decide/booth/static/crypto/bigint.js b/decide/static/crypto/bigint.js
similarity index 100%
rename from decide/booth/static/crypto/bigint.js
rename to decide/static/crypto/bigint.js
diff --git a/decide/booth/static/crypto/elgamal.js b/decide/static/crypto/elgamal.js
similarity index 100%
rename from decide/booth/static/crypto/elgamal.js
rename to decide/static/crypto/elgamal.js
diff --git a/decide/booth/static/crypto/jsbn.js b/decide/static/crypto/jsbn.js
similarity index 100%
rename from decide/booth/static/crypto/jsbn.js
rename to decide/static/crypto/jsbn.js
diff --git a/decide/booth/static/crypto/jsbn2.js b/decide/static/crypto/jsbn2.js
similarity index 100%
rename from decide/booth/static/crypto/jsbn2.js
rename to decide/static/crypto/jsbn2.js
diff --git a/decide/booth/static/crypto/sjcl.js b/decide/static/crypto/sjcl.js
similarity index 100%
rename from decide/booth/static/crypto/sjcl.js
rename to decide/static/crypto/sjcl.js
diff --git a/decide/static/css/mfb.css b/decide/static/css/mfb.css
new file mode 100644
index 0000000000..39063306c3
--- /dev/null
+++ b/decide/static/css/mfb.css
@@ -0,0 +1,671 @@
+/**
+ * CONTENTS
+ *
+ * #Introduction........Naming conventions used throughout the code.
+ *
+ * #SETTINGS
+ * Variables............Globally-available variables and config.
+ *
+ * #TOOLS
+ * Mixins...............Useful mixins.
+ *
+ * #GENERIC
+ * Demo styles..........Styles for demo only (consider removing these).
+ *
+ * #BASE
+ * Raw styles...........The very basic component wrapper.
+ * Modifiers............The basic styles dependant on component placement.
+ * Debuggers............The basic styles dependant on component placement.
+ *
+ * #BUTTONS
+ * Base..................Wrapping and constraining every button.
+ * Modifiers.............Styles that depends on state and settings.
+ * Animations............Main animations of the component.
+ * Debuggers.............Styles for development.
+ *
+ * #LABELS
+ * Base..................Wrapping and constraining every label.
+ * Modifiers.............Styles that depends on state and settings.
+ * Debuggers.............Styles for development.
+ *
+ * #DEVELOPMENT
+ * In development........These styles are in development and not yet finalised
+ * Debuggers.............Helper styles and flags for development.
+ */
+/*------------------------------------*\
+ #Introduction
+\*------------------------------------*/
+/**
+ * The code AND the comments use naming conventions to refer to each part of
+ * the UI put in place by this component. If you see that somewhere they are
+ * not followed please consider a Pull Request. The naming conventions are:
+ *
+ * "Component" : the widget itself as a whole. This is the last time it will be
+ * called anything different than "component". So, stay away from
+ * "widget", "button" or anything else when referring to the
+ * Component in general.
+ *
+ * "Main Button" : the button that is always in view. Hovering or clicking on it
+ * will reveal the child buttons.
+ *
+ * "Child buttons" : if you've read the previous point you know what they are.
+ * Did you read the previous point? :)
+ *
+ * "Label(s)" : the tooltip that fades in when hovering over a button.
+
+/*------------------------------------*\
+ #SETTINGS | Variables
+\*------------------------------------*/
+/**
+ * These variables are the default styles that serve as fallback and can be
+ * easily customised at compile time.
+ * Consider overriding them in your own style sheets rather than editing them
+ * here. Refer to the docs for more info.
+ */
+/* COLORS ----------------------------*/
+/* EFFECTS ---------------------------*/
+/* SPEEDS ----------------------------*/
+/* SIZES -----------------------------*/
+/* SPACING ---------------------------*/
+/* OTHER VARIABLES -------------------*/
+/*------------------------------------*\
+ #BASE | Raw styles
+\*------------------------------------*/
+/**
+ * The very core styling of the button.
+ * These styles are shared by every instance of the button.
+ * Styles placed here should NOT care about placement in the screen,
+ * options chosen by the user or state of the button.
+ */
+.mfb-component--tl, .mfb-component--tr, .mfb-component--bl, .mfb-component--br {
+ box-sizing: border-box;
+ margin: 25px;
+ position: fixed;
+ white-space: nowrap;
+ z-index: 30;
+ padding-left: 0;
+ list-style: none; }
+ .mfb-component--tl *, .mfb-component--tr *, .mfb-component--bl *, .mfb-component--br *, .mfb-component--tl *:before, .mfb-component--tr *:before, .mfb-component--bl *:before, .mfb-component--br *:before, .mfb-component--tl *:after, .mfb-component--tr *:after, .mfb-component--bl *:after, .mfb-component--br *:after {
+ box-sizing: inherit; }
+
+/*------------------------------------*\
+ #BASE | Modifiers
+\*------------------------------------*/
+/**
+ * These styles depends on the placement of the button.
+ * Styles can be:
+ * 1. Top-left: modified by the " --tl " suffix.
+ * 2. Top-right: modified by the " --tr " suffix.
+ * 3. Bottom-left: modified by the " --bl " suffix.
+ * 4. Bottom-right: modified by the " --br " suffix.
+ */
+.mfb-component--tl {
+ left: 0;
+ top: 0; }
+
+.mfb-component--tr {
+ right: 0;
+ top: 0; }
+
+.mfb-component--bl {
+ left: 0;
+ bottom: 0; }
+
+.mfb-component--br {
+ right: 0;
+ bottom: 0; }
+
+/*------------------------------------*\
+ #BUTTONS | Base
+\*------------------------------------*/
+.mfb-component__button--main, .mfb-component__button--child {
+ background-color: #E40A5D;
+ display: inline-block;
+ position: relative;
+ border: none;
+ border-radius: 50%;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.14), 0 4px 8px rgba(0, 0, 0, 0.28);
+ cursor: pointer;
+ outline: none;
+ padding: 0;
+ position: relative;
+ -webkit-user-drag: none;
+ color: #f1f1f1; }
+
+/**
+ * This is the unordered list for the list items that contain
+ * the child buttons.
+ *
+ */
+.mfb-component__list {
+ list-style: none;
+ margin: 0;
+ padding: 0; }
+ .mfb-component__list > li {
+ display: block;
+ position: absolute;
+ top: 0;
+ right: 1px;
+ padding: 10px 0;
+ margin: -10px 0; }
+
+/**
+ * These are the basic styles for all the icons inside the main button
+ */
+.mfb-component__icon, .mfb-component__main-icon--active,
+.mfb-component__main-icon--resting, .mfb-component__child-icon {
+ position: absolute;
+ font-size: 18px;
+ text-align: center;
+ line-height: 56px;
+ width: 100%; }
+
+.mfb-component__wrap {
+ padding: 25px;
+ margin: -25px; }
+
+[data-mfb-toggle="hover"]:hover .mfb-component__icon, [data-mfb-toggle="hover"]:hover .mfb-component__main-icon--active,
+[data-mfb-toggle="hover"]:hover .mfb-component__main-icon--resting, [data-mfb-toggle="hover"]:hover .mfb-component__child-icon,
+[data-mfb-state="open"] .mfb-component__icon,
+[data-mfb-state="open"] .mfb-component__main-icon--active,
+[data-mfb-state="open"] .mfb-component__main-icon--resting,
+[data-mfb-state="open"] .mfb-component__child-icon {
+ -webkit-transform: scale(1) rotate(0deg);
+ transform: scale(1) rotate(0deg); }
+
+/*------------------------------------*\
+ #BUTTONS | Modifiers
+\*------------------------------------*/
+.mfb-component__button--main {
+ height: 56px;
+ width: 56px;
+ z-index: 20; }
+
+.mfb-component__button--child {
+ height: 56px;
+ width: 56px; }
+
+.mfb-component__main-icon--active,
+.mfb-component__main-icon--resting {
+ -webkit-transform: scale(1) rotate(360deg);
+ transform: scale(1) rotate(360deg);
+ -webkit-transition: -webkit-transform 150ms cubic-bezier(0.4, 0, 1, 1);
+ transition: transform 150ms cubic-bezier(0.4, 0, 1, 1); }
+
+.mfb-component__child-icon,
+.mfb-component__child-icon {
+ line-height: 56px;
+ font-size: 18px; }
+
+.mfb-component__main-icon--active {
+ opacity: 0; }
+
+[data-mfb-toggle="hover"]:hover .mfb-component__main-icon,
+[data-mfb-state="open"] .mfb-component__main-icon {
+ -webkit-transform: scale(1) rotate(0deg);
+ transform: scale(1) rotate(0deg); }
+[data-mfb-toggle="hover"]:hover .mfb-component__main-icon--resting,
+[data-mfb-state="open"] .mfb-component__main-icon--resting {
+ opacity: 0;
+ position: absolute !important; }
+[data-mfb-toggle="hover"]:hover .mfb-component__main-icon--active,
+[data-mfb-state="open"] .mfb-component__main-icon--active {
+ opacity: 1; }
+
+/*------------------------------------*\
+ #BUTTONS | Animations
+\*------------------------------------*/
+/**
+ * SLIDE IN + FADE
+ * When hovering the main button, the child buttons slide out from beneath
+ * the main button while transitioning from transparent to opaque.
+ *
+ */
+.mfb-component--tl.mfb-slidein .mfb-component__list li,
+.mfb-component--tr.mfb-slidein .mfb-component__list li {
+ opacity: 0;
+ transition: all 0.5s; }
+.mfb-component--tl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li, .mfb-component--tl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li,
+.mfb-component--tr.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li,
+.mfb-component--tr.mfb-slidein[data-mfb-state="open"] .mfb-component__list li {
+ opacity: 1; }
+.mfb-component--tl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--tl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(70px);
+ transform: translateY(70px); }
+.mfb-component--tl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--tl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(140px);
+ transform: translateY(140px); }
+.mfb-component--tl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--tl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(210px);
+ transform: translateY(210px); }
+.mfb-component--tl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--tl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(280px);
+ transform: translateY(280px); }
+
+.mfb-component--bl.mfb-slidein .mfb-component__list li,
+.mfb-component--br.mfb-slidein .mfb-component__list li {
+ opacity: 0;
+ transition: all 0.5s; }
+.mfb-component--bl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li, .mfb-component--bl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li,
+.mfb-component--br.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li,
+.mfb-component--br.mfb-slidein[data-mfb-state="open"] .mfb-component__list li {
+ opacity: 1; }
+.mfb-component--bl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--bl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(-70px);
+ transform: translateY(-70px); }
+.mfb-component--bl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--bl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(-140px);
+ transform: translateY(-140px); }
+.mfb-component--bl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--bl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(-210px);
+ transform: translateY(-210px); }
+.mfb-component--bl.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--bl.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-slidein[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-slidein[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(-280px);
+ transform: translateY(-280px); }
+
+/**
+ * SLIDE IN SPRING
+ * Same as slide-in but with a springy animation.
+ *
+ */
+.mfb-component--tl.mfb-slidein-spring .mfb-component__list li,
+.mfb-component--tr.mfb-slidein-spring .mfb-component__list li {
+ opacity: 0;
+ transition: all 0.5s;
+ transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); }
+.mfb-component--tl.mfb-slidein-spring .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-slidein-spring .mfb-component__list li:nth-child(1) {
+ transition-delay: 0.05s; }
+.mfb-component--tl.mfb-slidein-spring .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-slidein-spring .mfb-component__list li:nth-child(2) {
+ transition-delay: 0.1s; }
+.mfb-component--tl.mfb-slidein-spring .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-slidein-spring .mfb-component__list li:nth-child(3) {
+ transition-delay: 0.15s; }
+.mfb-component--tl.mfb-slidein-spring .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-slidein-spring .mfb-component__list li:nth-child(4) {
+ transition-delay: 0.2s; }
+.mfb-component--tl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li, .mfb-component--tl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li,
+.mfb-component--tr.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li,
+.mfb-component--tr.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li {
+ opacity: 1; }
+.mfb-component--tl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--tl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ transition-delay: 0.05s;
+ -webkit-transform: translateY(70px);
+ transform: translateY(70px); }
+.mfb-component--tl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--tl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ transition-delay: 0.1s;
+ -webkit-transform: translateY(140px);
+ transform: translateY(140px); }
+.mfb-component--tl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--tl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ transition-delay: 0.15s;
+ -webkit-transform: translateY(210px);
+ transform: translateY(210px); }
+.mfb-component--tl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--tl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ transition-delay: 0.2s;
+ -webkit-transform: translateY(280px);
+ transform: translateY(280px); }
+
+.mfb-component--bl.mfb-slidein-spring .mfb-component__list li,
+.mfb-component--br.mfb-slidein-spring .mfb-component__list li {
+ opacity: 0;
+ transition: all 0.5s;
+ transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); }
+.mfb-component--bl.mfb-slidein-spring .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-slidein-spring .mfb-component__list li:nth-child(1) {
+ transition-delay: 0.05s; }
+.mfb-component--bl.mfb-slidein-spring .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-slidein-spring .mfb-component__list li:nth-child(2) {
+ transition-delay: 0.1s; }
+.mfb-component--bl.mfb-slidein-spring .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-slidein-spring .mfb-component__list li:nth-child(3) {
+ transition-delay: 0.15s; }
+.mfb-component--bl.mfb-slidein-spring .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-slidein-spring .mfb-component__list li:nth-child(4) {
+ transition-delay: 0.2s; }
+.mfb-component--bl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li, .mfb-component--bl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li,
+.mfb-component--br.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li,
+.mfb-component--br.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li {
+ opacity: 1; }
+.mfb-component--bl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--bl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ transition-delay: 0.05s;
+ -webkit-transform: translateY(-70px);
+ transform: translateY(-70px); }
+.mfb-component--bl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--bl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ transition-delay: 0.1s;
+ -webkit-transform: translateY(-140px);
+ transform: translateY(-140px); }
+.mfb-component--bl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--bl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ transition-delay: 0.15s;
+ -webkit-transform: translateY(-210px);
+ transform: translateY(-210px); }
+.mfb-component--bl.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--bl.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-slidein-spring[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-slidein-spring[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ transition-delay: 0.2s;
+ -webkit-transform: translateY(-280px);
+ transform: translateY(-280px); }
+
+/**
+ * ZOOM-IN
+ * When hovering the main button, the child buttons grow
+ * from zero to normal size.
+ *
+ */
+.mfb-component--tl.mfb-zoomin .mfb-component__list li,
+.mfb-component--tr.mfb-zoomin .mfb-component__list li {
+ -webkit-transform: scale(0);
+ transform: scale(0); }
+.mfb-component--tl.mfb-zoomin .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-zoomin .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(70px) scale(0);
+ transform: translateY(70px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.15s; }
+.mfb-component--tl.mfb-zoomin .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-zoomin .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(140px) scale(0);
+ transform: translateY(140px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.1s; }
+.mfb-component--tl.mfb-zoomin .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-zoomin .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(210px) scale(0);
+ transform: translateY(210px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.05s; }
+.mfb-component--tl.mfb-zoomin .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-zoomin .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(280px) scale(0);
+ transform: translateY(280px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0s; }
+.mfb-component--tl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--tl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(70px) scale(1);
+ transform: translateY(70px) scale(1);
+ transition-delay: 0.05s; }
+.mfb-component--tl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--tl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(140px) scale(1);
+ transform: translateY(140px) scale(1);
+ transition-delay: 0.1s; }
+.mfb-component--tl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--tl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(210px) scale(1);
+ transform: translateY(210px) scale(1);
+ transition-delay: 0.15s; }
+.mfb-component--tl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--tl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(280px) scale(1);
+ transform: translateY(280px) scale(1);
+ transition-delay: 0.2s; }
+
+.mfb-component--bl.mfb-zoomin .mfb-component__list li,
+.mfb-component--br.mfb-zoomin .mfb-component__list li {
+ -webkit-transform: scale(0);
+ transform: scale(0); }
+.mfb-component--bl.mfb-zoomin .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-zoomin .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(-70px) scale(0);
+ transform: translateY(-70px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.15s; }
+.mfb-component--bl.mfb-zoomin .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-zoomin .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(-140px) scale(0);
+ transform: translateY(-140px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.1s; }
+.mfb-component--bl.mfb-zoomin .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-zoomin .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(-210px) scale(0);
+ transform: translateY(-210px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.05s; }
+.mfb-component--bl.mfb-zoomin .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-zoomin .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(-280px) scale(0);
+ transform: translateY(-280px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0s; }
+.mfb-component--bl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--bl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(-70px) scale(1);
+ transform: translateY(-70px) scale(1);
+ transition-delay: 0.05s; }
+.mfb-component--bl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--bl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(-140px) scale(1);
+ transform: translateY(-140px) scale(1);
+ transition-delay: 0.1s; }
+.mfb-component--bl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--bl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(-210px) scale(1);
+ transform: translateY(-210px) scale(1);
+ transition-delay: 0.15s; }
+.mfb-component--bl.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--bl.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-zoomin[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-zoomin[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(-280px) scale(1);
+ transform: translateY(-280px) scale(1);
+ transition-delay: 0.2s; }
+
+/**
+ * FOUNTAIN
+ * When hovering the main button the child buttons
+ * jump into view from outside the viewport
+ */
+.mfb-component--tl.mfb-fountain .mfb-component__list li,
+.mfb-component--tr.mfb-fountain .mfb-component__list li {
+ -webkit-transform: scale(0);
+ transform: scale(0); }
+.mfb-component--tl.mfb-fountain .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-fountain .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(-70px) scale(0);
+ transform: translateY(-70px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.15s; }
+.mfb-component--tl.mfb-fountain .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-fountain .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(-140px) scale(0);
+ transform: translateY(-140px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.1s; }
+.mfb-component--tl.mfb-fountain .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-fountain .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(-210px) scale(0);
+ transform: translateY(-210px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.05s; }
+.mfb-component--tl.mfb-fountain .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-fountain .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(-280px) scale(0);
+ transform: translateY(-280px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0s; }
+.mfb-component--tl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--tl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--tr.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(70px) scale(1);
+ transform: translateY(70px) scale(1);
+ transition-delay: 0.05s; }
+.mfb-component--tl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--tl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--tr.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(140px) scale(1);
+ transform: translateY(140px) scale(1);
+ transition-delay: 0.1s; }
+.mfb-component--tl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--tl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--tr.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(210px) scale(1);
+ transform: translateY(210px) scale(1);
+ transition-delay: 0.15s; }
+.mfb-component--tl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--tl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--tr.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(280px) scale(1);
+ transform: translateY(280px) scale(1);
+ transition-delay: 0.2s; }
+
+.mfb-component--bl.mfb-fountain .mfb-component__list li,
+.mfb-component--br.mfb-fountain .mfb-component__list li {
+ -webkit-transform: scale(0);
+ transform: scale(0); }
+.mfb-component--bl.mfb-fountain .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-fountain .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(70px) scale(0);
+ transform: translateY(70px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.15s; }
+.mfb-component--bl.mfb-fountain .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-fountain .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(140px) scale(0);
+ transform: translateY(140px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.1s; }
+.mfb-component--bl.mfb-fountain .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-fountain .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(210px) scale(0);
+ transform: translateY(210px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0.05s; }
+.mfb-component--bl.mfb-fountain .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-fountain .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(280px) scale(0);
+ transform: translateY(280px) scale(0);
+ transition: all 0.5s;
+ transition-delay: 0s; }
+.mfb-component--bl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1), .mfb-component--bl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(1),
+.mfb-component--br.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(1) {
+ -webkit-transform: translateY(-70px) scale(1);
+ transform: translateY(-70px) scale(1);
+ transition-delay: 0.05s; }
+.mfb-component--bl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2), .mfb-component--bl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(2),
+.mfb-component--br.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(2) {
+ -webkit-transform: translateY(-140px) scale(1);
+ transform: translateY(-140px) scale(1);
+ transition-delay: 0.1s; }
+.mfb-component--bl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3), .mfb-component--bl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(3),
+.mfb-component--br.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(3) {
+ -webkit-transform: translateY(-210px) scale(1);
+ transform: translateY(-210px) scale(1);
+ transition-delay: 0.15s; }
+.mfb-component--bl.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4), .mfb-component--bl.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-fountain[data-mfb-toggle="hover"]:hover .mfb-component__list li:nth-child(4),
+.mfb-component--br.mfb-fountain[data-mfb-state="open"] .mfb-component__list li:nth-child(4) {
+ -webkit-transform: translateY(-280px) scale(1);
+ transform: translateY(-280px) scale(1);
+ transition-delay: 0.2s; }
+
+/*------------------------------------*\
+ #LABELS | base
+\*------------------------------------*/
+/**
+ * These are the labels associated to each button,
+ * exposed only when hovering the related button.
+ * They are called labels but are in fact data-attributes of
+ * each button (an anchor tag).
+ */
+[data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ opacity: 0;
+ transition: all 0.5s;
+ background: rgba(0, 0, 0, 0.4);
+ padding: 4px 10px;
+ border-radius: 3px;
+ color: rgba(255, 255, 255, 0.8);
+ font-size: 14px;
+ font-weight: normal;
+ pointer-events: none;
+ line-height: normal;
+ position: absolute;
+ top: 50%;
+ margin-top: -11px;
+ transition: all 0.5s; }
+
+[data-mfb-toggle="hover"] [data-mfb-label]:hover:after,
+[data-mfb-state="open"] [data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ opacity: 1;
+ transition: all 0.3s; }
+
+/*------------------------------------*\
+ #LABELS | Modifiers
+\*------------------------------------*/
+.mfb-component--br [data-mfb-label]:after, .mfb-component--tr [data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ right: 70px; }
+
+.mfb-component--br .mfb-component__list [data-mfb-label]:after, .mfb-component--tr .mfb-component__list [data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ right: 70px; }
+
+.mfb-component--tl [data-mfb-label]:after, .mfb-component--bl [data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ left: 70px; }
+
+.mfb-component--tl .mfb-component__list [data-mfb-label]:after, .mfb-component--bl .mfb-component__list [data-mfb-label]:after {
+ content: attr(data-mfb-label);
+ left: 70px; }
+
+/*------------------------------------*\
+ #DEVELOPMENT | In development
+\*------------------------------------*/
+/**
+ * This part is where unfinished code should stay.
+ * When a feature is ready(sh) move these styles to their proper place.
+ */
+/*------------------------------------*\
+ #DEVELOPMENT | Debuggers
+\*------------------------------------*/
+/**
+ * These are mainly helpers for development. They do not have to end up
+ * in production but it's handy to keep them when developing.
+ */
+/**
+ * Apply this class to the html tag when developing the slide-in button
+ */
+
+/*# sourceMappingURL=mfb.css.map */
diff --git a/decide/static/favicon/picaro.png b/decide/static/favicon/picaro.png
new file mode 100644
index 0000000000..19ac79cb3b
Binary files /dev/null and b/decide/static/favicon/picaro.png differ
diff --git a/decide/static/images/england.jpg b/decide/static/images/england.jpg
new file mode 100644
index 0000000000..0aff82d230
Binary files /dev/null and b/decide/static/images/england.jpg differ
diff --git a/decide/static/images/spain.jpg b/decide/static/images/spain.jpg
new file mode 100644
index 0000000000..2170439f63
Binary files /dev/null and b/decide/static/images/spain.jpg differ
diff --git a/decide/static/js/functions.js b/decide/static/js/functions.js
new file mode 100644
index 0000000000..aa6a988d32
--- /dev/null
+++ b/decide/static/js/functions.js
@@ -0,0 +1,120 @@
+$(document).ready(function() {
+
+ $('#id_password1').keyup(function() {
+ var password = $('#id_password1').val();
+ var confirmpassword = $('#id_password2').val();
+
+ if (checkStrength(password) == false) {
+ $('#reg_submit').attr('disabled', true);
+ }
+ });
+
+
+ $('#id_submit').hover(function() {
+ if ($('#id_submit').prop('disabled')) {
+ $('#id_submit').popover({
+ html: true,
+ trigger: 'hover',
+ placement: 'below',
+ offset: 20,
+ content: function() {
+ return $('#sign-up-popover').html();
+ }
+ });
+ }
+ });
+
+
+ function checkStrength(password) {
+ var strength = 0;
+
+ //If password contains both lower and uppercase characters, increase strength value.
+ if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {
+ strength += 1;
+ $('.low-upper-case').addClass('text-success');
+ $('.low-upper-case i').removeClass('fa-check').addClass('fa-check');
+ $('#reg-password-quality').addClass('hide');
+
+
+ } else {
+ $('.low-upper-case').removeClass('text-success');
+ $('.low-upper-case i').addClass('fa-check').removeClass('fa-check');
+ $('#reg-password-quality').removeClass('hide');
+ }
+
+ //If it has numbers and characters, increase strength value.
+ if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {
+ strength += 1;
+ $('.one-number').addClass('text-success');
+ $('.one-number i').removeClass('fa-check').addClass('fa-check');
+ $('#reg-password-quality').addClass('hide');
+
+ } else {
+ $('.one-number').removeClass('text-success');
+ $('.one-number i').addClass('fa-check').removeClass('fa-check');
+ $('#reg-password-quality').removeClass('hide');
+ }
+
+ //If it has one special character, increase strength value.
+ if (password.match(/([!,%,&,@,#,$,^,*,?,_,~])/)) {
+ strength += 1;
+ $('.one-special-char').addClass('text-success');
+ $('.one-special-char i').removeClass('fa-check').addClass('fa-check');
+ $('#reg-password-quality').addClass('hide');
+
+ } else {
+ $('.one-special-char').removeClass('text-success');
+ $('.one-special-char i').addClass('fa-check').removeClass('fa-check');
+ $('#reg-password-quality').removeClass('hide');
+ }
+
+ if (password.length > 7) {
+ strength += 1;
+ $('.eight-character').addClass('text-success');
+ $('.eight-character i').removeClass('fa-check').addClass('fa-check');
+ $('#reg-password-quality').removeClass('hide');
+
+ } else {
+ $('.eight-character').removeClass('text-success');
+ $('.eight-character i').addClass('fa-check').removeClass('fa-check');
+ $('#reg-password-quality').removeClass('hide');
+ }
+ // ------------------------------------------------------------------------------
+ // If value is less than 2
+ if (strength < 2) {
+ $('#reg-password-quality-result').removeClass()
+ $('#password-strength').addClass('progress-bar-danger');
+
+ $('#reg-password-quality-result').addClass('text-danger').text('zayıf');
+ $('#password-strength').css('width', '10%');
+ } else if (strength == 2) {
+ $('#reg-password-quality-result').addClass('good');
+ $('#password-strength').removeClass('progress-bar-danger');
+ $('#password-strength').addClass('progress-bar-warning');
+ $('#reg-password-quality-result').addClass('text-warning').text('idare eder')
+ $('#password-strength').css('width', '60%');
+ return 'Week'
+ } else if (strength == 4) {
+ $('#reg-password-quality-result').removeClass()
+ $('#reg-password-quality-result').addClass('strong');
+ $('#password-strength').removeClass('progress-bar-warning');
+ $('#password-strength').addClass('progress-bar-success');
+ $('#reg-password-quality-result').addClass('text-success').text('güçlü');
+ $('#password-strength').css('width', '100%');
+
+ return 'Strong'
+ }
+
+ }
+
+
+ });
+
+ function togglePassword() {
+
+ var element = document.getElementById('id_password1');
+ element.type = (element.type == 'password' ? 'text' : 'password');
+ var element2 = document.getElementById('id_password2');
+ element2.type = (element.type == 'password' ? 'text' : 'password');
+
+ };
\ No newline at end of file
diff --git a/decide/static/js/mfb.js b/decide/static/js/mfb.js
new file mode 100644
index 0000000000..ea6ce731fe
--- /dev/null
+++ b/decide/static/js/mfb.js
@@ -0,0 +1,98 @@
+/**
+ * Material floating button
+ * By: Nobita
+ * Repo and docs: https://github.com/nobitagit/material-floating-button
+ *
+ * License: MIT
+ */
+
+ // build script hook - don't remove
+ ;(function ( window, document, undefined ) {
+
+
+ 'use strict';
+
+ /**
+ * Some defaults
+ */
+ var clickOpt = 'click',
+ hoverOpt = 'hover',
+ toggleMethod = 'data-mfb-toggle',
+ menuState = 'data-mfb-state',
+ isOpen = 'open',
+ isClosed = 'closed',
+ mainButtonClass = 'mfb-component__button--main';
+
+ /**
+ * Internal references
+ */
+ var elemsToClick,
+ elemsToHover,
+ mainButton,
+ target,
+ currentState;
+
+ /**
+ * For every menu we need to get the main button and attach the appropriate evt.
+ */
+ function attachEvt( elems, evt ){
+ for( var i = 0, len = elems.length; i < len; i++ ){
+ mainButton = elems[i].querySelector('.' + mainButtonClass);
+ mainButton.addEventListener( evt , toggleButton, false);
+ }
+ }
+
+ /**
+ * Remove the hover option, set a click toggle and a default,
+ * initial state of 'closed' to menu that's been targeted.
+ */
+ function replaceAttrs( elems ){
+ for( var i = 0, len = elems.length; i < len; i++ ){
+ elems[i].setAttribute( toggleMethod, clickOpt );
+ elems[i].setAttribute( menuState, isClosed );
+ }
+ }
+
+ function getElemsByToggleMethod( selector ){
+ return document.querySelectorAll('[' + toggleMethod + '="' + selector + '"]');
+ }
+
+ /**
+ * The open/close action is performed by toggling an attribute
+ * on the menu main element.
+ *
+ * First, check if the target is the menu itself. If it's a child
+ * keep walking up the tree until we found the main element
+ * where we can toggle the state.
+ */
+ function toggleButton( evt ){
+
+ target = evt.target;
+ while ( target && !target.getAttribute( toggleMethod ) ){
+ target = target.parentNode;
+ if(!target) { return; }
+ }
+
+ currentState = target.getAttribute( menuState ) === isOpen ? isClosed : isOpen;
+
+ target.setAttribute(menuState, currentState);
+
+ }
+
+ /**
+ * On touch enabled devices we assume that no hover state is possible.
+ * So, we get the menu with hover action configured and we set it up
+ * in order to make it usable with tap/click.
+ **/
+ if ( window.Modernizr && Modernizr.touch ){
+ elemsToHover = getElemsByToggleMethod( hoverOpt );
+ replaceAttrs( elemsToHover );
+ }
+
+ elemsToClick = getElemsByToggleMethod( clickOpt );
+
+ attachEvt( elemsToClick, 'click' );
+
+// build script hook - don't remove
+})( window, document );
+
diff --git a/decide/travis_local_settings.py b/decide/travis_local_settings.py
new file mode 100644
index 0000000000..d4162dae92
--- /dev/null
+++ b/decide/travis_local_settings.py
@@ -0,0 +1,41 @@
+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://localhost:8000',
+ 'base': 'http://localhost:8000',
+ 'booth': 'http://localhost:8000',
+ 'census': 'http://localhost:8000',
+ 'mixnet': 'http://localhost:8000',
+ 'postproc': 'http://localhost:8000',
+ 'store': 'http://localhost:8000',
+ 'visualizer': 'http://localhost:8000',
+ 'voting': 'http://localhost:8000',
+}
+
+BASEURL = 'http://localhost:8000'
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql',
+ 'NAME': 'postgres',
+ 'USER': 'postgres',
+ 'HOST': 'localhost',
+ 'PORT': '5432',
+ }
+}
+
+# number of bits for the key, all auths should use the same number of bits
+KEYBITS = 256
diff --git a/decide/visualizer/templates/visualizer/visualizer.html b/decide/visualizer/templates/visualizer/visualizer.html
index 0faed6bac3..2f8676cdfc 100644
--- a/decide/visualizer/templates/visualizer/visualizer.html
+++ b/decide/visualizer/templates/visualizer/visualizer.html
@@ -1,48 +1,70 @@
{% extends "base.html" %}
{% load i18n static %}
+{% block title %} - Visualización{% endblock %}
{% block extrahead %}
-
-
-
+
+
+
+
+
+
+
+
{% endblock %}
{% block content %}
-
+
+
- Decide
+ {% trans "Decide Visualization" %}
+
+
+
+ {% trans "Light mode" %}
+ {% trans "Dark mode" %}
+
-
[[ voting.id ]] - [[ voting.name ]]
-
-
Votación no comenzada
-
Votación en curso
+
{% trans "Voting not started" %}
+
{% trans "Voting in progress" %}
-
Resultados:
-
-
-
-
- Opción
- Puntuación
- Votos
-
-
-
-
- [[opt.option]]
- [[opt.postproc]]
- [[opt.votes]]
-
-
-
+
{% trans "Results" %}
+
+
+
+
+ {% trans "Option" %}
+ {% trans "Punctuation" %}
+ {% trans "Votes" %}
+
+
+
+
+ [[opt.option]]
+ [[opt.postproc]]
+ [[opt.votes]]
+
+
+
+
-
+
+
+
+
+
+ {% trans "Evolution and Configuration Management" %} · Decide
+ Decide Pícaro - Visualización © 2021
+
+
+
+
{% endblock %}
@@ -51,7 +73,7 @@ Resultados:
-
+
-