def replay_filter(queryset, filter_name):
    filter_name = str(MySQLdb.escape_string(filter_name))
    filter_name_splited = filter_name.split()
    category = filter_name_splited[0]
    selected = filter_name_splited[1]
    args = filter_name_splited[2:]

    now = datetime.datetime.now(timezone.utc)
    today = datetime.datetime(year=now.year, month=now.month, day=now.day, tzinfo=timezone.utc)
    this_month_start = datetime.datetime(year=now.year, month=now.month, day=1, tzinfo=timezone.utc)
    this_month_end = datetime.datetime(
        year=now.year if now.month < 12 else now.year + 1,
        month=((now.month + 1) % 12 or 12),
        day=1,
        tzinfo=timezone.utc,
    ) - datetime.timedelta(microseconds=1)
    last_month_end = this_month_start - datetime.timedelta(microseconds=1)
    last_month_start = datetime.datetime(
        year=last_month_end.year, month=last_month_end.month, day=1, tzinfo=timezone.utc
    )
    this_year_start = datetime.datetime(year=now.year, month=1, day=1, tzinfo=timezone.utc)
    this_year_end = datetime.datetime(year=now.year + 1, month=1, day=1, tzinfo=timezone.utc) - datetime.timedelta(
        microseconds=1
    )
    last_year_end = this_year_start - datetime.timedelta(microseconds=1)
    last_year_start = datetime.datetime(year=last_year_end.year, month=1, day=1, tzinfo=timezone.utc)
    before_last_year = last_year_start - datetime.timedelta(microseconds=1)
    epoch = datetime.datetime.fromtimestamp(0, tz=timezone.utc)

    try:
        if category == "date":
            filterfnc = {
                "t_today": queryset.filter(unixTime__range=(today, today + datetime.timedelta(days=1))),
                "t_yesterday": queryset.filter(unixTime__range=(today - datetime.timedelta(days=1), today)),
                "t_this_month": queryset.filter(unixTime__range=(this_month_start, this_month_end)),
                "t_last_month": queryset.filter(unixTime__range=(last_month_start, last_month_end)),
                "t_this_year": queryset.filter(unixTime__range=(this_year_start, this_year_end)),
                "t_last_year": queryset.filter(unixTime__range=(last_year_start, last_year_end)),
                "t_ancient": queryset.filter(unixTime__range=(epoch, before_last_year)),
                "0": queryset,
            }
            if selected == "daterange":
                range_from = datetime.datetime.fromtimestamp(int(args[0]), timezone.utc)
                range_to = datetime.datetime.fromtimestamp(int(args[1]), timezone.utc)
                filterfnc["daterange"] = queryset.filter(unixTime__range=(range_from, range_to))
            return filterfnc[selected]
        elif category == "map":
            if int(selected) == 0:
                return queryset
            else:
                return queryset.filter(map_info=Map.objects.get(id=int(selected)))
        elif category == "tag":
            if int(selected) == 0:
                return queryset
            else:
                return queryset.filter(tags__id=int(selected))
        elif category == "game":
            if int(selected) == 0:
                return queryset
            else:
                return queryset.filter(
                    gametype__in=GameRelease.objects.filter(game__id=int(selected)).values_list("name", flat=True)
                )
        elif category == "gameversion":
            if int(selected) == 0:
                return queryset
            else:
                return queryset.filter(gametype=GameRelease.objects.get(id=int(selected)).name)
        elif category == "player":
            if int(selected) == 0:
                return queryset
            else:
                if args and args[0] == "spec":
                    # include spectators
                    return queryset.filter(player__account__accountid=int(selected))
                else:
                    return queryset.filter(
                        player__account__accountid=int(selected),
                        player__spectator=False,
                    )
        elif category == "autohost":
            if selected == "-":
                return queryset
            else:
                return queryset.filter(autohostname=selected)
        elif category == "uploader":
            if selected == "-":
                return queryset
            else:
                return queryset.filter(uploader__username=selected)
    except Exception as exc:
        logger.error("FIXME: to broad exception handling.")
        logger.debug(
            "Exception, prob from bad argument. filter_name: '%s', Exception: %s",
            filter_name,
            exc,
        )
        return queryset def all_page_infos(request):
    c = {}
    try:
        gameid = int(request.GET["game_pref"])
        request.session.modified = True  # force a reload in the client to update the top menu
        request.session["game_pref"] = gameid
        if (
            request.user.is_authenticated
            and request.user.userprofile.game_pref != gameid
            and not request.user.userprofile.game_pref_fixed
        ):
            request.user.userprofile.game_pref = gameid
            request.user.userprofile.save()
    except (TypeError, ValueError, MultiValueDictKeyError):
        # request.GET["game_pref"] not set (or not an int)
        pass
    if request.user.is_authenticated:
        try:
            c["logged_in_pa"] = PlayerAccount.objects.get(accountid=request.user.userprofile.accountid)
        except ObjectDoesNotExist:
            pass
        if not request.session.get("game_pref"):
            request.session["game_pref"] = request.user.userprofile.game_pref
    c["all_games_mainmenu"] = Game.objects.all()
    c["selfurl"] = request.path
    gids = request.session.get("page_history", [])
    c["page_history"] = Replay.objects.filter(gameID__in=gids)
    if request.session.get("game_pref") and request.session["game_pref"] > 0:
        game_pref = request.session["game_pref"]
        c["game_pref"] = game_pref
        c["game_pref_obj"] = get_object_or_404(Game, id=game_pref)
        c["game_pref_browse"] = "game={}".format(game_pref)
    return c class EditReplayForm(forms.Form):
    short = forms.CharField(
        widget=forms.TextInput(attrs={"size": "53"}),
        max_length=50,
        label="Short subject (50 char max)",
        required=False,
    )
    long_text = forms.CharField(
        widget=forms.Textarea(attrs={"rows": "4", "cols": "60"}),
        max_length=512,
        label="Comment (512 char max)",
        required=False,
    )
    tags = forms.CharField(
        widget=forms.TextInput(attrs={"size": "53"}),
        label="Tags (comma separated)",
        required=False,
    ) class GamePref(forms.Form):
    auto = forms.BooleanField(
        required=False,
        label="Restore previous state (default)",
        widget=forms.CheckboxInput(attrs={"onclick": "toggle_game_choice(this)"}),
    )
    game_choice = forms.ChoiceField(
        required=False,
        choices=_game_choices,
        widget=forms.Select(attrs={"class": "form-control"}),
    ) class SLDBPrivacyForm(forms.Form):
    MODE_CHOICES = (
        (
            0,
            "<p><b>Privacy disabled</b>:<br>Your exact trueskill rating is shown to<br>everyone on the replay "
            'website and in<br>"!status" output on the autohosts.</p>',
        ),
        (
            1,
            "<b>Basic privacy enabled <u>(default)</u></b>:<br>Players and website visitors only see a<br>rough "
            "estimate of your trueskill rating.<br>If you log into the website you will still<br>see your own "
            'exact ratings. On the autohosts<br>only privileged users can still see an exact value<br>in "!status" '
            "output.",
        ),
        (
            2,
            '<b>Full privacy enabled</b>:<br>Same as "Basic privacy", but even<br>privileged autohost users see only '
            'a rough<br>estimate in "!status" output.',
        ),
    )
    mode = forms.ChoiceField(required=False, label="", choices=MODE_CHOICES, widget=forms.RadioSelect()) class MatchStatsGeneration(object):
    def __init__(self, file_path):
        self.file_path = os.path.abspath(file_path)
        self.demofile = DemoFileReader(self.file_path)

    def make_teamstat_graphs(cls, teamstats):
        # type: (Dict[str, List[TeamStatistics]]) -> Dict[str, Dict[str, List[float]]]
        res = OrderedDict()  # type: Dict
        for ts_attr, label in TeamStats.graphid2label.items():
            res[ts_attr] = dict((player, [getattr(s, ts_attr) for s in stats]) for player, stats in teamstats.items())
        return res

    @staticmethod
    def checkteamstatlength(demofile):  # type: (DemoFileReader) -> bool
        """
        Copied from SpringStatsViewer/SpringStatsViewer.py to not have to depend on Tix.

        Return true if the length of at least one entry in the demo file teamstats > 1, false otherwise
        """
        vlength = 0
        for p in demofile.teamstatistics:
            if len(demofile.teamstatistics[p]) > vlength:
                vlength = len(demofile.teamstatistics[p])
                if vlength > 1:
                    return True
        if vlength > 1:
            return True
        else:
            return False

    def make_stats(self):
        # type: () -> Tuple[Dict[str, Dict[str, int]], Dict[str, List[Dict[str, Union[str, List[float]]]]]]
        playerstats_res = {}  # type: Dict[str, Dict[str, int]]
        teamstats_plotly_kwargs = {}  # type: Dict[str, List[Dict[str, Union[str, List[float]]]]]
        if (
            self.demofile.header()
            and self.demofile.script()
            and not self.demofile.incomplete
            and not self.demofile.crashed
        ):
            playerstats = self.demofile.playerstats()
            if playerstats:
                logger.debug("** We have playerstats: {!r}".format(playerstats))
                playerstats_res = dict((k, v.__dict__) for k, v in playerstats.items())
                teamstats = self.demofile.teamstats()
                if teamstats and self.checkteamstatlength(self.demofile):
                    logger.debug("** We have teamstats.")
                    graphs = self.make_teamstat_graphs(teamstats)
                    # create x-axis units
                    teamstatperiod_in_minutes = self.demofile.teamstatperiod / 60.0
                    x_axis = [0.0]
                    a_stat = graphs.values()[0]  # type: Dict[str, List[float]]
                    a_stat_list = a_stat.values()[0]  # type: List[float]
                    for i in range(len(a_stat_list) - 1):
                        x_axis.append(x_axis[-1] + teamstatperiod_in_minutes)
                    for ts_type, stats in graphs.items():
                        plot_data_kwargs = [
                            dict(x=x_axis, y=p_stats, name=player) for player, p_stats in stats.items()
                        ]  # type: List[Dict[str, Union[str, List[float]]]]
                        teamstats_plotly_kwargs[ts_type] = plot_data_kwargs
                else:
                    logger.debug("** We have NO teamstats.")
            else:
                logger.debug("** We have NO playerstats.")
        return playerstats_res, teamstats_plotly_kwargs

    @staticmethod
    def get_team_stat_plot(title, plotly_kwargs, graph_type="lines"):
        # type: (str, List[Dict[str, Union[str, List[float]]]], Optional[str]) -> str
        plot_data = [go.Scatter(mode=graph_type, **kwargs) for kwargs in plotly_kwargs]
        layout = go.Layout(
            title=title,
            autosize=True,
            # paper_bgcolor='#1c1e22',
            # plot_bgcolor='#2e3338',
            # legend = {'bgcolor': '#2e3338'},
            xaxis={"title": "minute"},
            yaxis={"title": "per second"},
        )
        return plotly.offline.plot(
            figure_or_data=go.Figure(data=plot_data, layout=layout),
            auto_open=False,
            output_type="div",
            include_plotlyjs=False,
            show_link=False,
        ) class Allyteam(models.Model):
    numallies = models.IntegerField()
    startrectbottom = models.FloatField(blank=True, null=True)
    startrectleft = models.FloatField(blank=True, null=True)
    startrectright = models.FloatField(blank=True, null=True)
    startrecttop = models.FloatField(blank=True, null=True)
    winner = models.BooleanField(default=False)
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    num = models.SmallIntegerField()

    def __unicode__(self):
        return "Allyteam({}) win:{}".format(self.id, self.winner) class CursedAwards(models.Model):
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    player = models.ForeignKey(Player, on_delete=CASCADE)

    air = models.PositiveIntegerField(default=0)
    bug = models.PositiveIntegerField(default=0)
    cap = models.PositiveIntegerField(default=0)
    dran = models.PositiveIntegerField(default=0)
    drankill = models.PositiveIntegerField(default=0)
    emp = models.PositiveIntegerField(default=0)
    fire = models.PositiveIntegerField(default=0)
    friend = models.PositiveIntegerField(default=0)
    heart = models.PositiveIntegerField(default=0)
    hero = models.PositiveIntegerField(default=0)
    herokill = models.PositiveIntegerField(default=0)
    hover = models.PositiveIntegerField(default=0)
    kam = models.PositiveIntegerField(default=0)
    lycan = models.PositiveIntegerField(default=0)
    mex = models.PositiveIntegerField(default=0)
    mexkill = models.PositiveIntegerField(default=0)
    nux = models.PositiveIntegerField(default=0)
    ouch = models.PositiveIntegerField(default=0)
    point = models.PositiveIntegerField(default=0)
    pwn = models.PositiveIntegerField(default=0)
    rezz = models.PositiveIntegerField(default=0)
    share = models.PositiveIntegerField(default=0)
    shell = models.PositiveIntegerField(default=0)
    slow = models.PositiveIntegerField(default=0)
    sweeper = models.PositiveIntegerField(default=0)

    field_to_image = dict(
        air="trophy_air.png",
        bug="trophy_bug.png",
        cap="trophy_cap.png",
        dran="trophy_dran.png",
        drankill="trophy_dragon.png",
        emp="trophy_emp.png",
        fire="trophy_fire.png",
        friend="trophy_friend.png",
        heart="trophy_heart.png",
        hero="trophy_hero.png",
        herokill="trophy_herokill.png",
        hover="trophy_hover.png",
        kam="trophy_kam.png",
        lycan="trophy_lycan.png",
        mex="trophy_mex.png",
        mexkill="trophy_mexkill.png",
        nux="trophy_nux.png",
        ouch="trophy_ouch.png",
        point="trophy_point.png",
        pwn="trophy_pwn.png",
        rezz="trophy_rezz.png",
        share="trophy_share.png",
        shell="trophy_shell.png",
        slow="trophy_slow.png",
        sweeper="trophy_sweeper.png",
    )

    field_to_name = dict(
        air="Airforce General",
        bug="Bug Hunter",
        cap="Mind Magician",
        dran="Dragons & Angels",
        drankill="Dragon & Angel Slayer",
        emp="Stun Wizard",
        fire="Master Grill-Chef",
        friend="Friendly Fire Award",
        heart="Queen Heart Breaker",
        hero="Heros do the job",
        herokill="Hero Hunter",
        hover="Hover Admiral",
        kam="Kamikaze Award",
        lycan="Werewolf Hunter",
        mex="Mineral Prospector",
        mexkill="Loot & Pillage",
        nux="Apocalyptic Achievement Award",
        ouch="Big Purple Heart",
        point="This is my Land",
        pwn="Total Pain",
        rezz="Vile Necromancer",
        share="Share Bear",
        shell="Turtle Shell",
        slow="Traffic Cop",
        sweeper="Land Sweeper",
    )

    @classmethod
    def sorted_field_names(cls):
        return sorted([f.name for f in cls._meta.fields if f.name not in ("id", "replay", "player")])

    def as_dict(self, only_positive=False):
        res = dict()
        for field_name in self.sorted_field_names():
            val = getattr(self, field_name)
            if only_positive and not val > 0:
                continue
            res[field_name] = val
        return res

    def __unicode__(self):
        return "CursedAwards({}, {}, {})".format(
            self.pk,
            self.replay.pk,
            [
                (f.name, f.value_from_object(self))
                for f in self._meta.fields
                if f.name not in ("id", "replay", "player") and f.value_from_object(self) > 0
            ],
        ) class ExtraReplayMedia(models.Model):
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    uploader = models.ForeignKey(User, on_delete=CASCADE)
    upload_date = models.DateTimeField(auto_now_add=True, db_index=True)
    comment = models.CharField(max_length=513)
    media = models.FileField(upload_to="media/%Y/%m/%d", blank=True)
    image = models.ImageField(upload_to="image/%Y/%m/%d", blank=True)
    media_magic_text = models.CharField(max_length=1024, blank=True, null=True)
    media_magic_mime = models.CharField(max_length=128, blank=True, null=True)

    @property
    def media_basename(self):
        return basename(self.media.name)

    @property
    def image_basename(self):
        return basename(self.image.name)

    def __unicode__(self):
        return "ExtraReplayMedia({}) replay: {} media: {} image: {} by: {} on: {}".format(
            self.pk,
            self.replay,
            self.media,
            self.image,
            self.uploader,
            self.upload_date,
        )

    class Meta:
        ordering = ["-upload_date"] class Game(models.Model):
    name = models.CharField(max_length=256, db_index=True)
    abbreviation = models.CharField(max_length=64, db_index=True)
    sldb_name = models.CharField(max_length=64, db_index=True)
    developer = models.ManyToManyField(User, blank=True)

    def __unicode__(self):
        return "Game({}, {}, {})".format(self.pk, self.abbreviation, self.name[:70])

    def get_absolute_url(self):
        return reverse("srs/browse_archive", args=["game={}".format(self.pk)])

    class Meta:
        ordering = ["name"] class GameRelease(models.Model):
    name = models.CharField(max_length=256, db_index=True)
    version = models.CharField(max_length=64)
    game = models.ForeignKey(Game, on_delete=CASCADE)

    def __unicode__(self):
        return "GameRelease({}, {}, {}, {})".format(self.pk, self.name[:70], self.version, self.game)

    def get_absolute_url(self):
        return reverse("srs/gamerelease", args=[self.name])

    @property
    def replay_count(self):
        return Replay.objects.filter(gametype=self.name).count()

    class Meta:
        ordering = ["name", "version"] class Map(models.Model):
    name = models.CharField(max_length=128, db_index=True)
    startpos = models.CharField(max_length=1024, blank=True, null=True)
    height = models.IntegerField()
    width = models.IntegerField()
    metadata2 = JSONTextField(blank=True, null=True, default=None)

    def __repr__(self):
        return "Map({}, {})".format(self.pk, self.name)

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("srs/rmap", args=[self.name])

    @property
    def replay_count(self):
        return Replay.objects.filter(map_info=self).count()

    class Meta:
        ordering = ["name"] class MapOption(MapModOption):
    pass class ModOption(MapModOption):
    pass class NewsItem(models.Model):
    text = models.CharField(max_length=1024)
    post_date = models.DateTimeField(auto_now=True, db_index=True)
    show = models.BooleanField(default=True, db_index=True)

    def __unicode__(self):
        return "NewsItem({}): {}".format(self.pk, self.text[:50]) class Player(models.Model):
    account = models.ForeignKey(PlayerAccount, blank=True, null=True, on_delete=CASCADE)
    name = models.CharField(max_length=128, db_index=True)
    rank = models.SmallIntegerField()
    skill = models.CharField(max_length=16, blank=True)
    skilluncertainty = models.SmallIntegerField(default=-1, blank=True)
    spectator = models.BooleanField(default=False)
    team = models.ForeignKey("Team", blank=True, null=True, on_delete=CASCADE)
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    startposx = models.FloatField(blank=True, null=True)
    startposy = models.FloatField(blank=True, null=True)
    startposz = models.FloatField(blank=True, null=True)

    def __unicode__(self):
        return "Player({}, {})".format(self.id, self.name)

    def get_absolute_url(self):
        return reverse("srs/player", args=[self.account.accountid])

    class Meta:
        ordering = ["name"] class PlayerAccount(models.Model):
    accountid = models.IntegerField(unique=True)
    countrycode = models.CharField(max_length=2)
    preffered_name = models.CharField(max_length=128, db_index=True)
    sldb_privacy_mode = models.SmallIntegerField(default=1)

    def __unicode__(self):
        return "PlayerAccount({}: {})".format(
            self.accountid,
            reduce(lambda x, y: "{}|{}".format(x, y), self.get_names())[:40],
        )

    def get_absolute_url(self):
        return reverse("srs/player", args=[self.accountid])

    @property
    def replay_count(self):
        return Player.objects.filter(account__in=self).count()

    @property
    def spectator_count(self):
        return Player.objects.filter(account__in=self, spectator=True).count()

    def get_rating(self, game, match_type):
        try:
            rating, _ = Rating.objects.get_or_create(playeraccount=self, game=game, match_type=match_type)
            return rating
        except MultipleObjectsReturned:
            # this shouldn't happen, was a bug, now delete last object and return first
            ratings = Rating.objects.filter(playeraccount=self, game=game, match_type=match_type)
            ratings.last().delete()
            return ratings.first()

    def get_names(self):
        pls = list(Player.objects.filter(account=self).values_list("name", flat=True))
        if pls:
            try:
                pls.remove(self.preffered_name)
            except ValueError:
                pass  # Player with name "pref_name"  was removed from DB, but PlayerAccount stayed
            else:
                pls.insert(0, self.preffered_name)
            return uniqify_list(pls)
        else:
            return [self.preffered_name]

    def get_all_games(self):
        gametypes = Replay.objects.filter(player__account=self, player__spectator=False).values_list(
            "gametype", flat=True
        )
        return Game.objects.filter(gamerelease__name__in=uniqify_list(gametypes)).distinct()

    def get_all_games_no_bots(self):
        gametypes = (
            Replay.objects.filter(player__account=self, player__spectator=False)
            .exclude(tags=Tag.objects.get(name="Bot"))
            .values_list("gametype", flat=True)
        )
        return Game.objects.filter(gamerelease__name__in=uniqify_list(gametypes)).distinct()

    def get_user(self):
        try:
            return User.objects.get(userprofile__accountid=self.accountid)
        except ObjectDoesNotExist:
            return None

    @property
    def bawards(self):
        """
        Find number of won BA awards (for all award types).

        :return: dict: {'cowAward': 0, 'dmgRecAward': 2, 'ecoAward': 3, ...}
        """
        res = dict()
        field_names = [
            field.name for field in BAwards._meta.get_fields() if "Award" in field.name and "Score" not in field.name
        ]
        for award in field_names:
            kwargs = {"{}__account".format(award): self}
            res[award] = BAwards.objects.filter(**kwargs).count()
        return res

    @property
    def xtawards(self):
        """
        Find top 10 hero and "lost in service" units for this player. Sorted by headcount.

        :return: tuple: heroes, lost. each is a list of tuples:
            ([(u'Sumo', 9), (u'Light Laser Tower', 8), ...], [(), ..])
        """
        awards = XTAwards.objects.filter(player__account=self, isAlive=1).values_list("unit", flat=True)
        co = Counter(awards)
        heroes = co.most_common(10)
        awards = XTAwards.objects.filter(player__account=self, isAlive=0).values_list("unit", flat=True)
        co = Counter(awards)
        lost = co.most_common(10)
        return heroes, lost

    @property
    def cursed_awards(self):
        """
        Find number of won cursed awards (for all award types).

        :return: [{'type': 'air', 'name': '..', 'img': images/..', 'count': 12}, ...]
        """
        awards = CursedAwards.objects.filter(player__account=self)
        co = Counter()
        for award in awards:
            co.update(award.as_dict(True).keys())
        return [
            {
                "type": c[0],
                "name": CursedAwards.field_to_name[c[0]],
                "img": os.path.join("img/cursed_awards", CursedAwards.field_to_image[c[0]]),
                "count": c[1],
            }
            for c in co.most_common()
        ]

    class Meta:
        ordering = ["accountid"] class RatingBase(models.Model):
    game = models.ForeignKey(Game, on_delete=CASCADE)
    MATCH_TYPE_CHOICES = (
        ("1", "1v1"),
        ("T", "Team"),
        ("F", "FFA"),
        ("G", "TeamFFA"),
        ("L", "Global"),
    )
    match_type = models.CharField(max_length=1, choices=MATCH_TYPE_CHOICES, db_index=True)
    playeraccount = models.ForeignKey(PlayerAccount, on_delete=CASCADE)
    playername = models.CharField(
        max_length=128, blank=True, null=True, db_index=True
    )  # this fields is redundant, but neccessary for db-side ordering of tables

    trueskill_mu = models.FloatField(default=25.0)
    trueskill_sigma = models.FloatField(default=25.0 / 3)

    @property
    def skilluncertainty(self):
        # from spads_0.11.9.pl - sendPlayerSkill()
        if self.trueskill_sigma > 3:
            return 3
        elif self.trueskill_sigma > 2:
            return 2
        elif self.trueskill_sigma > 1.5:
            return 1
        else:
            return 0

    def __unicode__(self):
        return "{}({}, {}, {}, {}) TS: ({}, {})".format(
            self.__class__.__name__,
            self.pk,
            self.playername,
            self.game.abbreviation,
            self.match_type,
            self.trueskill_mu,
            self.trueskill_sigma,
        )

    class Meta:
        ordering = ["-trueskill_mu"]
        abstract = True class RatingHistory(RatingBase):
    match = models.ForeignKey(Replay, on_delete=CASCADE)
    match_date = models.DateTimeField(
        blank=True, null=True, db_index=True
    )  # this fields is redundant, but neccessary for db-side ordering of tables

    def __unicode__(self):
        return "RatingHistory({}, {}): {}".format(self.pk, self.match_date, super(RatingHistory, self))

    class Meta:
        ordering = ["-match_date", "playername"] class Replay(models.Model):
    versionString = models.CharField(max_length=128)
    gameID = models.CharField(max_length=32, unique=True)
    unixTime = models.DateTimeField(verbose_name="date of match", db_index=True)
    wallclockTime = models.CharField(max_length=32, verbose_name="length of match")
    autohostname = models.CharField(max_length=128, blank=True, null=True, db_index=True)
    gametype = models.CharField(max_length=256, db_index=True)
    startpostype = models.IntegerField(blank=True, null=True)
    title = models.CharField(max_length=256, db_index=True)
    short_text = models.CharField(max_length=50, db_index=True)
    long_text = models.CharField(max_length=513, db_index=True)
    notcomplete = models.BooleanField(default=True)
    map_info = models.ForeignKey(Map, blank=True, null=True, on_delete=CASCADE)
    map_img = models.ForeignKey(MapImg, blank=True, null=True, on_delete=CASCADE)
    tags = models.ManyToManyField(Tag)
    uploader = models.ForeignKey(User, on_delete=CASCADE)
    upload_date = models.DateTimeField(auto_now_add=True, db_index=True)
    filename = models.CharField(max_length=256)
    path = models.CharField(max_length=256)
    download_count = models.IntegerField(default=0)
    comment_count = models.IntegerField(default=0)
    rated = models.BooleanField(default=False)
    published = models.BooleanField(default=False)

    def __unicode__(self):
        return "Replay({}, {!r}, {!r})".format(self.pk, self.gameID, self.title)

    def to_dict(self):
        return {
            "id": self.id,
            "versionString": self.versionString,
            "gameID": self.gameID,
            "unixTime": self.unixTime,
            "wallclockTime": self.wallclockTime,
            "autohostname": self.autohostname,
            "gametype": self.gametype,
            "startpostype": self.startpostype,
            "title": self.title,
            "short_text": self.short_text,
            "long_text": self.long_text,
            "map_name": self.map_info.name,
            "map_startpos": self.map_info.startpos,
            "map_width": self.map_info.width,
            "map_height": self.map_info.height,
            "tags": ",".join(self.tags.all().values_list("name", flat=True)) if self.tags.exists() else "",
            "uploader": self.uploader.username,
            "upload_date": self.upload_date,
        }

    def get_absolute_url(self):
        return reverse("srs/replay", args=[str(self.gameID)])

    @property
    def was_succ_uploaded(self):
        return not UploadTmp.objects.filter(replay=self).exists()

    class Meta:
        get_latest_by = "upload_date"
        ordering = ["-upload_date"]

    @property
    def game(self):
        return self.game_release.game

    @property
    def game_release(self):
        try:
            gr = GameRelease.objects.get(name=self.gametype)
            return gr
        except ObjectDoesNotExist:
            # new game release
            pass

        # look for existing game name
        game = None
        for i in range(len(self.gametype)):
            _game = Game.objects.filter(name=self.gametype[: -1 * i])
            if _game.count() == 1:
                game = _game.first()
                break
            elif _game.count() > 1:
                msg = "Found %r Games with name %r: %r.\ngametype: %r\ngameID: %r\nmatch: %r." % (
                    _game.count(),
                    self.gametype[: -1 * i],
                    _game,
                    self.gametype,
                    self.gameID,
                    self.__unicode__(),
                )
                logger.error(msg)
                send_mail([x[1] for x in settings.ADMINS], "SRS: ambiguous game name", msg)
            else:
                pass
        if not game:
            logger.debug("not game")
            msg = "Unknown gametype: %r\ngameID: %r\nmatch: %r." % (
                self.gametype,
                self.gameID,
                self.__unicode__(),
            )
            logger.error(msg)
            send_mail([x[1] for x in settings.ADMINS], "SRS: Unknown game name", msg)

            game_name = str()
            version = str()
            in_version = False
            for gr_name_part in self.gametype.split():
                version_start = ["v", "V", "b", "("]
                version_start.extend(map(str, range(10)))
                if (
                    in_version
                    or gr_name_part.upper().startswith("TEST")
                    or gr_name_part.upper().startswith("RC")
                    or gr_name_part[0] in version_start
                ):
                    in_version = True
                    version += "{} ".format(gr_name_part)
                else:
                    game_name += "{} ".format(gr_name_part)

            game_name = game_name.rstrip()
            while game_name[-1] in [" ", "-"]:
                game_name = game_name[:-1]
        else:
            game_name = game.name
            version = self.gametype[len(game.name) :]
        version = version.strip()
        if not version:
            version = "n/a"

        if version[0].upper() == "V" or version[0] == ".":
            version = version[1:]

        game, _ = Game.objects.get_or_create(
            name=game_name,
            defaults={"abbreviation": reduce(lambda x, y: x + y, [gn[0].upper() for gn in game_name.split()])},
        )
        return GameRelease.objects.create(name=self.gametype, game=game, version=version)

    @property
    def match_type(self):
        """returns string (from searching through tags): 1v1 / Team / FFA / TeamFFA'"""
        # quick and dirty using tags
        try:
            tag = self.tags.filter(name__regex=r"^[0-9]v[0-9]$")[0]
            if tag.name == "1v1":
                return "1v1"
            else:
                return "Team"
        except IndexError:
            pass
        try:
            tag = self.tags.get(name__regex=r"^TeamFFA$")
            return tag.name
        except ObjectDoesNotExist:
            pass
        try:
            tag = self.tags.get(name__regex=r"^FFA$")
            return tag.name
        except ObjectDoesNotExist:
            pass

        # thoroughly using player/team count
        allyteams = Allyteam.objects.filter(replay=self)
        if allyteams.count() == 2:
            if PlayerAccount.objects.filter(player__team__allyteam__in=allyteams).exclude(accountid=0).count() == 2:
                return "1v1"
            else:
                return "Team"
        elif allyteams.count() > 2:
            if (
                PlayerAccount.objects.filter(player__team__allyteam__in=allyteams).exclude(accountid=0).count()
                == allyteams.count()
            ):
                return "FFA"
            else:
                return "TeamFFA"
        else:
            # this is kind of a broken match, but not returning anything breaks the web site
            return "1v1"

    @property
    def match_type_short(self):
        """returns string (from match_type()): 1 / T / F / G"""
        if self.match_type == "TeamFFA":
            return "G"
        else:
            return self.match_type[0]

    @property
    def num_players(self):
        """returns string (from counting players): 1v1 / 1v5 / 6v6 / 2v2v2v2 / ..."""
        try:
            allyteams = Allyteam.objects.filter(replay=self)
            at_sizes = [PlayerAccount.objects.filter(player__team__allyteam=at).count() for at in allyteams]
            at_sizes.sort()
            return str(reduce(lambda x, y: "{}v{}".format(x, y), at_sizes))
        except:
            logger.exception("FIXME: to broad exception handling.")
            return "?v?"

    @property
    def match_end(self):
        length = self.wallclockTime.split(":")
        try:
            length2 = datetime.timedelta(seconds=int(length[2]), minutes=int(length[1]), hours=int(length[0]))
        except KeyError:
            return self.unixTime
        return self.unixTime + length2

    @property
    def duration_ISO_8601(self):
        length = self.wallclockTime.split(":")
        try:
            return "PT{0}H{1}M{2}S".format(*length)
        except IndexError:
            return self.wallclockTime

    playername = "N/A"  # something must exists, or PlayersReplayTable will always have an empty column

    def _playername(self, playeraccount):
        player = Player.objects.filter(replay=self, account=playeraccount)[
            0
        ]  # not using get() for the case of a spec-cheater
        return player.name

    def _faction_result(self, playeraccount):
        """
        Create cached entries, so this function (and the DB queries) runs only once per line in PlayersReplayTable.
        """
        try:
            player = Player.objects.get(replay=self, account=playeraccount)
        except MultipleObjectsReturned:  # happens on Bots player page
            if playeraccount.accountid == 0:
                player = Player.objects.filter(replay=self, account=playeraccount)[0]
            else:
                player = Player.objects.get(replay=self, account=playeraccount, spectator=False)
        try:
            team = Team.objects.filter(replay=self, teamleader=player)[0]
            if team.allyteam.winner:
                self._result_cache = (playeraccount, "won")
            else:
                self._result_cache = (playeraccount, "lost")
            self._faction_cache = (playeraccount, team.side)
        except (ObjectDoesNotExist, IndexError):
            self._result_cache = (playeraccount, "spec")
            self._faction_cache = (playeraccount, "spec")

    faction = "N/A"  # something must exists, or PlayersReplayTable will always have an empty column

    def _faction(self, playeraccount):
        """
        This is used by PlayersReplayTable
        """
        if not hasattr(self, "_faction_cache") or not self._faction_cache[0] == playeraccount:
            self._faction_result(playeraccount)
        return self._faction_cache[1]

    result = "N/A"  # something must exists, or PlayersReplayTable will always have an empty column

    def _result(self, playeraccount):
        """
        This is used by PlayersReplayTable
        """
        if not hasattr(self, "_result_cache") or not self._result_cache[0] == playeraccount:
            self._faction_result(playeraccount)
        return self._result_cache[1]

    @property
    def bawards(self):
        try:
            return BAwards.objects.get(replay=self)
        except ObjectDoesNotExist:
            return None

    @property
    def cursed_awards(self):
        """
        :return: [('shell', {
            'players': [(<Player a>, 12), (<Player b>, 34)],
            'img': 'trophy_shell.png',
            'name': 'Turtle Shell'
            }),
        ...]
        """
        res = dict()
        ca_fields = CursedAwards.sorted_field_names()
        try:
            for ca in CursedAwards.objects.filter(replay=self):
                for fn in ca_fields:
                    cav = getattr(ca, fn)
                    if cav:
                        res.setdefault(fn, {})["name"] = CursedAwards.field_to_name[fn]
                        res[fn]["img"] = os.path.join("img/cursed_awards", CursedAwards.field_to_image[fn])
                        res[fn].setdefault("players", []).append((ca.player, cav))
                        if len(res[fn]["players"]) > 1:
                            res[fn]["players"] = sorted(
                                res[fn]["players"],
                                key=operator.itemgetter(1),
                                reverse=True,
                            )
        except ObjectDoesNotExist:
            return None
        return sorted(res.items(), key=operator.itemgetter(0)) class SiteStats(models.Model):
    last_modified = models.DateTimeField(auto_now=True)
    replays = models.IntegerField()
    tags = models.CharField(max_length=1000)
    maps = models.CharField(max_length=1000)
    active_players = models.CharField(max_length=1000)
    all_players = JSONTextField()
    comments = models.CharField(max_length=1000)
    games = models.CharField(max_length=1000)
    bawards = JSONTextField()

    @property
    def bawards_decoded(self):
        return dict(
            (
                award,
                ((PlayerAccount.objects.get(pk=ps[0]), ps[1]) for ps in player_stats),
            )
            for award, player_stats in self.bawards.items()
        ) class SldbPlayerTSGraphCache(models.Model):
    last_modified = models.DateTimeField(auto_now_add=True)
    account = models.ForeignKey(PlayerAccount, on_delete=CASCADE)
    game = models.ForeignKey(Game, on_delete=CASCADE)
    filepath_global = models.CharField(max_length=256)
    filepath_duel = models.CharField(max_length=256)
    filepath_ffa = models.CharField(max_length=256)
    filepath_team = models.CharField(max_length=256)
    filepath_teamffa = models.CharField(max_length=256)

    match_type2sldb_name = {
        "1": "Duel",
        "T": "Team",
        "F": "FFA",
        "G": "Global",
        "L": "TeamFFA",
    }

    def __unicode__(self):
        return "SldbPlayerTSGraphCache({}) {} | {} | {}".format(self.pk, self.account, self.game, self.filepath_global)

    def remove_files(self):
        for filepath in (
            self.filepath_global,
            self.filepath_duel,
            self.filepath_ffa,
            self.filepath_team,
            self.filepath_teamffa,
        ):
            if "static/img/" in filepath:
                # don't remove static error / info images
                continue
            else:
                try:
                    os.remove(filepath)
                except OSError as exc:
                    logger.warning(
                        "Cannot remove file '%s' of cache entry %d: %s",
                        filepath,
                        self.id,
                        exc,
                    )

    @staticmethod
    def purge_old():
        """
        Remove >= 30 days old entries
        """
        old_entries = SldbPlayerTSGraphCache.objects.filter(
            last_modified__lt=datetime.datetime.now(tz=Replay.objects.latest().unixTime.tzinfo)
            - datetime.timedelta(days=30)
        )
        if old_entries.exists():
            logger.debug("Deleting stale SldbPlayerTSGraphCache objects: %s", old_entries)
            for entry in old_entries:
                entry.remove_files()
            old_entries.delete()

    def as_dict(self):
        return {
            "Global": self.filepath_global,
            "Duel": self.filepath_duel,
            "FFA": self.filepath_ffa,
            "Team": self.filepath_team,
            "TeamFFA": self.filepath_teamffa,
        } class Tag(models.Model):
    name = models.CharField(max_length=128, unique=True)

    def __repr__(self):
        return "Tag({}, {})".format(self.pk, self.name)

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        return reverse("srs/tag", args=[self.name])

    @property
    def replay_count(self):
        return Replay.objects.filter(tags=self).count() class Team(models.Model):
    allyteam = models.ForeignKey(Allyteam, on_delete=CASCADE)
    handicap = models.IntegerField()
    rgbcolor = models.CharField(max_length=23)
    side = models.CharField(max_length=32)
    startposx = models.IntegerField(blank=True, null=True)
    startposy = models.IntegerField(blank=True, null=True)
    startposz = models.IntegerField(blank=True, null=True)
    teamleader = models.ForeignKey(Player, related_name="+", on_delete=CASCADE)
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    num = models.SmallIntegerField()

    def __unicode__(self):
        return "Team({}, {})".format(self.pk, self.teamleader.name) class TeamStats(models.Model):
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    stat_type = models.CharField(max_length=32)
    stats = models.BinaryField(blank=True, null=True, default=None)

    graphid2label = OrderedDict(
        (
            ("metalUsed", "Metal Used"),
            ("metalProduced", "Metal Produced"),
            ("metalExcess", "Metal Excess"),
            ("metalReceived", "Metal Received"),
            ("metalSent", "Metal Sent"),
            ("energyUsed", "Energy Used"),
            ("energyProduced", "Energy Produced"),
            ("energyExcess", "Energy Excess"),
            ("energyReceived", "Energy Received"),
            ("energySent", "Energy Sent"),
            ("damageDealt", "Damage Dealt"),
            ("damageReceived", "Damage Received"),
            ("unitsProduced", "Units Produced"),
            ("unitsKilled", "Units Killed"),
            ("unitsDied", "Units Died"),
            ("unitsReceived", "Units Received"),
            ("unitsSent", "Units Sent"),
            ("unitsCaptured", "Units Captured"),
            ("unitsOutCaptured", "Units Stolen"),
        )
    )

    @property
    def decompressed(self):
        return ujson.loads(zlib.decompress(self.stats))

    @property
    def label(self):
        return self.graphid2label[self.stat_type]

    def __unicode__(self):
        return "TeamStats(pk={}, replay.pk={}, stat_type={}, {!r}...)".format(
            self.pk, self.replay.pk, self.stat_type, self.stats[:4]
        ) class UploadTmp(models.Model):
    replay = models.ForeignKey(Replay, on_delete=CASCADE)

    def __unicode__(self):
        return "UploadTmp({}, {})".format(self.pk, self.replay) class XTAwards(models.Model):
    replay = models.ForeignKey(Replay, on_delete=CASCADE)
    isAlive = models.SmallIntegerField(default=-1)
    player = models.ForeignKey(Player, on_delete=CASCADE)
    unit = models.CharField(max_length=1024)
    kills = models.IntegerField(default=-1)
    age = models.IntegerField(default=-1)

    def __unicode__(self):
        return "XTAwards({}) Replay: {} | isAlive: {} | player: {} | unit: {} | kills: {} | age: {}".format(
            self.pk,
            self.replay.pk,
            self.isAlive,
            self.player.name,
            self.unit,
            self.kills,
            self.age,
        ) def get_owner_list(uploader):
    res = [uploader]
    res.extend(AdditionalReplayOwner.objects.filter(uploader=uploader))
    return res @background
