Ejemplo n.º 1
0
def match_species_with_prompts(
        sptxt: Union[str,
                     int], search_all: bool) -> Tuple[str, Optional[Pokemon]]:
    """
    :param search_all: restrict to nestable species or not
    :param sptxt: pokémon name or number to search for
    :return: the pokémon that matches the species text
    """

    reslst = match_species_by_name_or_number(
        sptxt.split("*")[0],
        input_set=Pokemon.objects.all() if search_all else nestable_species(),
    )

    if not reslst.count():
        return sptxt, None
    if reslst.count() == 1:
        return reslst[0].name, reslst[0]

    choice = pick_from_qs("Index of species (not species number): ", reslst,
                          f"{sptxt} [None]")
    if choice > 0:
        choice -= 1  # deal with the 0 option

        return reslst[choice].name, reslst[choice]
    return sptxt, None
Ejemplo n.º 2
0
    def setUpTestData(cls):
        # setUpTestData: Run once to set up non-modified data for all class methods.
        # up bot IDs, and test nests here
        our_town: NstMetropolisMajor = NstMetropolisMajor.objects.create(
            name="Our Town", active=True)
        somewhere_else: NstMetropolisMajor = NstMetropolisMajor.objects.create(
            name="Somewhere Else", active=True)
        neighborhoods: List[NstNeighborhood] = [
            NstNeighborhood.objects.create(name="Far Far Away",
                                           major_city=somewhere_else),
            NstNeighborhood.objects.create(name="Right Here",
                                           major_city=our_town),
            NstNeighborhood.objects.create(name="Next Door",
                                           major_city=our_town),
        ]
        nest_dex_index: int = 0
        nest_objs: List = []
        for _ in range(5):
            for place in neighborhoods:
                nest_objs.append(
                    NstLocation(
                        permanent_species=nestable_species()[nest_dex_index],
                        neighborhood=place,
                    ))
        for place in neighborhoods:
            for _ in range(10):
                nest_objs.append(NstLocation(neighborhood=place))
        NstLocation.objects.bulk_create(nest_objs)

        NstAdminEmail.objects.create(pk=1, is_bot=2)  # system
        NstAdminEmail.objects.create(pk=2, is_bot=0, city=our_town)  # human
        NstAdminEmail.objects.create(pk=3, is_bot=1, city=our_town)  # bot
        pass
Ejemplo n.º 3
0
def pokemon_validator(value, isl=enabled_in_pogo(nestable_species())):
    match_count: int = match_species_by_name_or_number(
        sp_txt=value,
        only_one=True,
        input_set=isl,
        age_up=True,
        previous_evolution_search=True,
    ).count()
    stem: str = f"⚠️{QUOT_L}{value}{QUOT_R} "
    if match_count == 0:
        raise ValidationError(stem + f"did not match any pokémon.")
    elif match_count > 1:
        raise ValidationError(
            stem +
            f"matched {match_count} pokémon.{MAGIC_NEWLINE}  Please be more specific."
        )
Ejemplo n.º 4
0
def nsla_sp_filter(
    species: Union[str, int],
    nsla: "QuerySet[NstSpeciesListArchive]" = NstSpeciesListArchive.objects.all(),
    species_set: "Optional[QuerySet[Pokemon]]" = None,
    single_species: bool = False,
) -> "QuerySet[NstSpeciesListArchive]":
    if not species_set:  # putting this as a default param raises error
        species_set = enabled_in_pogo(nestable_species())
    return nsla.filter(
        Q(
            species_name_fk__in=match_species_by_name_or_number(
                sp_txt=species,
                previous_evolution_search=True,
                age_up=True,
                input_set=species_set,
                only_one=single_species,
            )
        )
        | Q(species_txt__icontains=species)  # for free-text row-matching
    )
