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 team_limit = db.Column(db.Integer) # 0 = no limit games = db.relationship('Game', backref="jam", lazy="subquery") participations = db.relationship("Participation", backref="jam", lazy="subquery") teams = db.relationship("Team", backref="jam", lazy="subquery") description = db.Column(db.Text) restrictions = db.Column(db.Text) registration_duration = db.Column(db.Integer) # hours packaging_duration = db.Column(db.Integer) # hours rating_duration = db.Column(db.Integer) # hours duration = db.Column(db.Integer) # hours # last notification that was sent, e.g. 0 = announcement, 1 = registration, (see status codes) last_notification_sent = db.Column(db.Integer, default=-1) def __init__(self, title, start_time, duration=48, team_limit=0, theme=''): self.title = title self.slug = get_slug(title) self.start_time = start_time self.duration = duration self.registration_duration = 24 * 14 self.packaging_duration = 24 * 1 self.rating_duration = 24 * 5 self.announced = datetime.utcnow() self.theme = theme self.team_limit = team_limit @property def participants(self): return [r.user for r in self.participations] @property def end_time(self): return self.start_time + timedelta(hours=self.duration) @property def packaging_deadline(self): return self.end_time + timedelta(hours=self.packaging_duration) @property def rating_end(self): return self.packaging_deadline + timedelta(hours=self.rating_duration) @property def registration_start(self): return self.start_time - timedelta(hours=self.registration_duration) def __repr__(self): return f"<Jam {self.slug}>" def get_status(self): now = datetime.utcnow() if self.registration_start > now: return JamStatus(JamStatusCode.ANNOUNCED, self.start_time) elif self.start_time > now: return JamStatus(JamStatusCode.REGISTRATION, 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, **kwargs): return url_for('jam_info', jam_slug=self.slug, **kwargs) def games_filtered_by_package_types(self, filters): games = Game.query.filter_by(is_deleted=False).filter_by( jam_id=self.id) if filters == set(): games = games elif 'packaged' in filters: games = games.join(GamePackage) else: games = games.filter( GamePackage.type.in_(filters)).join(GamePackage) return games.all() def games_by_score(self, filters=set()): e = self.games_filtered_by_package_types(filters) e.sort(key=Game.score.fget, reverse=True) return e def games_by_total_ratings(self, filters=set()): e = self.games_filtered_by_package_types(filters) e.sort(key=Game.number_ratings.fget) return e @property def show_theme(self): return self.get_status().code >= JamStatusCode.RUNNING and self.theme @property def show_ratings(self): return self.get_status().code == JamStatusCode.FINISHED def get_link(self): s = f'<a class="jam" href="{self.url()}">{self.title}</a>' if self.show_theme: s += f' <span class="theme">{self.theme}</span>' return Markup(s) def send_all_notifications(self): last = -1 for n in range(self.last_notification_sent + 1, self.get_status().code + 1): if self.send_notification(n): last = n return last def send_notification(self, n): if not JamStatusCode.ANNOUNCED <= n <= JamStatusCode.FINISHED: return False kwargs = {} if n == JamStatusCode.ANNOUNCED: template = "announcement" notify = "new_jam" subject = f"Jam announced: {self.title}" elif n == JamStatusCode.REGISTRATION: template = "registration_start" notify = "new_jam" subject = f"Registrations for {self.title} now open" elif n == JamStatusCode.RUNNING: template = "start" notify = "jam_start" subject = f"{self.title} starts now!" elif n == JamStatusCode.PACKAGING: template = "packaging_start" notify = "jam_finish" subject = f"{self.title} is over" elif n == JamStatusCode.RATING: template = "rating_start" notify = "jam_finish" subject = f"Rating for {self.title} starts now" elif n == JamStatusCode.FINISHED: template = "finished" notify = "jam_finish" subject = f"Rating for {self.title} finished - Winners" kwargs = {"games": self.games_by_score()[:3]} if n >= JamStatusCode.RUNNING and n != JamStatusCode.RATING: users = [r.user for r in self.participations] else: from flamejam.models.user import User users = User.query.all() # Set this first because we might send for longer than a minute at which point the # next tick will come around. self.last_notification_sent = n db.session.commit() subject = app.config["LONG_NAME"] + ": " + subject with mail.connect() as conn: for user in users: if getattr(user, "notify_" + notify): body = render_template("emails/jam/" + template + ".txt", recipient=user, jam=self, **kwargs) sender = app.config['MAIL_DEFAULT_SENDER'] recipients = [user.email] message = Message(subject=subject, sender=sender, body=body, recipients=recipients) try: conn.send(message) except SMTPRecipientsRefused: pass return True @property def livestream_teams(self): return [t for t in self.teams if t.livestream]
class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) password = db.Column(db.LargeBinary()) token = db.Column(db.BigInteger, nullable=True, default=None) email = db.Column(db.String(191), unique=True) new_email = db.Column(db.String(191), unique=True) is_admin = db.Column(db.Boolean, default=False) is_verified = db.Column(db.Boolean) is_deleted = db.Column(db.Boolean, default=False) registered = db.Column(db.DateTime) ratings = db.relationship('Rating', backref='user', lazy="dynamic") comments = db.relationship('Comment', backref='user', lazy="dynamic") invitations = db.relationship("Invitation", backref="user", lazy="dynamic") participations = db.relationship("Participation", backref=db.backref("user", lazy="joined"), lazy="subquery") ability_programmer = db.Column(db.Boolean) ability_gamedesigner = db.Column(db.Boolean) ability_2dartist = db.Column(db.Boolean) ability_3dartist = db.Column(db.Boolean) ability_composer = db.Column(db.Boolean) ability_sounddesigner = db.Column(db.Boolean) abilities_extra = db.Column(db.String(128)) location = db.Column(db.String(128)) location_coords = db.Column(db.String(128)) location_display = db.Column(db.String(128)) location_flag = db.Column(db.String(16), default="unknown") real_name = db.Column(db.String(128)) about = db.Column(db.Text) website = db.Column(db.String(128)) avatar = db.Column(db.String(128)) pm_mode = db.Column(db.Enum("email", "form", "disabled"), default="form") notify_new_jam = db.Column(db.Boolean, default=True) notify_jam_start = db.Column(db.Boolean, default=True) notify_jam_finish = db.Column(db.Boolean, default=True) notify_game_comment = db.Column(db.Boolean, default=True) notify_team_invitation = db.Column(db.Boolean, default=True) notify_newsletter = db.Column(db.Boolean, default=True) def __init__(self, username, password, email, is_admin=False, is_verified=False): self.username = username self.password = hash_password(password) self.email = email self.new_email = email self.is_admin = is_admin self.is_verified = is_verified self.registered = datetime.utcnow() @property def is_active(self): return self.is_verified @property def is_anonymous(self): return False @property def is_authenticated(self): return True def __repr__(self): return '<User %r>' % self.username def get_id(self): return self.id def getVerificationHash(self): # combine a few properties, hash it # take first 16 chars for simplicity # make it email specific hash = scrypt.hash(str(self.username) + str(self.new_email), app.config['SECRET_KEY']) return hash.encode('hex')[:16] def getResetToken(self): # combine a few properties, hash it # take first 16 chars for simplicity hash = scrypt.hash(str(self.token), app.config['SECRET_KEY']) return hash.encode('hex')[:16] def ratedGame(self, game): return self.ratings.filter_by(game=game).first() != None def getRatingCount(self, jam): i = 0 for r in self.ratings: if r.game.jam == jam: i += 1 return i @property def games(self): g = [] for p in self.participations: if p.team: for game in p.team.games: if not game.is_deleted: g.append(game) import operator g.sort(key=operator.attrgetter("created")) return g def url(self, **values): return url_for('show_user', username=self.username, **values) def getAvatar(self, size=32): if self.avatar: return self.avatar.replace("%s", str(size)) return "//gravatar.com/avatar/{0}?s={1}&d=identicon".format(md5(self.email.lower()).hexdigest(), size) def setLocation(self, location): if not location: self.location = "" self.location_display = "" self.location_coords = "" self.location_flag = "unknown" return True new_loc, new_coords, new_flag = findLocation(location) if not new_loc: return False self.location = location self.location_display = new_loc self.location_coords = new_coords self.location_flag = new_flag return True def getLocation(self): return Markup('<span class="location"><span class="flag %s"></span> <span class="city">%s</span></span>' % (self.location_flag, self.location_display or "n/a")) def getLink(self, class_ = "", real = True, avatar = True): if self.is_deleted: return Markup('<span class="user deleted">[DELETED]</span>') s = 16 if self.is_admin: class_ += " admin" link = '' link += '<a class="user {0}" href="{1}">'.format(class_, self.url()) if avatar: link += '<img width="{0}" height="{0}" src="{1}" class="icon"/> '.format(s, self.getAvatar(s)) link += '<span class="name"><span class="username">{0}</span>'.format(self.username) link += u' <span class="real">({0})</span>'.format(self.real_name) if self.real_name and real else '' link += '</span></a>' return Markup(link) @property def abilities(self): a = [] if self.ability_programmer: a.append("Programming") if self.ability_gamedesigner: a.append("Game Design") if self.ability_2dartist: a.append("Graphics / 2D Art") if self.ability_3dartist: a.append("Modelling / 3D Art") if self.ability_composer: a.append("Composing") if self.ability_sounddesigner: a.append("Sound Design") return a def abilityString(self): a = ", ".join(self.abilities) if self.abilities_extra: a += '<div class="ability-extra">' + self.abilities_extra + '</div>' return a def getParticipation(self, jam): return Participation.query.filter_by(user_id = self.id, jam_id = jam.id).first() def getTeam(self, jam): p = self.getParticipation(jam) return p.team if p and p.team else None def inTeam(self, team): return self in team.members def canRate(self, game): return not self.inTeam(game.team) def canEdit(self, game): return self.inTeam(game.team) def joinJam(self, jam, generateTeam = True): p = Participation(self, jam) db.session.add(p) db.session.commit() # need to commit so the team does not register us automatically if generateTeam: self.generateTeam(jam) else: db.session.commit() def generateTeam(self, jam): t = Team(self, jam) db.session.add(t) db.session.commit() def leaveJam(self, jam): # leave team if self.getTeam(jam): self.getTeam(jam).userLeave(self) # will destroy the team if then empty # delete registration if self.getParticipation(jam): db.session.delete(self.getParticipation(jam)) def numberOfGames(self): return len(self.games) @property def openInvitations(self): invitations = [] for invitation in self.invitations: if invitation.canAccept(): invitations.append(invitation) return invitations
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 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)) token = db.Column(db.Integer, nullable=True, default=None) email = db.Column(db.String(256), unique=True) is_admin = db.Column(db.Boolean, default=False) is_verified = db.Column(db.Boolean) receive_emails = db.Column(db.Boolean) registered = db.Column(db.DateTime) entries = db.relationship('Entry', backref='participant', lazy='dynamic') team_entries = db.relationship("Entry", secondary=team_members, backref="team") ratings = db.relationship('Rating', backref='participant', lazy='dynamic') comments = db.relationship('Comment', backref='participant', lazy='dynamic') jams = db.relationship('Jam', backref='author', lazy='dynamic') rating_skips = db.relationship('RatingSkip', backref='participant', lazy='dynamic') ratings = db.relationship('Rating', backref='participant', lazy='dynamic') def __init__(self, username, password, email, is_admin=False, is_verified=False, receive_emails=True): self.username = username self.password = sha512( (password + app.config['SECRET_KEY']).encode('utf-8')).hexdigest() self.email = email self.is_admin = is_admin self.is_verified = is_verified self.registered = datetime.utcnow() self.receive_emails = receive_emails def getVerificationHash(self): # combine a few properties, hash md5 # take first 8 chars for simplicity return md5(self.username + self.password + app.config['SECRET_KEY']).hexdigest()[:8] def getResetToken(self): # combine a few properties, hash md5 # take first 8 chars for simplicity return md5(str(self.token) + app.config['SECRET_KEY']).hexdigest()[:8] def skippedEntry(self, entry): return self.rating_skips.filter_by(entry=entry).first() != None def ratedEntry(self, entry): return self.ratings.filter_by(entry=entry).first() != None def getRatingCount(self, jam): i = 0 for r in self.ratings: if r.entry.jam == jam: i += 1 return i def getTotalEntryCount(self): return len(self.entries.all()) + len(self.team_entries) def getSkippedCount(self, jam): return len( self.rating_skips.filter(RatingSkip.participant_id == self.id and Entry.jam_id == jam.id).all()) def __repr__(self): return '<User %r>' % self.username def url(self, **values): return url_for('show_participant', username=self.username, **values) def getAvatar(self, size=32): return "http://www.gravatar.com/avatar/{0}?s={1}&d=retro".format( md5(self.email.lower()).hexdigest(), size) def getLink(self, class_=""): s = 14 if self.is_admin: class_ += " admin" return Markup( '<a class="user {4}" href="{0}"><img width="{2}" height="{2}" src="{3}" class="icon"/> {1}</a>' .format(self.url(), self.username, s, self.getAvatar(s), class_)) def canRate(self, entry): return entry.participant != self and not self in entry.team def canEdit(self, entry): return entry.participant == self def getEntryInJam(self, jam): for entry in self.entries: if entry.jam == jam: return entry return None def getTeamEntryInJam(self, jam): for entry in self.team_entries: if entry.jam == jam: return entry return None
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
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 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 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()