def update_stats(force=False):
    """
    some very expensive operations -> this runs only once per day
    :arg force: bool: run even if has already run in the last 24h
    """
    # statistics like most played maps, most used tags etc. (sed in the browse and all_players views)
    sist, created = SiteStats.objects.get_or_create(
        id=1,
        defaults={
            "replays": 0,
            "tags": "",
            "maps": "",
            "active_players": "",
            "all_players": "",
            "comments": "",
            "games": "",
            "bawards": "",
        },
    )
    if created or force or (datetime.datetime.now(tz=sist.last_modified.tzinfo) - sist.last_modified).days > 0:
        # update stats
        logger.info("starting stats update...")
        timer = SrsTiming()
        timer.start("update_stats()")
        replays = Replay.objects.count()
        now = datetime.datetime.now(timezone.get_current_timezone())
        start_date = now - datetime.timedelta(days=30)
        if settings.DEBUG:
            # no daily uploads on my dev pc - list can be empty
            start_date = now - datetime.timedelta(days=300)
        default_tags = Tag.objects.filter(
            name__in=[
                "1v1",
                "2v2",
                "3v3",
                "4v4",
                "5v5",
                "6v6",
                "7v7",
                "8v8",
                "Team",
                "FFA",
                "TeamFFA",
                "Tourney",
            ]
        )
        tags = default_tags.annotate(num_replay=Count("replay"))
        maps = (
            Map.objects.filter(replay__unixTime__range=(start_date, now))
            .annotate(num_replay=Count("replay"))
            .order_by("-num_replay")[:10]
        )

        # statistic on number of matches of each game (for browse page)
        timer.start("games")
        games = list()
        for game in Game.objects.all():
            games.append(
                (
                    game.id,
                    Replay.objects.filter(gametype__in=game.gamerelease_set.values_list("name", flat=True)).count(),
                )
            )
        games.sort(key=operator.itemgetter(1), reverse=True)
        timer.stop("games")

        # most active players in the last 30 days (for browse page)
        timer.start("active_players")
        active_players = (
            PlayerAccount.objects.filter(
                accountid__gt=0,
                player__spectator=False,
                player__replay__unixTime__range=(start_date, now),
            )
            .annotate(Count("player__replay"))
            .order_by("-player__replay__count", "accountid")
        )
        timer.stop("active_players")

        # newest comments (for index page)
        comments = Comment.objects.filter(is_removed=False).reverse()[:5]

        # encode stats into strings
        timer.start("strings")
        if tags.exists():
            tags_s = "|".join(["{}.{}".format(t.id, t.num_replay) for t in tags])
        else:
            tags_s = ""
        if maps.exists():
            # map.id . #matches_last_30d / #matches_total
            maps_s = "|".join(
                ["{}.{} / {}".format(m.id, m.num_replay, Replay.objects.filter(map_info=m).count()) for m in maps]
            )
        else:
            maps_s = ""
        if active_players:
            act_pls_s = "|".join(["{}.{}".format(pa.id, pa.player__replay__count) for pa in active_players[:20]])
        else:
            act_pls_s = ""
        if comments.exists():
            comments_s = "|".join(map(str, comments.values_list("id", flat=True)))
        else:
            comments_s = ""
        if games:
            games_s = "|".join(["{}.{}".format(*g) for g in games])
        else:
            games_s = ""
        timer.stop("strings")

        #  player names and urls (for all_players page)
        timer.start("all_players")

        pl_pas = (
            PlayerAccount.objects.filter(accountid__gt=0, player__isnull=False)
            .values("accountid", "player__name")
            .order_by("player__name")
            .distinct()
        )
        all_players = [("/player/{}/".format(pl["accountid"]), pl["player__name"]) for pl in pl_pas]
        timer.stop("all_players")

        # BAwards statistics (for hall of fame)
        timer.start("bawards")
        bawards_fields = [
            field.name for field in BAwards._meta.get_fields() if "Award" in field.name and "Score" not in field.name
        ]
        bawards_stats = dict([(field, defaultdict(int)) for field in bawards_fields])
        bawards_no_bots = BAwards.objects.exclude(replay__player__account__accountid=0)
        for field in bawards_fields:
            arg = {"{}__isnull".format(field): False}
            for pa_pk in PlayerAccount.objects.filter(
                player__in=bawards_no_bots.filter(**arg).values(field)
            ).values_list("pk", flat=True):
                bawards_stats[field][pa_pk] += 1
            # converting dict to list, sort, keep only top 10
            bawards_stats[field] = sorted(bawards_stats[field].items(), key=operator.itemgetter(1), reverse=True)[:10]
        timer.stop("bawards")

        timer.start("save()")
        sist.replays = replays
        sist.tags = tags_s
        sist.maps = maps_s
        sist.active_players = act_pls_s
        sist.all_players = all_players
        sist.comments = comments_s
        sist.games = games_s
        sist.bawards = bawards_stats
        sist.save()
        timer.stop("save()")
        timer.stop("update_stats()")
        logger.info("timings:\n%s", timer)
        return sist class SLDBError(Exception):
    pass def get_sldb_leaderboards(game, match_types=["1", "T", "F", "G", "L"]):
    """
    get_sldb_leaderboards("BA") -> QuerySet of SldbLeaderboardGame for requested game and gametypes
        Leaderboard data is requested only once per day from SLDB (cached for 1 day)!

    SLDB XmlRpc interface docu: https://github.com/Yaribz/SLDB/blob/master/XMLRPC#93
    """
    #     logger.debug("game: %s match_types: %s", game, match_types)
    # test args
    if game.sldb_name == "":
        raise SLDBbadArgumentError("game", game)
    try:
        [(matchtype2sldb_gametype[match_type], match_type) for match_type in match_types]
    except:
        logger.exception("FIXME: to broad exception handling.")
        raise SLDBbadArgumentError("match_types", match_types)

    refresh_lbg = list()
    for match_type in match_types:
        lbg, created = SldbLeaderboardGame.objects.get_or_create(game=game, match_type=match_type)
        if created or datetime.datetime.now(tz=lbg.last_modified.tzinfo) - lbg.last_modified > datetime.timedelta(1):
            # new entry or older than 1 day -> refresh
            #             logger.debug("Leaderboard cache stale for %s", lbg)
            refresh_lbg.append(lbg)
        else:
            #             logger.debug("Leaderboard cache hit   for %s", lbg)
            pass
    if refresh_lbg:
        query_args = (
            game.sldb_name,
            [matchtype2sldb_gametype[lbg.match_type] for lbg in refresh_lbg],
        )
        try:
            leaderboards = _query_sldb("getLeaderboards", *query_args)
        except SLDBConnectionError as exc:
            # problem fetching data from SLDB, mark existing data as stale, so it will be retried next website reload
            logger.error("getLeaderboards '%s': %s", query_args, exc)
            for lbg in refresh_lbg:
                lbg.last_modified = datetime.datetime(1970, 1, 1, tzinfo=lbg.last_modified.tzinfo)
                lbg.save()
            leaderboards = list()
        for leaderboard in leaderboards:
            if leaderboard["status"] != 0:
                logger.error(
                    "status: %d for gameType %s, got: %s",
                    leaderboard["status"],
                    leaderboard["gameType"],
                    leaderboard,
                )
            else:
                # find corresponding SldbLeaderboardGame
                lbg = SldbLeaderboardGame.objects.get(
                    game=game,
                    match_type=sldb_gametype2matchtype[leaderboard["gameType"]],
                )
                rank = 0
                for player in leaderboard["players"]:
                    # save player infos
                    rank += 1
                    defaults = {
                        "account": _get_PlayerAccount(player["accountId"], -1, player["name"]),
                        "trusted_skill": float(player["trustedSkill"]),
                        "estimated_skill": float(player["estimatedSkill"]),
                        "uncertainty": float(player["uncertainty"]),
                        "inactivity": player["inactivity"],
                    }
                    (
                        sldb_lb_player,
                        created,
                    ) = SldbLeaderboardPlayer.objects.get_or_create(leaderboard=lbg, rank=rank, defaults=defaults)
                    if not created:
                        for k, v in defaults.items():
                            setattr(sldb_lb_player, k, v)
                        sldb_lb_player.save()
                # remove unused ranks
                SldbLeaderboardPlayer.objects.filter(leaderboard=lbg, rank__gt=rank).delete()
                # update timestamp
                lbg.last_modified = datetime.datetime.now(tz=lbg.last_modified.tzinfo)
                lbg.save()
    else:
        pass
    return SldbLeaderboardGame.objects.filter(game=game, match_type__in=match_types) def get_sldb_match_skills(gameIDs):
    """
    get_sldb_match_skills(["b70f1f270d5a828e24bd446828e0f133"]) ->
    {'status': 0,
     'results': [{'gameId': 'b70f1f270d5a828e24bd446828e0f133',
                  'status': 0,
                  'gameType': 'Duel',
                  'players': [{'accountId': 123456,
                               'privacyMode': 1,
                               'skills': [[12.34, 1.00], [56.78, 2.00], [90.12, 3.00], [34.56, 4.00]],
                               'account': PlayerAccount},
                              {'accountId': 67890,
                               'privacyMode': 1,
                               'skills': [[12.34, 1.00], [56.78, 2.00], [90.12, 3.00], [34.56, 4.00]]
                               'account': PlayerAccount}
                             ]
                 }
                ]
     }

    SLDB XmlRpc interface docu: https://github.com/Yaribz/SLDB/blob/master/XMLRPC#L60
    """
    #     logger.debug("gameIDs: %s", gameIDs)

    # check cache
    SldbMatchSkillsCache.purge_old()
    cache_miss = dict()
    result = list()
    for gameid in gameIDs:
        cache_entry, created = SldbMatchSkillsCache.objects.get_or_create(gameID=gameid, defaults={"text": ""})
        if created or cache_entry.text == "":
            #             logger.debug("MatchSkills cache miss for %s", gameid)
            cache_miss[gameid] = cache_entry
        else:
            #             logger.debug("MatchSkills cache hit  for %s", gameid)
            sldbmatch_decoded = ujson.loads(cache_entry.text)
            result.append(sldbmatch_decoded)

    if cache_miss:
        match_skills = _query_sldb("getMatchSkills", list(cache_miss.keys()))
        for match in match_skills:
            if match["status"] != 0:
                logger.error(
                    "status: %d for match %s, got: %s",
                    match["status"],
                    match["gameId"],
                    match,
                )
            else:
                for player in match["players"]:
                    player["account"] = _get_PlayerAccount(player["accountId"], player["privacyMode"])
                    for i in range(4):
                        mu = float(player["skills"][i].split("|")[0])
                        si = float(player["skills"][i].split("|")[1])
                        player["skills"][i] = [mu, si]
                dbentry = cache_miss[match["gameId"]]
                dbentry.text = ujson.dumps(match)
                dbentry.save()
                result.append(match)

    return result def get_sldb_player_ts_history_graphs(game_abbr, accountid):
    """
    Returns a PNG image of the TrueSkill history of a player.

    SLDB XmlRpc interface docu: https://github.com/Yaribz/SLDB/blob/master/XMLRPC#L141

    :param str game_abbr: one of: "BA", "EVO", "KP", "NOTA", "S1944", "TA", "XTA", "ZK" (see "modShortName" in
        https://github.com/Yaribz/SLDB/blob/master/XMLRPC#L7)
    :param int accountid: accountID of playeraccount for which the graph should be fetched
    :return: Dict - SldbPlayerTSGraphCache.as_dict()
            or SLDBstatusError
            or SLDBbadArgumentError
    """
    # check parameters
    try:
        accountid = int(accountid)
        pa = PlayerAccount.objects.get(accountid=accountid)
    except ObjectDoesNotExist:
        raise SLDBbadArgumentError("accountid", accountid)
    try:
        game = Game.objects.get(sldb_name=game_abbr)
    except ObjectDoesNotExist:
        raise SLDBbadArgumentError("game_abbr", game_abbr)

    # check if we a have cached version
    SldbPlayerTSGraphCache.purge_old()
    try:
        cached_graphs = SldbPlayerTSGraphCache.objects.get(account=pa, game=game)
        logger.debug(
            "Cache hit for SldbPlayerTSGraphCache.objects.get(account=%s, game=%s)",
            pa,
            game.sldb_name,
        )
        return cached_graphs.as_dict()
    except SldbPlayerTSGraphCache.MultipleObjectsReturned:
        logger.error(
            "More than 1 entry returned for SldbPlayerTSGraphCache.objects.get(account=%s, game=%s). "
            "Removing all, creating new one.",
            pa,
            game.sldb_name,
        )
        cached_graphs = SldbPlayerTSGraphCache.objects.filter(account=pa, game=game)
        for graph in cached_graphs:
            graph.remove_files()
        cached_graphs.delete()
    except SldbPlayerTSGraphCache.DoesNotExist:
        logger.debug(
            "Cache miss for SldbPlayerTSGraphCache.objects.get(account=%s, game=%s)",
            pa,
            game.sldb_name,
        )

    # fetch new graphs
    settings.SLDB_TIMEOUT = 10  # give SLDB more time for this
    query = _query_sldb("getPlayerSkillGraphs", game_abbr, accountid)
    # this returns either:
    # {   'TeamFFA': {'status': 0, 'graph': <xmlrpclib.Binary instance>},
    #     'Duel'   : {'status': 0, 'graph': <xmlrpclib.Binary instance>},
    #     'Global' : {'status': 0, 'graph': <xmlrpclib.Binary instance>},
    #     'FFA'    : {'status': 0, 'graph': <xmlrpclib.Binary instance>},
    #     'Team'   : {'status': 0, 'graph': <xmlrpclib.Binary instance>}
    # }
    # or SLDBstatusError
    now = datetime.datetime.now(tz=Replay.objects.latest().unixTime.tzinfo)
    graph = SldbPlayerTSGraphCache(game=game, account=pa)
    for match_type, result in query.items():
        if result["status"] == 0:
            path = os.path.join(
                settings.TS_HISTORY_GRAPHS_PATH,
                "%d_%s_%s_%s.png" % (accountid, game_abbr, match_type, now.strftime("%Y-%m-%d")),
            )
            open(path, "wb").write(result["graph"].data)
        else:
            path = os.path.join(settings.IMG_PATH, "tsh_nodata.png")
        setattr(graph, "filepath_{}".format(match_type.lower()), path)
    graph.save()
    logger.debug("Created SldbPlayerTSGraphCache: %s", graph)
    return graph.as_dict() def get_sldb_pref(accountid, pref):
    """
    get_sldb_pref(130601, "privacyMode") -> {'status': 0, 'result': '0'}

    SLDB XmlRpc interface docu: https://github.com/Yaribz/SLDB/blob/master/XMLRPC#L30
    """
    return _query_sldb("getPref", accountid, pref) def privatize_skill(ts):
    return rank2skill[skill2rank(ts)] def set_sldb_pref(accountid, pref, value=None):
    """
    set_sldb_pref(130601, "privacyMode", "0") -> {'status': 0}
    "value" is optional, if not provided the preference is set back to default value in SLDB.

    SLDB XmlRpc interface docu: https://github.com/Yaribz/SLDB/blob/master/XMLRPC#L45
    """
    if value:
        return _query_sldb("setPref", accountid, pref, value)
    else:
        return _query_sldb("setPref", accountid, pref) class SpringMaps:
    def __init__(self, mapname):
        self.map_name = mapname
        self.full_image_filename = "{}.jpg".format(self.map_name)
        self.map_info = None
        self.thumb = None

    @property
    def full_base_image_filepath(self) -> str:
        return path_join(settings.MAPS_PATH, self.full_image_filename)

    @staticmethod
    def get_replay_image_filepath(map_name, gameID) -> str:
        return "{}_{}.jpg".format(map_name, gameID)

    @classmethod
    def get_full_replay_image_filepath(cls, map_name, gameID) -> str:
        filename = cls.get_replay_image_filepath(map_name, gameID)
        return path_join(settings.MAPS_PATH, filename)

    def fetch_info(self):
        """
        fetches map information from springfiles.springrts.com, stores it in self.map_info
        - may raise an Exception when connecting to server
        """
        try:
            self.map_info = fetch_map_info(self.map_name)
        except:
            logger.exception("FIXME: to broad exception handling.")
            logger.warning("Could not retrieve map-info for map %r.", self.map_name)
            self.map_info = None
        return self.map_info

    def fetch_img(self):
        """
        fetches map image from springfiles.springrts.com
        """
        if not self.map_info:
            self.fetch_info()
        if self.map_info:
            map_url = self.map_info[0]["mapimages"][0]
            logger.info(
                "Downloaded map image from %r into %r...",
                map_url,
                self.full_base_image_filepath,
            )
            response = requests.get(map_url, stream=True)
            if response.status_code == 200:
                with open(self.full_base_image_filepath, "wb") as fp:
                    for chunk in response:
                        fp.write(chunk)
            else:
                logger.error('Could not download image %r. Setting image to "map_img_not_avail.jpg".')
                copyfile(
                    path_join(settings.IMG_PATH, "map_img_not_avail.jpg"),
                    self.full_base_image_filepath,
                )
        else:
            logger.warning("We have no map-info, setting image to 'map_img_not_avail.jpg'.")
            copyfile(
                path_join(settings.IMG_PATH, "map_img_not_avail.jpg"),
                self.full_base_image_filepath,
            )
        return self.full_image_filename

    def make_home_thumb(self):
        if not self.map_info or not path_exists(self.full_base_image_filepath):
            self.fetch_img()
        image = Image.open(self.full_base_image_filepath)
        size = settings.THUMBNAIL_SIZES["home"]
        image.thumbnail(size, Image.ANTIALIAS)
        self.thumb = "{}_home.jpg".format(self.map_name)
        image.save(path_join(settings.MAPS_PATH, self.thumb), "JPEG")
        return self.thumb

    def create_map_with_boxes(self, replay):
        """
        create a map picture with start boxes (if any) and player start positions
        """
        img = Image.open(self.full_base_image_filepath)

        full_img_x, full_img_y = img.size
        # map positions for players are in pixel
        # map pixel size = Spring Map Size (like 16x16) * 512
        # from: http://springrts.com/wiki/Mapdev:diffuse#Image_File
        if replay.map_info.width > 128:
            # springfiles.springrts.com returnd pixel size
            map_px_x = replay.map_info.width
            map_px_y = replay.map_info.height
        else:
            # springfiles.springrts.com returnd Spring Map Size
            map_px_x = replay.map_info.width * 512
            map_px_y = replay.map_info.height * 512

        map_img_mult_x = map_px_x / full_img_x
        map_img_mult_y = map_px_y / full_img_y

        #
        # create start boxes
        #
        allyteams = Allyteam.objects.filter(
            replay=replay,
            startrectleft__isnull=False,
            startrecttop__isnull=False,
            startrectright__isnull=False,
            startrectbottom__isnull=False,
        )
        if allyteams.exists():
            # 6 defined colors
            colors = [
                (200, 0, 0),
                (0, 200, 0),
                (0, 0, 200),
                (200, 200, 0),
                (200, 0, 200),
                (0, 200, 200),
            ]
            if Allyteam.objects.filter(replay=replay).count() > 6:
                # random colors for the remaining AllyTeams
                for _ in range(0, Allyteam.objects.filter(replay=replay).count() - 6):
                    colors.append(
                        (
                            random.randint(0, 255),
                            random.randint(0, 255),
                            random.randint(0, 255),
                        )
                    )
            c_count = 0
            for at in allyteams:
                # transparency mask
                team_layer = Image.new(
                    "RGBA",
                    (
                        int(at.startrectright * full_img_x) - int(at.startrectleft * full_img_x),
                        int(at.startrectbottom * full_img_y) - int(at.startrecttop * full_img_y),
                    ),
                    (256, 256, 256, 96),
                )
                # draw border of box (no transparency)
                draw = ImageDraw.Draw(team_layer)
                tl_x, tl_y = team_layer.size
                for i in range(4):  # 4px border
                    draw.rectangle(
                        [(0 + i, 0 + i), (tl_x - i, tl_y - i)],
                        fill=None,
                        outline=(256, 256, 256, 256),
                    )
                del draw
                # overlay map with partly transparent box
                img.paste(
                    colors[c_count],
                    box=(
                        int(at.startrectleft * full_img_x),
                        int(at.startrecttop * full_img_y),
                        int(at.startrectright * full_img_x),
                        int(at.startrectbottom * full_img_y),
                    ),
                    mask=team_layer,
                )
                c_count += 1

        draw = ImageDraw.Draw(img)
        players = Player.objects.filter(
            replay=replay,
            spectator=False,
            startposx__isnull=False,
            startposz__isnull=False,
        )
        if players.exists():
            #
            # draw players actual start positions
            #
            font = ImageFont.truetype(path_join(settings.FONTS_PATH, "VeraMono.ttf"), 28)
            for player in players:
                pl_img_pos_x = player.startposx / map_img_mult_x
                pl_img_pos_y = player.startposz / map_img_mult_y  # z is in spring what y is in img
                team_color = ImageColor.getrgb("#{}".format(player.team.rgbcolor))
                # center number and circle above startpoint --> move up and left by text-size/2
                text_w, text_h = font.getsize(str(player.team.num))
                if player.team.num > 9:
                    w = 15
                else:
                    w = 0
                draw.ellipse(
                    (
                        pl_img_pos_x - 12 - text_w / 2,
                        pl_img_pos_y - 8 - text_h / 2,
                        pl_img_pos_x + 30 + w - text_w / 2,
                        pl_img_pos_y + 36 - text_h / 2,
                    ),
                    outline=team_color,
                    fill=None,
                )
                draw.ellipse(
                    (
                        pl_img_pos_x - 11 - text_w / 2,
                        pl_img_pos_y - 7 - text_h / 2,
                        pl_img_pos_x + 29 + w - text_w / 2,
                        pl_img_pos_y + 35 - text_h / 2,
                    ),
                    outline=team_color,
                    fill=None,
                )
                draw.ellipse(
                    (
                        pl_img_pos_x - 10 - text_w / 2,
                        pl_img_pos_y - 6 - text_h / 2,
                        pl_img_pos_x + 28 + w - text_w / 2,
                        pl_img_pos_y + 34 - text_h / 2,
                    ),
                    outline=team_color,
                    fill=None,
                )
                draw.ellipse(
                    (
                        pl_img_pos_x - 9 - text_w / 2,
                        pl_img_pos_y - 5 - text_h / 2,
                        pl_img_pos_x + 27 + w - text_w / 2,
                        pl_img_pos_y + 33 - text_h / 2,
                    ),
                    outline=team_color,
                    fill="black",
                )
                draw.text(
                    (pl_img_pos_x - text_w / 2, pl_img_pos_y - text_h / 2),
                    str(player.team.num),
                    font=font,
                )
        else:
            #
            # draw start positions saved in map
            #
            for startpos in [
                (float(pair.split(",")[0]), float(pair.split(",")[1])) for pair in replay.map_info.startpos.split("|")
            ]:
                draw_pos_x = startpos[0] / map_img_mult_x
                draw_pos_y = startpos[1] / map_img_mult_y
                draw.ellipse(
                    (draw_pos_x - 5, draw_pos_y - 5, draw_pos_x + 5, draw_pos_y + 5),
                    outline="white",
                    fill="green",
                )
        del draw

        # img.thumbnail(settings.THUMBNAIL_SIZES["replay"], Image.ANTIALIAS)
        full_path = self.get_full_replay_image_filepath(self.map_name, replay.gameID)
        try:
            img.save(full_path, "JPEG")
        except:
            logger.exception("FIXME: to broad exception handling.")
            logger.exception("Could not save %r.", full_path)
        return self.get_replay_image_filepath(self.map_name, replay.gameID) def save_desc(replay, short, long_text, autotag):
    replay.short_text = short
    replay.long_text = long_text
    if not short:
        short = "{} on {}".format(replay.match_type, replay.map_info.name)
        if replay.match_type != "1v1":
            short = "{} {}".format(replay.num_players, short)
        replay.title = short
    else:
        logger.debug("short")
        if autotag in short.split():
            replay.title = short
        else:
            replay.title = "{} {}".format(autotag, short)

    num_tag = replay.num_players
    if num_tag not in short.split():
        replay.title = "{} {}".format(num_tag, short) def save_tags(replay, tags):
    if tags:
        # strip comma separated tags and remove empty ones
        tags_ = [t.strip() for t in tags.split(",") if t.strip()]
        for tag in tags_:
            t_obj, _ = Tag.objects.get_or_create(name__iexact=tag, defaults={"name": tag})
            replay.tags.add(t_obj) def set_autotag(replay):
    autotag = replay.match_type

    tag, created = Tag.objects.get_or_create(name=autotag, defaults={})
    if created:
        replay.tags.add(tag)
    else:
        if not replay.tags.filter(name=autotag).exists():
            replay.tags.add(tag)

    if autotag != "1v1":
        num_tag = replay.num_players
        tag, created = Tag.objects.get_or_create(name=num_tag, defaults={})
        if created:
            replay.tags.add(tag)
        else:
            if not replay.tags.filter(name=num_tag).exists():
                replay.tags.add(tag)

    return autotag def fix_missing_winner(replay):
    logger.info("fix_missing_winner(%s)", replay)
    SldbMatchSkillsCache.objects.filter(gameID=replay.gameID).delete()
    try:
        match_skills = get_sldb_match_skills([replay.gameID])
        if match_skills:
            match_skills = match_skills[0]
    except SLDBError as exc:
        logger.error("get_sldb_match_skills(%s): %s", [replay.gameID], exc)
        return

    if match_skills and match_skills["status"] == 0:
        match_skills_by_pa = dict()
        for player in match_skills["players"]:
            match_skills_by_pa[player["account"]] = [s[0] for s in player["skills"][:2]]
    else:
        logger.error("No TS data for match from SLDB available, no fix possible.")
        return
    for at in replay.allyteam_set.all():
        pas = [team.teamleader.account for team in at.team_set.all()]
        try:
            at_sum_old = sum(match_skills_by_pa[pa][0] for pa in pas)
            at_sum_new = sum(match_skills_by_pa[pa][1] for pa in pas)
        except KeyError:
            logger.error("Player ID inconsistency between SLDB and replay data detected. Ignoring result.")
            logger.error(
                "match_skills=%r match_skills_by_pa=%r",
                match_skills,
                match_skills_by_pa,
            )
            return

        logger.info("Allyteam %s has rating change %r -> %r.", at, at_sum_old, at_sum_new)
        if at_sum_new > at_sum_old:
            logger.info("Allyteam %s has won.", at)
            at.winner = True
            at.save()
        else:
            logger.info("Allyteam %s has lost.", at)
            at.winner = False
            at.save()
