class AbstractUserSocialAuth(models.Model, DjangoUserMixin): """Abstract Social Auth association model""" user = models.ForeignKey(USER_MODEL, related_name='social_auth') provider = models.CharField(max_length=32) uid = models.CharField(max_length=UID_LENGTH) extra_data = JSONField() objects = UserSocialAuthManager() def __str__(self): return str(self.user) class Meta: abstract = True @classmethod def get_social_auth(cls, provider, uid): try: return cls.objects.select_related('user').get(provider=provider, uid=uid) except UserSocialAuth.DoesNotExist: return None @classmethod def username_max_length(cls): username_field = cls.username_field() field = UserSocialAuth.user_model()._meta.get_field(username_field) return field.max_length @classmethod def user_model(cls): user_model = UserSocialAuth._meta.get_field('user').rel.to if isinstance(user_model, six.string_types): app_label, model_name = user_model.split('.') return models.get_model(app_label, model_name) return user_model
class UserSocialAuth(models.Model, DjangoUserMixin): """Social Auth association model""" user = models.ForeignKey(USER_MODEL, related_name='social_auth') provider = models.CharField(max_length=32) uid = models.CharField(max_length=UID_LENGTH) extra_data = JSONField() class Meta: """Meta data""" unique_together = ('provider', 'uid') db_table = 'social_auth_usersocialauth' @classmethod def get_social_auth(cls, provider, uid): try: return cls.objects.select_related('user').get(provider=provider, uid=uid) except UserSocialAuth.DoesNotExist: return None @classmethod def username_max_length(cls): username_field = cls.username_field() field = UserSocialAuth.user_model()._meta.get_field(username_field) return field.max_length @classmethod def user_model(cls): return UserSocialAuth._meta.get_field('user').rel.to
class SteamCache(models.Model): uid = models.CharField( max_length=UID_LENGTH, db_index=True, ) extra_data = JSONField(default=b'{}')
class Event(models.Model): name = models.CharField(max_length=255) facebook_id = models.BigIntegerField(null=True, blank=True, unique=True) facebook_data = JSONField(null=True, blank=True) description = models.TextField(null=True, blank=True) image = models.URLField(default=get_random_photo) start_time = models.DateTimeField() end_time = models.DateTimeField() hidden = models.BooleanField(default=False) def __str__(self): return self.name def serious_hours(self): return sorted(list(self.hours.all()) + list(self.subevents.all()), key=attrgetter("time"))
class Player(models.Model): replay = models.ForeignKey( Replay, ) player_name = models.CharField( max_length=100, db_index=True, ) team = models.IntegerField() # 1.06 data score = models.PositiveIntegerField( default=0, blank=True, ) goals = models.PositiveIntegerField( default=0, blank=True, ) shots = models.PositiveIntegerField( default=0, blank=True, ) assists = models.PositiveIntegerField( default=0, blank=True, ) saves = models.PositiveIntegerField( default=0, blank=True, ) platform = models.CharField( max_length=100, blank=True, null=True, db_index=True, ) online_id = models.CharField( max_length=128, blank=True, null=True, db_index=True, ) bot = models.BooleanField( default=False, ) spectator = models.BooleanField( default=False, ) heatmap = models.FileField( upload_to='uploads/heatmap_files', blank=True, null=True, ) user_entered = models.BooleanField( default=False, ) # Taken from the netstream. actor_id = models.PositiveIntegerField( default=0, blank=True, null=True, ) unique_id = models.CharField( max_length=128, blank=True, null=True, ) party_leader = models.ForeignKey( 'self', blank=True, null=True, ) camera_settings = JSONField( blank=True, null=True, ) vehicle_loadout = JSONField( blank=True, null=True, ) total_xp = models.IntegerField( default=0, blank=True, null=True, ) # Other stuff. boost_data = JSONField( blank=True, null=True, ) @cached_property def get_rating_data(self): from ..users.models import LeagueRating from ..users.templatetags.ratings import tier_name if self.replay.playlist not in settings.RANKED_PLAYLISTS: return try: rating = LeagueRating.objects.get_or_request( platform=self.platform, online_id=self.online_id if PLATFORMS_MAPPINGS[self.platform] == PLATFORM_STEAM else self.player_name, playlist=self.replay.playlist, ) if not rating: return { 'image': static('img/tiers/icons/0.png'), 'tier_name': tier_name(0) } return { 'image': static('img/tiers/icons/{}.png'.format(rating.tier)), 'tier_name': tier_name(rating.tier) } except LeagueRating.DoesNotExist: pass return { 'image': static('img/tiers/icons/0.png'), 'tier_name': 'Unranked' } @cached_property def vehicle_data(self): """ { "RocketTrail": {"Name": "Boost_HolyLight", "Id": 44}, "Topper": {"Name": "Hat_Tiara", "Id": 495}, "Version": 12, "Wheels": {"Name": "WHEEL_Atlantis", "Id": 359}, "Body": {"Name": "Body_Force", "Id": 22}, "Antenna": {"Name": null, "Id": 0}, "Decal": {"Name": "Skin_Force_Junk", "Id": 1178}, "Unknown2": 0, "Unknown1": 0 } """ components = {} if not self.vehicle_loadout: return components if type(self.vehicle_loadout) == list: """ [ 403, # Body 0, # Decal. 330 = Flames 376, # Wheels. 386 = Christiano, 376 = OEM 63, # Rocket Trail. 578 = Candy Corn 0, # Antenna. 1 = 8-Ball 0, # Topper. 796 = Deadmau5 0 # ], """ if len(self.vehicle_loadout) == 9: self.vehicle_loadout = self.vehicle_loadout[1:-1] assert len(self.vehicle_loadout) == 7 component_maps = [ 'body', 'decal', 'wheels', 'trail', 'antenna', 'topper', ] for index, component in enumerate(self.vehicle_loadout): if component > 0: get_component = Component.objects.filter( type=component_maps[index], internal_id=component, ) if get_component.exists(): components[component_maps[index]] = get_component[0] else: components[component_maps[index]] = Component.objects.create( type=component_maps[index], internal_id=component, name='Unknown', ) elif type(self.vehicle_loadout) == dict: component_maps = { 'Body': {'type': 'body', 'replace': 'Body_'}, 'Decal': {'type': 'decal', 'replace': 'Skin_'}, 'Wheels': {'type': 'wheels', 'replace': 'WHEEL_'}, 'RocketTrail': {'type': 'trail', 'replace': 'Boost_'}, 'Antenna': {'type': 'antenna', 'replace': 'Antenna '}, 'Topper': {'type': 'topper', 'replace': 'Hat_'}, } for component_type, mappings in component_maps.items(): if component_type in self.vehicle_loadout and self.vehicle_loadout[component_type]['Name']: try: components[mappings['type']] = Component.objects.get_or_create( type=mappings['type'], internal_id=self.vehicle_loadout[component_type]['Id'], defaults={ 'name': self.vehicle_loadout[component_type]['Name'].replace(mappings['replace'], '').replace('_', ' ') } )[0] if components[mappings['type']].name == 'Unknown': components[mappings['type']].name = self.vehicle_loadout[component_type]['Name'].replace(mappings['replace'], '').replace('_', ' ') components[mappings['type']].save() except Exception: pass return components def get_absolute_url(self): if self.bot or self.platform == '0' or not self.platform: return '#1' try: return reverse('users:player', kwargs={ 'platform': PLATFORMS_MAPPINGS[self.platform], 'player_id': self.online_id if int(self.platform) == PLATFORM_STEAM else self.player_name, }) except Exception: return '#2' def __str__(self): return '{} on Team {}'.format( self.player_name, self.team, ) class Meta: ordering = ('team', '-score', 'player_name')
class Replay(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, db_index=True, ) season = models.ForeignKey( Season, default=get_default_season, ) title = models.CharField( "replay name", max_length=128, blank=True, null=True, ) playlist = models.PositiveIntegerField( choices=[(v, k) for k, v in settings.PLAYLISTS.items()], default=0, blank=True, null=True, ) file = models.FileField( upload_to='uploads/replay_files', ) heatmap_json_file = models.FileField( upload_to='uploads/replay_json_files', blank=True, null=True, ) location_json_file = models.FileField( upload_to='uploads/replay_location_json_files', blank=True, null=True, ) replay_id = models.CharField( "replay ID", max_length=100, blank=True, null=True, db_index=True, ) player_name = models.CharField( max_length=100, blank=True, null=True, ) player_team = models.IntegerField( default=0, blank=True, null=True, ) map = models.ForeignKey( Map, blank=True, null=True, db_index=True, ) server_name = models.CharField( max_length=100, blank=True, null=True, ) timestamp = models.DateTimeField( blank=True, null=True, ) date_created = models.DateTimeField( default=now, ) team_sizes = models.PositiveIntegerField( blank=True, null=True, db_index=True, ) team_0_score = models.IntegerField( default=0, blank=True, null=True, db_index=True, ) team_1_score = models.IntegerField( default=0, blank=True, null=True, db_index=True, ) match_type = models.CharField( max_length=16, blank=True, null=True, ) privacy = models.PositiveIntegerField( 'replay privacy', choices=[ (PRIVACY_PRIVATE, 'Private'), (PRIVACY_UNLISTED, 'Unlisted'), (PRIVACY_PUBLIC, 'Public') ], default=3, ) # Parser V2 values. keyframe_delay = models.FloatField( blank=True, null=True, ) max_channels = models.IntegerField( default=1023, blank=True, null=True, ) max_replay_size_mb = models.IntegerField( "max replay size (MB)", default=10, blank=True, null=True, ) num_frames = models.IntegerField( blank=True, null=True, ) record_fps = models.FloatField( "record FPS", default=30.0, blank=True, null=True, ) shot_data = JSONField( blank=True, null=True, ) excitement_factor = models.FloatField( default=0.00, ) show_leaderboard = models.BooleanField( default=False, ) average_rating = models.PositiveIntegerField( blank=True, null=True, default=0, ) crashed_heatmap_parser = models.BooleanField( default=False, ) processed = models.BooleanField( default=False, ) @cached_property def uuid(self): return re.sub(r'([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', r'\1-\2-\3-\4-\5', self.replay_id).lower() def team_x_player_list(self, team): return [ "{}{}".format( player.player_name, " ({})".format(player.goal_set.count()) if player.goal_set.count() > 0 else '', ) for player in self.player_set.filter( team=team, ) ] def team_x_players(self, team): return ', '.join(self.team_x_player_list(team)) def team_0_players(self): return self.team_x_players(0) def team_1_players(self): return self.team_x_players(1) def team_0_player_list(self): return self.team_x_player_list(0) def team_1_player_list(self): return self.team_x_player_list(1) def player_pairs(self): return zip_longest(self.team_0_player_list(), self.team_1_player_list()) @cached_property def region(self): if not self.server_name: return 'N/A' match = re.search(settings.SERVER_REGEX, self.server_name) if match: return match.groups()[1] return 'N/A' def lag_report_url(self): base_url = 'https://psyonixhr.wufoo.com/forms/game-server-performance-report' if not self.server_name: return base_url # Split out the server name. match = re.search(r'(EU|USE|USW|OCE|SAM)(\d+)(-([A-Z][a-z]+))?', self.server_name).groups() return "{}/def/field1={}&field2={}&field13={}".format( base_url, *match ) @cached_property def match_length(self): if not self.num_frames or not self.record_fps: return 'N/A' calculation = self.num_frames / self.record_fps minutes, seconds = divmod(calculation, 60) return '%d:%02d' % ( int(minutes), int(seconds), ) def calculate_excitement_factor(self): # Multiplers for use in factor tweaking. swing_rating_multiplier = 8 goal_count_multiplier = 1.2 # Calculate how the swing changed throughout the game. swing = 0 swing_values = [] for goal in self.goal_set.all(): if goal.player.team == 0: swing -= 1 else: swing += 1 swing_values.append(swing) if self.team_0_score > self.team_1_score: # Team 0 won, but were they ever losing? deficit_values = [x for x in swing_values if x < 0] if deficit_values: deficit = max(swing_values) else: deficit = 0 score_min_def = self.team_0_score - deficit else: # Team 1 won, but were they ever losing? deficit_values = [x for x in swing_values if x < 0] if deficit_values: deficit = abs(min(deficit_values)) else: deficit = 0 score_min_def = self.team_1_score - deficit if score_min_def != 0: swing_rating = float(deficit) / score_min_def * swing_rating_multiplier else: swing_rating = 0 # Now we have the swing rating, adjust it by the total number of goals. # This gives us a "base value" for each replay and allows replays with # lots of goals but not much swing to get reasonable rating. Cap the goal # multiplier at 5. total_goals = self.team_0_score + self.team_1_score if total_goals > 5: total_goals = 5 swing_rating += total_goals * goal_count_multiplier return swing_rating def calculate_average_rating(self): from ..users.models import LeagueRating players = self.player_set.exclude( online_id__isnull=True, ) num_player_ratings = 0 total_player_ratings = 0 for player in players: try: # Get the latest rating for this player. rating = LeagueRating.objects.get( platform=player.platform, online_id=player.online_id, playlist=self.playlist, ) total_player_ratings += rating.tier num_player_ratings += 1 except LeagueRating.DoesNotExist: # Should we get the ratings? continue if num_player_ratings > 0: return math.ceil(total_player_ratings / num_player_ratings) return 0 def eligible_for_feature(self, feature): features = { 'playback': settings.PATREON_PLAYBACK_PRICE, 'boost_analysis': settings.PATREON_BOOST_PRICE, } patreon_amount = features[feature] # Import here to avoid circular imports. from ..site.templatetags.site import patreon_pledge_amount # Is the uploader a patron? if self.user: pledge_amount = patreon_pledge_amount({}, user=self.user) if pledge_amount >= patreon_amount: return True # Are any of the players patron? players = self.player_set.filter( platform__in=['OnlinePlatform_Steam', '1'], ) for player in players: pledge_amount = patreon_pledge_amount({}, steam_id=player.online_id) if pledge_amount >= patreon_amount: return True return False @property def queue_priority(self): # Returns one of 'tournament', 'priority', 'general', where 'tournament' # is the highest priority. # TODO: Add tournament logic. if self.eligible_for_playback: return 'priority' return 'general' # Feature eligibility checks. @cached_property def eligible_for_playback(self): return self.eligible_for_feature('playback') @cached_property def show_playback(self): # First of all, is there even a JSON file? if not self.location_json_file: return False return self.eligible_for_feature('playback') @cached_property def eligible_for_boost_analysis(self): return self.eligible_for_feature('boost_analysis') @cached_property def show_boost_analysis(self): # Have we got any boost data yet? if self.boostdata_set.count() == 0: return False return self.eligible_for_feature('boost_analysis') # Other stuff @cached_property def get_human_playlist(self): if not self.playlist: return 'Unknown' display = self.get_playlist_display() if display == self.playlist: display = 'Unknown' return settings.HUMAN_PLAYLISTS.get(self.playlist, display) def get_absolute_url(self): if self.replay_id: return reverse('replay:detail', kwargs={ 'replay_id': re.sub(r'([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})', r'\1-\2-\3-\4-\5', self.replay_id).lower(), }) return reverse('replay:detail', kwargs={ 'pk': self.pk, }) class Meta: ordering = ['-timestamp', '-pk'] def __str__(self): return self.title or str(self.pk) or '[{}] {} {} game on {}. Final score: {}, Uploaded by {}.'.format( self.timestamp, '{size}v{size}'.format(size=self.team_sizes), self.match_type, self.map, '{}-{}'.format(self.team_0_score, self.team_1_score), self.player_name, ) def clean(self): if self.pk: return if self.file: # Ensure we're at the start of the file as `clean()` can sometimes # be called multiple times (for some reason..) self.file.seek(0) file_url = self.file.url # To help the exception handler try: replay = Pyrope(self.file.read()) except bitstring.ReadError: raise ValidationError("The file you selected does not seem to be a valid replay file.") # Check if this replay has already been uploaded. replays = Replay.objects.filter( replay_id=replay.header['Id'], ) if replays.count() > 0: raise ValidationError(mark_safe("This replay has already been uploaded, <a target='_blank' href='{}'>you can view it here</a>.".format( replays[0].get_absolute_url() ))) self.replay_id = replay.header['Id'] def save(self, *args, **kwargs): parse_netstream = False if 'parse_netstream' in kwargs: parse_netstream = kwargs.pop('parse_netstream') super(Replay, self).save(*args, **kwargs) if self.file and not self.processed: try: if parse_netstream: # Header parse? parse_replay_netstream(self.pk) else: parse_replay_header(self.pk) except: logger.exception('Replay save failed')
class Profile(models.Model): user = models.OneToOneField(User) patreon_email_address = models.EmailField( unique=True, blank=True, null=True, ) twitter_username = models.CharField( b"Twitter username", max_length=100, blank=True, null=True, ) twitch_username = models.CharField( b"Twitch.tv username", max_length=100, blank=True, null=True, ) reddit_username = models.CharField( b"reddit username", max_length=100, blank=True, null=True, ) youtube_url = models.URLField( b"YouTube URL", blank=True, null=True, ) facebook_url = models.URLField( b"Facebook URL", blank=True, null=True, ) stream_settings = JSONField( blank=True, null=True, ) privacy = models.PositiveIntegerField( 'replay privacy', choices=[(1, b'Private'), (2, b'Unlisted'), (3, b'Public')], default=3, ) def latest_ratings(self): # This method will return the ratings for each of the platforms that the # user has associated with their account. For most people this will only # be one, but it's useful to handle all cases regardless. accounts = [] if self.has_steam_connected(): accounts.append((PLATFORMS_MAPPINGS['steam'], self.user.social_auth.get(provider='steam').uid)) account_data = {} for platform, online_id in accounts: account_data[platform] = LeagueRating.objects.filter( platform=platform, online_id=online_id, ).order_by('playlist') return account_data def has_steam_connected(self): try: self.user.social_auth.get(provider='steam') return True except: return False def steam_info(self): steam = self.user.social_auth.get(provider='steam') # Have we updated this profile recently? if 'last_updated' in steam.extra_data: # Parse the last updated date. last_date = parse_datetime(steam.extra_data['last_updated']) seconds_ago = (now() - last_date).seconds # 3600 seconds = 1 hour if seconds_ago < 3600: return steam.extra_data['player'] try: player = requests.get(USER_INFO, params={ 'key': settings.SOCIAL_AUTH_STEAM_API_KEY, 'steamids': steam.uid }).json() if len(player['response']['players']) > 0: steam.extra_data = { 'player': player['response']['players'][0], 'last_updated': now().isoformat(), } steam.save() except: pass return steam.extra_data['player'] def has_had_trial(self): from ..site.models import PatronTrial return PatronTrial.objects.filter(user=self.user, ).count() > 0 @property def clean_twitch_username(self): if 'twitch.tv' in self.twitch_username: return self.twitch_username.split('/')[-1] return self.twitch_username def get_absolute_url(self): if self.has_steam_connected(): return reverse('users:player', kwargs={ 'platform': 'steam', 'player_id': self.user.social_auth.get(provider='steam').uid }) return reverse('users:profile', kwargs={'username': self.user.username})
class Player(models.Model): replay = models.ForeignKey( Replay, ) player_name = models.CharField( max_length=100, db_index=True, ) team = models.IntegerField() # 1.06 data score = models.PositiveIntegerField( default=0, blank=True, ) goals = models.PositiveIntegerField( default=0, blank=True, ) shots = models.PositiveIntegerField( default=0, blank=True, ) assists = models.PositiveIntegerField( default=0, blank=True, ) saves = models.PositiveIntegerField( default=0, blank=True, ) platform = models.CharField( max_length=100, blank=True, null=True, db_index=True, ) online_id = models.CharField( max_length=128, blank=True, null=True, db_index=True, ) bot = models.BooleanField( default=False, ) spectator = models.BooleanField( default=False, ) heatmap = models.FileField( upload_to='uploads/heatmap_files', blank=True, null=True, ) user_entered = models.BooleanField( default=False, ) # Taken from the netstream. actor_id = models.PositiveIntegerField( default=0, blank=True, null=True, ) unique_id = models.CharField( max_length=128, blank=True, null=True, ) party_leader = models.ForeignKey( 'self', blank=True, null=True, ) camera_settings = JSONField( blank=True, null=True, ) total_xp = models.PositiveIntegerField( default=0, blank=True, null=True, ) def __str__(self): return '{} on Team {}'.format( self.player_name, self.team, ) class Meta: ordering = ('team', '-score', 'player_name') unique_together = [('unique_id', 'replay')]