Browse Source

Merge pull request 'b_sprint3' (#10) from b_sprint3 into master

master
crunch 5 months ago
parent
commit
de1dcfa72f
100 changed files with 6697 additions and 1280 deletions
  1. +2
    -1
      .drone.yml
  2. +7
    -3
      .gitignore
  3. +20
    -0
      .vscode/launch.json
  4. +3
    -0
      .vscode/settings.json
  5. +26
    -5
      Consulta_project/Consulta_project/dev_settings.py
  6. +39
    -7
      Consulta_project/Consulta_project/settings.py
  7. +22
    -3
      Consulta_project/Consulta_project/urls.py
  8. +1
    -0
      Consulta_project/landing/static/css/styles.min.css
  9. BIN
      Consulta_project/landing/static/img/mountain_bg.jpg
  10. +1
    -0
      Consulta_project/landing/static/js/bundle.js
  11. +133
    -0
      Consulta_project/landing/static/js/stats.js
  12. +223
    -0
      Consulta_project/landing/static/js/stats_compiled.js
  13. +7
    -8
      Consulta_project/landing/templates/landing/index.html
  14. +58
    -54
      Consulta_project/landing/templates/landing/index_base.html
  15. +0
    -6
      Consulta_project/landing/urls.py
  16. +11
    -5
      Consulta_project/landing/views.py
  17. +236
    -2
      Consulta_project/polls/admin.py
  18. +98
    -0
      Consulta_project/polls/fixtures/user_boilerplate.json
  19. +48
    -0
      Consulta_project/polls/migrations/0001_initial.py
  20. +21
    -0
      Consulta_project/polls/migrations/0002_answer_user.py
  21. +0
    -0
      Consulta_project/polls/migrations/__init__.py
  22. +53
    -8
      Consulta_project/polls/models.py
  23. +18
    -0
      Consulta_project/polls/serializers.py
  24. +26
    -0
      Consulta_project/polls/templates/admin/guardian/model/change_form.html
  25. +9
    -4
      Consulta_project/polls/templates/admin/polls/includes/fieldset.html
  26. +2
    -2
      Consulta_project/polls/templates/admin/polls/poll/change_form_object_tools.html
  27. +54
    -0
      Consulta_project/polls/templates/admin/polls/poll/submit_line.html
  28. +17
    -0
      Consulta_project/polls/templates/admin/polls/question/change_form.html
  29. +8
    -0
      Consulta_project/polls/templates/admin/polls/question/change_form_object_tools.html
  30. +55
    -0
      Consulta_project/polls/templates/admin/polls/question/submit_line.html
  31. +12
    -0
      Consulta_project/polls/templates/django_registration/registration_complete.html
  32. +71
    -0
      Consulta_project/polls/templates/django_registration/registration_form.html
  33. +50
    -0
      Consulta_project/polls/templates/polls/login.html
  34. +52
    -23
      Consulta_project/polls/templates/polls/poll.html
  35. +34
    -27
      Consulta_project/polls/templates/polls/polls.html
  36. +0
    -8
      Consulta_project/polls/templates/polls/polls_base.html
  37. +43
    -29
      Consulta_project/polls/templates/polls/question.html
  38. +18
    -11
      Consulta_project/polls/templates/polls/share.html
  39. +61
    -15
      Consulta_project/polls/templates/polls/stats.html
  40. +9
    -7
      Consulta_project/polls/urls.py
  41. +55
    -11
      Consulta_project/polls/views.py
  42. +0
    -23
      Consulta_project/user.json
  43. +0
    -12
      admin_templates/404.html
  44. +0
    -17
      admin_templates/500.html
  45. +0
    -23
      admin_templates/actions.html
  46. +0
    -18
      admin_templates/app_index.html
  47. +0
    -10
      admin_templates/auth/user/add_form.html
  48. +0
    -60
      admin_templates/auth/user/change_password.html
  49. +0
    -93
      admin_templates/base.html
  50. +0
    -9
      admin_templates/base_site.html
  51. +0
    -81
      admin_templates/change_form.html
  52. +0
    -82
      admin_templates/change_list.html
  53. +0
    -12
      admin_templates/change_list_object_tools.html
  54. +0
    -38
      admin_templates/change_list_results.html
  55. +0
    -16
      admin_templates/date_hierarchy.html
  56. +0
    -52
      admin_templates/delete_confirmation.html
  57. +0
    -55
      admin_templates/delete_selected_confirmation.html
  58. +0
    -25
      admin_templates/edit_inline/stacked.html
  59. +0
    -75
      admin_templates/edit_inline/tabular.html
  60. +0
    -8
      admin_templates/filter.html
  61. +0
    -7
      admin_templates/includes/object_delete_summary.html
  62. +0
    -86
      admin_templates/index.html
  63. +0
    -13
      admin_templates/invalid_setup.html
  64. +0
    -66
      admin_templates/login.html
  65. +0
    -42
      admin_templates/object_history.html
  66. +0
    -12
      admin_templates/pagination.html
  67. +0
    -11
      admin_templates/popup_response.html
  68. +0
    -6
      admin_templates/prepopulated_fields_js.html
  69. +0
    -16
      admin_templates/search_form.html
  70. +0
    -14
      admin_templates/submit_line.html
  71. +0
    -6
      admin_templates/widgets/clearable_file_input.html
  72. +0
    -1
      admin_templates/widgets/foreign_key_raw_id.html
  73. +0
    -1
      admin_templates/widgets/many_to_many_raw_id.html
  74. +0
    -1
      admin_templates/widgets/radio.html
  75. +0
    -31
      admin_templates/widgets/related_widget_wrapper.html
  76. +0
    -4
      admin_templates/widgets/split_datetime.html
  77. +0
    -1
      admin_templates/widgets/url.html
  78. +15
    -0
      babel.config.json
  79. +0
    -1
      create_superuser.sh
  80. +0
    -4
      migrate_db.sh
  81. +4886
    -0
      package-lock.json
  82. +27
    -0
      package.json
  83. +0
    -2
      requirements.txt
  84. +0
    -2
      set_prod-env.sh
  85. +0
    -3
      start_dev-env.sh
  86. BIN
      utils/Consulta.bsdesign
  87. +2
    -0
      utils/compile_&_bundle.sh
  88. +1
    -0
      utils/create_superuser.sh
  89. +14
    -0
      utils/django_factory.py
  90. +2
    -2
      utils/install_django_env.sh
  91. +2
    -0
      utils/load_fixtures.sh
  92. +4
    -0
      utils/migrate_db.sh
  93. +3
    -0
      utils/open_shell.sh
  94. +11
    -0
      utils/permission-checking.py
  95. +1
    -0
      utils/pw.txt
  96. +6
    -0
      utils/requirements.txt
  97. +40
    -0
      utils/script.js
  98. +2
    -0
      utils/set_prod-env.sh
  99. +3
    -0
      utils/start_dev-env.sh
  100. +75
    -0
      utils/test.html

+ 2
- 1
.drone.yml View File

@@ -7,9 +7,10 @@ steps:
- name: test
image: python:3.8
commands:
- pip install -r requirements.txt
- pip install -r utils/requirements.txt
- cd Consulta_project
- if ! [ -d static ]; then mkdir static; fi
- export DJANGO_SETTINGS_MODULE=Consulta_project.dev_settings
- python manage.py makemigrations
- python manage.py migrate --run-syncdb
- python manage.py collectstatic --noinput

+ 7
- 3
.gitignore View File

@@ -65,6 +65,11 @@ instance/
# Scrapy stuff:
.scrapy

# Bootstrap Studio Export Folder
Bootstrap Studio Export

node_modules/

# Sphinx documentation
docs/_build/

@@ -113,7 +118,6 @@ dmypy.json

# Pyre type checker
.pyre/
.vscode

migrations
DjangoEnv
DjangoEnv
/admin

+ 20
- 0
.vscode/launch.json View File

@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"env": {"DJANGO_SETTINGS_MODULE": "Consulta_project.dev_settings"},
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/Consulta_project/manage.py",
"args": [
"runserver",
"--noreload"
],
"django": true
}
]
}

