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 %} +
+ +
+ + +{% 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 %} +
+ +
+{% 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 %} + + + + + +
+
+
+
+
+
Register
+
+ + + + + +
+
+
+ {% csrf_token %} +
+ + +
+ + +
+ + + + +
+ + +
+ +
+
+ +
+
+
+
+ + +
+ +
+
    +
  • + Your password can't be too similar to your other personal information. +
  • +
  • + Your password must contain at least 8 characters. +
  • +
  • + Your password can't be a commonly used password. +
  • +
  • + Your password can't be entirely numeric. +
  • +
+
+
+ +
+ + +
+ + + + +
+ + +
+ +
+ +
+
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+ +
+
+ + + +
+ Are you user? Login +
+
+
+
+
+
+{% 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 %} +
+ +
+ + +{% 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 %} - + +
+ {% csrf_token %} + + +
{% 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 %} +
+ +
+{% 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 %} +
+ +
+{% 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" %} + + + + + +
-

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

- -

Votación no comenzada

-

Votación en curso

+

{% trans "Voting not started" %}

+

{% trans "Voting in progress" %}

-

Resultados:

- - - - - - - - - - - - - - - - -
OpciónPuntuaciónVotos
[[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:

- + - + + + + + + + + {% endblock %} diff --git a/decide/visualizer/urls.py b/decide/visualizer/urls.py index 4baef5f2b9..96f5d0b843 100644 --- a/decide/visualizer/urls.py +++ b/decide/visualizer/urls.py @@ -1,7 +1,9 @@ -from django.urls import path +from django.urls import path, include from .views import VisualizerView - +from django.conf.urls import url urlpatterns = [ path('/', VisualizerView.as_view()), + url('i18n/', include('django.conf.urls.i18n')), ] + diff --git a/docker/Dockerfile b/docker/Dockerfile index 032eed28e2..3bb98458b2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -from python:alpine +from python:3.7-alpine RUN apk add --no-cache git postgresql-dev gcc libc-dev RUN apk add --no-cache gcc g++ make libffi-dev python3-dev build-base @@ -10,7 +10,7 @@ RUN pip install ipython WORKDIR /app -RUN git clone https://github.com/wadobo/decide.git . +RUN git clone https://github.com/pabfrasan/decide . RUN pip install -r requirements.txt WORKDIR /app/decide diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 095583eb02..4689c5ff98 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -4,11 +4,13 @@ services: db: restart: always container_name: decide_db - image: postgres:alpine + image: postgres:10.15-alpine volumes: - db:/var/lib/postgresql/data networks: - decide + environment: + - POSTGRES_PASSWORD=postgres web: restart: always container_name: decide_web diff --git a/docker/docker-settings.py b/docker/docker-settings.py index 01e643d936..49efb3d78f 100644 --- a/docker/docker-settings.py +++ b/docker/docker-settings.py @@ -5,6 +5,7 @@ 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'postgres', 'USER': 'postgres', + 'PASSWORD' : 'postgres', 'HOST': 'db', 'PORT': 5432, } diff --git a/loadtest/accounts.json b/loadtest/accounts.json new file mode 100644 index 0000000000..89a0da8b27 --- /dev/null +++ b/loadtest/accounts.json @@ -0,0 +1,22 @@ +{ + "acc1": "password123", + "acc2": "password123", + "acc3": "password123", + "acc4": "password123", + "acc5": "password123", + "acc6": "password123", + "acc7": "password123", + "acc8": "password123", + "acc9": "password123", + "acc10": "password123", + "acc11": "password123", + "acc12": "password123", + "acc13": "password123", + "acc14": "password123", + "acc15": "password123", + "acc16": "password123", + "acc17": "password123", + "acc18": "password123", + "acc19": "password123", + "acc20": "password123" +} \ No newline at end of file diff --git a/loadtest/locustfile.py b/loadtest/locustfile.py index 85cae8ee99..c79306f0bf 100644 --- a/loadtest/locustfile.py +++ b/loadtest/locustfile.py @@ -62,14 +62,50 @@ def voting(self): def on_quit(self): self.voter = None +#Definimos las actividades de las que se compone el test de carga. +#Estas son cargar los usuarios del .json, iniciar sesión con esos datos, visitar el perfil +#y cerrar la sesión +class DefAuth(SequentialTaskSet): + + def on_start(self): + with open('accounts.json') as f: + self.users = json.loads(f.read()) + self.u = choice(list(self.users.items())) + + @task + def login(self): + username, pwd = self.u + self.token = self.client.post("/authentication/login/", { + "username": username, + "password": pwd, + }).json() + + @task + def profile(self): + username, pwd = self.u + self.token = self.client.get("/authentication/profile/" + username) + + @task + def logout(self): + username, pwd = self.u + self.token = self.client.get("/authentication/logoutgui/") + + def on_quit(self): + self.voter = None + class Visualizer(HttpUser): host = HOST tasks = [DefVisualizer] wait_time = between(3,5) - class Voters(HttpUser): host = HOST tasks = [DefVoters] wait_time= between(3,5) + +#Definimos la ejecución del test con sus atributos +class Auth(HttpUser): + host = HOST + tasks = [DefAuth] + wait_time= between(3,5) diff --git a/requirements.txt b/requirements.txt index d5860a1eb4..a5cee9557a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,12 @@ djangorestframework==3.7.7 django-cors-headers==2.1.0 requests==2.18.4 django-filter==1.1.0 -psycopg2==2.7.4 +psycopg2-binary==2.8.4 django-rest-swagger==2.2.0 coverage==4.5.2 django-nose==1.4.6 jsonnet==0.12.1 +django-heroku +gunicorn +social-auth-app-django +python-environ \ No newline at end of file diff --git a/vagrant/.python.yml.swp b/vagrant/.python.yml.swp new file mode 100644 index 0000000000..9b9c508ee8 Binary files /dev/null and b/vagrant/.python.yml.swp differ diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile index 87c50e7375..54ed0beb3a 100644 --- a/vagrant/Vagrantfile +++ b/vagrant/Vagrantfile @@ -58,8 +58,8 @@ Vagrant.configure("2") do |config| # end config.vm.provider "virtualbox" do |v| - v.memory = 512 - v.cpus = 1 + v.memory = 1024 + v.cpus = 2 end # View the documentation for the provider you are using for more