mirror of
https://github.com/godotengine/godot-showreel-voting.git
synced 2025-12-31 17:49:06 +03:00
* initial backend setup
* copied db setup
* added login
* added autoincrement to ids
* update db again
* added script to populate with test data
* added functionality to fetch videos
* added vote functionality
* added logic to update video info
* added history
* updated readme
* Update thank you page
* fixed small bugs
* small improvements
* added admin view, UI fix
* fix vote buttons style
* added pagination
* added command to load sample data from csv
* Autoplay videos
* Adding before you vote page
* added yt video id extractor
* improve visibility for the history page votes
* added logic for before you vote disclaimer
* Remind people to set videos to unlisted and allowed for embed
* Update home.html
* Update home.html
* Add progress bar to total
* Small adjustments
* fix home {% if user %}
* Update macros.html
* limit vote views to users with specific roles
* added endpoint to delete votes
* added confirmation to the delete vote action
* Update non-fund members CTA
* allow voting to specific fund members
* added result csv endpoint
* updated auth
* revert adding 'vote_allowed' column, merged it into is_staff
* added `is_fund_member` attribute to table and updated code
* refactored code
* update style for voting page
* updated readme
* Add static files
* remove options from dropdown for users that can't vote
* added keyboard shortcuts to vote buttons
* added azerty support for shortcuts
* update skip to S key
* remove placeholder
---------
Co-authored-by: Emi <2206700+coppolaemilio@users.noreply.github.com>
165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
import csv
|
|
from io import StringIO
|
|
from sqlalchemy import func
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
from flask import Blueprint, Response, current_app, g, redirect, render_template, request, url_for
|
|
|
|
from gdshowreelvote import auth
|
|
from gdshowreelvote.blueprints.forms import VOTE_ACTIONS, CastVoteForm, SelectVideoForm
|
|
from gdshowreelvote.database import DB, User, Video, Vote
|
|
from gdshowreelvote.utils import choose_random_video, get_total_votes, vote_data
|
|
|
|
|
|
bp = Blueprint('votes', __name__)
|
|
|
|
|
|
@bp.route('/')
|
|
def home():
|
|
content = render_template('home.html', user=g.user)
|
|
return render_template('default.html', content = content, user=g.user)
|
|
|
|
|
|
@bp.route('/about')
|
|
def about():
|
|
content = render_template('about.html')
|
|
return render_template('default.html', content = content, user=g.user)
|
|
|
|
|
|
@bp.route('/before-you-vote')
|
|
def before_you_vote():
|
|
content = render_template('before-you-vote.html')
|
|
return render_template('default.html', content = content, user=g.user)
|
|
|
|
@bp.route('/vote', methods=['GET'])
|
|
@bp.route('/vote/<int:video_id>', methods=['GET'])
|
|
@auth.vote_role_required
|
|
def vote_get(video_id=None):
|
|
if video_id:
|
|
video = DB.session.query(Video).filter(Video.id == video_id).first()
|
|
if not video:
|
|
current_app.logger.warning(f"Video with ID {video_id} not found.")
|
|
return "Video not found", 404
|
|
else:
|
|
video = choose_random_video(g.user)
|
|
|
|
data, progress = vote_data(g.user, video)
|
|
|
|
content = render_template('vote.html', data=data, progress=progress, cast_vote_form=CastVoteForm(), select_specific_video_form=SelectVideoForm())
|
|
return render_template('default.html', content = content, user=g.user, hide_nav=True)
|
|
|
|
|
|
@bp.route('/vote', methods=['POST'])
|
|
@auth.vote_role_required
|
|
def vote():
|
|
cast_vote_form = CastVoteForm()
|
|
select_specific_video_form = SelectVideoForm()
|
|
skip_videos = []
|
|
if cast_vote_form.validate():
|
|
action = cast_vote_form.action.data
|
|
video = DB.session.query(Video).filter(Video.id == cast_vote_form.video_id.data).first()
|
|
if not video:
|
|
current_app.logger.warning(f"Video with ID {cast_vote_form.video_id.data} not found.")
|
|
return "Video not found", 404
|
|
VOTE_ACTIONS[action](g.user, video)
|
|
if action == 'skip':
|
|
skip_videos.append(video.id)
|
|
else:
|
|
current_app.logger.warning(f"Form validation failed: {cast_vote_form.errors} {select_specific_video_form.errors}")
|
|
return "Invalid form submission", 400
|
|
|
|
video = choose_random_video(g.user, skip_videos)
|
|
data, progress = vote_data(g.user, video)
|
|
|
|
return render_template('vote.html', data=data, progress=progress, cast_vote_form=cast_vote_form, select_specific_video_form=select_specific_video_form)
|
|
|
|
|
|
@bp.route('/vote/<int:video_id>/delete', methods=['POST'])
|
|
@auth.vote_role_required
|
|
def delete_vote(video_id: int):
|
|
vote = DB.session.query(Vote).filter(Vote.user_id == g.user.id).filter(Vote.video_id == video_id).first()
|
|
if not vote:
|
|
current_app.logger.warning(f"Video with ID {video_id} not found.")
|
|
return "Video not found", 404
|
|
|
|
DB.session.delete(vote)
|
|
DB.session.commit()
|
|
|
|
return redirect(url_for('votes.history'))
|
|
|
|
|
|
@bp.route('/history')
|
|
@auth.vote_role_required
|
|
def history():
|
|
page = int(request.args.get('page', 1))
|
|
total_video_count = DB.session.query(Video).count()
|
|
total_user_votes = DB.session.query(Vote).filter(Vote.user_id == g.user.id).count()
|
|
progress = {
|
|
'total': total_video_count,
|
|
'current': total_user_votes,
|
|
}
|
|
query = DB.session.query(Vote).filter(Vote.user_id == g.user.id).order_by(Vote.created_at.desc())
|
|
|
|
try:
|
|
submitted_votes = DB.paginate(query, page=page, per_page=30)
|
|
except NotFound:
|
|
submitted_votes = DB.paginate(query, page=1, per_page=30)
|
|
|
|
|
|
# We probably want to add pagination here
|
|
content = render_template('history.html', progress=progress, submitted_votes=submitted_votes)
|
|
if request.args.get('page'):
|
|
return content
|
|
return render_template('default.html', content = content, user=g.user)
|
|
|
|
|
|
@bp.route('/admin')
|
|
@auth.admin_required
|
|
def admin_view():
|
|
page = int(request.args.get('page', 1))
|
|
vote_tally = get_total_votes(page)
|
|
|
|
content = render_template('admin.html', vote_tally=vote_tally)
|
|
if request.args.get('page'):
|
|
return content
|
|
return render_template('default.html', content = content, user=g.user)
|
|
|
|
|
|
@bp.route('/results')
|
|
@auth.admin_required
|
|
def download_vote_results():
|
|
result = (
|
|
DB.session.query(
|
|
Video,
|
|
func.count(Vote.id).filter(Vote.rating == 1).label("plus_votes"),
|
|
func.count(Vote.id).filter(Vote.rating == -1).label("minus_votes"),
|
|
func.count(Vote.id).filter((User.is_staff == True)).label("staff_votes"),
|
|
func.count(Vote.id).filter((User.is_fund_member == True)).label("fund_member_votes"),
|
|
)
|
|
.outerjoin(Vote, Vote.video_id == Video.id)
|
|
.outerjoin(User, User.id == Vote.user_id)
|
|
.group_by(Video.id)
|
|
.order_by(func.coalesce(func.sum(Vote.rating), 0).desc()).all()
|
|
)
|
|
|
|
csv_file = StringIO()
|
|
writer = csv.writer(csv_file)
|
|
writer.writerow(['Author', 'Follow-me link', 'Game', 'Video link', 'Download link', 'Contact email', 'Store Link', 'Positive votes', 'Negative votes', 'staff', 'fund_member'])
|
|
|
|
for video, plus_votes, minus_votes, staff_votes, fund_member_votes in result:
|
|
writer.writerow([
|
|
video.author_name,
|
|
video.follow_me_link,
|
|
video.game,
|
|
video.video_link,
|
|
video.video_download_link,
|
|
video.contact_email,
|
|
video.store_link,
|
|
plus_votes,
|
|
minus_votes,
|
|
staff_votes,
|
|
fund_member_votes
|
|
])
|
|
response = Response(csv_file.getvalue(), mimetype='text/csv')
|
|
response.headers["Content-Disposition"] = "attachment; filename=vote_results.csv"
|
|
return response |