1st commit
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
git *.log
|
||||||
|
*.pot
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
media
|
||||||
|
venv
|
||||||
|
.vscode
|
||||||
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# Django-Poll-App
|
||||||
|
|
||||||
|
Django poll app is a full featured polling app. You have to register in this app to show the polls and to vote. If you already voted you can not vote again. Only the owner of a poll can add poll , edit poll, update poll, delete poll , add choice, update choice, delete choice and end a poll. If a poll is ended it can not be voted. Ended poll only shows user the final result of the poll. There is a search option for polls. Also user can filter polls by name, publish date, and by number of voted. Pagination will work even after applying filter.
|
||||||
|
|
||||||
|
<h1>Getting Started</h1>
|
||||||
|
<p>These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.</p>
|
||||||
|
|
||||||
|
<h2>Prerequisites</h2>
|
||||||
|
<code>python== 3.5 or up and django==2.0 or up</code>
|
||||||
|
|
||||||
|
<h2>Installing</h2>
|
||||||
|
<pre>open terminal and type</pre>
|
||||||
|
<code>git clone https://github.com/devmahmud/Django-poll-app.git</code><br><br>
|
||||||
|
|
||||||
|
<h4>or simply download using the url below</h4>
|
||||||
|
<code>https://github.com/devmahmud/Django-poll-app.git</code><br>
|
||||||
|
|
||||||
|
<h2>To migrate the database open terminal in project directory and type</h2>
|
||||||
|
<code>python manage.py makemigrations</code><br>
|
||||||
|
<code>python manage.py migrate</code>
|
||||||
|
|
||||||
|
<h2>To use admin panel you need to create superuser using this command </h2>
|
||||||
|
<code>python manage.py createsuperuser</code>
|
||||||
|
|
||||||
|
<h2>To Create some dummy text data for your app follow the step below:</h2>
|
||||||
|
<code>pip install faker</code>
|
||||||
|
<code>python manage.py shell</code>
|
||||||
|
<code>import seeder</code>
|
||||||
|
<code>seeder.seed_all(30)</code>
|
||||||
|
<p>Here 30 is a number of entry. You can use it as your own</p>
|
||||||
|
|
||||||
|
<h2> To run the program in local server use the following command </h2>
|
||||||
|
<code>python manage.py runserver</code>
|
||||||
|
|
||||||
|
<p>Then go to http://127.0.0.1:8000 in your browser</p>
|
||||||
|
|
||||||
|
<h2>Project snapshot</h2>
|
||||||
|
<h3>Home page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409444-0e40a600-1b8c-11e9-9ab0-27d759db8973.jpg" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Login Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409509-36c8a000-1b8c-11e9-845a-65b49262aa53.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Registration Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409562-5cee4000-1b8c-11e9-82f6-1aa2df159528.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Poll List Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409728-d423d400-1b8c-11e9-8903-4c08ba64512e.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Poll Add Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409796-fe759180-1b8c-11e9-941b-c1202956cca4.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Polling page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409843-1e0cba00-1b8d-11e9-9109-cceb79a6a623.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Poll Result Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51409932-60ce9200-1b8d-11e9-9c83-c59ba498ca8b.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Poll Edit Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51410008-92dff400-1b8d-11e9-8172-c228e4b60e28.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Choice Update Delete Page</h3>
|
||||||
|
<div align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/19981097/51410442-dc7d0e80-1b8e-11e9-8f8e-18e6d7bb70fb.png" width="100%"</img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Author</h2>
|
||||||
|
<blockquote>
|
||||||
|
Mahmudul alam<br>
|
||||||
|
Email: expelmahmud@gmail.com
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h3>========Thank You !!!=========</h3>
|
||||||
|
</div>
|
||||||
0
accounts/__init__.py
Normal file
0
accounts/__init__.py
Normal file
3
accounts/admin.py
Normal file
3
accounts/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
accounts/apps.py
Normal file
5
accounts/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsConfig(AppConfig):
|
||||||
|
name = 'accounts'
|
||||||
14
accounts/forms.py
Normal file
14
accounts/forms.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class UserRegistrationForm(forms.Form):
|
||||||
|
username = forms.CharField(label='Username', max_length=100, min_length=5,
|
||||||
|
widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||||
|
email = forms.EmailField(label='Email', max_length=35, min_length=5,
|
||||||
|
widget=forms.EmailInput(attrs={'class': 'form-control'}))
|
||||||
|
password1 = forms.CharField(label='Password', max_length=50, min_length=5,
|
||||||
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
|
||||||
|
password2 = forms.CharField(label='Confirm Password',
|
||||||
|
max_length=50, min_length=5,
|
||||||
|
widget=forms.PasswordInput(attrs={'class': 'form-control'}))
|
||||||
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
3
accounts/models.py
Normal file
3
accounts/models.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
41
accounts/templates/accounts/login.html
Normal file
41
accounts/templates/accounts/login.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="vh-100 d-flex justify-content-center align-items-center p-5" id="login-content">
|
||||||
|
<div class="col-md-5 p-5 shadow-sm border rounded-5 border-primary bg-white">
|
||||||
|
<h2 class="text-center mb-4 text-primary">Login</h2>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control border border-primary" id="username"
|
||||||
|
aria-describedby="username" name="username" placeholder="Enter Username" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input type="password" class="form-control border border-primary" name="password"
|
||||||
|
placeholder="Password" required>
|
||||||
|
</div>
|
||||||
|
<div class="d-grid">
|
||||||
|
<button class="btn btn-primary" type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="mt-3">
|
||||||
|
<p class="mb-0 text-center">Don't have an account? <a href="{% url 'accounts:register' %}"
|
||||||
|
class="text-primary fw-bold">Sign
|
||||||
|
Up</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
27
accounts/templates/accounts/register.html
Normal file
27
accounts/templates/accounts/register.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row center">
|
||||||
|
<div class="col-md-5 mx-auto p-5 shadow-sm border rounded border-primary">
|
||||||
|
<p>Already have an account? <a href="{% url 'accounts:login' %}">Login Here</a></p>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<button type="submit" class="btn btn-primary">Sign Up</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
3
accounts/tests.py
Normal file
3
accounts/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
10
accounts/urls.py
Normal file
10
accounts/urls.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "accounts"
|
||||||
|
|
||||||
|
urlpatterns=[
|
||||||
|
path('login/', views.login_user, name='login'),
|
||||||
|
path('logout/', views.logout_user, name='logout'),
|
||||||
|
path('register/', views.create_user, name='register'),
|
||||||
|
]
|
||||||
68
accounts/views.py
Normal file
68
accounts/views.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from .forms import UserRegistrationForm
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
def login_user(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.POST.get('username')
|
||||||
|
password = request.POST.get('password')
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
redirect_url = request.GET.get('next', 'home')
|
||||||
|
return redirect(redirect_url)
|
||||||
|
else:
|
||||||
|
messages.error(request, "Username Or Password is incorrect!",
|
||||||
|
extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
|
||||||
|
return render(request, 'accounts/login.html')
|
||||||
|
|
||||||
|
|
||||||
|
def logout_user(request):
|
||||||
|
logout(request)
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
check1 = False
|
||||||
|
check2 = False
|
||||||
|
check3 = False
|
||||||
|
form = UserRegistrationForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
username = form.cleaned_data['username']
|
||||||
|
password1 = form.cleaned_data['password1']
|
||||||
|
password2 = form.cleaned_data['password2']
|
||||||
|
email = form.cleaned_data['email']
|
||||||
|
|
||||||
|
if password1 != password2:
|
||||||
|
check1 = True
|
||||||
|
messages.error(request, 'Password did not match!',
|
||||||
|
extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
if User.objects.filter(username=username).exists():
|
||||||
|
check2 = True
|
||||||
|
messages.error(request, 'Username already exists!',
|
||||||
|
extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
if User.objects.filter(email=email).exists():
|
||||||
|
check3 = True
|
||||||
|
messages.error(request, 'Email already registered!',
|
||||||
|
extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
|
||||||
|
if check1 or check2 or check3:
|
||||||
|
messages.error(
|
||||||
|
request, "Registration Failed!", extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
return redirect('accounts:register')
|
||||||
|
else:
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username=username, password=password1, email=email)
|
||||||
|
messages.success(
|
||||||
|
request, f'Thanks for registering {user.username}.', extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect('accounts:login')
|
||||||
|
else:
|
||||||
|
form = UserRegistrationForm()
|
||||||
|
return render(request, 'accounts/register.html', {'form': form})
|
||||||
15
manage.py
Normal file
15
manage.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pollme.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
0
pollme/__init__.py
Normal file
0
pollme/__init__.py
Normal file
125
pollme/settings.py
Normal file
125
pollme/settings.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
"""
|
||||||
|
Django settings for pollme project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 2.1.5.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/2.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/2.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "x*za6xf&_80ofdpae!yzq61g9ffikkx9$*iygbl$j7rr4wlf8t"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"polls.apps.PollsConfig",
|
||||||
|
"accounts.apps.AccountsConfig",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "pollme.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "pollme.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/2.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "Asia/Dhaka"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/2.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
25
pollme/urls.py
Normal file
25
pollme/urls.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""pollme URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/2.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.home, name='home'),
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('accounts/', include('accounts.urls', namespace="accounts")),
|
||||||
|
path('polls/', include('polls.urls', namespace="polls")),
|
||||||
|
]
|
||||||
5
pollme/views.py
Normal file
5
pollme/views.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
def home(request):
|
||||||
|
return render(request,'home.html')
|
||||||
16
pollme/wsgi.py
Normal file
16
pollme/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for pollme project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pollme.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
0
polls/__init__.py
Normal file
0
polls/__init__.py
Normal file
29
polls/admin.py
Normal file
29
polls/admin.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Poll, Choice, Vote
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceInline(admin.TabularInline): # or admin.StackedInline for a different layout
|
||||||
|
model = Choice
|
||||||
|
extra = 1
|
||||||
|
|
||||||
|
@admin.register(Poll)
|
||||||
|
class PollAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["text", "owner", "pub_date", "active", "created_at"]
|
||||||
|
search_fields = ["text", "owner__username"]
|
||||||
|
list_filter = ["active", 'created_at', 'pub_date']
|
||||||
|
date_hierarchy = "pub_date"
|
||||||
|
inlines = [ChoiceInline]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Choice)
|
||||||
|
class ChoiceAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["choice_text", "poll", 'created_at', 'updated_at']
|
||||||
|
search_fields = ["choice_text", "poll__text"]
|
||||||
|
autocomplete_fields = ["poll"]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Vote)
|
||||||
|
class VoteAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["choice", "poll", "user", 'created_at']
|
||||||
|
search_fields = ["choice__choice_text", "poll__text", "user__username"]
|
||||||
|
autocomplete_fields = ["choice", "poll", "user"]
|
||||||
5
polls/apps.py
Normal file
5
polls/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PollsConfig(AppConfig):
|
||||||
|
name = 'polls'
|
||||||
35
polls/forms.py
Normal file
35
polls/forms.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from django import forms
|
||||||
|
from .models import Poll, Choice
|
||||||
|
|
||||||
|
|
||||||
|
class PollAddForm(forms.ModelForm):
|
||||||
|
|
||||||
|
choice1 = forms.CharField(label='Choice 1', max_length=100, min_length=1,
|
||||||
|
widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||||
|
choice2 = forms.CharField(label='Choice 2', max_length=100, min_length=1,
|
||||||
|
widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Poll
|
||||||
|
fields = ['text', 'choice1', 'choice2']
|
||||||
|
widgets = {
|
||||||
|
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'cols': 20}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EditPollForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Poll
|
||||||
|
fields = ['text', ]
|
||||||
|
widgets = {
|
||||||
|
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 5, 'cols': 20}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ChoiceAddForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Choice
|
||||||
|
fields = ['choice_text', ]
|
||||||
|
widgets = {
|
||||||
|
'choice_text': forms.TextInput(attrs={'class': 'form-control', })
|
||||||
|
}
|
||||||
49
polls/migrations/0001_initial.py
Normal file
49
polls/migrations/0001_initial.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Generated by Django 3.1.5 on 2021-01-29 16:38
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Choice',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('choice_text', models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Poll',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('text', models.TextField()),
|
||||||
|
('pub_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('active', models.BooleanField(default=True)),
|
||||||
|
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Vote',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('choice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.choice')),
|
||||||
|
('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.poll')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='choice',
|
||||||
|
name='poll',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.poll'),
|
||||||
|
),
|
||||||
|
]
|
||||||
42
polls/migrations/0002_auto_20231018_1318.py
Normal file
42
polls/migrations/0002_auto_20231018_1318.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Generated by Django 3.1.14 on 2023-10-18 07:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('polls', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='choice',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='choice',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='poll',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vote',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vote',
|
||||||
|
name='updated_at',
|
||||||
|
field=models.DateTimeField(auto_now=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
polls/migrations/__init__.py
Normal file
0
polls/migrations/__init__.py
Normal file
73
polls/models.py
Normal file
73
polls/models.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
|
||||||
|
class Poll(models.Model):
|
||||||
|
owner = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
text = models.TextField()
|
||||||
|
pub_date = models.DateTimeField(default=timezone.now)
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def user_can_vote(self, user):
|
||||||
|
"""
|
||||||
|
Return False if user already voted
|
||||||
|
"""
|
||||||
|
user_votes = user.vote_set.all()
|
||||||
|
qs = user_votes.filter(poll=self)
|
||||||
|
if qs.exists():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_vote_count(self):
|
||||||
|
return self.vote_set.count()
|
||||||
|
|
||||||
|
def get_result_dict(self):
|
||||||
|
res = []
|
||||||
|
for choice in self.choice_set.all():
|
||||||
|
d = {}
|
||||||
|
alert_class = ['primary', 'secondary', 'success',
|
||||||
|
'danger', 'dark', 'warning', 'info']
|
||||||
|
|
||||||
|
d['alert_class'] = secrets.choice(alert_class)
|
||||||
|
d['text'] = choice.choice_text
|
||||||
|
d['num_votes'] = choice.get_vote_count
|
||||||
|
if not self.get_vote_count:
|
||||||
|
d['percentage'] = 0
|
||||||
|
else:
|
||||||
|
d['percentage'] = (choice.get_vote_count /
|
||||||
|
self.get_vote_count)*100
|
||||||
|
|
||||||
|
res.append(d)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
|
||||||
|
class Choice(models.Model):
|
||||||
|
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
|
||||||
|
choice_text = models.CharField(max_length=255)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_vote_count(self):
|
||||||
|
return self.vote_set.count()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.poll.text[:25]} - {self.choice_text[:25]}"
|
||||||
|
|
||||||
|
|
||||||
|
class Vote(models.Model):
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
|
||||||
|
choice = models.ForeignKey(Choice, on_delete=models.CASCADE)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.poll.text[:15]} - {self.choice.choice_text[:15]} - {self.user.username}'
|
||||||
38
polls/templates/polls/add_choice.html
Normal file
38
polls/templates/polls/add_choice.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row center">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
{% if edit_choice %}
|
||||||
|
<h2>Update choice</h2>
|
||||||
|
{% else %}
|
||||||
|
<h2>Add new choice</h2>
|
||||||
|
{% endif %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ field.errors }}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if edit_choice %}
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
<a class="btn btn-danger" href="{% url 'polls:choice_delete' choice.id %}" role="button" onclick="return confirm('Are you sure you want to delete this?')">Delete</a>
|
||||||
|
{% else %}
|
||||||
|
<button type="submit" class="btn btn-primary">Add</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
30
polls/templates/polls/add_poll.html
Normal file
30
polls/templates/polls/add_poll.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row center">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<h2>Create new poll</h2>
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ field.errors }}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary">Add Poll</button>
|
||||||
|
<a class="btn btn-warning" href="{% url 'polls:list' %}" role="button">Back</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
43
polls/templates/polls/endpoll.html
Normal file
43
polls/templates/polls/endpoll.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-sm-2">
|
||||||
|
<h3 class="mt-3 mb-3 text-center">Result for: {{ poll.text }}</h3>
|
||||||
|
<!-- progress bar -->
|
||||||
|
<div class="progress mt-3">
|
||||||
|
|
||||||
|
{% for choice in poll.get_result_dict %}
|
||||||
|
<div class="progress-bar bg-{{ choice.alert_class }}" role="progressbar" style="width: {{ choice.percentage }}%;" aria-valuenow="30" aria-valuemin="0"
|
||||||
|
aria-valuemax="100"><b>{{ choice.text }}-{{ choice.percentage|floatformat }}%</b></div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for choice in poll.choice_set.all %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ choice.choice_text }}
|
||||||
|
<span class="badge badge-primary badge-pill">{{ choice.get_vote_count }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<a class="btn btn-primary mt-3" href="{% url 'polls:list' %}" role="button">Back To Polls</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
33
polls/templates/polls/poll_detail.html
Normal file
33
polls/templates/polls/poll_detail.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Polls details page</h1>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<h2 class="mt-3 mb-3">{{ poll }}</h2>
|
||||||
|
<form action="{% url 'polls:vote' poll.id %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for choice in poll.choice_set.all %}
|
||||||
|
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
|
||||||
|
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label>
|
||||||
|
<br>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Vote" class="btn btn-primary mt-3">
|
||||||
|
<a class="btn btn-warning mt-3" href="{% url 'polls:list' %}" role="button">Cancel</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
49
polls/templates/polls/poll_edit.html
Normal file
49
polls/templates/polls/poll_edit.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row center">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<h2>Edit poll</h2>
|
||||||
|
{% if messages %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<form action="" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ field.errors }}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
{{ field }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary">Update</button>
|
||||||
|
<a class="btn btn-danger" href="{% url 'polls:delete_poll' poll.id %}" role="button" onclick="return confirm('Are you sure?')">Delete</a>
|
||||||
|
<a class="btn btn-warning" href="{% url 'polls:add_choice' poll.id %}" role="button">Add Choice</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="choices">
|
||||||
|
<h2 class="text-center mt-3">Choices</h2>
|
||||||
|
<hr>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for choice in poll.choice_set.all %}
|
||||||
|
<li class="list-group-item"><a href="{% url 'polls:choice_edit' choice.id %}"><i class="fas fa-pencil-alt"></i></a> 
|
||||||
|
{{ choice.choice_text }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
48
polls/templates/polls/poll_result.html
Normal file
48
polls/templates/polls/poll_result.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-sm-2">
|
||||||
|
{% if poll.active %}
|
||||||
|
<h3 class="mt-3 mb-3 text-center">Result for: {{ poll.text }}</h3>
|
||||||
|
{% else %}
|
||||||
|
<h3 class="mt-3 mb-3 text-center">"{{ poll.text }}" Has Ended Polling!</h3>
|
||||||
|
{% endif %}
|
||||||
|
<h3 class="mb-2 text-center">Total: {{ poll.get_vote_count }} votes</h3>
|
||||||
|
<!-- progress bar -->
|
||||||
|
<div class="progress mt-3 mb-2">
|
||||||
|
{% for choice in poll.get_result_dict %}
|
||||||
|
<div class="progress-bar bg-{{ choice.alert_class }}" role="progressbar" style="width: {{ choice.percentage }}%;"
|
||||||
|
aria-valuenow="30" aria-valuemin="0" aria-valuemax="100"><b>
|
||||||
|
{{choice.text|truncatewords:2}}-{{choice.percentage|floatformat}}%</b>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for choice in poll.choice_set.all %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ choice.choice_text }}
|
||||||
|
<span class="badge badge-primary badge-pill">{{ choice.get_vote_count }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<a class="btn btn-primary mt-3" href="{% url 'polls:list' %}" role="button">Back To Polls</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
78
polls/templates/polls/polls_list.html
Normal file
78
polls/templates/polls/polls_list.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-sm-2">
|
||||||
|
<h1 class="text-center mb-5">Welcome to polls List!</h1>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="messages">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div {% if message.tags %} class="{{ message.tags }}" {% endif %}>{{ message }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<a class="btn btn-{% if 'name' in request.GET %}warning{% else %}primary{% endif %} mb-3" href="?name=True"
|
||||||
|
role="button"><i class="fas fa-sort-alpha-down"></i>
|
||||||
|
Name</a>
|
||||||
|
<a class="btn btn-{% if 'date' in request.GET %}warning{% else %}primary{% endif %} mb-3" href="?date=True"
|
||||||
|
role="button"><i class="far fa-clock"></i> Date</a>
|
||||||
|
<a class="btn btn-{% if 'vote' in request.GET %}warning{% else %}primary{% endif %} mb-3" href="?vote=True"
|
||||||
|
role="button"><i class="fas fa-poll"></i> Vote</a>
|
||||||
|
|
||||||
|
<a class="btn btn-primary mb-3 float-right" href="{% url 'polls:add' %}" role="button">Add <i class="fas fa-plus"></i></a>
|
||||||
|
|
||||||
|
<form class="form-inline">
|
||||||
|
<div class="form-group mr-sm-2 mb-2">
|
||||||
|
<input type="search" class="form-control" name="search" placeholder="Search" value={{ search_term }}>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary mb-2"><i class="fas fa-search"></i></button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for poll in polls %}
|
||||||
|
<li class="list-group-item"><a href="{% url 'polls:detail' poll.id %}">{{ poll.text|truncatewords:5 }}
|
||||||
|
{% if not poll.active%}
|
||||||
|
<i class="fas fa-check-circle ml-2"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% if request.user == poll.owner %}
|
||||||
|
{% if poll.active %}
|
||||||
|
<a href="{% url 'polls:end_poll' poll.id %}" data-toggle="tooltip" data-placement="top" title="End Poll"
|
||||||
|
onclick="return confirm('Are you sure ?')"><i class="fas fa-step-forward float-right btn btn-danger btn-sm"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'polls:edit' poll.id %}" class="mr-3" data-toggle="tooltip" data-placement="top"
|
||||||
|
title="Edit Poll"><i class="fas fa-pencil-alt float-right btn btn-primary btn-sm mr-1"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% if polls.paginator.num_pages > 1 %}
|
||||||
|
<nav class="mt-3">
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if polls.has_previous %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=1&{{ params }}">First</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page={{ polls.previous_page_number }}&{{ params }}">Previous</a></li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="page-item active"><a class="page-link" href="">{{ polls.number }}</a></li>
|
||||||
|
|
||||||
|
{% if polls.has_next %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?page={{ polls.next_page_number }}&{{ params }}">Next</a></li>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page={{ polls.paginator.num_pages }}&{{ params }}">Last</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
44
polls/tests.py
Normal file
44
polls/tests.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from .models import Poll, Vote
|
||||||
|
|
||||||
|
|
||||||
|
class PollModelTest(TestCase):
|
||||||
|
def test_user_can_vote(self):
|
||||||
|
user = User.objects.create_user('john')
|
||||||
|
poll = Poll.objects.create(owner=user)
|
||||||
|
self.assertTrue(poll.user_can_vote(user))
|
||||||
|
|
||||||
|
choice = poll.choice_set.create(choice_text='pizza')
|
||||||
|
Vote.objects.create(user=user, poll=poll, choice=choice)
|
||||||
|
self.assertFalse(poll.user_can_vote(user))
|
||||||
|
|
||||||
|
|
||||||
|
class PollViewTest(TestCase):
|
||||||
|
def test_home(self):
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_login(self):
|
||||||
|
User.objects.create_user(username='john', password='rambo')
|
||||||
|
response = self.client.post(
|
||||||
|
'/accounts/login/', {'username': 'john', 'password': 'rambo'}
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, '/')
|
||||||
|
|
||||||
|
def test_register(self):
|
||||||
|
response = self.client.post(
|
||||||
|
'/accounts/register/',
|
||||||
|
{
|
||||||
|
'username': 'johny',
|
||||||
|
'password1': 'rambo',
|
||||||
|
'password2': 'rambo',
|
||||||
|
'email': 'johny.rambo@usarmy.gov',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertRedirects(response, '/accounts/login/')
|
||||||
|
# assert that user got actually created in the backend
|
||||||
|
self.assertIsNotNone(authenticate(username='johny', password='rambo'))
|
||||||
19
polls/urls.py
Normal file
19
polls/urls.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "polls"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('list/', views.polls_list, name='list'),
|
||||||
|
path('list/user/', views.list_by_user, name='list_by_user'),
|
||||||
|
path('add/', views.polls_add, name='add'),
|
||||||
|
path('edit/<int:poll_id>/', views.polls_edit, name='edit'),
|
||||||
|
path('delete/<int:poll_id>/', views.polls_delete, name='delete_poll'),
|
||||||
|
path('end/<int:poll_id>/', views.end_poll, name='end_poll'),
|
||||||
|
path('edit/<int:poll_id>/choice/add/', views.add_choice, name='add_choice'),
|
||||||
|
path('edit/choice/<int:choice_id>/', views.choice_edit, name='choice_edit'),
|
||||||
|
path('delete/choice/<int:choice_id>/',
|
||||||
|
views.choice_delete, name='choice_delete'),
|
||||||
|
path('<int:poll_id>/', views.poll_detail, name='detail'),
|
||||||
|
path('<int:poll_id>/vote/', views.poll_vote, name='vote'),
|
||||||
|
]
|
||||||
223
polls/views.py
Normal file
223
polls/views.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.contrib import messages
|
||||||
|
from .models import Poll, Choice, Vote
|
||||||
|
from .forms import PollAddForm, EditPollForm, ChoiceAddForm
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def polls_list(request):
|
||||||
|
all_polls = Poll.objects.all()
|
||||||
|
search_term = ''
|
||||||
|
if 'name' in request.GET:
|
||||||
|
all_polls = all_polls.order_by('text')
|
||||||
|
|
||||||
|
if 'date' in request.GET:
|
||||||
|
all_polls = all_polls.order_by('pub_date')
|
||||||
|
|
||||||
|
if 'vote' in request.GET:
|
||||||
|
all_polls = all_polls.annotate(Count('vote')).order_by('vote__count')
|
||||||
|
|
||||||
|
if 'search' in request.GET:
|
||||||
|
search_term = request.GET['search']
|
||||||
|
all_polls = all_polls.filter(text__icontains=search_term)
|
||||||
|
|
||||||
|
paginator = Paginator(all_polls, 6) # Show 6 contacts per page
|
||||||
|
page = request.GET.get('page')
|
||||||
|
polls = paginator.get_page(page)
|
||||||
|
|
||||||
|
get_dict_copy = request.GET.copy()
|
||||||
|
params = get_dict_copy.pop('page', True) and get_dict_copy.urlencode()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'polls': polls,
|
||||||
|
'params': params,
|
||||||
|
'search_term': search_term,
|
||||||
|
}
|
||||||
|
return render(request, 'polls/polls_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def list_by_user(request):
|
||||||
|
all_polls = Poll.objects.filter(owner=request.user)
|
||||||
|
paginator = Paginator(all_polls, 7) # Show 7 contacts per page
|
||||||
|
|
||||||
|
page = request.GET.get('page')
|
||||||
|
polls = paginator.get_page(page)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'polls': polls,
|
||||||
|
}
|
||||||
|
return render(request, 'polls/polls_list.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required()
|
||||||
|
def polls_add(request):
|
||||||
|
if request.user.has_perm('polls.add_poll'):
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = PollAddForm(request.POST)
|
||||||
|
if form.is_valid:
|
||||||
|
poll = form.save(commit=False)
|
||||||
|
poll.owner = request.user
|
||||||
|
poll.save()
|
||||||
|
Choice(
|
||||||
|
poll=poll, choice_text=form.cleaned_data['choice1']).save()
|
||||||
|
Choice(
|
||||||
|
poll=poll, choice_text=form.cleaned_data['choice2']).save()
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
request, "Poll & Choices added successfully.", extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
|
||||||
|
return redirect('polls:list')
|
||||||
|
else:
|
||||||
|
form = PollAddForm()
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
}
|
||||||
|
return render(request, 'polls/add_poll.html', context)
|
||||||
|
else:
|
||||||
|
return HttpResponse("Sorry but you don't have permission to do that!")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def polls_edit(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = EditPollForm(request.POST, instance=poll)
|
||||||
|
if form.is_valid:
|
||||||
|
form.save()
|
||||||
|
messages.success(request, "Poll Updated successfully.",
|
||||||
|
extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect("polls:list")
|
||||||
|
|
||||||
|
else:
|
||||||
|
form = EditPollForm(instance=poll)
|
||||||
|
|
||||||
|
return render(request, "polls/poll_edit.html", {'form': form, 'poll': poll})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def polls_delete(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
poll.delete()
|
||||||
|
messages.success(request, "Poll Deleted successfully.",
|
||||||
|
extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect("polls:list")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_choice(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ChoiceAddForm(request.POST)
|
||||||
|
if form.is_valid:
|
||||||
|
new_choice = form.save(commit=False)
|
||||||
|
new_choice.poll = poll
|
||||||
|
new_choice.save()
|
||||||
|
messages.success(
|
||||||
|
request, "Choice added successfully.", extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect('polls:edit', poll.id)
|
||||||
|
else:
|
||||||
|
form = ChoiceAddForm()
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
}
|
||||||
|
return render(request, 'polls/add_choice.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def choice_edit(request, choice_id):
|
||||||
|
choice = get_object_or_404(Choice, pk=choice_id)
|
||||||
|
poll = get_object_or_404(Poll, pk=choice.poll.id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ChoiceAddForm(request.POST, instance=choice)
|
||||||
|
if form.is_valid:
|
||||||
|
new_choice = form.save(commit=False)
|
||||||
|
new_choice.poll = poll
|
||||||
|
new_choice.save()
|
||||||
|
messages.success(
|
||||||
|
request, "Choice Updated successfully.", extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect('polls:edit', poll.id)
|
||||||
|
else:
|
||||||
|
form = ChoiceAddForm(instance=choice)
|
||||||
|
context = {
|
||||||
|
'form': form,
|
||||||
|
'edit_choice': True,
|
||||||
|
'choice': choice,
|
||||||
|
}
|
||||||
|
return render(request, 'polls/add_choice.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def choice_delete(request, choice_id):
|
||||||
|
choice = get_object_or_404(Choice, pk=choice_id)
|
||||||
|
poll = get_object_or_404(Poll, pk=choice.poll.id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
choice.delete()
|
||||||
|
messages.success(
|
||||||
|
request, "Choice Deleted successfully.", extra_tags='alert alert-success alert-dismissible fade show')
|
||||||
|
return redirect('polls:edit', poll.id)
|
||||||
|
|
||||||
|
|
||||||
|
def poll_detail(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, id=poll_id)
|
||||||
|
|
||||||
|
if not poll.active:
|
||||||
|
return render(request, 'polls/poll_result.html', {'poll': poll})
|
||||||
|
loop_count = poll.choice_set.count()
|
||||||
|
context = {
|
||||||
|
'poll': poll,
|
||||||
|
'loop_time': range(0, loop_count),
|
||||||
|
}
|
||||||
|
return render(request, 'polls/poll_detail.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def poll_vote(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
|
choice_id = request.POST.get('choice')
|
||||||
|
if not poll.user_can_vote(request.user):
|
||||||
|
messages.error(
|
||||||
|
request, "You already voted this poll!", extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
return redirect("polls:list")
|
||||||
|
|
||||||
|
if choice_id:
|
||||||
|
choice = Choice.objects.get(id=choice_id)
|
||||||
|
vote = Vote(user=request.user, poll=poll, choice=choice)
|
||||||
|
vote.save()
|
||||||
|
print(vote)
|
||||||
|
return render(request, 'polls/poll_result.html', {'poll': poll})
|
||||||
|
else:
|
||||||
|
messages.error(
|
||||||
|
request, "No choice selected!", extra_tags='alert alert-warning alert-dismissible fade show')
|
||||||
|
return redirect("polls:detail", poll_id)
|
||||||
|
return render(request, 'polls/poll_result.html', {'poll': poll})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def end_poll(request, poll_id):
|
||||||
|
poll = get_object_or_404(Poll, pk=poll_id)
|
||||||
|
if request.user != poll.owner:
|
||||||
|
return redirect('home')
|
||||||
|
|
||||||
|
if poll.active is True:
|
||||||
|
poll.active = False
|
||||||
|
poll.save()
|
||||||
|
return render(request, 'polls/poll_result.html', {'poll': poll})
|
||||||
|
else:
|
||||||
|
return render(request, 'polls/poll_result.html', {'poll': poll})
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
asgiref==3.6.0
|
||||||
|
Django==4.2.30
|
||||||
|
pytz==2020.5
|
||||||
|
sqlparse==0.4.4
|
||||||
117
seeder.py
Normal file
117
seeder.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from polls.models import Choice, Poll, Vote
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
import datetime
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from faker import Faker
|
||||||
|
fake = Faker()
|
||||||
|
|
||||||
|
|
||||||
|
def seed_users(num_entries=10, overwrite=False):
|
||||||
|
"""
|
||||||
|
Creates num_entries worth a new users
|
||||||
|
"""
|
||||||
|
if overwrite:
|
||||||
|
print("Overwriting Users")
|
||||||
|
User.objects.all().delete()
|
||||||
|
count = 0
|
||||||
|
for _ in range(num_entries):
|
||||||
|
first_name = fake.first_name()
|
||||||
|
last_name = fake.last_name()
|
||||||
|
u = User.objects.create_user(
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
email=first_name + "." + last_name + "@fakermail.com",
|
||||||
|
username=first_name + last_name,
|
||||||
|
password="password"
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
percent_complete = count / num_entries * 100
|
||||||
|
print(
|
||||||
|
"Adding {} new Users: {:.2f}%".format(
|
||||||
|
num_entries, percent_complete),
|
||||||
|
end='\r',
|
||||||
|
flush=True
|
||||||
|
)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def seed_polls(num_entries=10, choice_min=2, choice_max=5, overwrite=False):
|
||||||
|
"""
|
||||||
|
Seeds num_entries poll with random users as owners
|
||||||
|
Each poll will be seeded with # choices from choice_min to choice_max
|
||||||
|
"""
|
||||||
|
if overwrite:
|
||||||
|
print('Overwriting polls')
|
||||||
|
Poll.objects.all().delete()
|
||||||
|
users = list(User.objects.all())
|
||||||
|
count = 0
|
||||||
|
for _ in range(num_entries):
|
||||||
|
p = Poll(
|
||||||
|
owner=random.choice(users),
|
||||||
|
text=fake.paragraph(),
|
||||||
|
pub_date=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
num_choices = random.randrange(choice_min, choice_max + 1)
|
||||||
|
for _ in range(num_choices):
|
||||||
|
c = Choice(
|
||||||
|
poll=p,
|
||||||
|
choice_text=fake.sentence()
|
||||||
|
).save()
|
||||||
|
count += 1
|
||||||
|
percent_complete = count / num_entries * 100
|
||||||
|
print(
|
||||||
|
"Adding {} new Polls: {:.2f}%".format(
|
||||||
|
num_entries, percent_complete),
|
||||||
|
end='\r',
|
||||||
|
flush=True
|
||||||
|
)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def seed_votes():
|
||||||
|
"""
|
||||||
|
Creates a new vote on every poll for every user
|
||||||
|
Voted for choice is selected random.
|
||||||
|
Deletes all votes prior to adding new ones
|
||||||
|
"""
|
||||||
|
Vote.objects.all().delete()
|
||||||
|
users = User.objects.all()
|
||||||
|
polls = Poll.objects.all()
|
||||||
|
count = 0
|
||||||
|
number_of_new_votes = users.count() * polls.count()
|
||||||
|
for poll in polls:
|
||||||
|
choices = list(poll.choice_set.all())
|
||||||
|
for user in users:
|
||||||
|
v = Vote(
|
||||||
|
user=user,
|
||||||
|
poll=poll,
|
||||||
|
choice=random.choice(choices)
|
||||||
|
).save()
|
||||||
|
count += 1
|
||||||
|
percent_complete = count / number_of_new_votes * 100
|
||||||
|
print(
|
||||||
|
"Adding {} new votes: {:.2f}%".format(
|
||||||
|
number_of_new_votes, percent_complete),
|
||||||
|
end='\r',
|
||||||
|
flush=True
|
||||||
|
)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def seed_all(num_entries=10, overwrite=False):
|
||||||
|
"""
|
||||||
|
Runs all seeder functions. Passes value of overwrite to all
|
||||||
|
seeder function calls.
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
# run seeds
|
||||||
|
seed_users(num_entries=num_entries, overwrite=overwrite)
|
||||||
|
seed_polls(num_entries=num_entries, overwrite=overwrite)
|
||||||
|
seed_votes()
|
||||||
|
# get time
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
minutes = int(elapsed_time // 60)
|
||||||
|
seconds = int(elapsed_time % 60)
|
||||||
|
print("Script Execution took: {} minutes {} seconds".format(minutes, seconds))
|
||||||
17
static/css/home_style.css
Normal file
17
static/css/home_style.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
html, body {
|
||||||
|
height:100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
html{
|
||||||
|
background-image: url('../img/background.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background: rgba(0, 0, 0, 0.466);
|
||||||
|
}
|
||||||
|
#home-content{
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20%;
|
||||||
|
}
|
||||||
BIN
static/img/background.jpg
Normal file
BIN
static/img/background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
37
templates/base.html
Normal file
37
templates/base.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Poll Me | Polls List</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
|
||||||
|
{% block custom_css %}{% endblock custom_css %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include 'includes/navbar.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- All javascript files goes under here -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
21
templates/home.html
Normal file
21
templates/home.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% block custom_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/home_style.css' %}">
|
||||||
|
{% endblock custom_css %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div id="home-content">
|
||||||
|
<h1>PollMe - Get Started!</h1>
|
||||||
|
<h3>Your Voice Matters!</h3>
|
||||||
|
<hr>
|
||||||
|
<a class="btn btn-light btn-lg" href="{% url 'polls:list' %}" role="button">Get Started !</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
28
templates/includes/navbar.html
Normal file
28
templates/includes/navbar.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<nav class="navbar navbar-expand-sm navbar-light bg-light mb-5">
|
||||||
|
<a class="navbar-brand" href="{% url 'home' %}"><i class="fas fa-person-booth"></i></a>
|
||||||
|
<button class="navbar-toggler d-lg-none" type="button" data-toggle="collapse" data-target="#collapsibleNavId"
|
||||||
|
aria-controls="collapsibleNavId" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="collapsibleNavId">
|
||||||
|
<ul class="navbar-nav mr-auto mt-2 mt-lg-0">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" href="{% url 'home' %}">Home <span class="sr-only">(current)</span></a>
|
||||||
|
</li>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'polls:list' %}">Polls</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-nav ml-auto">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<a class="nav-link" href="{% url 'polls:list_by_user' %}">My Polls</a>
|
||||||
|
<a class="nav-link" href="{% url 'accounts:logout' %}">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="nav-link" href="{% url 'accounts:login' %}">Login</a>
|
||||||
|
<a class="nav-link" href="{% url 'accounts:register' %}">Register</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
Reference in New Issue
Block a user