import gzip
import logging
import operator
import os.path
import re
import magic
import MySQLdb
from functools import reduce
from django.conf import settings
from django.contrib.auth import login as django_contrib_auth_login, logout as django_contrib_auth_logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.utils.html import strip_tags
from django.views.decorators.cache import never_cache
from django_comments.models import Comment
from .ajax_views import replay_filter
from .common import all_page_infos
from .forms import EditReplayForm, GamePref, SLDBPrivacyForm
from .match_stats import MatchStatsGeneration
from .models import (
    Allyteam,
    CursedAwards,
    ExtraReplayMedia,
    Game,
    GameRelease,
    Map,
    MapOption,
    ModOption,
    NewsItem,
    Player,
    PlayerAccount,
    RatingBase,
    RatingHistory,
    Replay,
    SiteStats,
    SldbPlayerTSGraphCache,
    Tag,
    Team,
    TeamStats,
    UploadTmp,
    XTAwards,
    get_owner_list,
    update_stats,
)
from .sldb import (
    SLDBError,
    get_sldb_leaderboards,
    get_sldb_match_skills,
    get_sldb_player_ts_history_graphs,
    get_sldb_pref,
    privatize_skill,
    set_sldb_pref,
)
from .springmaps import SpringMaps
from .upload import save_desc, save_tags, set_autotag
from .utils import fix_missing_winner

    page_history = request.session.get("page_history")
    if page_history and isinstance(page_history, list):
        # check data (session data is user input)
        for page in list(page_history):
            if not gameid_re.findall(page):
                page_history.remove(page)
        if gameID not in page_history:
            if len(page_history) > 4:
                page_history.remove(page)
            page_history.insert(0, gameID)
    else:
        page_history = [gameID]
    request.session["page_history"] = page_history

    return render(request, "replay.html", c)