Ejemplo n.º 5
0
def add_a_report(
    name: str,
    nest: Union[int, str],
    timestamp: datetime,
    species: Union[int, str],
    bot_id: int,
    server: Optional[str] = None,
    rotation: Optional[NstRotationDate] = None,
    confirmation: Optional[bool] = None,
    search_all: bool = False,
    subsearch_place: Optional[int] = None,
    subsearch_type: str = "city",
) -> ReportStatus:
    """
    Adds a raw report and updates the NSLA if applicable
    This thing is **long** and full of ugly business logic

    For "bots" with an is_bot != 1, reports always succeed at updating
    non-bot "bots" do not generate a NstRawReport entry if they do not modify anything

    :param subsearch_type: "city"/"region"/"neighborhood" specifies which model to use on query_nests
    :param subsearch_place: numeric id of the above
    :param search_all: search for all species or just the currently nestable ones
    :param confirmation: leave None to let the system decide how to handle this
    :param name: who submitted the report
    :param server: server identifier
    :param nest: ID of nest, assumed to be unique
    :param timestamp: timestamp of report
    :param species: string or int of the species, assumed to be unique
    :param bot_id: bot ID
    :param rotation: pre-calculated rotation number
    :return: (see ReportStatus docstring)
    """

    def handle_validation_errors() -> ReportStatus:
        """Handles on reporting on multiple errors"""
        by_code: dict = {}
        for location in error_list.keys():
            code, text, bad_value = error_list[location]
            by_code[code] = (location, text, bad_value)

        return ReportStatus(None, 9, by_code, error_list)

    def record_report(status: int) -> ReportStatus:
        """Shoves the report into NstRawRpt with appropriate links"""
        rpt = NstRawRpt.objects.create(
            action=status,
            attempted_dex_num=sp_lnk,
            bot=bot,
            calculated_rotation=rotation,
            nsla_pk=nsla_link,
            nsla_pk_unlink=nsla_link.pk,
            raw_park_info=nest,
            raw_species_num=species,
            timestamp=timestamp,
            user_name=name,
            server_name=server,
            parklink=park_link,
        )
        return ReportStatus(rpt, status, None, None)

    def update_nsla(status_code: int) -> ReportStatus:
        """Updates the NSLA and leaves"""
        nsla_link.confirmation = confirmation
        nsla_link.species_name_fk = sp_lnk
        nsla_link.species_no = sp_lnk.dex_number if sp_lnk else None
        nsla_link.species_txt = sp_lnk.name if sp_lnk else species
        nsla_link.last_mod_by = bot
        nsla_link.save()
        return record_report(status_code)

    #
    # setup & validate internal variables from input
    #
    error_list: Dict[str, Tuple[int, str, str]] = {}
    name = name.strip()
    if not name:
        error_list["user_name"] = (417, "No name given", "")
    if not timestamp:
        error_list["timestamp"] = (416, "Timestamp is emtpy", "")
    try:  # bot id
        bot: Optional[NstAdminEmail] = NstAdminEmail.objects.get(pk=bot_id)
    except NstAdminEmail.DoesNotExist:
        error_list["bot_id"] = (401, "Bad bot ID", f"{bot_id}")
        bot = None
    restricted: bool = bot.restricted() if bot else True
    try:  # species link
        sp_lnk: Optional[Pokemon] = match_species_by_name_or_number(
            species,
            only_one=True,
            input_set=Pokemon.objects.all()
            if search_all
            else enabled_in_pogo(nestable_species()),
            age_up=True,
            previous_evolution_search=True,
        ).get()
    except Pokemon.DoesNotExist:
        if restricted:
            error_list["pokémon"] = (404, "not found", f"{species}")
        sp_lnk = None  # free-text pokémon entries
    except Pokemon.MultipleObjectsReturned:
        if restricted:
            error_list["pokémon"] = (412, "too many results", f"{species}")
        sp_lnk = None  # free-text it for human entries
    try:  # park link
        if not subsearch_place:
            subsearch_place = bot.city.pk if bot.city else None
        park_link: Optional[NstLocation] = get_true_self(
            query_nests(
                nest,
                location_type=subsearch_type,  # could change later for more specific report forms
                location_id=subsearch_place,
                only_one=True,
                exclude_permanent=True if restricted else False,
                restrict_city=bot.city if bot else None,
            ).get()
        )
    except NstLocation.MultipleObjectsReturned:
        error_list["nest"] = (412, "too many results", f"{nest}")
        park_link = None
    except NstLocation.DoesNotExist:
        error_list["nest"] = (404, "not found", f"{nest}")
        park_link = None
    if rotation is None:  # rotation
        try:
            rotation = get_rotation(timestamp)
        except ValueError:
            error_list["timestamp"] = (417, "Invalid timestamp", f"{timestamp}")
        except NstRotationDate.DoesNotExist:
            error_list["timestamp"] = (404, "no rotation found", f"{timestamp}")
    if error_list:
        # this could be higher for marginal performance gain in a high-write environment
        return handle_validation_errors()

    #
    # check for prior art and create NSLA row if none exists
    #
    nsla_link, fresh = NstSpeciesListArchive.objects.get_or_create(
        rotation_num=rotation,
        nestid=park_link,  # if this is None, it would have errored already
        defaults={
            "confirmation": confirmation,
            "species_name_fk": sp_lnk,
            "species_no": sp_lnk.dex_number if sp_lnk else None,
            "species_txt": sp_lnk.name if sp_lnk else species,
            "last_mod_by": bot,
        },
    )
    if fresh:  # we're done if it's a new report
        return record_report(2 if confirmation else 1)

    # duplicate-checking and conflict resolution
    prior_reports: "QuerySet[NstRawRpt]" = NstRawRpt.objects.filter(
        Q(nsla_pk=nsla_link) | Q(nsla_pk_unlink=nsla_link.pk)
    ).order_by("-timestamp")

    # no change from manual edit
    if (
        nsla_link.species_name_fk == sp_lnk
        and bool(nsla_link.confirmation) == bool(confirmation)
        and not restricted
    ):
        return ReportStatus(None, 0, None, None)
    # force change from manual edit
    if not restricted:
        return update_nsla(7)
    # it's only bot posting from here on

    if sp_lnk == nsla_link.species_name_fk:  # confirmations and duplicates
        if prior_reports.filter(
            user_name__iexact=name, attempted_dex_num=sp_lnk
        ).count():
            return record_report(0)  # exact duplicates
        if nsla_link.confirmation:
            return record_report(2)  # previously-confirmed nests
        confirmation = True  # freshly-confirmed reports
        return update_nsla(2)

    #
    # conflicted nests should be all that's left by now
    #

    # only take one report to update to the next species
    # unless it's confirmed by a human or system bot
    if sp_lnk in get_surrounding_species(
        nsla_link.species_name_fk, nestable_species()
    ).values() and (nsla_link.last_mod_by.restricted() or not nsla_link.confirmation):
        confirmation = False
        return update_nsla(1)
    # human and bot confirmations need to go through the normal double agreement to overturn process

    # count the nests from this rotation, then select the nest that most recently has two reports that agree
    # this assumes that the report being added is always the most recent one (so it may break on historic data import)
    if prior_reports.filter(attempted_dex_num=sp_lnk).count():
        # there was a prior report for this nest that agrees with the species given here
        confirmation = True if nsla_link.last_mod_by.restricted() else False
        return update_nsla(2)

    # update if the same user reports the nest again with better data
    if (
        prior_reports.first() is not None
        and prior_reports.first().user_name.lower() == name.lower()
    ):  # preserve case when saving but ignore it for comparison
        confirmation = False
        return update_nsla(1)

    # anything from here on is a conflict that can't get updated
    if prior_reports.exclude(attempted_dex_num=sp_lnk).count():
        return record_report(4)

    # always return something, even if I screwed up the logic elsewhere
    error_list["unknown"] = (
        500,
        "Something got missed",
        "nestlist.models.add_a_report",
    )
    return handle_validation_errors()