mirror of
https://github.com/godotengine/godot-showreel-voting.git
synced 2026-09-03 18:15:13 +03:00
added management for showreel status and submission history for users
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
from urllib.parse import urlparse
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import IntegerField, StringField, ValidationError, EmailField
|
||||
from wtforms import IntegerField, SelectField, StringField, ValidationError, EmailField
|
||||
from wtforms.validators import InputRequired
|
||||
|
||||
from gdshowreelvote.database import ShowreelStatus
|
||||
from gdshowreelvote.utils import downvote_video, skip_video, upvote_video
|
||||
|
||||
|
||||
@@ -42,3 +43,13 @@ class VideoSubmissionForm(FlaskForm):
|
||||
video_download_link = StringField('Video Download Link', validators=[InputRequired(), validate_urls])
|
||||
follow_me_link = StringField('Follow Me Link', validators=[InputRequired(), validate_urls])
|
||||
store_link = StringField('Store Link', validators=[InputRequired(), validate_urls])
|
||||
|
||||
|
||||
class ManageShowreelsForm(FlaskForm):
|
||||
showreel_id = SelectField('Showreel', validators=[InputRequired()], choices=[])
|
||||
showreel_status = SelectField('Showreel Status', validators=[InputRequired()],
|
||||
choices=[
|
||||
(ShowreelStatus.OPENED_TO_SUBMISSIONS.value, ShowreelStatus.OPENED_TO_SUBMISSIONS.value),
|
||||
(ShowreelStatus.VOTE.value, ShowreelStatus.VOTE.value),
|
||||
(ShowreelStatus.CLOSED.value, ShowreelStatus.CLOSED.value)
|
||||
])
|
||||
|
||||
@@ -5,8 +5,10 @@ from werkzeug.exceptions import NotFound
|
||||
|
||||
from flask import Blueprint, Response, current_app, g, redirect, render_template, request, url_for
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import or_
|
||||
from gdshowreelvote import auth
|
||||
from gdshowreelvote.blueprints.forms import VOTE_ACTIONS, CastVoteForm, SelectVideoForm, VideoSubmissionForm
|
||||
from gdshowreelvote.blueprints.forms import VOTE_ACTIONS, CastVoteForm, ManageShowreelsForm, SelectVideoForm, VideoSubmissionForm
|
||||
from gdshowreelvote.database import DB, Showreel, ShowreelStatus, User, Video, Vote
|
||||
from gdshowreelvote.utils import choose_random_video, get_total_votes, video_data, vote_data, voting_possible
|
||||
|
||||
@@ -130,9 +132,13 @@ def history():
|
||||
@bp.route('/admin')
|
||||
@auth.admin_required
|
||||
def admin_view():
|
||||
form = ManageShowreelsForm()
|
||||
showreels = DB.session.query(Showreel).all()
|
||||
form.showreel_id.choices = [(showreel.id, showreel.title) for showreel in showreels]
|
||||
|
||||
total_votes, positive_votes, vote_tally = get_total_votes()
|
||||
|
||||
content = render_template('admin.html', vote_tally=vote_tally, total_votes=total_votes, positive_votes=positive_votes)
|
||||
content = render_template('admin.html', form=form, vote_tally=vote_tally, total_votes=total_votes, positive_votes=positive_votes)
|
||||
if request.args.get('page'):
|
||||
return content
|
||||
return render_template('default.html', content = content, user=g.user)
|
||||
@@ -214,7 +220,11 @@ def post_submit():
|
||||
if not active_showreel or len(active_showreel) > 1:
|
||||
current_app.logger.warning("No active showreel or multiple active showreels found.")
|
||||
return render_template('error.html', title="Submissions Closed", message="Submissions are currently closed.")
|
||||
|
||||
duplicate_video = DB.session.query(Video).filter(or_(Video.video_link == form.video_link.data, Video.video_download_link == form.video_download_link.data)).first()
|
||||
if duplicate_video:
|
||||
form.errors.setdefault('video_link', []).append('A video with the same link or download link has already been submitted.')
|
||||
return render_template('submit.html', user=g.user, form=form)
|
||||
|
||||
active_showreel = active_showreel[0]
|
||||
new_video = Video(
|
||||
game=form.game.data,
|
||||
@@ -227,7 +237,100 @@ def post_submit():
|
||||
author=g.user,
|
||||
showreel=active_showreel
|
||||
)
|
||||
DB.session.add(new_video)
|
||||
try:
|
||||
DB.session.add(new_video)
|
||||
DB.session.commit()
|
||||
except IntegrityError as e:
|
||||
current_app.logger.error(f"Database integrity error while submitting video: {e}")
|
||||
DB.session.rollback()
|
||||
return render_template('submit.html', user=g.user, form=form)
|
||||
return render_template('home.html', user=g.user, submission_success=True) # TODO: Add flag to show submission success message
|
||||
|
||||
|
||||
@bp.route('/showreel/update-status', methods=['POST'])
|
||||
@auth.admin_required
|
||||
def update_showreel_status():
|
||||
form = ManageShowreelsForm()
|
||||
showreels = DB.session.query(Showreel).all()
|
||||
form.showreel_id.choices = [(showreel.id, showreel.title) for showreel in showreels]
|
||||
if not form.validate():
|
||||
return redirect(url_for('votes.admin_view'))
|
||||
showreel = DB.session.query(Showreel).filter(Showreel.id == form.showreel_id.data).first()
|
||||
if not showreel:
|
||||
return render_template('error.html', title="Showreel Not Found", message="The requested showreel was not found.")
|
||||
|
||||
showreel.status = form.showreel_status.data
|
||||
DB.session.commit()
|
||||
return redirect(url_for('votes.admin_view'))
|
||||
|
||||
|
||||
@bp.route('/user/submissions', methods=['GET'])
|
||||
@auth.login_required
|
||||
def user_submissions():
|
||||
open_showreel = DB.session.query(Showreel).filter(Showreel.status == ShowreelStatus.OPENED_TO_SUBMISSIONS).first()
|
||||
open_submissions = DB.session.query(Video).filter(Video.author_id == g.user.id).filter(Video.showreel == open_showreel).all()
|
||||
closed_submissions = DB.session.query(Video).filter(Video.author_id == g.user.id).filter(Video.showreel != open_showreel).all()
|
||||
|
||||
content = render_template('user-submissions.html', user=g.user, open_submissions=open_submissions, closed_submissions=closed_submissions, open_showreel=open_showreel)
|
||||
return render_template('default.html', content = content, user=g.user)
|
||||
|
||||
|
||||
@bp.route('/user/submissions/<int:video_id>/manage', methods=['GET'])
|
||||
@auth.login_required
|
||||
def manage_submission(video_id: int):
|
||||
video = DB.session.query(Video).filter(Video.id == video_id).filter(Video.author == g.user).first()
|
||||
|
||||
if not video:
|
||||
return render_template('error.html', title="Video Not Found", message="The requested video submission was not found.")
|
||||
|
||||
if video.showreel.status != ShowreelStatus.OPENED_TO_SUBMISSIONS:
|
||||
return render_template('error.html', title="Cannot Manage Submission", message="Submissions can only be managed while the showreel is open to submissions.")
|
||||
|
||||
form = VideoSubmissionForm(obj=video)
|
||||
|
||||
content = render_template('manage-submission.html', user=g.user, form=form, video_id=video.id)
|
||||
return render_template('default.html', content = content, user=g.user)
|
||||
|
||||
|
||||
@bp.route('/user/submissions/<int:video_id>/delete', methods=['POST'])
|
||||
@auth.login_required
|
||||
def delete_submission(video_id: int):
|
||||
video = DB.session.query(Video).filter(Video.id == video_id).filter(Video.author == g.user).first()
|
||||
|
||||
if not video:
|
||||
return render_template('error.html', title="Video Not Found", message="The requested video submission was not found.")
|
||||
|
||||
if video.showreel.status != ShowreelStatus.OPENED_TO_SUBMISSIONS:
|
||||
return render_template('error.html', title="Cannot Delete Submission", message="Submissions can only be deleted while the showreel is open to submissions.")
|
||||
|
||||
DB.session.delete(video)
|
||||
DB.session.commit()
|
||||
|
||||
return render_template('home.html', user=g.user, submission_success=True) # TODO: Add flag to show submission success message
|
||||
return redirect(url_for('votes.user_submissions'))
|
||||
|
||||
|
||||
@bp.route('/user/submissions/<int:video_id>/update', methods=['POST'])
|
||||
@auth.login_required
|
||||
def update_submission(video_id: int):
|
||||
video = DB.session.query(Video).filter(Video.id == video_id).filter(Video.author == g.user).first()
|
||||
|
||||
if not video:
|
||||
return render_template('error.html', title="Video Not Found", message="The requested video submission was not found.")
|
||||
|
||||
if video.showreel.status != ShowreelStatus.OPENED_TO_SUBMISSIONS:
|
||||
return render_template('error.html', title="Cannot Update Submission", message="Submissions can only be updated while the showreel is open to submissions.")
|
||||
|
||||
form = VideoSubmissionForm()
|
||||
if not form.validate():
|
||||
return render_template('update-submissions.html', user=g.user, submissions=[video], open_showreel=video.showreel, form=form)
|
||||
|
||||
video.game = form.game.data
|
||||
video.author_name = form.author_name.data
|
||||
video.contact_email = form.contact_email.data
|
||||
video.video_link = form.video_link.data
|
||||
video.video_download_link = form.video_download_link.data
|
||||
video.follow_me_link = form.follow_me_link.data
|
||||
video.store_link = form.store_link.data
|
||||
DB.session.commit()
|
||||
|
||||
return redirect(url_for('votes.user_submissions'))
|
||||
|
||||
@@ -24,7 +24,7 @@ migrate = Migrate()
|
||||
|
||||
|
||||
class ShowreelStatus(enum.Enum):
|
||||
OPENED_TO_SUBMISSIONS = 'OPEN'
|
||||
OPENED_TO_SUBMISSIONS = 'OPENED_TO_SUBMISSIONS'
|
||||
VOTE = 'VOTE'
|
||||
CLOSED = 'CLOSED'
|
||||
|
||||
|
||||
@@ -13,27 +13,39 @@
|
||||
}
|
||||
</style>
|
||||
<main id="admin">
|
||||
<h1 style="margin-bottom: 20px;">Vote results</h1>
|
||||
<div>
|
||||
<p>Total votes: <strong>{{ total_votes }}</strong></p>
|
||||
<p>Positive votes: <strong>{{ positive_votes }}</strong></p>
|
||||
<p>Negative votes: <strong>{{ total_votes - positive_votes }}</strong></p>
|
||||
</div>
|
||||
<br>
|
||||
<form action="{{ url_for('votes.download_vote_results') }}" method="get" style="display:inline;">
|
||||
<button type="submit" class="button primary small">Download results .csv</button>
|
||||
</form>
|
||||
<div>
|
||||
<div class="entries">
|
||||
{% for entry in vote_tally %}
|
||||
<div class="entry panel padded">
|
||||
<h2><a href="{{ url_for('votes.video_view', video_id=entry[0].id) }}">{{ entry[0].game }}</a></h2>
|
||||
<p>Category: <strong>{{ entry[0].showreel.title }}</strong></p>
|
||||
<p>Author: <strong>{{ entry[0].author_name }}</strong></p>
|
||||
<p>Vote sum: <strong>{{ entry[1] }}</strong></p>
|
||||
<p>Votes received: <strong>{{ entry[2] }}</strong></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<h1>Manage Shorwreels</h1>
|
||||
<form action="{{ url_for('votes.update_showreel_status') }}" method="post" style="display:inline;">
|
||||
{{ form.csrf_token }}
|
||||
|
||||
{{ form.showreel_id.label }}
|
||||
{{ form.showreel_id() }}
|
||||
|
||||
{{ form.showreel_status.label }}
|
||||
{{ form.showreel_status() }}
|
||||
|
||||
<button type="submit" class="button primary small">Save</button>
|
||||
</form>
|
||||
<h1 style="margin-bottom: 20px;">Vote results</h1>
|
||||
<div>
|
||||
<p>Total votes: <strong>{{ total_votes }}</strong></p>
|
||||
<p>Positive votes: <strong>{{ positive_votes }}</strong></p>
|
||||
<p>Negative votes: <strong>{{ total_votes - positive_votes }}</strong></p>
|
||||
</div>
|
||||
<br>
|
||||
<form action="{{ url_for('votes.download_vote_results') }}" method="get" style="display:inline;">
|
||||
<button type="submit" class="button primary small">Download results .csv</button>
|
||||
</form>
|
||||
<div>
|
||||
<div class="entries">
|
||||
{% for entry in vote_tally %}
|
||||
<div class="entry panel padded">
|
||||
<h2><a href="{{ url_for('votes.video_view', video_id=entry[0].id) }}">{{ entry[0].game }}</a></h2>
|
||||
<p>Category: <strong>{{ entry[0].showreel.title }}</strong></p>
|
||||
<p>Author: <strong>{{ entry[0].author_name }}</strong></p>
|
||||
<p>Vote sum: <strong>{{ entry[1] }}</strong></p>
|
||||
<p>Votes received: <strong>{{ entry[2] }}</strong></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -16,6 +16,9 @@
|
||||
</a>
|
||||
<div id="dropdown-menu" class="dropdown-content">
|
||||
<a href="/about">About</a>
|
||||
{% if user %}
|
||||
<a href="/user/submissions">Submissions</a>
|
||||
{% endif %}
|
||||
{% if user.can_vote() %}
|
||||
<a href="/vote">Vote</a>
|
||||
<a href="/history">History</a>
|
||||
|
||||
54
templates/manage-submission.html
Normal file
54
templates/manage-submission.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<main id="submission-form">
|
||||
<div>
|
||||
<h1>Showreel Submission</h1>
|
||||
<p>Fill in the form to submit your video for the Godot Showreel.</p>
|
||||
</div>
|
||||
<hr>
|
||||
<form method="POST" enctype="multipart/form-data" hx-post="{{ url_for('votes.update_submission', video_id=video_id, _method='POST') }}" hx-target='#submission-form' hx-swap='outerHTML'>
|
||||
{{ form.csrf_token }}
|
||||
|
||||
{{ form.game.label }}<br>
|
||||
{{ form.game(size=100) }}<br>
|
||||
{% for error in form.game.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.author_name.label }}<br>
|
||||
{{ form.author_name(size=100) }}<br>
|
||||
{% for error in form.author_name.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.contact_email.label }}<br>
|
||||
{{ form.contact_email(size=100) }}<br>
|
||||
{% for error in form.contact_email.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.video_link.label }}<br>
|
||||
{{ form.video_link(size=100) }}<br>
|
||||
{% for error in form.video_link.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.video_download_link.label }}<br>
|
||||
{{ form.video_download_link(size=100) }}<br>
|
||||
{% for error in form.video_download_link.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.follow_me_link.label }}<br>
|
||||
{{ form.follow_me_link(size=100) }}<br>
|
||||
{% for error in form.follow_me_link.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
{{ form.store_link.label }}<br>
|
||||
{{ form.store_link(size=100) }}<br>
|
||||
{% for error in form.store_link.errors %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endfor %}<br>
|
||||
|
||||
<button type="submit" class="button secondary">Update</button>
|
||||
</form>
|
||||
</main>
|
||||
89
templates/user-submissions.html
Normal file
89
templates/user-submissions.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<style>
|
||||
.entry {
|
||||
.info {
|
||||
display: grid;
|
||||
margin-bottom: 5px;
|
||||
gap: 10px;
|
||||
@media (min-width: 768px) {
|
||||
grid-template-columns: 120px 1fr;
|
||||
|
||||
}
|
||||
.category {
|
||||
font-size: 12px;
|
||||
margin: 4px 0px;
|
||||
}
|
||||
}
|
||||
.image-container {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
img {
|
||||
max-width: 100%;
|
||||
margin-bottom: -15px;
|
||||
margin-top: -11px;
|
||||
width: auto;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<main id="user-submissions">
|
||||
<h1>User Submissions</h1>
|
||||
<hr>
|
||||
{% if open_submissions %}
|
||||
<h2>Open Submissions</h2>
|
||||
<div>
|
||||
{% for entry in open_submissions %}
|
||||
<div class="entry panel padded">
|
||||
<div class="info">
|
||||
<div>
|
||||
<div class="image-container">
|
||||
<img src="https://img.youtube.com/vi/{{ entry.parse_youtube_video_id() }}/default.jpg" alt="Video thumbnail" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>{{ entry.game }}</h2>
|
||||
<p><strong>{{ entry.author_name }}</strong></p>
|
||||
<p class="category">{{ entry.showreel.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{url_for('votes.manage_submission', video_id=entry.id) }}" style="text-decoration: none;">
|
||||
<form action="{{ url_for('votes.manage_submission', video_id=entry.id) }}" method="get" style="display:inline-block; margin-top: 10px;">
|
||||
<button type="submit" class="button secondary">Edit entry</button>
|
||||
</form>
|
||||
</a>
|
||||
<form action="{{ url_for('votes.delete_submission', video_id=entry.id) }}" method="post" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this entry?');">
|
||||
<button type="submit" class="button tertiary">Delete entry</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr>
|
||||
{% endif %}
|
||||
{% if closed_submissions %}
|
||||
<h2>Closed Submissions</h2>
|
||||
<div>
|
||||
{% for entry in closed_submissions %}
|
||||
<div class="entry panel padded">
|
||||
<div class="info">
|
||||
<div>
|
||||
<div class="image-container">
|
||||
<img src="https://img.youtube.com/vi/{{ entry.parse_youtube_video_id() }}/default.jpg" alt="Video thumbnail" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2>{{ entry.game }}</h2>
|
||||
<p><strong>{{ entry.author_name }}</strong></p>
|
||||
<p class="category">{{ entry.showreel.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not open_submissions and not closed_submissions %}
|
||||
<p>You haven't submitted any entries yet.</p>
|
||||
{% endif %}
|
||||
</main>
|
||||
Reference in New Issue
Block a user