Skip to content

Commit

Permalink
Merge pull request #4428 from magfest/fix-magdev1339
Browse files Browse the repository at this point in the history
Add guidebook images to MIVS show info step
  • Loading branch information
kitsuta authored Nov 5, 2024
2 parents 77699b4 + 8199304 commit 7712b82
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Add header/thumbnail flag to MIVS images
Revision ID: f01a2ad10d79
Revises: 58756e2dfe4d
Create Date: 2024-11-05 15:23:12.065060
"""


# revision identifiers, used by Alembic.
revision = 'f01a2ad10d79'
down_revision = '58756e2dfe4d'
branch_labels = None
depends_on = None

from alembic import op
import sqlalchemy as sa



try:
is_sqlite = op.get_context().dialect.name == 'sqlite'
except Exception:
is_sqlite = False

if is_sqlite:
op.get_context().connection.execute('PRAGMA foreign_keys=ON;')
utcnow_server_default = "(datetime('now', 'utc'))"
else:
utcnow_server_default = "timezone('utc', current_timestamp)"

def sqlite_column_reflect_listener(inspector, table, column_info):
"""Adds parenthesis around SQLite datetime defaults for utcnow."""
if column_info['default'] == "datetime('now', 'utc')":
column_info['default'] = utcnow_server_default

sqlite_reflect_kwargs = {
'listeners': [('column_reflect', sqlite_column_reflect_listener)]
}

# ===========================================================================
# HOWTO: Handle alter statements in SQLite
#
# def upgrade():
# if is_sqlite:
# with op.batch_alter_table('table_name', reflect_kwargs=sqlite_reflect_kwargs) as batch_op:
# batch_op.alter_column('column_name', type_=sa.Unicode(), server_default='', nullable=False)
# else:
# op.alter_column('table_name', 'column_name', type_=sa.Unicode(), server_default='', nullable=False)
#
# ===========================================================================


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('indie_game_image', sa.Column('is_header', sa.Boolean(), server_default='False', nullable=False))
op.add_column('indie_game_image', sa.Column('is_thumbnail', sa.Boolean(), server_default='False', nullable=False))
op.create_unique_constraint(op.f('uq_lottery_application_attendee_id'), 'lottery_application', ['attendee_id'])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('uq_lottery_application_attendee_id'), 'lottery_application', type_='unique')
op.drop_column('indie_game_image', 'is_thumbnail')
op.drop_column('indie_game_image', 'is_header')
# ### end Alembic commands ###
40 changes: 30 additions & 10 deletions uber/models/mivs.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,23 +491,41 @@ def guidebook_location(self):
return ''

@property
def guidebook_image(self):
return self.best_screenshot_download_filenames()[0]
def guidebook_header(self):
for image in self.images:
if image.is_header:
return image
return ''

@property
def guidebook_thumbnail(self):
return self.best_screenshot_download_filenames()[1] \
if len(self.best_screenshot_download_filenames()) > 1 else self.best_screenshot_download_filenames()[0]
for image in self.images:
if image.is_thumbnail:
return image
return ''

@property
def guidebook_images(self):
image_filenames = [self.best_screenshot_download_filenames()[0]]
images = [self.best_screenshot_downloads()[0]]
if self.guidebook_image != self.guidebook_thumbnail:
image_filenames.append(self.guidebook_thumbnail)
images.append(self.best_screenshot_downloads()[1])
if not self.images:
return ['', '']

header = None
thumbnail = None
for image in self.images:
if image.is_header and not header:
header = image
if image.is_thumbnail and not thumbnail:
thumbnail = image

if not header:
header = self.images[0]
if not thumbnail:
thumbnail = self.images[1] if len(self.images) > 1 else self.images[0]

return image_filenames, images
if header == thumbnail:
return [header.filename], [header]
else:
return [header.filename, thumbnail.filename], [header, thumbnail]


class IndieGameImage(MagModel):
Expand All @@ -518,6 +536,8 @@ class IndieGameImage(MagModel):
description = Column(UnicodeText)
use_in_promo = Column(Boolean, default=False)
is_screenshot = Column(Boolean, default=True)
is_header = Column(Boolean, default=False)
is_thumbnail = Column(Boolean, default=False)

@property
def url(self):
Expand Down
14 changes: 14 additions & 0 deletions uber/site_sections/guests.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from uber.decorators import ajax, all_renderable, render
from uber.errors import HTTPRedirect
from uber.models import GuestMerch, GuestDetailedTravelPlan, GuestTravelPlans
from uber.model_checks import mivs_show_info_required_fields
from uber.utils import check
from uber.tasks.email import send_email

Expand Down Expand Up @@ -564,6 +565,19 @@ def mivs_show_info(self, session, guest_id, message='', **params):
if not params.get('show_info_updated'):
message = "Please confirm you have updated your studio's and game's information."

if not message and not guest.group.studio.contact_phone:
message = 'Please update your show information to enter a contact phone number for MIVS staff.'

if not message:
for game in guest.group.studio.games:
if not game.guidebook_header or not game.guidebook_thumbnail:
message = "Please upload a Guidebook header and thumbnail."
else:
message = mivs_show_info_required_fields(game)
if message:
message = f"{game.title} show info is missing something: {message}"
break

if not message:
guest.group.studio.show_info_updated = True
session.add(guest)
Expand Down
19 changes: 6 additions & 13 deletions uber/site_sections/mits.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,14 @@
from uber.errors import HTTPRedirect
from uber.models import Email, MITSDocument, MITSPicture, MITSTeam
from uber.tasks.email import send_email
from uber.utils import check, check_image_size, localized_now


def _check_pic_filetype(pic):
if pic.filename.split('.')[-1].lower() not in c.GUIDEBOOK_ALLOWED_IMAGE_TYPES:
return f'Image {pic.filename} is not one of the allowed extensions: '\
f'{readable_join(c.GUIDEBOOK_ALLOWED_IMAGE_TYPES)}.'
return ''
from uber.utils import check, check_image_size, localized_now, check_guidebook_image_filetype


def add_new_image(pic, game):
new_pic = MITSPicture(game_id=game.id,
filename=pic.filename,
content_type=pic.content_type.value,
extension=pic.filename.split('.')[-1].lower())
filename=pic.filename,
content_type=pic.content_type.value,
extension=pic.filename.split('.')[-1].lower())
with open(new_pic.filepath, 'wb') as f:
shutil.copyfileobj(pic.file, f)
return new_pic
Expand Down Expand Up @@ -230,7 +223,7 @@ def game(self, session, message='', **params):
# MITSPicture objects BEFORE checking image size

if header_image and header_image.filename:
message = _check_pic_filetype(header_image)
message = check_guidebook_image_filetype(header_image)
if not message:
header_pic = add_new_image(header_image, game)
header_pic.is_header = True
Expand All @@ -241,7 +234,7 @@ def game(self, session, message='', **params):

if not message:
if thumbnail_image and thumbnail_image.filename:
message = _check_pic_filetype(thumbnail_image)
message = check_guidebook_image_filetype(thumbnail_image)
if not message:
thumbnail_pic = add_new_image(thumbnail_image, game)
thumbnail_pic.is_thumbnail = True
Expand Down
63 changes: 50 additions & 13 deletions uber/site_sections/mivs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@
from cherrypy.lib.static import serve_file

from uber.config import c
from uber.custom_tags import format_image_size
from uber.decorators import all_renderable, csrf_protected
from uber.errors import HTTPRedirect
from uber.models import Attendee, Group, GuestGroup, IndieDeveloper, IndieStudio
from uber.utils import add_opt, check, check_csrf
from uber.models import Attendee, Group, GuestGroup, IndieDeveloper, IndieGameImage
from uber.utils import add_opt, check, check_csrf, check_image_size, check_guidebook_image_filetype


def add_new_image(pic, game):
new_pic = IndieGameImage(game_id=game.id,
filename=pic.filename,
content_type=pic.content_type.value,
extension=pic.filename.split('.')[-1].lower())
with open(new_pic.filepath, 'wb') as f:
shutil.copyfileobj(pic.file, f)
return new_pic


@all_renderable(public=True)
Expand Down Expand Up @@ -253,30 +264,56 @@ def confirm(self, session, csrf_token=None, decision=None):
'developers': developers
}

def show_info(self, session, id, message='', promo_image=None, **params):
def show_info(self, session, id, message='', **params):
game = session.indie_game(id=id)
header_pic, thumbnail_pic = None, None
cherrypy.session['studio_id'] = game.studio.id
if cherrypy.request.method == 'POST':
header_image = params.get('header_image')
thumbnail_image = params.get('thumbnail_image')
game.apply(params, bools=['tournament_at_event', 'has_multiplayer', 'leaderboard_challenge'],
restricted=False) # Setting restricted to false lets us define custom bools and checkgroups
game.studio.name = params.get('studio_name', '')

if not params.get('contact_phone', ''):
message = "Please enter a phone number for MIVS staff to contact your studio."
else:
game.studio.contact_phone = params.get('contact_phone', '')
if promo_image:
image = session.indie_game_image(params)
image.game = game
image.content_type = promo_image.content_type.value
image.extension = promo_image.filename.split('.')[-1].lower()
image.is_screenshot = False
message = check(image)

if header_image and header_image.filename:
message = check_guidebook_image_filetype(header_image)
if not message:
with open(image.filepath, 'wb') as f:
shutil.copyfileobj(promo_image.file, f)
message = check(game) or check(game.studio)
header_pic = add_new_image(header_image, game)
header_pic.is_header = True
if not check_image_size(header_pic.filepath, c.GUIDEBOOK_HEADER_SIZE):
message = f"Your header image must be {format_image_size(c.GUIDEBOOK_HEADER_SIZE)}."
elif not game.guidebook_header:
message = f"You must upload a {format_image_size(c.GUIDEBOOK_HEADER_SIZE)} header image."

if not message:
if thumbnail_image and thumbnail_image.filename:
message = check_guidebook_image_filetype(thumbnail_image)
if not message:
thumbnail_pic = add_new_image(thumbnail_image, game)
thumbnail_pic.is_thumbnail = True
if not check_image_size(thumbnail_pic.filepath, c.GUIDEBOOK_THUMBNAIL_SIZE):
message = f"Your thumbnail image must be {format_image_size(c.GUIDEBOOK_THUMBNAIL_SIZE)}."
elif not game.guidebook_thumbnail:
message = f"You must upload a {format_image_size(c.GUIDEBOOK_THUMBNAIL_SIZE)} thumbnail image."

if not message:
message = check(game) or check(game.studio)
if not message:
session.add(game)
if header_pic:
if game.guidebook_header:
session.delete(game.guidebook_header)
session.add(header_pic)
if thumbnail_pic:
if game.guidebook_thumbnail:
session.delete(game.guidebook_thumbnail)
session.add(thumbnail_pic)

if game.studio.group.guest:
raise HTTPRedirect('../guests/mivs_show_info?guest_id={}&message={}',
game.studio.group.guest.id, 'Game information uploaded')
Expand Down
37 changes: 33 additions & 4 deletions uber/templates/mivs/show_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ <h2>Show Information for MIVS</h2>
<h4>Gameplay Images</h4>
Please mark your two best gameplay images, or upload new ones.
<table>
{% for screenshot in game.screenshots %}
{% for screenshot in game.screenshots|rejectattr('is_header')|rejectattr('is_thumbnail') %}
<tr>
<td><ul><li></li></ul></td>
<td><a target="_blank" href="{{ screenshot.url }}">{{ screenshot.filename }}</a></td>
Expand All @@ -49,10 +49,39 @@ <h4>Gameplay Images</h4>
{% endfor %}
</table>
<a class="btn btn-primary" href="screenshot?game_id={{ game.id }}&use_in_promo=True">Upload a Screenshot</a>

{# TODO: Add Guidebook image upload!! #}

<br/><br/>
<h4>Guidebook Images</h4>
Please upload images to accompany {{ game.title }}'s entry on Guidebook.<br/><br/>

<form method="post" enctype="multipart/form-data" class="form-horizontal" action="show_info">
<div class="form-group">
<label class="col-sm-3 control-label">Header Image</label>
<div class="col-sm-6">
<input type="file" name="header_image" />
{% if game.guidebook_header %}
<a target="_blank" href="{{ game.guidebook_header.url }}">{{ game.guidebook_header.filename }}</a>
{% endif %}
</div>
<p class="help-block col-sm-9 col-sm-offset-3">
A {{ c.GUIDEBOOK_HEADER_SIZE|format_image_size }} image to display on the schedule next to your game details.<br/>
{% if game.guidebook_header %}Uploading a file will replace the existing image.{% endif %}
</p>
</div>

<div class="form-group">
<label class="col-sm-3 control-label">Thumbnail Image</label>
<div class="col-sm-6">
<input type="file" name="thumbnail_image" />
{% if game.guidebook_thumbnail %}
<a target="_blank" href="{{ game.guidebook_thumbnail.url }}">{{ game.guidebook_thumbnail.filename }}</a>
{% endif %}
</div>
<p class="help-block col-sm-9 col-sm-offset-3">
A {{ c.GUIDEBOOK_THUMBNAIL_SIZE|format_image_size }} image to display on the schedule next to your game name.<br/>
{% if game.guidebook_thumbnail %}Uploading a file will replace the existing image.{% endif %}
</p>
</div>

{{ csrf_token() }}
<input type="hidden" name="id" value="{{ game.id }}" />

Expand Down
11 changes: 10 additions & 1 deletion uber/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,10 +637,19 @@ def check_image_size(image, size_list):
try:
return Image.open(image).size == tuple(map(int, size_list))
except OSError:
# This probably isn't an image, so it's not a header image
# This probably isn't an image at all
return


def check_guidebook_image_filetype(pic):
from uber.custom_tags import readable_join

if pic.filename.split('.')[-1].lower() not in c.GUIDEBOOK_ALLOWED_IMAGE_TYPES:
return f'Image {pic.filename} is not one of the allowed extensions: '\
f'{readable_join(c.GUIDEBOOK_ALLOWED_IMAGE_TYPES)}.'
return ''


def validate_model(forms, model, preview_model=None, is_admin=False):
from wtforms import validators

Expand Down

0 comments on commit 7712b82

Please sign in to comment.