+ 3
- 0
.vscode/settings.json View File

@@ -0,0 +1,3 @@
{
"python.pythonPath": "DjangoEnv/bin/python"
}

+ 26
- 5
Consulta_project/Consulta_project/dev_settings.py View File

@@ -30,17 +30,28 @@ ALLOWED_HOSTS = []
# Application definition

INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls.apps.PollsConfig',
'landing.apps.LandingConfig',
'qr_code',
'guardian',
'rest_framework',
'corsheaders',
]

REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
@@ -54,9 +65,17 @@ CACHES = {

QR_CODE_CACHE_ALIAS = 'qr-code'

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend',
)

GUARDIAN_RAISE_403 = True

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -64,6 +83,10 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

X_FRAME_OPTIONS = 'ALLOW'

CORS_ORIGIN_ALLOW_ALL = True

ROOT_URLCONF = 'Consulta_project.urls'

TEMPLATES = [
@@ -120,7 +143,7 @@ AUTH_PASSWORD_VALIDATORS = [

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Berlin'

USE_I18N = True

@@ -133,6 +156,4 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, '/static/'),
]
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

+ 39
- 7
Consulta_project/Consulta_project/settings.py View File

@@ -20,17 +20,17 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'byu*aa*1pg1i3_fm_+*(7xo0c9y8g@&ijf51ctx0_ok1bvyi)0'
SECRET_KEY = os.environ['SECRET_KEY']

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*']

# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
@@ -39,8 +39,19 @@ INSTALLED_APPS = [
'polls.apps.PollsConfig',
'landing.apps.LandingConfig',
'qr_code',
'guardian',
'rest_framework',
'corsheaders',
]

REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
@@ -54,9 +65,15 @@ CACHES = {

QR_CODE_CACHE_ALIAS = 'qr-code'

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend',
)

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@@ -64,6 +81,8 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True

ROOT_URLCONF = 'Consulta_project.urls'

TEMPLATES = [
@@ -88,14 +107,22 @@ WSGI_APPLICATION = 'Consulta_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DB_PASSWORD = os.environ['DB_PASSWORD']

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'consulta_db',
'USER': 'django',
'PASSWORD': DB_PASSWORD,
'HOST': '192.18.0.2',
'PORT': '5432',
}
}


X_FRAME_OPTIONS = 'ALLOW'

# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

@@ -120,7 +147,7 @@ AUTH_PASSWORD_VALIDATORS = [

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Berlin'

USE_I18N = True

@@ -131,6 +158,11 @@ USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]



STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')


+ 22
- 3
Consulta_project/Consulta_project/urls.py View File

@@ -14,12 +14,31 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.urls import path, include, reverse
from django.conf.urls import url
from polls.admin import polls_admin
from django.conf.urls.static import static
from django.conf import settings
from polls.views import PollViewSet, QuestionViewSet, AnswerViewSet
from rest_framework import routers
from landing import views
from django_registration.backends.one_step.views import RegistrationView

# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'polls', PollViewSet)
router.register(r'questions', QuestionViewSet)
router.register(r'answers', AnswerViewSet)

