polls: added models
This commit is contained in:
parent
c0bb2f5448
commit
d2c5ddd874
|
@ -18,9 +18,7 @@ class Comment(Post):
|
|||
thread = db.relationship('Thread',
|
||||
backref=backref('comments', lazy='dynamic'),
|
||||
foreign_keys=thread_id)
|
||||
|
||||
# Other fields populated automatically through relations:
|
||||
# <poll> A poll attached to the comment (of class Poll)
|
||||
|
||||
|
||||
def __init__(self, author, text, thread):
|
||||
"""
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
from app import db
|
||||
from enum import Enum
|
||||
from sqlalchemy.orm import backref
|
||||
|
||||
|
||||
class PollType(Enum):
|
||||
"""Polls types: single/multiple answers. Easier than inheritance"""
|
||||
SINGLE = 1
|
||||
MULTIPLE = 2
|
||||
from datetime import datetime, timedelta
|
||||
from collections import Counter
|
||||
|
||||
|
||||
class Poll(db.Model):
|
||||
"""Some poll, with different options"""
|
||||
"""Default class for polls"""
|
||||
|
||||
__tablename__ = 'poll'
|
||||
|
||||
# Names of templates
|
||||
template = 'defaultpoll.html'
|
||||
|
||||
# Unique ID
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# Owner comment
|
||||
comment_id = db.Column(db.Integer, db.ForeignKey('comment.id'))
|
||||
comment = db.relationship('Comment', uselist=False, back_populates="poll",
|
||||
foreign_keys=thread_id)
|
||||
|
||||
# Type
|
||||
type = db.Column(db.Enum(PollType))
|
||||
type = db.Column(db.String(20))
|
||||
|
||||
# Author
|
||||
author_id = db.Column(db.Integer, db.ForeignKey('member.id'))
|
||||
author = db.relationship('Member', backref=backref('polls'),
|
||||
foreign_keys=author_id)
|
||||
|
||||
# Title/question
|
||||
title = db.Column(db.UnicodeText)
|
||||
|
||||
# Start datetime
|
||||
start = db.Column(db.DateTime, default=datetime.now())
|
||||
|
||||
# End datetime
|
||||
end = db.Column(db.DateTime)
|
||||
|
||||
|
@ -41,6 +43,60 @@ class Poll(db.Model):
|
|||
# Other fields populated automatically through relations:
|
||||
# <answers> The list of answers (of type PollAnswer)
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': __tablename__,
|
||||
'polymorphic_on':type
|
||||
}
|
||||
|
||||
def __init__(self, author, title, choices, start=datetime.now(), end=datetime.now()):
|
||||
self.author = author
|
||||
self.title = title
|
||||
self.choices = choices
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def delete(self):
|
||||
"""Deletes a poll and its answers"""
|
||||
# TODO: move this out of class definition?
|
||||
for answer in SpecialPrivilege.query.filter_by(poll_id=self.id).all():
|
||||
db.session.delete(answer)
|
||||
db.session.commit()
|
||||
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
|
||||
# Common properties and methods
|
||||
@property
|
||||
def started(self):
|
||||
"""Returns whether the poll is open"""
|
||||
print(self.start, datetime.now())
|
||||
return self.start <= datetime.now()
|
||||
|
||||
@property
|
||||
def ended(self):
|
||||
"""Returns whether the poll is closed"""
|
||||
return self.end < datetime.now()
|
||||
|
||||
def has_voted(self, user):
|
||||
"""Returns wheter the user has voted"""
|
||||
# TODO: use ORM for this dirty request
|
||||
return user in [a.author for a in self.answers]
|
||||
|
||||
def can_vote(self, user):
|
||||
"""Returns true if the current user can vote.
|
||||
More conditions may be added in the future"""
|
||||
return user.is_authenticated
|
||||
|
||||
# Poll-specific methods. Must be overrided per-poll definition
|
||||
def vote(self, user, data):
|
||||
"""Return a PollAnswer object from specified user and data"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
"""Returns an easy-to-use object with answers of the poll."""
|
||||
return None
|
||||
|
||||
|
||||
class PollAnswer(db.Model):
|
||||
"""An answer to a poll"""
|
||||
|
@ -52,7 +108,7 @@ class PollAnswer(db.Model):
|
|||
|
||||
# Poll
|
||||
poll_id = db.Column(db.Integer, db.ForeignKey('poll.id'))
|
||||
poll = db.relationship('Poll', back_populates="answers",
|
||||
poll = db.relationship('Poll', backref=backref('answers'),
|
||||
foreign_keys=poll_id)
|
||||
|
||||
# Author. Must be Member
|
||||
|
@ -60,4 +116,8 @@ class PollAnswer(db.Model):
|
|||
author = db.relationship('Member', foreign_keys=author_id)
|
||||
|
||||
# Choice(s)
|
||||
choices = db.Column(db.PickleType)
|
||||
answer = db.Column(db.PickleType)
|
||||
|
||||
def __init__(self, user, answer):
|
||||
self.author = user
|
||||
self.answer = answer
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
from app import db
|
||||
from app.models.poll import Poll, PollAnswer
|
||||
from collections import Counter
|
||||
|
||||
class SimplePoll(Poll):
|
||||
"""Poll with only one answer allowed"""
|
||||
|
||||
__tablename__ = 'simplepoll'
|
||||
|
||||
# Names of templates
|
||||
template = 'simplepoll.html'
|
||||
|
||||
__mapper_args__ = {
|
||||
'polymorphic_identity': __tablename__,
|
||||
}
|
||||
|
||||
def __init__(self, author, title, choices, **kwargs):
|
||||
choices = [Choice(i, t) for i, t in enumerate(choices)]
|
||||
super().__init__(author, title, choices, **kwargs)
|
||||
|
||||
# Mandatory methods
|
||||
def vote(self, user, data):
|
||||
data = [data] # TODO
|
||||
answer = PollAnswer(user, data)
|
||||
return answer
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
values = {c: 0 for c in self.choices}
|
||||
counter = Counter(values)
|
||||
for answer in self.answers:
|
||||
counter.update([answer.answer])
|
||||
return counter
|
||||
|
||||
|
||||
class Choice():
|
||||
def __init__(self, id, title):
|
||||
self.id = id
|
||||
self.title = title
|
|
@ -121,6 +121,7 @@ class Member(User):
|
|||
|
||||
# Other fields populated automatically through relations:
|
||||
# <notifications> List of unseen notifications (of type Notification)
|
||||
# <polls> Polls created by the member (of class Poll)
|
||||
|
||||
def __init__(self, name, email, password):
|
||||
"""Register a new user."""
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
from app import app, db
|
||||
from flask import request
|
||||
from flask_login import current_user
|
||||
|
||||
from app.models.poll import Poll
|
||||
|
||||
@app.route("/poll/<int:poll_id>", methods=['POST'])
|
||||
def poll_submit(poll_id):
|
||||
p = Poll.query.first_or_404()
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return 401
|
||||
if p.has_voted(current_user):
|
||||
return 403
|
||||
|
||||
try:
|
||||
resp = request.get_json()['text']
|
||||
except BadRequestKeyError:
|
||||
abort(400)
|
||||
return str(md(markdown))
|
|
@ -0,0 +1,37 @@
|
|||
{% macro wpoll(p) %}
|
||||
|
||||
{% import "widgets/polls/"+p.template as poll_template with context %}
|
||||
|
||||
<div class="poll">
|
||||
<h3>{{ p.title }}</h3>
|
||||
{# Poll has not begin #}
|
||||
{% if not p.started %}
|
||||
<p><i>Le sondage ouvrira le {{ p.start | date }}.</i></p>
|
||||
|
||||
{# Poll has ended: display results #}
|
||||
{% elif p.ended %}
|
||||
<div>Ce sondage est terminé. Voici les résultats.</div>
|
||||
{{ poll_template.results(p) }}
|
||||
|
||||
{# Current user is a guest #}
|
||||
{% elif not current_user.is_authenticated %}
|
||||
<p><i>Seuls les membres peuvent voter</i></p>
|
||||
|
||||
{# Current user cannot vote #}
|
||||
{% elif not p.can_vote(current_user) %}
|
||||
<p><i>Vous n'avez pas le droit de voter dans ce sondage. Désolé…</i></p>
|
||||
|
||||
{# Current user has already voted #}
|
||||
{% elif p.has_voted(current_user) %}
|
||||
<p><i>Vous avez déjà voté. Revenez le {{ p.ended | date }} pour voir les résultats</i></p>
|
||||
|
||||
{# Current user can vote #}
|
||||
{% else %}
|
||||
<form class="poll" action="/poll/{{ p.id }}" method="post" enctype="multipart/form-data">
|
||||
{{ poll_template.choices(p) }}
|
||||
<input type="submit" value="Envoyer">
|
||||
<input id="csrf_token" name="csrf_token" type="hidden" value="{{ csrf_token() }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
|
@ -0,0 +1,7 @@
|
|||
{% macro choices(p) %}
|
||||
<div>Default choices</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro results(p) %}
|
||||
<div>Default results.</div>
|
||||
{% endmacro %}
|
|
@ -0,0 +1,23 @@
|
|||
{% macro choices(poll) %}
|
||||
<fieldset>
|
||||
{% for choice in poll.choices %}
|
||||
<input type="radio" id="{{ poll.id }}-{{ choice.id }}" name="pollanwsers" value="{{ choice.title }}" />
|
||||
<label for="{{ poll.id }}-{{ choice.id }}">{{ choice.title }}</label><br>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro results(poll) %}
|
||||
<table>
|
||||
{% for choice, votes in poll.results.most_common() %}
|
||||
<tr>
|
||||
<td><label for="{{ poll.id }}-{{ choice.id }}">{{ choice.title }}</label></td>
|
||||
<td>
|
||||
<progress id="{{ poll.id }}-{{ choice.id }}" value="{{ votes }}" max="{{ len(poll.answers) }}">
|
||||
{{ votes / len(poll.answers) if len(poll.answers) else 0 }} % ({{ votes }})
|
||||
</progress>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endmacro %}
|
Loading…
Reference in New Issue