added management for showreel status and submission history for users

This commit is contained in:
InigoAllende
2026-01-23 16:22:46 +01:00
parent 5ccdcabe8b
commit 96ce95ead7
7 changed files with 301 additions and 29 deletions

View File

@@ -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)
])

View File

@@ -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'))

View File

@@ -24,7 +24,7 @@ migrate = Migrate()
class ShowreelStatus(enum.Enum):
OPENED_TO_SUBMISSIONS = 'OPEN'
OPENED_TO_SUBMISSIONS = 'OPENED_TO_SUBMISSIONS'
VOTE = 'VOTE'
CLOSED = 'CLOSED'

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>