Source code for inatcog.projects
"""Module to handle projects."""
from typing import Union
from redbot.core import Config
from pyinaturalist.models import Project
from .client import iNatClient
from .converters.base import QuotedContextMemberConverter
from .utils import get_home_server, get_hub_server
[docs]async def get_event_project_config(guild_config: Config, abbrev: str):
event_projects = await guild_config.event_projects()
event_project = event_projects.get(abbrev)
return event_project
[docs]async def get_event_project_id(guild_config: Config, abbrev: str):
event_project = await get_event_project_config(guild_config, abbrev)
event_project_id = int(event_project["project_id"]) if event_project else 0
if not (event_project and event_project_id > 0):
raise LookupError("Event project not known.")
return event_project_id
[docs]async def get_event_project(guild_config: Config, abbrev: str, client: iNatClient):
event_project_id = await get_event_project_id(guild_config, abbrev)
paginator = client.projects.from_ids(event_project_id, limit=1)
projects = await paginator.async_all() if paginator else None
if projects:
return projects[0]
raise LookupError("iNat project not found.")
[docs]class UserProject(Project):
"""A collection project for observations by specific users.
This class handles projects that include observations by members, with membership
being managed either solely by the user, soley by the admins, or a combination
of both:
1. Open membership, users join on the web:
- members_only: True
- do not set any "observed_by_user?" rules
2. Closed membership, admins edit the project to join users:
- members_only: False
- set at least one "observed_by_user?" rule
3. Closed membership, users join on web and admins edit project to "approve" join:
- members_only: True
- set at least one "observed_by_user?" rule
- a user's observations are included when both the user joins *and* the
admin "approves" the join by adding a rule for them
"""
[docs] def members_only(self):
return next(
iter(
[
param.get("value")
for param in self.search_parameters
if param.get("field") == "members_only"
]
),
False,
)
[docs] def observed_by_ids(self):
"""Valid observer user ids for the project.
TODO: clarify what the iNat code actually does for these cases and fix
as needed. we implement, based on some reasonable assumptions:
- for closed membership projects, exclude any user that has both
an included rule (observed_by_user?) and an excluded rule
(not_observed_by_user?)
"""
exclude_user_ids = [
rule.get("operand_id")
for rule in self.project_observation_rules
if rule.get("operator") == "not_observed_by_user?"
]
include_user_rules = [
rule
for rule in self.project_observation_rules
if rule.get("operator") == "observed_by_user?"
and rule.get("operand_id") not in exclude_user_ids
]
if self.members_only():
if include_user_rules:
# i.e. case 3, Closed (user joins & admin approves the join)
include_user_ids = [
rule.get("operand_id")
for rule in include_user_rules
if rule.get("operand_id") in self.user_ids
]
else:
# i.e. case 1, Open (user joins)
include_user_ids = [
user_id
for user_id in self.user_ids
if user_id not in exclude_user_ids
]
else:
if include_user_rules:
# i.e. case 2, Closed (admin joins the user)
include_user_ids = [
rule.get("operand_id") for rule in include_user_rules
]
else:
# i.e. fallback - project membership is undefined, so is empty
# - as in case 2 but admins haven't joined anyone yet
# - note: exclusions are irrelevant; an empty membership minus
# some specific users is still "empty"
include_user_ids = []
return include_user_ids
[docs]class INatProjectTable:
"""Lookup helper for projects."""
def __init__(self, cog):
self.cog = cog
[docs] async def get_project(
self,
guild,
query: Union[int, str],
user: QuotedContextMemberConverter = None,
):
"""Get project by guild abbr or via id#/keyword lookup in API."""
async def _get_project_abbrev(guild, abbrev):
response = None
guild_config = self.cog.config.guild(guild)
projects = await guild_config.projects()
if abbrev in projects:
response = await self.cog.api.get_projects(projects[abbrev])
return response
abbrev = query.lower() if isinstance(query, str) else None
project = None
response = None
_guild = guild or await get_home_server(self.cog, user)
if _guild and abbrev:
response = await _get_project_abbrev(_guild, abbrev)
if not response:
hub_server = await get_hub_server(self.cog, _guild)
if hub_server:
response = await _get_project_abbrev(hub_server, abbrev)
if not response:
if isinstance(query, int) or query.isnumeric():
project_id = int(query)
response = await self.cog.api.get_projects(project_id)
if not response:
response = await self.cog.api.get_projects("autocomplete", q=query)
if response:
results = response.get("results")
if results:
project = results[0]
if project:
return Project.from_json(project)
raise LookupError("iNat project not known.")