1st commit
This commit is contained in:
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})
|
||||
Reference in New Issue
Block a user