urlpatterns = [
path('', include('landing.urls')),
path('', views.index, name='index'),
path('api/', include(router.urls)),
path('accounts/', include('django_registration.backends.one_step.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('admin/', admin.site.urls),
path('editor/', polls_admin.urls),
path('logout/', views.log_out, name='logout'),
path('editor/', polls_admin.urls, name='editor'),
path('polls/', include('polls.urls')),
path('api-auth/', include('rest_framework.urls')),
path('accounts/register/', RegistrationView.as_view(success_url='index'), name='register'),
]

+ 1
- 0
Consulta_project/landing/static/css/styles.min.css View File

@@ -0,0 +1 @@
.footer-dark h3,.navbar-light .navbar-brand{font-weight:700}.footer-dark{padding:50px 0;color:#f0f9ff;background-color:#282d32}.footer-dark h3{margin-top:0;margin-bottom:12px;font-size:16px}.footer-dark .item.text{margin-bottom:36px}@media (max-width:767px){.footer-dark .item:not(.social){text-align:center;padding-bottom:20px}.footer-dark .item.text{margin-bottom:0}}.footer-dark .item.text p{opacity:.6;margin-bottom:0}.header-dark{background:url(/static/img/mountain_bg.jpg) #444;background-size:cover;padding-bottom:80px}@media (min-width:768px){.header-dark{padding-bottom:120px}}.login-one-form{transform:translate(-50%,-50%);top:50%;max-width:350px;width:350px;left:50%;background-color:#fff;padding:30px;border-radius:5px;position:absolute;box-shadow:3px 3px 4px rgba(0,0,0,.2)}.login-one{background-color:rgba(0,0,0,.1);min-height:100%;height:1000px;background-size:cover}.form-group{text-align:center}#input{margin:10px}#button{width:100%;margin:10px}#heading{padding-bottom:10px}.errorlist{color:aqua;}

BIN
Consulta_project/landing/static/img/mountain_bg.jpg View File

Before After
Width: 1900  |  Height: 1000  |  Size: 72KB

+ 1
- 0
Consulta_project/landing/static/js/bundle.js
File diff suppressed because it is too large
View File


+ 133
- 0
Consulta_project/landing/static/js/stats.js View File

@@ -0,0 +1,133 @@
import "core-js/stable";
import "regenerator-runtime/runtime";

// Selects all Divs that have Questions with answers
const question_selectors = document.querySelectorAll("[data-id]");

// API URLs
const question_url = "/api/questions/";
const answer_url = "/api/answers/";

// Pulls QuestionIDs from all Question Divs with answers
function getQuestionIds() {
var question_ids = [];
question_selectors.forEach(data_id => {
question_ids.push(data_id.dataset.id);
});
return question_ids;
}

// Pulls Question Object from Backend API via given Question ID - this is a middleware function for getting Answer IDs from the returned Question Object
async function getQuestion(q_id) {
return await fetch(question_url + q_id).then(response => response.json());
}

async function createChartForQuestion(q_id) {
const labels = [];
const series = [];

const answers = (await getQuestion(q_id)).answers;

let index = 0;

await answers.forEach(async answerId => {
const answer = await (await fetch(answer_url + answerId)).json();

/* console.log("Labels: " + answer.answer_string + " Series: " + answer.count); */
labels.push(answer.answer_string);
series.push(answer.count);

if (index == answers.length - 1) {
let base_target_id = "chart-"
let bar_target_id = base_target_id + q_id + "-bar";
let pie_target_id = base_target_id + q_id + "-pie"
createBarChart(series, labels, bar_target_id);
createPieChart(series, labels, pie_target_id)
} else {
index++;
}
});
}

function createBarChart(series, labels, target_id) {
/* console.log(series + " " + labels + " " + target_id); */
var ctx = document.getElementById(target_id).getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'bar',

// The data for our dataset
data: {
labels: labels,
datasets: [{
label: 'Number of votes',
backgroundColor: 'rgb(0,188,140)',
borderColor: '#282d32',
data: series
}]
},

// Configuration options go here
options: {
events: null,
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
}

function createPieChart(series, labels, target_id) {
/* console.log(series + " " + labels + " " + target_id); */
var colorArray = [];

function dynamicColor() {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
return "rgb(" + r + "," + g + "," + b + ")";
}

for (const d in series) {
colorArray.push(dynamicColor())
}

var ctx = document.getElementById(target_id).getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'pie',

// The data for our dataset
data: {
labels: labels,
datasets: [{
backgroundColor: colorArray,
borderColor: '#282d32',
data: series
}]
},

// Configuration options go here
options: {
events: null,
tooltips: {
mode: 'point'
}
}
});
}

// This function loops through all Divs that have Answers to their question and creates a chart
async function createCharts() {
getQuestionIds().forEach(q_id => {
createChartForQuestion(q_id)
});
}

$(document).ready(function() {
createCharts()
});

+ 223
- 0
Consulta_project/landing/static/js/stats_compiled.js View File

@@ -0,0 +1,223 @@
"use strict";

require("core-js/stable");

require("regenerator-runtime/runtime");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

// Selects all Divs that have Questions with answers
var question_selectors = document.querySelectorAll("[data-id]"); // API URLs

var question_url = "/api/questions/";
var answer_url = "/api/answers/"; // Pulls QuestionIDs from all Question Divs with answers

function getQuestionIds() {
var question_ids = [];
question_selectors.forEach(function (data_id) {
question_ids.push(data_id.dataset.id);
});
return question_ids;
} // Pulls Question Object from Backend API via given Question ID - this is a middleware function for getting Answer IDs from the returned Question Object


function getQuestion(_x) {
return _getQuestion.apply(this, arguments);
}

function _getQuestion() {
_getQuestion = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(q_id) {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return fetch(question_url + q_id).then(function (response) {
return response.json();
});

case 2:
return _context.abrupt("return", _context.sent);

case 3:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _getQuestion.apply(this, arguments);
}

function createChartForQuestion(_x2) {
return _createChartForQuestion.apply(this, arguments);
}

function _createChartForQuestion() {
_createChartForQuestion = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee3(q_id) {
var labels, series, answers, index;
return regeneratorRuntime.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
labels = [];
series = [];
_context3.next = 4;
return getQuestion(q_id);

case 4:
answers = _context3.sent.answers;
index = 0;
_context3.next = 8;
return answers.forEach( /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(answerId) {
var answer, base_target_id, bar_target_id, pie_target_id;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return fetch(answer_url + answerId);

case 2:
_context2.next = 4;
return _context2.sent.json();

case 4:
answer = _context2.sent;

/* console.log("Labels: " + answer.answer_string + " Series: " + answer.count); */
labels.push(answer.answer_string);
series.push(answer.count);

if (index == answers.length - 1) {
base_target_id = "chart-";
bar_target_id = base_target_id + q_id + "-bar";
pie_target_id = base_target_id + q_id + "-pie";
createBarChart(series, labels, bar_target_id);
createPieChart(series, labels, pie_target_id);
} else {
index++;
}

case 8:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));

return function (_x3) {
return _ref.apply(this, arguments);
};
}());

case 8:
case "end":
return _context3.stop();
}
}
}, _callee3);
}));
return _createChartForQuestion.apply(this, arguments);
}

function createBarChart(series, labels, target_id) {
/* console.log(series + " " + labels + " " + target_id); */
var ctx = document.getElementById(target_id).getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'bar',
// The data for our dataset
data: {
labels: labels,
datasets: [{
label: 'Number of votes',
backgroundColor: 'rgb(0,188,140)',
borderColor: '#282d32',
data: series
}]
},
// Configuration options go here
options: {
events: null,
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
}

function createPieChart(series, labels, target_id) {
/* console.log(series + " " + labels + " " + target_id); */
var colorArray = [];

function dynamicColor() {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
return "rgb(" + r + "," + g + "," + b + ")";
}

for (var d in series) {
colorArray.push(dynamicColor());
}

var ctx = document.getElementById(target_id).getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'pie',
// The data for our dataset
data: {
labels: labels,
datasets: [{
backgroundColor: colorArray,
borderColor: '#282d32',
data: series
}]
},
// Configuration options go here
options: {
events: null,
tooltips: {
mode: 'point'
}
}
});
} // This function loops through all Divs that have Answers to their question and creates a chart


function createCharts() {
return _createCharts.apply(this, arguments);
}

function _createCharts() {
_createCharts = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee4() {
return regeneratorRuntime.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
getQuestionIds().forEach(function (q_id) {
createChartForQuestion(q_id);
});

case 1:
case "end":
return _context4.stop();
}
}
}, _callee4);
}));
return _createCharts.apply(this, arguments);
}

$(document).ready(function () {
createCharts();
});

+ 7
- 8
Consulta_project/landing/templates/landing/index.html View File

@@ -1,10 +1,9 @@
{% extends "landing/index_base.html" %}
{% load static %}

{% block content %}
<div class="container">
<div class="center">
<h2>polling made easy.</h2>
</div>
{% extends "landing/index_base.html" %} {% load static %} {% block content %}
<div
class="d-flex d-print-flex d-sm-flex d-md-flex d-lg-flex d-xl-flex justify-content-center align-items-center justify-content-sm-center align-items-sm-center justify-content-md-center align-items-md-center justify-content-xl-center align-items-xl-center header-dark"
style="height: 75vh;margin-top: 30px;">
<h1 class="text-center" style="font-family: Inter;margin-bottom: 0;">
Free Polling Software
</h1>
</div>
{% endblock content %}

+ 58
- 54
Consulta_project/landing/templates/landing/index_base.html View File

@@ -1,66 +1,70 @@
<!doctype html>
<html lang="en">
{% load static %}
<!DOCTYPE html>
<html style="font-family: 'Inter' , sans-serif;">