def replay_by_id(request, replayid):
    try:
        r = Replay.objects.get(id=replayid)
        return HttpResponseRedirect(r.get_absolute_url())
    except ObjectDoesNotExist:
        raise Http404("No replay with ID {!r} found.".format(strip_tags(replayid)))


@login_required
@never_cache
def edit_replay(request, gameID):
    c = all_page_infos(request)
    try:
        replay = Replay.objects.prefetch_related().get(gameID=gameID)
        c["replay"] = replay
    except ObjectDoesNotExist:
        raise Http404("No replay with ID {!r} found.".format(strip_tags(gameID)))

    if request.user != replay.uploader:
        return render(request, "edit_replay_wrong_user.html", c)

    if request.method == "POST":
        form = EditReplayForm(request.POST)
        if form.is_valid():
            short = request.POST["short"]
            long_text = request.POST["long_text"]
            tags = request.POST["tags"]

            for tag in replay.tags.all():
                if tag.replay_count == 1 and tag.pk > 10:
                    # this tag was used only by this replay and is not one of the default ones (see srs/sql/tag.sql)
                    tag.delete()
            replay.tags.clear()
            autotag = set_autotag(replay)
            save_tags(replay, tags)
            save_desc(replay, short, long_text, autotag)
            replay.save()
            logger.info(
                "User '%s' modified replay '%s': short: '%s' title:'%s' long_text:'%s' tags:'%s'",
                request.user,
                replay.gameID,
                replay.short_text,
                replay.title,
                replay.long_text,
                ", ".join(Tag.objects.filter(replay=replay).values_list("name", flat=True)),
            )
            return HttpResponseRedirect(replay.get_absolute_url())
    else:
        form = EditReplayForm(
            {
                "short": replay.short_text,
                "long_text": replay.long_text,
                "tags": ", ".join(Tag.objects.filter(replay=replay).values_list("name", flat=True)),
            }
        )
    c["form"] = form
    c["replay_details"] = True

    return render(request, "edit_replay.html", c)


