class Rating(db.Model): """The rating of a category is set to 0 to disable this category. It is then not counted into the average score. """ id = db.Column(db.Integer, primary_key=True) score = db.Column(db.SmallInteger) text = db.Column(db.Text) posted = db.Column(db.DateTime) game_id = db.Column(db.Integer, db.ForeignKey('game.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __init__(self, game, user, text, score): self.game = game self.user = user self.text = text self.posted = datetime.utcnow() self.score = score def __repr__(self): return f"<Rating {self.id}:{self.score}>" def set(self, category, value): if category in (None, "overall"): self.score = value else: setattr(self, "score_" + category, value) def get(self, category): return self.score if category in (None, "overall") else getattr( self, "score_" + category)
class RatingSkip(db.Model): id = db.Column(db.Integer, primary_key=True) reason = db.Column(db.Enum("platform", "uninteresting", "crash")) entry_id = db.Column(db.Integer, db.ForeignKey("entry.id")) participant_id = db.Column(db.Integer, db.ForeignKey("participant.id")) def __init__(self, participant, entry, reason): self.participant = participant self.entry = entry self.reason = reason def __repr__(self): return "<RatingSkip %r>" % self.id
class Participation(db.Model): id = db.Column(db.Integer, primary_key = True) team_id = db.Column(db.Integer, db.ForeignKey("team.id")) jam_id = db.Column(db.Integer, db.ForeignKey("jam.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) show_in_finder = db.Column(db.Boolean, default = True) registered = db.Column(db.DateTime) def __init__(self, user, jam, show_in_finder = True): self.user = user self.jam = jam self.show_in_finder = show_in_finder self.registered = datetime.utcnow()
class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) text = db.Column(db.Text) posted = db.Column(db.DateTime) entry_id = db.Column(db.Integer, db.ForeignKey('entry.id')) participant_id = db.Column(db.Integer, db.ForeignKey('participant.id')) def __init__(self, text, entry, participant): self.text = text self.entry = entry self.participant = participant self.posted = datetime.utcnow() def __repr__(self): return '<Comment %r>' % self.id
class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) text = db.Column(db.Text) posted = db.Column(db.DateTime) game_id = db.Column(db.Integer, db.ForeignKey('game.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) def __init__(self, text, game, user): self.text = text self.game = game self.user = user self.posted = datetime.utcnow() def __repr__(self): return f'<Comment {self.id}>'
class EntryPackage(db.Model): id = db.Column(db.Integer, primary_key=True) url = db.Column(db.String(256)) entry_id = db.Column(db.Integer, db.ForeignKey("entry.id")) type = db.Column( db.Enum( "web", # Flash, html5, js... "linux", # Linux binaries (e.g. *.tar.gz) "linux32", # Linux32 binaries (e.g. *.tar.gz) "linux64", # Linux64 binaries (e.g. *.tar.gz) "windows", # Windows binaries (e.g. *.zip, *.exe) "windows64", # Windows64 binaries (e.g. *.zip, *.exe) "mac", # MacOS application packages "combi", # Linux + Windows + Source (and more, optional) "love", # Löve packages "blender", # Blender save file (*.blend) "source", # Source package (e.g. *.zip or *.tar.gz) "git", # Version control repository: GIT "svn", # Version control repository: SVN "hg", # Version control repository: HG "unknown")) def __init__(self, entry, url, type="unknown"): self.url = url self.type = type self.entry = entry def __repr__(self): return "<EntryPackage %r>" % self.id def typeString(self): return entry_package_type_string(self.type)
class GameScreenshot(db.Model): id = db.Column(db.Integer, primary_key=True) url = db.Column(db.String(255)) caption = db.Column(db.Text) index = db.Column(db.Integer) # 0..n-1 game_id = db.Column(db.Integer, db.ForeignKey("game.id")) def __init__(self, url, caption, game): self.game = game self.url = url self.caption = caption self.index = len(self.game.screenshots) - 1 def __repr__(self): return "<GameScreenshot %r>" % self.id def move(self, x): all = self.game.screenshotsOrdered old = self.index new = self.index + x if new >= len(all): new = len(all) - 1 if new < 0: new = 0 if new != self.index: other = all[new] self.index = new other.index = old
class Vote(db.Model): id = db.Column(db.Integer, primary_key=True) game_id = db.Column(db.Integer, db.ForeignKey('game.id')) jam_id = db.Column(db.Integer, db.ForeignKey('jam.id')) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) voted_at = db.Column(db.DateTime) def __init__(self, game, user): self.game = game self.jam = game.jam self.user = user self.voted_at = datetime.utcnow() self.game_id = game.id self.jam_id = game.jam_id self.user_id = user.id
class JamPhoto(db.Model): id = db.Column(db.Integer, primary_key=True) jam_id = db.Column(db.Integer, db.ForeignKey('jam.id')) photo = db.Column(LONGBLOB) def __init__(self, jam_id, photo): self.jam_id = jam_id self.photo = photo
class EntryScreenshot(db.Model): id = db.Column(db.Integer, primary_key=True) url = db.Column(db.String(256)) caption = db.Column(db.Text) entry_id = db.Column(db.Integer, db.ForeignKey("entry.id")) def __init__(self, url, caption, entry): self.entry = entry self.url = url self.caption = caption def __repr__(self): return "<EntryScreenshot %r>" % self.id
class Rating(db.Model): id = db.Column(db.Integer, primary_key=True) score_gameplay = db.Column(db.SmallInteger) score_graphics = db.Column(db.SmallInteger) score_audio = db.Column(db.SmallInteger) score_innovation = db.Column(db.SmallInteger) score_story = db.Column(db.SmallInteger) score_technical = db.Column(db.SmallInteger) score_controls = db.Column(db.SmallInteger) score_overall = db.Column(db.SmallInteger) text = db.Column(db.Text) posted = db.Column(db.DateTime) entry_id = db.Column(db.Integer, db.ForeignKey('entry.id')) participant_id = db.Column(db.Integer, db.ForeignKey('participant.id')) def __init__(self, score_gameplay, score_graphics, score_audio, score_innovation, score_story, score_technical, score_controls, score_overall, text, entry, participant): self.score_gameplay = score_gameplay self.score_graphics = score_graphics self.score_audio = score_audio self.score_innovation = score_innovation self.score_story = score_story self.score_technical = score_technical self.score_controls = score_controls self.score_overall = score_overall self.text = text self.entry = entry self.participant = participant self.posted = datetime.utcnow() def __repr__(self): return '<Rating %r>' % self.id def getAverage(self): return (self.score_gameplay + self.score_graphics + self.score_audio + self.score_innovation + self.score_story + self.score_technical + self.score_controls + self.score_overall) * 1.0 / 8.0
class Invitation(db.Model): id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, db.ForeignKey("team.id")) user_id = db.Column(db.Integer, db.ForeignKey("user.id")) def __init__(self, team, user): self.team = team self.user = user def url(self, **values): return url_for("invitation", id=self.id, _external=True, **values) def canAccept(self): return self.team.jam.getStatus().code <= JamStatusCode.PACKAGING def accept(self): self.team.userJoin(self.user) db.session.delete(self) db.session.commit() def decline(self): db.session.delete(self) db.session.commit()
class GamescomApplication(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) year = db.Column(db.Integer) title = db.Column(db.String(128)) city = db.Column(db.String(128)) country = db.Column(db.String(128)) zip_code = db.Column(db.String(128)) street = db.Column(db.String(128)) job_title = db.Column(db.String(128)) experience = db.Column(db.String(128)) reason = db.Column(db.String(255)) travel_funding_amount = db.Column(db.Integer) travel_funding_reason = db.Column(db.String(255)) def __init__(self, user): self.user = user self.user_id = user.id
class Game(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(128)) slug = db.Column(db.String(128)) created = db.Column(db.DateTime) description = db.Column(db.Text) technology = db.Column(db.Text) help = db.Column(db.Text) is_deleted = db.Column(db.Boolean, default=False) has_cheated = db.Column(db.Boolean, default=False) jam_id = db.Column(db.Integer, db.ForeignKey('jam.id')) team_id = db.Column(db.Integer, db.ForeignKey('team.id')) ratings = db.relationship('Rating', backref='game', lazy="subquery") comments = db.relationship('Comment', backref='game', lazy="subquery") packages = db.relationship('GamePackage', backref='game', lazy="subquery") screenshots = db.relationship('GameScreenshot', backref='game', lazy="subquery") votes = db.relationship('Vote', backref='game', lazy="subquery") # score_CATEGORY_enabled = db.Column(db.Boolean, default = True) def __init__(self, team, title): self.team = team self.jam = team.jam self.title = title self.slug = get_slug(title) self.created = datetime.utcnow() def __repr__(self): return '<Game %r>' % self.title def destroy(self): # destroy all ratings, comments, packages, screenshots for rating in self.ratings: db.session.delete(rating) for comment in self.comments: db.session.delete(comment) for package in self.packages: db.session.delete(package) for screenshot in self.screenshots: db.session.delete(screenshot) for vote in self.votes: db.session.delete(vote) db.session.delete(self) def url(self, **values): return url_for("show_game", jam_slug=self.jam.slug, game_id=self.id, **values) @property def screenshotsOrdered(self): return sorted(self.screenshots, lambda s1, s2: int(s1.index - s2.index)) @property def firstScreenshot(self): return self.screenshots.first() @property def score(self): if self.has_cheated: return -10 return average([r.score for r in self.ratings if not r.user.is_deleted]) or 0 def feedbackAverage(self, category): if category in (None, "overall"): return self.score return average_non_zero([r.get(category) for r in self.ratings]) @property def rank(self): db.engine.execute("SET @rank=0;") rank = db.engine.execute( "SELECT rank FROM (SELECT @rank:=@rank+1 AS rank, title, gameId, points FROM (SELECT v.game_id as gameId, g.title as title, count(*) as points FROM vote v LEFT JOIN game g ON g.id = v.game_id where v.jam_id = " + str( self.jam_id) + " GROUP BY v.game_id ORDER BY points DESC) t1) t2 WHERE gameId = " + str(self.id)).first() return rank.rank @property def numberRatings(self): return len(self.ratings) @property def numberVotes(self): return len(self.votes) def getVoteByUser(self, user): return Vote.query.filter_by(user_id=user.id).first() def getVoteCountByUser(self, user): return Vote.query.filter_by(user_id=user.id, jam_id=self.jam_id).count() @property def ratingCategories(self): return [c for c in RATING_CATEGORIES if getattr(self, "score_" + c + "_enabled")] def getRatingByUser(self, user): return Rating.query.filter_by(user_id=user.id).first()
class Game(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(128)) slug = db.Column(db.String(128)) created = db.Column(db.DateTime) description = db.Column(db.Text) technology = db.Column(db.Text) help = db.Column(db.Text) is_deleted = db.Column(db.Boolean, default=False) has_cheated = db.Column(db.Boolean, default=False) jam_id = db.Column(db.Integer, db.ForeignKey('jam.id')) team_id = db.Column(db.Integer, db.ForeignKey('team.id')) ratings = db.relationship('Rating', backref='game', lazy="subquery") comments = db.relationship('Comment', backref='game', lazy="subquery") packages = db.relationship('GamePackage', backref='game', lazy="subquery") screenshots = db.relationship('GameScreenshot', backref='game', lazy="subquery") def __init__(self, team, title): self.team = team self.jam = team.jam self.title = title self.slug = get_slug(title) self.created = datetime.utcnow() def __repr__(self): return f"<Game {self.title}>" def destroy(self): # destroy all ratings, comments, packages, screenshots for rating in self.ratings: db.session.delete(rating) for comment in self.comments: db.session.delete(comment) for package in self.packages: db.session.delete(package) for screenshot in self.screenshots: db.session.delete(screenshot) db.session.delete(self) def url(self, **kwargs): return url_for("show_game", jam_slug=self.jam.slug, game_id=self.id, **kwargs) @property def screenshots_ordered(self): return sorted(self.screenshots, key=lambda s: s.index) @property def score(self): if self.has_cheated: return -10 return average( [r.score for r in self.ratings if not r.user.is_deleted]) or 0 def feedback_average(self, category): if category in (None, "overall"): return self.score return average_non_zero([r.get(category) for r in self.ratings]) @property def rank(self): jam_games = list(self.jam.games.all()) jam_games.sort(key="score", reverse=True) return jam_games.index(self) + 1 @property def number_ratings(self): return len(self.ratings) @property def rating_categories(self): return [ c for c in RATING_CATEGORIES if getattr(self, "score_" + c + "_enabled") ] def get_rating_by_user(self, user): return Rating.query.filter_by(user_id=user.id).first()
class Team(db.Model): id = db.Column(db.Integer, primary_key=True) jam_id = db.Column(db.Integer, db.ForeignKey("jam.id")) name = db.Column(db.String(80)) description = db.Column(db.Text) livestreams = db.Column(db.Text) # list of livestreams, one URL per file irc = db.Column(db.String(128)) participations = db.relationship("Participation", backref="team", lazy="subquery") invitations = db.relationship("Invitation", backref="team", lazy="subquery") games = db.relationship("Game", backref="team", lazy="subquery") def __init__(self, user, jam): self.jam = jam self.user_join(user) self.name = user.username + "'s team" @property def members(self): return [r.user for r in self.participations] @property def game(self): return self.games[0] if self.games else None @property def is_single_team(self): return len(self.participations) == 1 def url(self, **kwargs): return url_for("jam_team", jam_slug=self.jam.slug, team_id=self.id, **kwargs) def user_join(self, user): r = user.get_participation(self.jam) if not r: # register user, but do not create automatic team, we don't need # that anyway user.join_jam(self.jam, False) elif r in self.participations: return # user is already in this team elif r.team and r.team != self: r.team.user_leave(user) r.team = self db.session.commit() def user_leave(self, user): r = user.get_participation(self.jam) if r.team != self: return # not in this team, nevermind ;) if self.is_single_team: # only user in team, we can destroy this team self.destroy() r.team = None db.session.commit() def destroy(self): # also destroy all the games, invitations for game in self.games: game.destroy() for invitation in self.invitations: db.session.delete(invitation) db.session.delete(self) @property def number_members_and_invitations(self): return len(self.members) + len(self.invitations) def can_invite(self, user): return user in self.members and ( self.jam.team_limit == 0 or self.jam.team_limit > self.number_members_and_invitations) def get_invitation(self, user): return Invitation.query.filter_by(user_id=user.id, team_id=self.id).first() def invite_user(self, user, sender): # sender: which user sent the invitation if not user.notify_team_invitation: return None if self.get_invitation(user): i = self.get_invitation(user) # already invited else: i = Invitation(self, user) db.session.add(i) db.session.commit() body = render_template("emails/invitation.txt", team=self, sender=sender, recipient=user, invitation=i) mail.send_message(subject=app.config["LONG_NAME"] + ": You have been invited to " + self.name, recipients=[user.email], body=body) return i
class GamePackage(db.Model): id = db.Column(db.Integer, primary_key=True) url = db.Column(db.String(255)) game_id = db.Column(db.Integer, db.ForeignKey("game.id")) type = db.Column( db.Enum( "web", # Flash, html5, js... "linux", # Linux binaries (e.g. *.tar.gz) "linux32", # Linux32 binaries (e.g. *.tar.gz) "linux64", # Linux64 binaries (e.g. *.tar.gz) "windows", # Windows binaries (e.g. *.zip, *.exe) "windows64", # Windows64 binaries (e.g. *.zip, *.exe) "mac", # MacOS application packages "combi", # Linux + Windows + Source (and more, optional) "love", # Löve packages "blender", # Blender save file (*.blend) "source", # Source package (e.g. *.zip or *.tar.gz) "git", # Version control repository: GIT "svn", # Version control repository: SVN "hg", # Version control repository: HG "unknown")) def __init__(self, game, url, type="unknown"): self.url = url self.type = type self.game = game def getLink(self): return Markup('<a href="%s" target="_blank">%s</a>' % (self.url, GamePackage.typeString(self.type))) def getLinkShort(self): return Markup('<a href="%s">%s</a>' % (self.url, GamePackage.typeStringShort(self.type))) def __repr__(self): return "<GamePackage %r>" % self.id @staticmethod def typeString(type): if type in PACKAGE_TYPES: return PACKAGE_TYPES[type][0] return "Unknown" @staticmethod def typeStringShort(type): if type in PACKAGE_TYPES: return PACKAGE_TYPES[type][1] return "Unknown" @staticmethod def packageTypes(): return PACKAGE_TYPES @staticmethod def compare(left, right): x = right.getTotalScore() - left.getTotalScore() if x > 0: return 1 elif x < 0: return -1 else: return 0
from hashlib import sha512, md5 from flamejam import app, db, filters from flask import url_for, Markup import re import random # rating: # Participant <one2many> RatingSkip <many2one> Entry # Participant <one2many> Rating <many2one> Entry # Teams: # Participant <many2many> Entry team_members = db.Table( 'team_members', db.Column('entry_id', db.Integer, db.ForeignKey('entry.id')), db.Column('participant_id', db.Integer, db.ForeignKey('participant.id')), ) def get_slug(s): s = s.lower() s = re.sub(r"[\s_+]+", "-", s) s = re.sub("[^a-z0-9\-]", "", s) return s class Participant(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) password = db.Column(db.String(128))
class Jam(db.Model): id = db.Column(db.Integer, primary_key=True) slug = db.Column(db.String(128), unique=True) title = db.Column(db.String(128), unique=True) theme = db.Column(db.String(128)) announced = db.Column(db.DateTime) # Date on which the jam was announced start_time = db.Column(db.DateTime) # The jam starts at this moment end_time = db.Column(db.DateTime) # The jamming phase ends at this moment packaging_deadline = db.Column( db.DateTime) # Packaging ends at this moment rating_end = db.Column(db.DateTime) # Rating period ends and jam is over team_jam = db.Column(db.Boolean) entries = db.relationship('Entry', backref='jam', lazy='dynamic') author_id = db.Column(db.Integer, db.ForeignKey('participant.id')) def __init__(self, title, author, start_time, end_time=None, packaging_deadline=None, voting_end=None, team_jam=False, theme=''): self.title = title self.slug = get_slug(title) self.start_time = start_time self.theme = theme self.author = author self.team_jam = team_jam if end_time is None: self.end_time = start_time + timedelta(days=2) else: self.end_time = end_time if packaging_deadline is None: self.packaging_deadline = start_time + timedelta(days=3) else: self.packaging_deadline = packaging_deadline if self.rating_end is None: self.rating_end = start_time + timedelta(days=7) else: self.rating_end = rating_end self.announced = datetime.utcnow() def __repr__(self): return '<Jam %r>' % self.slug def getStatus(self): now = datetime.utcnow() if self.start_time > now: return JamStatus(JamStatusCode.ANNOUNCED, self.start_time) elif self.end_time > now: return JamStatus(JamStatusCode.RUNNING, self.end_time) elif self.packaging_deadline > now: return JamStatus(JamStatusCode.PACKAGING, self.packaging_deadline) elif self.rating_end > now: return JamStatus(JamStatusCode.RATING, self.rating_end) else: return JamStatus(JamStatusCode.FINISHED, self.end_time) def url(self, **values): return url_for('show_jam', jam_slug=self.slug, **values) def getTopEntries(self): e = list(self.entries.all()) e.sort(cmp=entryCompare) return e def getShuffledEntries(self): e = list(self.entries.all()) random.shuffle(e) return e
class Entry(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(128)) slug = db.Column(db.String(128)) description = db.Column(db.Text) posted = db.Column(db.DateTime) jam_id = db.Column(db.Integer, db.ForeignKey('jam.id')) participant_id = db.Column(db.Integer, db.ForeignKey('participant.id')) rating_skips = db.relationship('RatingSkip', backref='entry', lazy='dynamic') ratings = db.relationship('Rating', backref='entry', lazy='dynamic') comments = db.relationship('Comment', backref='entry', lazy='dynamic') packages = db.relationship('EntryPackage', backref='entry', lazy='dynamic') screenshots = db.relationship('EntryScreenshot', backref='entry', lazy='dynamic') def __init__(self, title, description, jam, participant): self.title = title self.slug = get_slug(title) self.description = description self.jam = jam self.participant = participant self.posted = datetime.utcnow() def __repr__(self): return '<Entry %r>' % self.title def url(self, action="", **values): return url_for("show_entry", jam_slug=self.jam.slug, entry_slug=self.slug, action=action, **values) def getAverageRating(self): categories = [ "gameplay", "graphics", "audio", "innovation", "story", "technical", "controls", "overall" ] r = {"average": 0} for c in categories: r[c] = 0 ratings = len(self.ratings.all()) if ratings > 0: for rating in self.ratings: for c in categories: r[c] += getattr(rating, "score_" + c) r["average"] += rating.getAverage() for c in categories: r[c] *= 1.0 / ratings r["average"] *= 1.0 / ratings return r def getTotalScore(self): s = 0 c = 0 av = self.getAverageRating() for x in av: s += av[x] c += 1 return s * 1.0 / c def getRank(self): jam_entries = list(self.jam.entries.all()) jam_entries.sort(cmp=entryCompare) return jam_entries.index(self) + 1