<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% load static %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>{% block title %}Consulta - polling made easy.{% endblock title %}</title>
{% block misc_scripts %}
{% endblock misc_scripts %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,700,400italic">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Bitter:400,700">
<link href="https://fonts.googleapis.com/css?family=Inter&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{% static "css/styles.min.css" %}">
</head>

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{% static "css/landing.css" %}" />
<body style="font-family: Inter;">
<nav class="navbar navbar-light navbar-expand-md" style="background-color: rgb(40,45,50);margin-bottom: -30px;">
<div class="container-fluid"><a class="navbar-brand" href="{% url 'index' %}">Consulta</a><button
data-toggle="collapse" class="navbar-toggler" data-target="#navcol-1"><span class="sr-only">Toggle
navigation</span><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navcol-1">
<ul class="nav navbar-nav">
<li class="nav-item" role="presentation"><a class="nav-link" href="{% url 'polls' %}">Polls</a></li>

<title>{% block title %}Consulta - polling made easy.{% endblock title %}</title>
</head>
{% if request.user.is_staff %}
<li class="nav-item" role="presentation"><a class="nav-link" href="/editor">Poll Editor</a></li>
{% else %}
<li class="nav-item" role="presentation"><a class="nav-link" data-toggle="tooltip" style="color:dimgray"
data-bs-tooltip="" data-placement="bottom"
title="You must be part of staff to create &amp; edit polls.">Poll Editor</a></li>
{% endif %}

{% if request.user.is_anonymous %}
<li class="nav-item" role="presentation"><a class="nav-link" href="/editor">Login</a></li>
<li class="nav-item" role="presentation"><a class="nav-link"
href="{% url 'register' %}">Register</a></li>
{% else %}
<li class="nav-item" role="presentation"><a class="nav-link" href="{% url 'logout' %}">Logout</a></li>
<li class="nav-item" role="presentation"><a class="nav-link">Logged in as "{{ request.user.username }}"</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>

<body>
{% block content %}
{% endblock content %}

<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="{% url 'index' %}">Consulta</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText"
aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav mr-auto">
{% block polls_nav %}
<li class="nav-item">
<a class="nav-link" href="{% url 'polls' %}">Polls</span></a>
</li>
{% endblock polls_nav %}
{% block poll_editor_nav %}
<li class="nav-item">
<a class="nav-link" href="/editor">Poll Editor</a>
</li>
{% endblock poll_editor_nav %}
{% block legal_nav %}
<li class="nav-item">
<a class="nav-link disabled" href="#">Legal</a>
</li>
{% endblock legal_nav %}
</ul>
<span class="navbar-text">
Made with ❤️ & Mate in Essen.
</span>
<div class="footer-dark" style="padding-bottom: 20px;">
<footer>
<div class="container">
<div class="row justify-content-center">
<div class="col text-center item text">
<h3>Consulta</h3>
<p>Polling you can count on.</p>
</div>
</div>
</div>
</footer>
</div>
</nav>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.bundle.min.js"></script>

{% block content %}
{% endblock content %}
{% block bottom_scripts %}{% endblock %}

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>

</html>

+ 0
- 6
Consulta_project/landing/urls.py View File

@@ -1,6 +0,0 @@
from django.urls import path
from . import views

urlpatterns = [
path('', views.index, name='index'),
]

+ 11
- 5
Consulta_project/landing/views.py View File

@@ -1,9 +1,15 @@
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth import logout
from django.urls import reverse
from django.http import HttpResponseForbidden, HttpResponseRedirect

# Create your views here.
def index(request):
context = {
'article_text': 'hello there',
}
return render(request, 'landing/index.html', context)
return render(request, 'landing/index.html')

def log_out(request):
if request.user.is_anonymous:
return HttpResponseForbidden("You are not logged in, so you can not log out!")
else:
logout(request)
return HttpResponseRedirect(reverse('index'))

+ 236
- 2
Consulta_project/polls/admin.py View File

@@ -1,11 +1,25 @@
from django.contrib.admin import AdminSite
from .models import Poll, Question, Answer
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User, Group
from django.utils.safestring import mark_safe
from django.urls import reverse
from django.utils.encoding import force_text
from guardian.admin import GuardedModelAdmin
from guardian.shortcuts import get_objects_for_user, assign_perm, get_user_perms, get_perms_for_model
from django.views.decorators.csrf import csrf_protect
from django.http.response import HttpResponseForbidden
from django.db import transaction, router
from django.shortcuts import get_object_or_404

def lock_poll(modeladmin, request, queryset):
queryset.update(unlocked=False)
lock_poll.short_description = "Lock selected Polls"

def unlock_poll(modeladmin, request, queryset):
queryset.update(unlocked=True)
unlock_poll.short_description = "Unlock selected Polls"

class QuestionInline(admin.StackedInline):
model = Question
@@ -24,23 +38,243 @@ class AnswerInline(admin.TabularInline):
readonly_fields = ["count"]


class PollAdmin(admin.ModelAdmin):
class PollAdmin(GuardedModelAdmin):
inlines = [
QuestionInline,
]

actions = [lock_poll, unlock_poll]

prepopulated_fields = {"slug": ("name",)}

exclude = ('user',)

user_can_access_owned_objects_only = True

# add_form_template = "polls/polls.html"

def get_actions(self, request):
actions = super().get_actions(request)
return actions

def get_queryset(self, request):
"""
Returns a QuerySet of Polls to be shown to the user.

The QuerySet is selected by querying for permitted and owned polls
"""
permitted_polls = get_objects_for_user(user=request.user, perms="polls.view_poll", accept_global_perms = False)
owned_polls = Poll.objects.filter(user=request.user)

if (request.user.is_superuser):
return Poll.objects.all()

return (permitted_polls | owned_polls).distinct()

def add_view(self, request, form_url='', extra_context=None):
extra_context = {}
extra_context['new'] = True
return self.changeform_view(request, None, form_url, extra_context)

def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = {}
poll = Poll.objects.get(pk=object_id)
for perm in get_user_perms(request.user, Poll.objects.get(pk=object_id)):
extra_context[str (perm)] = True

extra_context['owner'] = bool(request.user == poll.get_owner())
extra_context['unlocked'] = poll.is_unlocked
extra_context['poll'] = poll
extra_context['begin_url'] = request.build_absolute_uri(reverse('poll-begin', args=(poll.slug, )))
extra_context['stats_url'] = request.build_absolute_uri(reverse('poll-stats', args=(poll.slug, )))
extra_context['end_url'] = request.build_absolute_uri(reverse('poll-end', args=(poll.slug, )))
extra_context['superuser'] = request.user.is_superuser

return self.changeform_view(request, object_id, form_url, extra_context)

def delete_view(self, request, object_id, extra_context=None):
if(request.user.is_superuser):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._delete_view(request, object_id, extra_context)
if not("delete_poll" in get_user_perms(request.user, Poll.objects.get(pk = object_id))):
return HttpResponseForbidden("You are not allowed to delete this Poll!")
with transaction.atomic(using=router.db_for_write(self.model)):
return self._delete_view(request, object_id, extra_context)

def obj_perms_manage_view(self, request, object_pk):
poll = Poll.objects.get(pk=object_pk)

if not(request.user.is_superuser or poll.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Poll to change its permissions.")
return super().obj_perms_manage_view(request, object_pk)

def obj_perms_manage_user_view(self, request, object_pk, user_id):
poll = Poll.objects.get(pk=object_pk)

if not(request.user.is_superuser or poll.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Poll to change its permissions.")
return super().obj_perms_manage_user_view(request, object_pk, user_id)

def obj_perms_manage_group_view(self, request, object_pk, group_id):
poll = Poll.objects.get(pk=object_pk)

if not(request.user.is_superuser or poll.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Poll to change its permissions.")
return super().obj_perms_manage_group_view(request, object_pk, group_id)

def save_model(self, request, obj, form, change):
if not(obj.user):
obj.user = request.user

super().save_model(request, obj, form, change)

if not(request.user.is_superuser):
assign_perm("delete_poll", obj.user, obj)
assign_perm("add_poll", obj.user, obj)
assign_perm("change_poll", obj.user, obj)
assign_perm("view_poll", obj.user, obj)

return

super().save_model(request, obj, form, change)
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if not (instance.user):
instance.user = request.user
instance.save()
if not(request.user.is_superuser):
assign_perm("delete_question", obj.user, obj)
assign_perm("add_question", obj.user, obj)
assign_perm("change_question", obj.user, obj)
assign_perm("view_question", obj.user, obj)
continue
instance.save()
formset.save_m2m()

class QuestionAdmin(admin.ModelAdmin):

class QuestionAdmin(GuardedModelAdmin):
inlines = [
AnswerInline,
]

exclude = ('user',)

user_can_access_owned_objects_only = True

""" def formfield_for_foreignkey(self, db_field, request, **kwargs):
if request.user.is_superuser or bool(request.user == ):
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super().formfield_for_foreignkey(db_field, request, **kwargs) """

def add_view(self, request, form_url='', extra_context=None):
extra_context = {}
extra_context['new'] = True
return self.changeform_view(request, None, form_url, extra_context)

def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = {}
question = Question.objects.get(pk=object_id)
for perm in get_user_perms(request.user, Question.objects.get(pk=object_id)):
extra_context[str (perm)] = True

extra_context['owner'] = bool(request.user == question.get_owner())
extra_context['superuser'] = request.user.is_superuser

return self.changeform_view(request, object_id, form_url, extra_context)

def delete_view(self, request, object_id, extra_context=None):
if(request.user.is_superuser):
with transaction.atomic(using=router.db_for_write(self.model)):
return self._delete_view(request, object_id, extra_context)
if not("delete_question" in get_user_perms(request.user, Question.objects.get(pk = object_id))):
return HttpResponseForbidden("You are not allowed to delete this Question!")
with transaction.atomic(using=router.db_for_write(self.model)):
return self._delete_view(request, object_id, extra_context)

def obj_perms_manage_view(self, request, object_pk):
question = Question.objects.get(pk=object_pk)

if not(request.user.is_superuser or question.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Question to change its permissions.")
return super().obj_perms_manage_view(request, object_pk)

def obj_perms_manage_user_view(self, request, object_pk, user_id):
question = Question.objects.get(pk=object_pk)

if not(request.user.is_superuser or question.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Question to change its permissions.")
return super().obj_perms_manage_user_view(request, object_pk, user_id)

def obj_perms_manage_group_view(self, request, object_pk, group_id):
question = Question.objects.get(pk=object_pk)

if not(request.user.is_superuser or question.get_owner() == request.user):
return HttpResponseForbidden("You need to own this Question to change its permissions.")
return super().obj_perms_manage_group_view(request, object_pk, group_id)

def save_model(self, request, obj, form, change):
if not(obj.user):
obj.user = request.user

super().save_model(request, obj, form, change)

if not(request.user.is_superuser):
assign_perm("delete_question", obj.user, obj)
assign_perm("add_question", obj.user, obj)
assign_perm("change_question", obj.user, obj)
assign_perm("view_question", obj.user, obj)

return

super().save_model(request, obj, form, change)

def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if not (instance.user):
instance.user = request.user
instance.save()
if not(request.user.is_superuser):
assign_perm("delete_answer", obj.user, obj)
assign_perm("add_answer", obj.user, obj)
assign_perm("change_answer", obj.user, obj)
assign_perm("view_answer", obj.user, obj)
continue
instance.save()
formset.save_m2m()

def formfield_for_foreignkey(self, db_field, request, **kwargs):

if db_field.name == "poll":
if(request.user.is_superuser):
kwargs["queryset"] = Poll.objects.all()
return super().formfield_for_foreignkey(db_field, request, **kwargs)

permitted_polls = get_objects_for_user(user=request.user, perms="polls.change_question", accept_global_perms = False)
owned_polls = Question.objects.filter(user=request.user)

kwargs["queryset"] = (permitted_polls | owned_polls).distinct()

return super().formfield_for_foreignkey(db_field, request, **kwargs)

class PollAdminSite(AdminSite):
site_header = 'Consulta Administration'
site_title = 'Consulta Administration'
index_title = 'Poll Admin'
login_template = 'polls/login.html'

admin.site.register(User)
admin.site.register(Group)

polls_admin = PollAdminSite(name='poll_admin')
polls_admin.register(Poll, PollAdmin)

+ 98
- 0
Consulta_project/polls/fixtures/user_boilerplate.json View File

@@ -0,0 +1,98 @@
[
{
"model": "auth.group",
"fields": {
"name": "Poll Creator",
"permissions": [
["add_groupobjectpermission", "guardian", "groupobjectpermission"],
["change_groupobjectpermission", "guardian", "groupobjectpermission"],
["delete_groupobjectpermission", "guardian", "groupobjectpermission"],
["view_groupobjectpermission", "guardian", "groupobjectpermission"],
["add_userobjectpermission", "guardian", "userobjectpermission"],
["change_userobjectpermission", "guardian", "userobjectpermission"],
["delete_userobjectpermission", "guardian", "userobjectpermission"],
["view_userobjectpermission", "guardian", "userobjectpermission"],
["add_answer", "polls", "answer"],
["change_answer", "polls", "answer"],
["delete_answer", "polls", "answer"],
["view_answer", "polls", "answer"],
["add_poll", "polls", "poll"],
["change_poll", "polls", "poll"],
["delete_poll", "polls", "poll"],
["view_poll", "polls", "poll"],
["add_question", "polls", "question"],
["change_question", "polls", "question"],
["delete_question", "polls", "question"],
["view_question", "polls", "question"]
]
}
},
{
"model": "auth.user",
"fields": {
"password": "!bOa8cATMzNU8EkMcj7KtlmpNQwUm2fA7chsTwib8",
"last_login": null,
"is_superuser": false,
"username": "AnonymousUser",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": false,
"is_active": true,
"date_joined": "2020-02-18T17:35:53.419Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "auth.user",
"fields": {
"password": "pbkdf2_sha256$150000$fIHjCOJT7Z2d$zyMpA6x1t5gV109pKLO7guP0lwaLCpZJ9eEGjrKqWZ4=",
"last_login": "2020-02-18T17:48:22Z",
"is_superuser": true,
"username": "root",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2020-02-18T17:36:15Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "auth.user",
"fields": {
"password": "pbkdf2_sha256$150000$Cam5prCtlzGQ$SPahbeNT9pYZnMGApSFmBwpYho5MIEx6l/0pnrrREz0=",
"last_login": "2020-02-18T17:46:17Z",
"is_superuser": false,
"username": "alice",
"first_name": "",
"last_name": "",
"email": "alice@consulta.org",
"is_staff": true,
"is_active": true,
"date_joined": "2020-02-18T17:46:17Z",
"groups": [["Poll Creator"]],
"user_permissions": []
}
},
{
"model": "auth.user",
"fields": {
"password": "pbkdf2_sha256$150000$5CXjsLpiZtzA$Ytq6L1G1kZD3rHjJnBUzTGJcXhvKDhiUyPtCcTtmDdY=",
"last_login": "2020-02-18T17:46:25Z",
"is_superuser": false,
"username": "bob",
"first_name": "",
"last_name": "",
"email": "bob@consulta.org",
"is_staff": true,
"is_active": true,
"date_joined": "2020-02-18T17:46:25Z",
"groups": [["Poll Creator"]],
"user_permissions": []
}
}
]

+ 48
- 0
Consulta_project/polls/migrations/0001_initial.py View File

@@ -0,0 +1,48 @@
# Generated by Django 2.2.9 on 2020-01-26 20:29

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='Poll',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=70, unique=True)),
('slug', models.SlugField(max_length=70, unique=True)),
('semester', models.CharField(blank=True, default=None, max_length=70, null=True)),
('topic', models.CharField(blank=True, default=None, max_length=70, null=True)),
('lecture', models.CharField(blank=True, default=None, max_length=70, null=True)),
('unlocked', models.BooleanField(default=False)),
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_string', models.CharField(max_length=100)),
('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Poll')),
('user', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Answer',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('answer_string', models.CharField(max_length=70)),
('count', models.IntegerField(default=0)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question')),
],
),
]

+ 21
- 0
Consulta_project/polls/migrations/0002_answer_user.py View File

@@ -0,0 +1,21 @@
# Generated by Django 2.2.10 on 2020-02-20 18:18

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('polls', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='answer',
name='user',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

+ 0
- 0
Consulta_project/polls/migrations/__init__.py View File


+ 53
- 8
Consulta_project/polls/models.py View File

@@ -1,13 +1,24 @@
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import User
from django.core.serializers import json
import json

# Create your models here.


class Poll(models.Model):
name = models.CharField(max_length=70)
name = models.CharField(max_length=70, unique=True)
slug = models.SlugField(max_length=70, unique=True)
semester = models.CharField(
max_length=70, default=None, null=True, blank=True)
topic = models.CharField(
max_length=70, default=None, null=True, blank=True)
lecture = models.CharField(
max_length=70, default=None, null=True, blank=True)
unlocked = models.BooleanField(default=False)
user = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True, default=None)

def __str__(self):
return 'Poll: ' + self.name
@@ -18,26 +29,58 @@ class Poll(models.Model):
def get_questions(self):
return Question.objects.filter(poll=self)

def get_owner(self):
return self.user

def questions(self):
questions = []
for question in self.get_questions():
questions.append(question.id)
return questions

def is_unlocked(self):
if(self.unlocked == True):
return True
else:
return False
return self.unlocked


class Question(models.Model):
question_string = models.CharField(max_length=100)
user = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True, default=None)

poll = models.ForeignKey(
'Poll',
on_delete=models.CASCADE,
)

def __str__(self):
return 'Question: ' + self.question_string
def answers(self):
answers = []
for answer in self.get_answers():
answers.append(answer.id)
return answers

def answer_strings(self):
answer_strings = []

for answer in self.get_answers():
answer_strings.append(answer.answer_string)
return answer_strings

def answers_have_votes(self):
hasVotes = False
for answer in self.get_answers():
if (answer.count > 0):
hasVotes = True

return hasVotes

def get_owner(self):
return self.user

def get_absolute_url(self):
return "/polls/poll/{}/question/{}/".format(self.poll.slug, self.pk)
return reverse('question-detail', args=[self.poll.slug, self.pk, ])

def __str__(self):
return self.question_string

def get_answers(self):
return Answer.objects.filter(question=self)
@@ -50,6 +93,8 @@ class Answer(models.Model):
'Question',
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User, on_delete=models.CASCADE, null=True, blank=True, default=None)

def __str__(self):
return 'Answer: ' + self.answer_string

+ 18
- 0
Consulta_project/polls/serializers.py View File

@@ -0,0 +1,18 @@
from rest_framework import serializers
from .models import Poll, Question, Answer

# Serializers define the API representation.
class PollSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Poll
fields = ['id', 'name', 'semester', 'topic', 'lecture', 'url', 'questions']

class AnswerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Answer
fields = ['id', 'answer_string', 'count', 'question',]

class QuestionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Question
fields = ['id', 'question_string', 'poll', 'answers']

+ 26
- 0
Consulta_project/polls/templates/admin/guardian/model/change_form.html View File

@@ -0,0 +1,26 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}

{% block field_sets %}
{% if change_poll or superuser %}
{% if poll %}
<p style="font-weight: bolder;">Enter these links into the PowerPoint Liveslides Add-In:</p>
<p style="color:darkcyan; font-weight: bold;"><small style="color: black; font-weight: lighter;">Begin URL:</small> {{ begin_url }}</p>
<p style="color:darkcyan; font-weight: bold;"><small style="color: black; font-weight: lighter;">Stats URL:</small> {{ stats_url }}</p>
<p style="color:darkcyan; font-weight: bold;"><small style="color: black; font-weight: lighter;">End URL:</small> {{ end_url }}</p>
{% endif %}
{% endif %}
{% for fieldset in adminform %}
{% include "admin/polls/includes/fieldset.html" %}
{% endfor %}
{% endblock %}

{% block object-tools-items %}
{% url opts|admin_urlname:'permissions' original.pk|admin_urlquote as history_url %}

{% if owner or superuser %}
<li><a href="{% add_preserved_filters history_url %}" class="permissionslink">{% trans "Object Permissions" %}</a></li>
{% endif %}

{{ block.super }}
{% endblock %}

admin_templates/includes/fieldset.html → Consulta_project/polls/templates/admin/polls/includes/fieldset.html View File

@@ -10,13 +10,18 @@
<div{% if not line.fields|length_is:'1' %} class="fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}"{% elif field.is_checkbox %} class="checkbox-row"{% endif %}>
{% if not line.fields|length_is:'1' and not field.is_readonly %}{{ field.errors }}{% endif %}
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% if change_poll or request.user.is_superuser or new or change_question %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }} <div class="readonly">{{ field.field.value }}</div>
{% endif %}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{% if change_poll or request.user.is_superuser or new or change_question %}
{{ field.field }}
{% else %}
<div class="readonly">{{ field.field.value }}</div>
{% endif %}
{% endif %}
{% if field.field.help_text %}