def download(request, gameID):
    replay = get_object_or_404(Replay, gameID=gameID)

    replay.download_count += 1
    replay.save()

    path = os.path.join(replay.path, replay.filename)
    try:
        filemagic = magic.from_file(path, mime=True)
    except IOError:
        errmsg = "File for replay(id=%r) %r not found: %r" % (replay.id, replay, path)
        logger.error(errmsg)
        raise Http404(errmsg)
    if filemagic.endswith("gzip") and not replay.filename.endswith(".sdfz"):
        myopen = gzip.open
    else:
        myopen = open
    if replay.filename.endswith(".gz"):
        filename = replay.filename[:-3]
    else:
        filename = replay.filename
    with myopen(path, "rb") as demofile:
        response = HttpResponse(demofile.read(), content_type="application/x-spring-demo")
    response["Content-Disposition"] = 'attachment; filename="%s"' % filename
    return response


def respect_privacy(request, accountid):
    """
    Check if the currently logged in user is allowed to see private TS data
    :param request: django request object - request.user is the website user
    :param accountid: int - user with the TrueSkill data that may be privacy protected
    :return: bool - if True: use only rounded numbers / don't show TS history graphs / etc
    """
    accountid = int(accountid)
    if request.user.is_authenticated and accountid == request.user.userprofile.accountid:
        # if the logged in user has the same accountid as the parameter accountid -> no privacy protection
        privacy = False
    else:
        # request privacyMode of player from SLDB
        try:

            privacy_mode = get_sldb_pref(accountid, "privacyMode")