mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 23:41:27 +00:00
rename query argument to models and refs
This commit is contained in:
@@ -13,7 +13,7 @@ router = Router(prefix="")
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@router.command(query="CryptoEOD")
|
||||
@router.command(model="CryptoEOD")
|
||||
def load(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -59,7 +59,7 @@ def corecpi(
|
||||
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
@router.command(query="CPI")
|
||||
@router.command(model="CPI")
|
||||
def cpi(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -15,7 +15,7 @@ from pydantic import BaseModel
|
||||
router = Router(prefix="")
|
||||
|
||||
|
||||
@router.command(query="TreasuryRates")
|
||||
@router.command(model="TreasuryRates")
|
||||
def treasury(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -13,7 +13,7 @@ router = Router(prefix="")
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@router.command(query="ForexPairs")
|
||||
@router.command(model="ForexPairs")
|
||||
def pairs(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -25,7 +25,7 @@ def pairs(
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@router.command(query="ForexEOD")
|
||||
@router.command(model="ForexEOD")
|
||||
def load(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -16,7 +16,7 @@ from pydantic import BaseModel
|
||||
router = Router(prefix="")
|
||||
|
||||
|
||||
@router.command(query="GlobalNews")
|
||||
@router.command(model="GlobalNews")
|
||||
def globalnews(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -16,7 +16,7 @@ from pydantic import BaseModel
|
||||
router = Router(prefix="/ca")
|
||||
|
||||
|
||||
@router.command(query="StockPeers")
|
||||
@router.command(model="StockPeers")
|
||||
def get(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -15,7 +15,7 @@ from pydantic import BaseModel
|
||||
router = Router(prefix="/dd")
|
||||
|
||||
|
||||
@router.command(query="SECFilings")
|
||||
@router.command(model="SECFilings")
|
||||
def sec(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -22,7 +22,7 @@ def analysis() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="BalanceSheet")
|
||||
@router.command(model="BalanceSheet")
|
||||
def balance(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -33,7 +33,7 @@ def balance(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="HistoricalDividends")
|
||||
@router.command(model="HistoricalDividends")
|
||||
def cal(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -44,7 +44,7 @@ def cal(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="CashFlowStatement")
|
||||
@router.command(model="CashFlowStatement")
|
||||
def cash(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -55,7 +55,7 @@ def cash(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="ExecutiveCompensation")
|
||||
@router.command(model="ExecutiveCompensation")
|
||||
def comp(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -66,7 +66,7 @@ def comp(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="StockSplitCalendar")
|
||||
@router.command(model="StockSplitCalendar")
|
||||
def comsplit(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -101,7 +101,7 @@ def dupont() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="EarningsCalendar")
|
||||
@router.command(model="EarningsCalendar")
|
||||
def earning(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -112,7 +112,7 @@ def earning(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="HistoricalEmployees")
|
||||
@router.command(model="HistoricalEmployees")
|
||||
def emp(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -135,7 +135,7 @@ def epsfc() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="AnalystEstimates")
|
||||
@router.command(model="AnalystEstimates")
|
||||
def est(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -175,7 +175,7 @@ def historical_5() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="IncomeStatement")
|
||||
@router.command(model="IncomeStatement")
|
||||
def income(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -186,7 +186,7 @@ def income(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="StockInsiderTrading")
|
||||
@router.command(model="StockInsiderTrading")
|
||||
def ins(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -202,7 +202,7 @@ def key() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="KeyMetrics")
|
||||
@router.command(model="KeyMetrics")
|
||||
def metrics(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -213,7 +213,7 @@ def metrics(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="KeyExecutives")
|
||||
@router.command(model="KeyExecutives")
|
||||
def mgmt(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -235,7 +235,7 @@ def news() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="CompanyOverview")
|
||||
@router.command(model="CompanyOverview")
|
||||
def overview(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -246,7 +246,7 @@ def overview(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="InstitutionalOwnership")
|
||||
@router.command(model="InstitutionalOwnership")
|
||||
def own(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -257,7 +257,7 @@ def own(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="PriceTargetConsensus")
|
||||
@router.command(model="PriceTargetConsensus")
|
||||
def pt(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -268,7 +268,7 @@ def pt(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="PriceTarget")
|
||||
@router.command(model="PriceTarget")
|
||||
def pta(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -297,7 +297,7 @@ def revfc() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="RevenueGeographic")
|
||||
@router.command(model="RevenueGeographic")
|
||||
def revgeo(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -308,7 +308,7 @@ def revgeo(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="RevenueBusinessLine")
|
||||
@router.command(model="RevenueBusinessLine")
|
||||
def revseg(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -341,7 +341,7 @@ def shares() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="ShareStatistics")
|
||||
@router.command(model="ShareStatistics")
|
||||
def shrs(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -352,7 +352,7 @@ def shrs(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="HistoricalStockSplits")
|
||||
@router.command(model="HistoricalStockSplits")
|
||||
def split(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -369,7 +369,7 @@ def supplier() -> CommandOutput[Empty]: # type: ignore
|
||||
return CommandOutput(results=Empty())
|
||||
|
||||
|
||||
@router.command(query="EarningsCallTranscript")
|
||||
@router.command(model="EarningsCallTranscript")
|
||||
def transcript(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -33,7 +33,7 @@ router.include_router(ins_router)
|
||||
router.include_router(options_router)
|
||||
|
||||
|
||||
@router.command(query="StockEOD")
|
||||
@router.command(model="StockEOD")
|
||||
def load(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -44,7 +44,7 @@ def load(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="StockNews")
|
||||
@router.command(model="StockNews")
|
||||
def news(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
@@ -55,7 +55,7 @@ def news(
|
||||
return CommandOutput(results=Query(**locals()).execute())
|
||||
|
||||
|
||||
@router.command(query="StockMultiples")
|
||||
@router.command(model="StockMultiples")
|
||||
def multiples(
|
||||
cc: CommandContext,
|
||||
provider_choices: ProviderChoices,
|
||||
|
||||
@@ -43,23 +43,23 @@ class ProviderInterface:
|
||||
----------
|
||||
map : Dict[str, Dict[str, Dict[str, Any]]]
|
||||
Dictionary of provider information.
|
||||
query_providers : Dict[str, ProviderChoices]
|
||||
Dictionary of provider choices by query.
|
||||
model_providers : Dict[str, ProviderChoices]
|
||||
Dictionary of provider choices by model.
|
||||
params : Dict[str, Dict[str, Union[StandardParams, ExtraParams]]]
|
||||
Dictionary of params by query.
|
||||
Dictionary of params by model.
|
||||
merged_data : Dict[str, BaseModel]
|
||||
Dictionary of data by query.
|
||||
Dictionary of data by model.
|
||||
providers_literal : type
|
||||
Literal of provider names.
|
||||
provider_choices : type
|
||||
Dataclass with literal of provider names.
|
||||
queries : List[str]
|
||||
List of query names.
|
||||
models : List[str]
|
||||
List of model names.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__map = build_provider_mapping()
|
||||
self.__query_providers_map = self.__generate_query_providers_dc()
|
||||
self.__model_providers_map = self.__generate_model_providers_dc()
|
||||
self.__params = self.__generate_params_dc()
|
||||
self.__data = self.__generate_data_dc()
|
||||
self.__merged_data = self.__merge_data_dc(self.__data)
|
||||
@@ -69,8 +69,8 @@ class ProviderInterface:
|
||||
return self.__map
|
||||
|
||||
@property
|
||||
def query_providers(self) -> Dict[str, ProviderChoices]:
|
||||
return self.__query_providers_map
|
||||
def model_providers(self) -> Dict[str, ProviderChoices]:
|
||||
return self.__model_providers_map
|
||||
|
||||
@property
|
||||
def params(self) -> Dict[str, Dict[str, Union[StandardParams, ExtraParams]]]:
|
||||
@@ -104,7 +104,7 @@ class ProviderInterface:
|
||||
)
|
||||
|
||||
@property
|
||||
def queries(self) -> List[str]:
|
||||
def models(self) -> List[str]:
|
||||
return list(self.__map.keys())
|
||||
|
||||
@staticmethod
|
||||
@@ -161,9 +161,9 @@ class ProviderInterface:
|
||||
standard: Dict[str, Tuple[str, Any, Any]] = {}
|
||||
extra: Dict[str, Tuple[str, Any, Any]] = {}
|
||||
|
||||
for provider_name, query_details in providers.items():
|
||||
for provider_name, model_details in providers.items():
|
||||
if provider_name == "openbb":
|
||||
for name, field in query_details["QueryParams"]["fields"].items():
|
||||
for name, field in model_details["QueryParams"]["fields"].items():
|
||||
incoming = cls.__create_field(name, field, query=True)
|
||||
|
||||
standard[incoming.name] = (
|
||||
@@ -172,7 +172,7 @@ class ProviderInterface:
|
||||
incoming.default,
|
||||
)
|
||||
else:
|
||||
for name, field in query_details["QueryParams"]["fields"].items():
|
||||
for name, field in model_details["QueryParams"]["fields"].items():
|
||||
if name not in providers["openbb"]["QueryParams"]["fields"]:
|
||||
incoming = cls.__create_field(
|
||||
name, field, provider_name, query=True
|
||||
@@ -208,9 +208,9 @@ class ProviderInterface:
|
||||
standard: Dict[str, Tuple[str, Any, Any]] = {}
|
||||
extra: Dict[str, Tuple[str, Any, Any]] = {}
|
||||
|
||||
for provider_name, query_details in providers.items():
|
||||
for provider_name, model_details in providers.items():
|
||||
if provider_name == "openbb":
|
||||
for name, field in query_details["Data"]["fields"].items():
|
||||
for name, field in model_details["Data"]["fields"].items():
|
||||
incoming = cls.__create_field(name, field, provider_name)
|
||||
|
||||
standard[incoming.name] = (
|
||||
@@ -219,7 +219,7 @@ class ProviderInterface:
|
||||
incoming.default,
|
||||
)
|
||||
else:
|
||||
for name, field in query_details["Data"]["fields"].items():
|
||||
for name, field in model_details["Data"]["fields"].items():
|
||||
if name not in providers["openbb"]["Data"]["fields"]:
|
||||
incoming = cls.__create_field(name, field, provider_name)
|
||||
|
||||
@@ -261,27 +261,27 @@ class ProviderInterface:
|
||||
|
||||
result: Dict = {}
|
||||
|
||||
for query_name, providers in self.__map.items():
|
||||
for model_name, providers in self.__map.items():
|
||||
standard, extra = self.__extract_params(providers)
|
||||
|
||||
result[query_name] = {
|
||||
result[model_name] = {
|
||||
"standard": make_dataclass( # type: ignore
|
||||
cls_name=query_name,
|
||||
cls_name=model_name,
|
||||
fields=list(standard.values()),
|
||||
bases=(StandardParams,),
|
||||
),
|
||||
"extra": make_dataclass( # type: ignore
|
||||
cls_name=query_name,
|
||||
cls_name=model_name,
|
||||
fields=list(extra.values()),
|
||||
bases=(ExtraParams,),
|
||||
),
|
||||
}
|
||||
return result
|
||||
|
||||
def __generate_query_providers_dc(self) -> Dict[str, ProviderChoices]:
|
||||
"""Generate dataclasses for provider choices by query.
|
||||
def __generate_model_providers_dc(self) -> Dict[str, ProviderChoices]:
|
||||
"""Generate dataclasses for provider choices by model.
|
||||
|
||||
This creates a dictionary that maps query names to dataclasses that can be used
|
||||
This creates a dictionary that maps model names to dataclasses that can be used
|
||||
as provider choices.
|
||||
|
||||
Example:
|
||||
@@ -293,13 +293,13 @@ class ProviderInterface:
|
||||
|
||||
result: Dict = {}
|
||||
|
||||
for query_name, providers in self.__map.items():
|
||||
for model_name, providers in self.__map.items():
|
||||
choices = list(providers.keys())
|
||||
if "openbb" in choices:
|
||||
choices.remove("openbb")
|
||||
|
||||
result[query_name] = make_dataclass( # type: ignore
|
||||
cls_name=query_name,
|
||||
result[model_name] = make_dataclass( # type: ignore
|
||||
cls_name=model_name,
|
||||
fields=[("provider", Literal[tuple(choices)])], # type: ignore
|
||||
bases=(ProviderChoices,),
|
||||
)
|
||||
@@ -326,16 +326,16 @@ class ProviderInterface:
|
||||
"""
|
||||
result: Dict = {}
|
||||
|
||||
for query_name, providers in self.__map.items():
|
||||
for model_name, providers in self.__map.items():
|
||||
standard, extra = self.__extract_data(providers)
|
||||
result[query_name] = {
|
||||
result[model_name] = {
|
||||
"standard": make_dataclass( # type: ignore
|
||||
cls_name=query_name,
|
||||
cls_name=model_name,
|
||||
fields=list(standard.values()),
|
||||
bases=(StandardData,),
|
||||
),
|
||||
"extra": make_dataclass( # type: ignore
|
||||
cls_name=query_name,
|
||||
cls_name=model_name,
|
||||
fields=list(extra.values()),
|
||||
bases=(ExtraData,),
|
||||
),
|
||||
@@ -348,7 +348,7 @@ class ProviderInterface:
|
||||
) -> Dict[str, BaseModel]:
|
||||
"""Merge standard data with extra data into a single BaseModel"""
|
||||
result: Dict = {}
|
||||
for query_name, dataclasses in data.items():
|
||||
for model_name, dataclasses in data.items():
|
||||
standard = dataclasses["standard"]
|
||||
extra = dataclasses["extra"]
|
||||
|
||||
@@ -367,8 +367,8 @@ class ProviderInterface:
|
||||
class Config(BaseConfig):
|
||||
extra = Extra.allow
|
||||
|
||||
result[query_name] = create_model( # type: ignore
|
||||
query_name,
|
||||
result[model_name] = create_model( # type: ignore
|
||||
model_name,
|
||||
__config__=Config,
|
||||
**fields_dict, # type: ignore
|
||||
)
|
||||
|
||||
@@ -184,12 +184,12 @@ class Router:
|
||||
|
||||
api_router = self._api_router
|
||||
|
||||
query = kwargs.pop("query", "")
|
||||
if query:
|
||||
model = kwargs.pop("model", "")
|
||||
if model:
|
||||
kwargs["response_model_exclude_unset"] = True
|
||||
kwargs["openapi_extra"] = {"query": query}
|
||||
kwargs["openapi_extra"] = {"model": model}
|
||||
|
||||
func = SignatureInspector.complete_signature(func, query)
|
||||
func = SignatureInspector.complete_signature(func, model)
|
||||
|
||||
CommandValidator.check(func=func)
|
||||
|
||||
@@ -216,14 +216,14 @@ class Router:
|
||||
class SignatureInspector:
|
||||
@classmethod
|
||||
def complete_signature(
|
||||
cls, func: Callable[P, CommandOutput], query: str
|
||||
cls, func: Callable[P, CommandOutput], model: str
|
||||
) -> Callable[P, CommandOutput]:
|
||||
"""Complete function signature."""
|
||||
provider_interface = get_provider_interface()
|
||||
if query:
|
||||
if query not in provider_interface.queries:
|
||||
if model:
|
||||
if model not in provider_interface.models:
|
||||
raise AttributeError(
|
||||
f"Invalid query: {query}. Check available queries in ProviderInterface().queries"
|
||||
f"Invalid model: {model}. Check available models in ProviderInterface().models"
|
||||
)
|
||||
|
||||
cls.validate_signature(func=func)
|
||||
@@ -231,22 +231,22 @@ class SignatureInspector:
|
||||
func = cls.inject_dependency(
|
||||
func=func,
|
||||
arg="provider_choices",
|
||||
callable_=provider_interface.query_providers[query],
|
||||
callable_=provider_interface.model_providers[model],
|
||||
)
|
||||
|
||||
func = cls.inject_dependency(
|
||||
func=func,
|
||||
arg="standard_params",
|
||||
callable_=provider_interface.params[query]["standard"],
|
||||
callable_=provider_interface.params[model]["standard"],
|
||||
)
|
||||
|
||||
func = cls.inject_dependency(
|
||||
func=func,
|
||||
arg="extra_params",
|
||||
callable_=provider_interface.params[query]["extra"],
|
||||
callable_=provider_interface.params[model]["extra"],
|
||||
)
|
||||
|
||||
data_type = provider_interface.merged_data[query]
|
||||
data_type = provider_interface.merged_data[model]
|
||||
func.__annotations__["return"] = CommandOutput[List[data_type]] # type: ignore
|
||||
elif (
|
||||
"provider_choices" in func.__annotations__
|
||||
@@ -261,7 +261,7 @@ class SignatureInspector:
|
||||
|
||||
@staticmethod
|
||||
def validate_signature(func: Callable[P, CommandOutput]) -> None:
|
||||
"""Validate function signature before binding to query."""
|
||||
"""Validate function signature before binding to model."""
|
||||
if "provider_choices" not in func.__annotations__:
|
||||
raise AttributeError(
|
||||
f"Invalid signature: {func.__name__}. Missing provider_choices parameter."
|
||||
@@ -350,9 +350,9 @@ class CommandMap:
|
||||
for route in api_router.routes:
|
||||
openapi_extra = getattr(route, "openapi_extra")
|
||||
if openapi_extra:
|
||||
query = openapi_extra.get("query", None)
|
||||
if query:
|
||||
providers = list(mapping[query].keys())
|
||||
model = openapi_extra.get("model", None)
|
||||
if model:
|
||||
providers = list(mapping[model].keys())
|
||||
if "openbb" in providers:
|
||||
providers.remove("openbb")
|
||||
for provider in providers:
|
||||
@@ -373,9 +373,9 @@ class CommandMap:
|
||||
for route in api_router.routes:
|
||||
openapi_extra = getattr(route, "openapi_extra")
|
||||
if openapi_extra:
|
||||
query = openapi_extra.get("query", None)
|
||||
if query:
|
||||
providers = list(mapping[query].keys())
|
||||
model = openapi_extra.get("model", None)
|
||||
if model:
|
||||
providers = list(mapping[model].keys())
|
||||
if "openbb" in providers:
|
||||
providers.remove("openbb")
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ try:
|
||||
)
|
||||
except ImportError:
|
||||
app = None
|
||||
raise
|
||||
else:
|
||||
|
||||
class App(CLASS_4ebd0208_8328_5d69_8c44_ec50939c0967):
|
||||
|
||||
Reference in New Issue
Block a user