admin_templates/change_form_object_tools.html → Consulta_project/polls/templates/admin/polls/poll/change_form_object_tools.html View File

@@ -4,5 +4,5 @@
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
</li>
{% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif %}
{% endblock %}
{% if has_absolute_url and unlocked %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on Site" %}</a></li>{% endif %}
{% endblock %}

+ 54
- 0
Consulta_project/polls/templates/admin/polls/poll/submit_line.html View File

@@ -0,0 +1,54 @@
{% load i18n admin_urls %}
{% load guardian_tags %}

<!-- Debug for Guardian Permissions
{% if add_poll %}
Poll can be added
{% endif %}

{% if change_poll %}
Poll can be changed
{% endif %}

{% if view_poll %}
Poll can be viewed
{% endif %}

{% if delete_poll %}
Poll can be deleted
{% endif %}

{% if superuser %}
Logged in user is superuser
{% endif %}

{% if new %}
This poll was not created yet
{% endif %} -->

<div class="submit-row">
{% block submit-row %}


{% if delete_poll or superuser %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
{% endif %}


<!-- {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">{% endif %}
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% if can_change %}{% trans 'Save and continue editing' %}{% else %}{% trans 'Save and view' %}{% endif %}" name="_continue">{% endif %}
{% if show_close %}<a href="{% url opts|admin_urlname:'changelist' %}" class="closelink">{% trans 'Close' %}</a>{% endif %} -->
{% if change_poll or superuser %}
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">
<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">
<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">
<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue">
{% elif new %}
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">
{% endif %}
{% endblock %}

</div>

+ 17
- 0
Consulta_project/polls/templates/admin/polls/question/change_form.html View File

@@ -0,0 +1,17 @@
{% extends "admin/change_form.html" %}
{% load i18n admin_urls %}

{% block field_sets %}
{% for fieldset in adminform %}
{% include "admin/polls/includes/fieldset.html" %}
{% endfor %}
{% endblock %}

{% block object-tools-items %}
{% url opts|admin_urlname:'permissions' original.pk|admin_urlquote as history_url %}
{% if owner or superuser %}
<li><a href="{% add_preserved_filters history_url %}" class="permissionslink">{% trans "Object Permissions" %}</a></li>
{% endif %}

{{ block.super }}
{% endblock %}

+ 8
- 0
Consulta_project/polls/templates/admin/polls/question/change_form_object_tools.html View File

@@ -0,0 +1,8 @@
{% load i18n admin_urls %}
{% block object-tools-items %}
<li>
{% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
<a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
</li>
{% if has_absolute_url and unlocked %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on Site" %}</a></li>{% endif %}
{% endblock %}

+ 55
- 0
Consulta_project/polls/templates/admin/polls/question/submit_line.html View File

@@ -0,0 +1,55 @@
{% load i18n admin_urls %}
{% load guardian_tags %}


<!-- Debug for Guardian Permissions
{% if add_question %}
Question can be added
{% endif %}

{% if change_question %}
Question can be changed
{% endif %}

{% if view_question %}
Question can be viewed
{% endif %}

{% if delete_question %}
Question can be deleted
{% endif %}

{% if superuser %}
Logged in user is superuser
{% endif %}

{% if new %}
This question was not created yet
{% endif %} -->

<div class="submit-row">
{% block submit-row %}


{% if delete_question or superuser %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
{% endif %}


<!-- {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">{% endif %}
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% if can_change %}{% trans 'Save and continue editing' %}{% else %}{% trans 'Save and view' %}{% endif %}" name="_continue">{% endif %}
{% if show_close %}<a href="{% url opts|admin_urlname:'changelist' %}" class="closelink">{% trans 'Close' %}</a>{% endif %} -->
{% if change_question or superuser %}
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">
<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">
<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">
<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue">
{% elif new %}
<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">
{% endif %}
{% endblock %}

</div>

+ 12
- 0
Consulta_project/polls/templates/django_registration/registration_complete.html View File

@@ -0,0 +1,12 @@
{% extends "landing/index_base.html" %}
{% load static %}

{% block content %}
<div
class="d-flex d-print-flex d-sm-flex d-md-flex d-lg-flex d-xl-flex justify-content-center align-items-center justify-content-sm-center align-items-sm-center justify-content-md-center align-items-md-center justify-content-xl-center align-items-xl-center"
style="height: 75vh;margin-top: 30px;">
<h4 class="text-center" style="font-family: Inter;margin-bottom: 0;">
Registration complete.
</h4>
</div>
{% endblock content %}

+ 71
- 0
Consulta_project/polls/templates/django_registration/registration_form.html View File

@@ -0,0 +1,71 @@
{% extends "landing/index_base.html" %} {% load static %} {% block content %}
<div class="d-flex justify-content-center align-items-center login-one" id="login-one"
style='color: rgb(0,0,0);font-family: Inter;background-repeat: no-repeat;background-color: transparent;background-image: url("/static/img/mountain_bg.jpg");margin-top: 30px;height: 75vh;'>
<div class="container">
<div class="row">
<div class="col">
<form class="login-one-form" method="POST"
style="background-color: rgba(137,142,140,0.52);margin-top: 15px;">
{% csrf_token %}
<div
class="form-row text-center d-flex d-xl-flex justify-content-center align-items-center justify-content-xl-center align-items-xl-center">
<div class="col">
<div class="d-flex justify-content-center align-items-center">
<h3 id="heading" style="color: #dfe8ee;font-family: Inter;font-size: 36px;">
Create an Account
</h3>
</div>
</div>
</div>

<div class="form-row">
<div class="col">
<div class="form-group d-flex justify-content-center align-items-center">
<input class="form-control" type="text" name="{{ form.username.html_name }}"
id="{{ form.username.id_for_label }}" placeholder="Username" />
</div>
</div>
</div>
{{ form.username.errors }}

<div class="form-row d-flex justify-content-center align-items-center">
<div class="col">
<div class="form-group text-center d-flex justify-content-center align-items-center">
<input id="{{ form.email.id_for_label }}" class="form-control" type="email"
name="{{ form.email.html_name }}" placeholder="E-Mail address" />
</div>
</div>
</div>
{{ form.email.errors }}

<div class="form-row d-flex justify-content-center align-items-center">
<div class="col">
<div class="form-group d-flex justify-content-center align-items-center">
<input class="form-control" type="password" id="{{ form.password1.id_for_label }}"
placeholder="Password" name="{{ form.password1.html_name }}" />
</div>
</div>
<div class="col">
<div class="form-group d-flex justify-content-center align-items-center">
<input class="form-control" type="password" id="{{ form.password2.id_for_label }}"
placeholder="Repeat" name="{{ form.password2.html_name }}" />
</div>
</div>
</div>
{{ form.password1.errors }}
{{ form.password2.errors }}

<div class="form-row d-flex justify-content-center align-items-center">
<button class="btn btn-light" id="button" type="submit"
style="background-color: rgb(0,188,140);color: rgb(0,0,0);">
Register
</button>
</div>
<small class="form-text text-center text-muted" style="color: rgba(0,0,0,0.2);">Password must
contain at least 8 characters</small>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

+ 50
- 0
Consulta_project/polls/templates/polls/login.html View File

@@ -0,0 +1,50 @@
{% extends 'landing/index_base.html' %}
{% load static %}
{% block content %}

<div class="d-flex justify-content-center align-items-center login-one" id="login-one" style="color: rgb(0,0,0);font-family: Inter;background-repeat: no-repeat;background-color: transparent;margin-top: 30px;background-image: url(&quot;/static/img/mountain_bg.jpg&quot;);height: 75vh;">
<div class="container">
<div class="row">
<div class="col">
<form action="{{ app_path }}" method="post" class="login-one-form" style="background-color: rgba(137,142,140,0.52);" id="login-form">
{% csrf_token %}
<div class="form-row text-center d-flex d-xl-flex justify-content-center align-items-center justify-content-xl-center align-items-xl-center">
<div class="col">
<div class="d-flex justify-content-center align-items-center">
<h3 id="heading" style="color: #dfe8ee;font-family: Inter;font-size: 36px;">Log In</h3>
</div>
</div>
</div>
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="errornote errorlist" style="margin-top:5px; margin-bottom: 10px;">
{{ error }}
</p>
{% endfor %}
{% endif %}
{{ form.username.errors }}
<div class="form-row">
<div class="col">
<div class="form-group d-flex justify-content-center align-items-center"><input class="form-control" type="text" id="{{ form.username.id_for_label }}" placeholder="Username" name="{{ form.username.html_name }}"></div>
</div>
</div>
{{ form.password.errors }}
<div class="form-row d-flex justify-content-center align-items-center">
<div class="col">
<div class="form-group d-flex justify-content-center align-items-center"><input class="form-control" type="password" id="{{ form.password.id_for_label }}" placeholder="Password" name="{{ form.password.html_name }}"></div>
</div>
</div>
<div class="form-row d-flex justify-content-center align-items-center">
<button class="btn btn-light" id="button" type="submit" style="background-color: rgb(0,188,140);color: rgb(0,0,0);">Log In</button>
</div>
</form>
</div>
</div>
<div class="row">
</div>
</div>
</div>

{% endblock %}

+ 52
- 23
Consulta_project/polls/templates/polls/poll.html View File

@@ -1,26 +1,55 @@
{% extends "landing/index_base.html" %}

{% block content %}

<div class="container" style="text-align: center; margin-top: 6em;">

{% if poll.get_questions %}
<h1>Questions:</h1>
<a href="{% url 'poll-stats' poll.slug %}">Stats</a>
<a href="{% url 'poll-share' poll.slug %}">Share</a>
<hr>
{% endif %}

{% for question in poll.get_questions %}
<a href="{{ question.get_absolute_url }}">
<h2>{{ question.question_string }}</h2>
</a>

{% empty %}

<h2>There are currently no questions in this poll.</h2>
{% endfor %}

{% extends "landing/index_base.html" %} {% block content %}

<div class="d-flex justify-content-center align-items-center"
style="text-align: center;margin-top: 30px;height: 75vh; background-color: #1f1f1f;">
<div class="container" style="padding-bottom: 30px;padding-top: 30px;font-family: Inter;">
<div class="row d-flex justify-content-center align-items-center">
<div
class="col d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<h1>{{ poll.name }}</h1>
</div>
</div>

{% if poll.get_questions %}
<div class="row" style="margin-top: 15px;margin-bottom: 15px;">
<div
class="col d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<h3>Available Questions:</h3>
</div>
</div>
<div class="row d-flex d-sm-flex d-md-flex d-lg-flex d-xl-flex justify-content-center justify-content-sm-center justify-content-md-center justify-content-lg-center justify-content-xl-center"
style="margin-top: 15px;margin-bottom: 15px;">
<div
class="col-auto d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<a href="{% url 'poll-stats' poll.slug %}">
<h5>Stats</h5>
</a>
</div>
<div
class="col-auto d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<a href="{% url 'poll-share' poll.slug %}">
<h5>Share</h5>
</a>
</div>
</div>
{% endif %}
<div class="row d-flex d-xl-flex justify-content-center align-items-center justify-content-xl-center"
style="margin-right: 15vw;margin-left: 15vw;">
<div class="col-12 col-md-8 col-lg-9 col-xl-7">
{% for question in poll.get_questions %}

<a href="{{ question.get_absolute_url }}">
<h4>{{ question.question_string }}</h4>
</a>

{% empty %}

<h4>There are currently no questions in this poll.</h4>

{% endfor %}
</div>
</div>
</div>
</div>

{% endblock content %}

+ 34
- 27
Consulta_project/polls/templates/polls/polls.html View File

@@ -1,33 +1,40 @@
{% extends "polls/polls_base.html" %}
{% extends "landing/index_base.html" %}

{% block content %}

<div class="container" style="text-align: center; margin-top: 6em;">

{% if polls %}
<h1>Polls:</h1>
<hr>
{% endif %}

{% for poll in polls %}

{% if poll.unlocked == True %}
<a href="{{ poll.get_absolute_url }}">
<h2>{{ poll.name }}</h2>
</a>
{% else %}
<h2 style="color: gray;">{{ poll.name }}</h2>
{% endif %}



{% empty %}

<h2>There are currently no public polls.</h2>

{% endfor %}

<div class="d-flex justify-content-center align-items-center" style="background-color: rgba(0,0,0,0.1);margin-top: 30px;font-family: Inter;min-height: 75vh;">
<div class="container" style="padding-bottom: 30px;padding-top: 30px;">
<div class="row d-flex justify-content-center" style="margin-top: 15px;margin-bottom: 15px;">
<div class="col d-flex justify-content-center">
<h2>Available Polls:</h2>
</div>
</div>


{% for poll in polls %}
{% if poll.unlocked %}
<div class="row d-flex justify-content-center" style="margin-right: 15vw;margin-left: 15vw;">
<div class="col d-flex justify-content-center align-items-center">
<h4><a href="{{ poll.get_absolute_url }}">{{ poll.name }}</a></h4>
</div>
</div>
{% else %}
<div class="row d-flex justify-content-center" style="margin-right: 15vw;margin-left: 15vw;">
<div class="col d-flex justify-content-center align-items-center">
<h4 style="color: gray;">{{ poll.name }}</h4>
</div>
</div>
{% endif %}
{% empty %}
<div class="row d-flex justify-content-center" style="margin-top: 15px;margin-bottom: 15px;">
<div class="col d-flex justify-content-center">
<h4>There are currently no public polls.</h4>
</div>
</div>

{% endfor %}
</div>
</div>


{% endblock content %}

+ 0
- 8
Consulta_project/polls/templates/polls/polls_base.html View File

@@ -1,8 +0,0 @@
{% extends "landing/index_base.html" %}
{% load static %}

{% block polls_nav %}
<li class="nav-item">
<a class="nav-link active" href="{% url 'polls' %}">Polls</span></a>
</li>
{% endblock polls_nav %}

+ 43
- 29
Consulta_project/polls/templates/polls/question.html View File

@@ -2,35 +2,49 @@

{% block content %}

<div class="container" style="text-align: center; margin-top: 6em;">

{% if question.get_answers %}
<h1>{{ question.question_string }}</h1>
<form action="{% url 'vote' question.id %}" method="post">
<hr>
{% endif %}



{% csrf_token %}

{% for answer in question.get_answers %}

<input type="radio" name="choice" id="choice{{ forloop.counter}}" value="{{ answer.id }}">
<label for="choice{{ forloop.counter }}">{{ answer.answer_string }}</label>
<br>

{% empty %}

<h2>There are currently no available answers to this question.</h2>

{% endfor %}

{% if question.get_answers %}
<input type="submit" value="Vote">
</form>
{% endif %}

<div class="d-flex justify-content-center align-items-center" style="background-color: rgba(0,0,0,0.1);margin-top: 30px;height: 75vh;">
<div class="container" style="padding-bottom: 30px;padding-top: 30px;">
{% if question.get_answers %}
<div class="row d-flex justify-content-center align-items-center">
<div class="col d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<h1>{{ question.poll.name }}</h1>
</div>
</div>
<div class="row" style="margin-top: 15px;margin-bottom: 15px;">
<div class="col d-flex d-xl-flex justify-content-center justify-content-lg-center justify-content-xl-center">
<h3>{{ question.question_string }}</h3>
</div>
</div>
<div class="row" style="margin-right: 15vw;margin-left: 15vw;">
<div class="col d-flex justify-content-center" style="font-size: 18px;">