mirror of
https://github.com/home-assistant/core
synced 2024-09-06 10:29:55 +02:00
Revert "Allow setting an entity's name by its device class" (#93539)
This commit is contained in:
parent
f7404e2ee0
commit
a71c097aeb
@ -28,7 +28,7 @@ from homeassistant.const import (
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -105,13 +105,6 @@ def sensor_update_to_bluetooth_data_update(
|
||||
adv: Aranet4Advertisement,
|
||||
) -> PassiveBluetoothDataUpdate:
|
||||
"""Convert a sensor update to a Bluetooth data update."""
|
||||
entity_names: dict[PassiveBluetoothEntityKey, str | None] = {}
|
||||
for key, desc in SENSOR_DESCRIPTIONS.items():
|
||||
# PassiveBluetoothDataUpdate does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
assert desc.name is not DEVICE_CLASS_NAME
|
||||
entity_names[_device_key_to_bluetooth_entity_key(adv.device, key)] = desc.name
|
||||
return PassiveBluetoothDataUpdate(
|
||||
devices={adv.device.address: _sensor_device_info_to_hass(adv)},
|
||||
entity_descriptions={
|
||||
@ -124,7 +117,10 @@ def sensor_update_to_bluetooth_data_update(
|
||||
)
|
||||
for key in SENSOR_DESCRIPTIONS
|
||||
},
|
||||
entity_names=entity_names,
|
||||
entity_names={
|
||||
_device_key_to_bluetooth_entity_key(adv.device, key): desc.name
|
||||
for key, desc in SENSOR_DESCRIPTIONS.items()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
from pybalboa import EVENT_UPDATE, SpaClient
|
||||
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
|
||||
from homeassistant.helpers.entity import DeviceClassName, DeviceInfo, Entity
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
@ -12,9 +12,7 @@ from .const import DOMAIN
|
||||
class BalboaBaseEntity(Entity):
|
||||
"""Balboa base entity."""
|
||||
|
||||
def __init__(
|
||||
self, client: SpaClient, name: str | DeviceClassName | None = None
|
||||
) -> None:
|
||||
def __init__(self, client: SpaClient, name: str | None = None) -> None:
|
||||
"""Initialize the control."""
|
||||
mac = client.mac_address
|
||||
model = client.model
|
||||
|
@ -35,11 +35,6 @@ class BondButtonEntityDescription(
|
||||
):
|
||||
"""Class to describe a Bond Button entity."""
|
||||
|
||||
# BondEntity does not support DEVICE_CLASS_NAME
|
||||
# Restrict the type to satisfy the type checker and catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
name: str | None = None
|
||||
|
||||
|
||||
STOP_BUTTON = BondButtonEntityDescription(
|
||||
key=Action.STOP,
|
||||
|
@ -88,7 +88,7 @@ class BruntDevice(
|
||||
self._attr_attribution = ATTRIBUTION
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)}, # type: ignore[arg-type]
|
||||
name=self._thing.name,
|
||||
name=self._attr_name,
|
||||
via_device=(DOMAIN, self._entry_id),
|
||||
manufacturer="Brunt",
|
||||
sw_version=self._thing.fw_version,
|
||||
|
@ -30,8 +30,8 @@ class FreeboxHomeEntity(Entity):
|
||||
self._node = node
|
||||
self._sub_node = sub_node
|
||||
self._id = node["id"]
|
||||
self._device_name = node["label"].strip()
|
||||
self._attr_name = self._device_name
|
||||
self._attr_name = node["label"].strip()
|
||||
self._device_name = self._attr_name
|
||||
self._attr_unique_id = f"{self._router.mac}-node_{self._id}"
|
||||
|
||||
if sub_node is not None:
|
||||
|
@ -28,10 +28,6 @@ class IncomfortSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes Incomfort sensor entity."""
|
||||
|
||||
extra_key: str | None = None
|
||||
# IncomfortSensor does not support DEVICE_CLASS_NAME
|
||||
# Restrict the type to satisfy the type checker and catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
name: str | None = None
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[IncomfortSensorEntityDescription, ...] = (
|
||||
|
@ -14,7 +14,6 @@ from homeassistant.const import (
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
@ -74,11 +73,6 @@ class ModbusBinarySensor(BasePlatform, RestoreEntity, BinarySensorEntity):
|
||||
# this ensures that idx = bit position of value in result
|
||||
# polling is done with the base class
|
||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
||||
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in _attr_name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
self._coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
|
@ -17,7 +17,6 @@ from homeassistant.const import (
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
@ -80,11 +79,6 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity):
|
||||
# this ensures that idx = bit position of value in result
|
||||
# polling is done with the base class
|
||||
name = self._attr_name if self._attr_name else "modbus_sensor"
|
||||
|
||||
# DataUpdateCoordinator does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in _attr_name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
self._coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
|
@ -67,7 +67,7 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(POINT_DOMAIN, home_id)},
|
||||
manufacturer="Minut",
|
||||
name=self._home["name"],
|
||||
name=self._attr_name,
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
|
@ -19,7 +19,6 @@ from homeassistant.helpers.device_registry import (
|
||||
async_get as dr_async_get,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName
|
||||
from homeassistant.helpers.entity_registry import async_get as er_async_get
|
||||
from homeassistant.helpers.typing import EventType
|
||||
from homeassistant.util.dt import utcnow
|
||||
@ -73,16 +72,12 @@ def get_number_of_channels(device: BlockDevice, block: Block) -> int:
|
||||
def get_block_entity_name(
|
||||
device: BlockDevice,
|
||||
block: Block | None,
|
||||
description: str | DeviceClassName | None = None,
|
||||
description: str | None = None,
|
||||
) -> str:
|
||||
"""Naming for block based switch and sensors."""
|
||||
channel_name = get_block_channel_name(device, block)
|
||||
|
||||
if description:
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as description.
|
||||
assert description is not DEVICE_CLASS_NAME
|
||||
return f"{channel_name} {description.lower()}"
|
||||
|
||||
return channel_name
|
||||
@ -306,16 +301,12 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str:
|
||||
|
||||
|
||||
def get_rpc_entity_name(
|
||||
device: RpcDevice, key: str, description: str | DeviceClassName | None = None
|
||||
device: RpcDevice, key: str, description: str | None = None
|
||||
) -> str:
|
||||
"""Naming for RPC based switch and sensors."""
|
||||
channel_name = get_rpc_channel_name(device, key)
|
||||
|
||||
if description:
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as description.
|
||||
assert description is not DEVICE_CLASS_NAME
|
||||
return f"{channel_name} {description.lower()}"
|
||||
|
||||
return channel_name
|
||||
|
@ -30,7 +30,7 @@ from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceClassName, DeviceInfo
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MODULES
|
||||
@ -279,17 +279,13 @@ class SystemBridgeEntity(CoordinatorEntity[SystemBridgeDataUpdateCoordinator]):
|
||||
coordinator: SystemBridgeDataUpdateCoordinator,
|
||||
api_port: int,
|
||||
key: str,
|
||||
name: str | DeviceClassName | None,
|
||||
name: str | None,
|
||||
) -> None:
|
||||
"""Initialize the System Bridge entity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._hostname = coordinator.data.system.hostname
|
||||
self._key = f"{self._hostname}_{key}"
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME as name.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
self._name = f"{self._hostname} {name}"
|
||||
self._configuration_url = (
|
||||
f"http://{self._hostname}:{api_port}/app/settings.html"
|
||||
|
@ -32,7 +32,6 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.unit_conversion import DistanceConverter, SpeedConverter
|
||||
@ -350,10 +349,6 @@ class BaseTomorrowioSensorEntity(TomorrowioEntity, SensorEntity):
|
||||
"""Initialize Tomorrow.io Sensor Entity."""
|
||||
super().__init__(config_entry, coordinator, api_version)
|
||||
self.entity_description = description
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
assert description.name is not DEVICE_CLASS_NAME
|
||||
self._attr_name = f"{self._config_entry.data[CONF_NAME]} - {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{self._config_entry.unique_id}_{slugify(description.name)}"
|
||||
|
@ -22,12 +22,7 @@ from pyunifiprotect.data import (
|
||||
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.entity import (
|
||||
DEVICE_CLASS_NAME,
|
||||
DeviceInfo,
|
||||
Entity,
|
||||
EntityDescription,
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||
|
||||
from .const import (
|
||||
ATTR_EVENT_ID,
|
||||
@ -204,10 +199,6 @@ class ProtectDeviceEntity(Entity):
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self.device.mac}_{description.key}"
|
||||
name = description.name or ""
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
assert name is not DEVICE_CLASS_NAME
|
||||
self._attr_name = f"{self.device.display_name} {name.title()}"
|
||||
|
||||
self._attr_attribution = DEFAULT_ATTRIBUTION
|
||||
|
@ -131,8 +131,6 @@ async def async_setup_entry(
|
||||
class VizioDevice(MediaPlayerEntity):
|
||||
"""Media Player implementation which performs REST requests to device."""
|
||||
|
||||
_attr_name: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config_entry: ConfigEntry,
|
||||
|
@ -28,10 +28,6 @@ from .wemo_device import DeviceCoordinator
|
||||
class AttributeSensorDescription(SensorEntityDescription):
|
||||
"""SensorEntityDescription for WeMo AttributeSensor entities."""
|
||||
|
||||
# AttributeSensor does not support DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
name: str | None = None
|
||||
state_conversion: Callable[[StateType], StateType] | None = None
|
||||
unique_id_suffix: str | None = None
|
||||
|
||||
|
@ -8,7 +8,7 @@ from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import DEVICE_CLASS_NAME, DeviceInfo, Entity
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
from .discovery import ZwaveDiscoveryInfo
|
||||
@ -149,10 +149,6 @@ class ZWaveBaseEntity(Entity):
|
||||
and self.entity_description
|
||||
and self.entity_description.name
|
||||
):
|
||||
# It's not possible to do string manipulations on DEVICE_CLASS_NAME
|
||||
# the assert satisfies the type checker and will catch attempts
|
||||
# to use DEVICE_CLASS_NAME in the entity descriptions.
|
||||
assert self.entity_description.name is not DEVICE_CLASS_NAME
|
||||
name = self.entity_description.name
|
||||
|
||||
if name_prefix:
|
||||
|
@ -115,7 +115,7 @@ async def async_setup_platforms(
|
||||
class ZWaveMeEntity(Entity):
|
||||
"""Representation of a ZWaveMe device."""
|
||||
|
||||
def __init__(self, controller: ZWaveMeController, device: ZWaveMeData) -> None:
|
||||
def __init__(self, controller, device):
|
||||
"""Initialize the device."""
|
||||
self.controller = controller
|
||||
self.device = device
|
||||
@ -124,9 +124,13 @@ class ZWaveMeEntity(Entity):
|
||||
f"{self.controller.config.unique_id}-{self.device.id}"
|
||||
)
|
||||
self._attr_should_poll = False
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.device.deviceIdentifier)},
|
||||
name=device.title,
|
||||
name=self._attr_name,
|
||||
manufacturer=self.device.manufacturer,
|
||||
sw_version=self.device.firmware,
|
||||
suggested_area=self.device.locationName,
|
||||
|
@ -52,16 +52,6 @@ DATA_ENTITY_SOURCE = "entity_info"
|
||||
SOURCE_CONFIG_ENTRY = "config_entry"
|
||||
SOURCE_PLATFORM_CONFIG = "platform_config"
|
||||
|
||||
|
||||
class DeviceClassName(Enum):
|
||||
"""Singleton to use device class name."""
|
||||
|
||||
_singleton = 0
|
||||
|
||||
|
||||
DEVICE_CLASS_NAME = DeviceClassName._singleton # pylint: disable=protected-access
|
||||
|
||||
|
||||
# Used when converting float states to string: limit precision according to machine
|
||||
# epsilon to make the string representation readable
|
||||
FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
|
||||
@ -229,7 +219,7 @@ class EntityDescription:
|
||||
force_update: bool = False
|
||||
icon: str | None = None
|
||||
has_entity_name: bool = False
|
||||
name: str | DeviceClassName | None = None
|
||||
name: str | None = None
|
||||
translation_key: str | None = None
|
||||
unit_of_measurement: str | None = None
|
||||
|
||||
@ -298,7 +288,7 @@ class Entity(ABC):
|
||||
_attr_extra_state_attributes: MutableMapping[str, Any]
|
||||
_attr_force_update: bool
|
||||
_attr_icon: str | None
|
||||
_attr_name: str | DeviceClassName | None
|
||||
_attr_name: str | None
|
||||
_attr_should_poll: bool = True
|
||||
_attr_state: StateType = STATE_UNKNOWN
|
||||
_attr_supported_features: int | None = None
|
||||
@ -328,24 +318,10 @@ class Entity(ABC):
|
||||
return self.entity_description.has_entity_name
|
||||
return False
|
||||
|
||||
def _device_class_name(self) -> str | None:
|
||||
"""Return a translated name of the entity based on its device class."""
|
||||
assert self.platform
|
||||
if not self.has_entity_name:
|
||||
return None
|
||||
device_class_key = self.device_class or "_"
|
||||
name_translation_key = (
|
||||
f"component.{self.platform.domain}.entity_component."
|
||||
f"{device_class_key}.name"
|
||||
)
|
||||
return self.platform.component_translations.get(name_translation_key)
|
||||
|
||||
@property
|
||||
def name(self) -> str | None:
|
||||
"""Return the name of the entity."""
|
||||
if hasattr(self, "_attr_name"):
|
||||
if self._attr_name is DEVICE_CLASS_NAME:
|
||||
return self._device_class_name()
|
||||
return self._attr_name
|
||||
if self.translation_key is not None and self.has_entity_name:
|
||||
assert self.platform
|
||||
@ -353,12 +329,10 @@ class Entity(ABC):
|
||||
f"component.{self.platform.platform_name}.entity.{self.platform.domain}"
|
||||
f".{self.translation_key}.name"
|
||||
)
|
||||
if name_translation_key in self.platform.platform_translations:
|
||||
name: str = self.platform.platform_translations[name_translation_key]
|
||||
if name_translation_key in self.platform.entity_translations:
|
||||
name: str = self.platform.entity_translations[name_translation_key]
|
||||
return name
|
||||
if hasattr(self, "entity_description"):
|
||||
if self.entity_description.name is DEVICE_CLASS_NAME:
|
||||
return self._device_class_name()
|
||||
return self.entity_description.name
|
||||
return None
|
||||
|
||||
|
@ -125,8 +125,7 @@ class EntityPlatform:
|
||||
self.entity_namespace = entity_namespace
|
||||
self.config_entry: config_entries.ConfigEntry | None = None
|
||||
self.entities: dict[str, Entity] = {}
|
||||
self.component_translations: dict[str, Any] = {}
|
||||
self.platform_translations: dict[str, Any] = {}
|
||||
self.entity_translations: dict[str, Any] = {}
|
||||
self._tasks: list[asyncio.Task[None]] = []
|
||||
# Stop tracking tasks after setup is completed
|
||||
self._setup_complete = False
|
||||
@ -280,15 +279,7 @@ class EntityPlatform:
|
||||
full_name = f"{self.domain}.{self.platform_name}"
|
||||
|
||||
try:
|
||||
self.component_translations = await translation.async_get_translations(
|
||||
hass, hass.config.language, "entity_component", {self.domain}
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
_LOGGER.debug(
|
||||
"Could not load translations for %s", self.domain, exc_info=err
|
||||
)
|
||||
try:
|
||||
self.platform_translations = await translation.async_get_translations(
|
||||
self.entity_translations = await translation.async_get_translations(
|
||||
hass, hass.config.language, "entity", {self.platform_name}
|
||||
)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
|
@ -172,8 +172,6 @@ class _TemplateAttribute:
|
||||
class TemplateEntity(Entity):
|
||||
"""Entity that uses templates to calculate attributes."""
|
||||
|
||||
_attr_name: str | None
|
||||
|
||||
_attr_available = True
|
||||
_attr_entity_picture = None
|
||||
_attr_icon = None
|
||||
|
@ -1,10 +1,9 @@
|
||||
"""esphome session fixtures."""
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import Event
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aioesphomeapi import APIClient, APIVersion, DeviceInfo, ReconnectLogic
|
||||
from aioesphomeapi import APIClient, APIVersion, DeviceInfo
|
||||
import pytest
|
||||
from zeroconf import Zeroconf
|
||||
|
||||
@ -159,18 +158,10 @@ async def mock_voice_assistant_v1_entry(
|
||||
mock_client.device_info = AsyncMock(return_value=device_info)
|
||||
mock_client.subscribe_voice_assistant = AsyncMock(return_value=Mock())
|
||||
|
||||
try_connect_done = Event()
|
||||
real_try_connect = ReconnectLogic._try_connect
|
||||
|
||||
async def mock_try_connect(self):
|
||||
"""Set an event when ReconnectLogic._try_connect has been awaited."""
|
||||
result = await real_try_connect(self)
|
||||
try_connect_done.set()
|
||||
return result
|
||||
|
||||
with patch.object(ReconnectLogic, "_try_connect", mock_try_connect):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await try_connect_done.wait()
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user