<commit_before>"""Services for the Plex integration."""
import json
import logging

from plexapi.exceptions import NotFound
import voluptuous as vol
from yarl import URL

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import (
    DOMAIN,
    PLEX_UPDATE_PLATFORMS_SIGNAL,
    PLEX_URI_SCHEME,
    SERVERS,
    SERVICE_REFRESH_LIBRARY,
    SERVICE_SCAN_CLIENTS,
)
from .errors import MediaNotFound
from .models import PlexMediaSearchResult

REFRESH_LIBRARY_SCHEMA = vol.Schema(
    {vol.Optional("server_name"): str, vol.Required("library_name"): str}
)

_LOGGER = logging.getLogger(__package__)

async def async_setup_services(hass):
    """Set up services for the Plex component."""

    async def async_refresh_library_service(service_call: ServiceCall) -> None:
        await hass.async_add_executor_job(refresh_library, hass, service_call)

    async def async_scan_clients_service(_: ServiceCall) -> None:
        _LOGGER.warning(
            "This service is deprecated in favor of the scan_clients button entity. "
            "Service calls will still work for now but the service will be removed in a future release"
        )
        for server_id in hass.data[DOMAIN][SERVERS]:
            async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))

    hass.services.async_register(
        DOMAIN,
        SERVICE_REFRESH_LIBRARY,
        async_refresh_library_service,
        schema=REFRESH_LIBRARY_SCHEMA,
    )
    hass.services.async_register(
        DOMAIN, SERVICE_SCAN_CLIENTS, async_scan_clients_service
    )

    return True

def refresh_library(hass: HomeAssistant, service_call: ServiceCall) -> None:
    """Scan a Plex library for new and updated media."""
    plex_server_name = service_call.data.get("server_name")
    library_name = service_call.data["library_name"]

    plex_server = get_plex_server(hass, plex_server_name)

    try:
        library = plex_server.library.section(title=library_name)
    except NotFound:
        _LOGGER.error(
            "Library with name '%s' not found in %s",
            library_name,
            [x.title for x in plex_server.library.sections()],
        )
        return

    _LOGGER.debug("Scanning %s for new and updated media", library_name)
    library.update()

def get_plex_server(hass, plex_server_name=None, plex_server_id=None):
    """Retrieve a configured Plex server by name."""
    if DOMAIN not in hass.data:
        raise HomeAssistantError("Plex integration not configured")
    plex_servers = hass.data[DOMAIN][SERVERS].values()
    if not plex_servers:
        raise HomeAssistantError("No Plex servers available")

    if plex_server_id:
        return hass.data[DOMAIN][SERVERS][plex_server_id]

    if plex_server_name:
        plex_server = next(
            (x for x in plex_servers if x.friendly_name == plex_server_name), None
        )
        if plex_server is not None:
            return plex_server
        friendly_names = [x.friendly_name for x in plex_servers]
        raise HomeAssistantError(
            f"Requested Plex server '{plex_server_name}' not found in {friendly_names}"
        )

    if len(plex_servers) == 1:
        return next(iter(plex_servers))

    friendly_names = [x.friendly_name for x in plex_servers]
    raise HomeAssistantError(
        f"Multiple Plex servers configured, choose with 'plex_server' key: {friendly_names}"
    )

def process_plex_payload(
    hass, content_type, content_id, default_plex_server=None, supports_playqueues=True
) -> PlexMediaSearchResult:
    """Look up Plex media using media_player.play_media service payloads."""
    plex_server = default_plex_server
    extra_params = {}

    if content_id.startswith(PLEX_URI_SCHEME + "{"):
        # Handle the special payload of 'plex://{<json>}'
        content_id = content_id[len(PLEX_URI_SCHEME) :]
        content = json.loads(content_id)
    elif content_id.startswith(PLEX_URI_SCHEME):
        # Handle standard media_browser payloads
        plex_url = URL(content_id)
        if plex_url.name:
            if len(plex_url.parts) == 2:
                # The path contains a single item, will always be a ratingKey
                content = int(plex_url.name)
            else:
                # For "special" items like radio stations
                content = plex_url.path
            server_id = plex_url.host
            plex_server = get_plex_server(hass, plex_server_id=server_id)
        else:
            # Handle legacy payloads without server_id in URL host position
            content = int(plex_url.host)  # type: ignore[arg-type]
        extra_params = dict(plex_url.query)
    else:
        content = json.loads(content_id)

    if isinstance(content, dict):
        if plex_server_name := content.pop("plex_server", None):
            plex_server = get_plex_server(hass, plex_server_name)

    if not plex_server:
        plex_server = get_plex_server(hass)

    if content_type == "station":
        if not supports_playqueues:
            raise HomeAssistantError("Plex stations are not supported on this device")
        playqueue = plex_server.create_station_playqueue(content)
        return PlexMediaSearchResult(playqueue)

    if isinstance(content, int):
        content = {"plex_key": content}
        content_type = DOMAIN

    content.update(extra_params)

    if playqueue_id := content.pop("playqueue_id", None):
        if not supports_playqueues:
            raise HomeAssistantError("Plex playqueues are not supported on this device")
        try:
            playqueue = plex_server.get_playqueue(playqueue_id)
        except NotFound as err:
            raise MediaNotFound(
                f"PlayQueue '{playqueue_id}' could not be found"
            ) from err
        return PlexMediaSearchResult(playqueue, content)

    search_query = content.copy()
    shuffle = search_query.pop("shuffle", 0)

    media = plex_server.lookup_media(content_type, **search_query)

    if supports_playqueues and (isinstance(media, list) or shuffle):
        playqueue = plex_server.create_playqueue(media, shuffle=shuffle)
        return PlexMediaSearchResult(playqueue, content)

    return PlexMediaSearchResult(media, content)
<commit_msg>Speed up Plex playback for multiple videos (#69821)<commit_after>"""Services for the Plex integration."""
import json
import logging

from plexapi.exceptions import NotFound
import voluptuous as vol
from yarl import URL

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_send

from .const import (
    DOMAIN,
    PLEX_UPDATE_PLATFORMS_SIGNAL,
    PLEX_URI_SCHEME,
    SERVERS,
    SERVICE_REFRESH_LIBRARY,
    SERVICE_SCAN_CLIENTS,
)
from .errors import MediaNotFound
from .models import PlexMediaSearchResult

REFRESH_LIBRARY_SCHEMA = vol.Schema(
    {vol.Optional("server_name"): str, vol.Required("library_name"): str}
)

_LOGGER = logging.getLogger(__package__)

async def async_setup_services(hass):
    """Set up services for the Plex component."""

    async def async_refresh_library_service(service_call: ServiceCall) -> None:
        await hass.async_add_executor_job(refresh_library, hass, service_call)

    async def async_scan_clients_service(_: ServiceCall) -> None:
        _LOGGER.warning(
            "This service is deprecated in favor of the scan_clients button entity. "
            "Service calls will still work for now but the service will be removed in a future release"
        )
        for server_id in hass.data[DOMAIN][SERVERS]:
            async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id))

    hass.services.async_register(
        DOMAIN,
        SERVICE_REFRESH_LIBRARY,
        async_refresh_library_service,
        schema=REFRESH_LIBRARY_SCHEMA,
    )
    hass.services.async_register(
        DOMAIN, SERVICE_SCAN_CLIENTS, async_scan_clients_service
    )

    return True

def refresh_library(hass: HomeAssistant, service_call: ServiceCall) -> None:
    """Scan a Plex library for new and updated media."""
    plex_server_name = service_call.data.get("server_name")
    library_name = service_call.data["library_name"]

    plex_server = get_plex_server(hass, plex_server_name)

    try:
        library = plex_server.library.section(title=library_name)
    except NotFound:
        _LOGGER.error(
            "Library with name '%s' not found in %s",
            library_name,
            [x.title for x in plex_server.library.sections()],
        )
        return

    _LOGGER.debug("Scanning %s for new and updated media", library_name)
    library.update()

def get_plex_server(hass, plex_server_name=None, plex_server_id=None):
    """Retrieve a configured Plex server by name."""
    if DOMAIN not in hass.data:
        raise HomeAssistantError("Plex integration not configured")
    plex_servers = hass.data[DOMAIN][SERVERS].values()
    if not plex_servers:
        raise HomeAssistantError("No Plex servers available")

    if plex_server_id:
        return hass.data[DOMAIN][SERVERS][plex_server_id]

    if plex_server_name:
        plex_server = next(
            (x for x in plex_servers if x.friendly_name == plex_server_name), None
        )
        if plex_server is not None:
            return plex_server
        friendly_names = [x.friendly_name for x in plex_servers]
        raise HomeAssistantError(
            f"Requested Plex server '{plex_server_name}' not found in {friendly_names}"
        )

    if len(plex_servers) == 1:
        return next(iter(plex_servers))

    friendly_names = [x.friendly_name for x in plex_servers]
    raise HomeAssistantError(
        f"Multiple Plex servers configured, choose with 'plex_server' key: {friendly_names}"
    )

def process_plex_payload(
    hass, content_type, content_id, default_plex_server=None, supports_playqueues=True
) -> PlexMediaSearchResult:
    """Look up Plex media using media_player.play_media service payloads."""
    plex_server = default_plex_server
    extra_params = {}

    if content_id.startswith(PLEX_URI_SCHEME + "{"):
        # Handle the special payload of 'plex://{<json>}'
        content_id = content_id[len(PLEX_URI_SCHEME) :]
        content = json.loads(content_id)
    elif content_id.startswith(PLEX_URI_SCHEME):
        # Handle standard media_browser payloads
        plex_url = URL(content_id)
        if plex_url.name:
            if len(plex_url.parts) == 2:
                # The path contains a single item, will always be a ratingKey
                content = int(plex_url.name)
            else:
                # For "special" items like radio stations
                content = plex_url.path
            server_id = plex_url.host
            plex_server = get_plex_server(hass, plex_server_id=server_id)
        else:
            # Handle legacy payloads without server_id in URL host position
            content = int(plex_url.host)  # type: ignore[arg-type]
        extra_params = dict(plex_url.query)
    else:
        content = json.loads(content_id)

    if isinstance(content, dict):
        if plex_server_name := content.pop("plex_server", None):
            plex_server = get_plex_server(hass, plex_server_name)

    if not plex_server:
        plex_server = get_plex_server(hass)

    if content_type == "station":
        if not supports_playqueues:
            raise HomeAssistantError("Plex stations are not supported on this device")
        playqueue = plex_server.create_station_playqueue(content)
        return PlexMediaSearchResult(playqueue)

    if isinstance(content, int):
        content = {"plex_key": content}
        content_type = DOMAIN

    content.update(extra_params)

    if playqueue_id := content.pop("playqueue_id", None):
        if not supports_playqueues:
            raise HomeAssistantError("Plex playqueues are not supported on this device")
        try:
            playqueue = plex_server.get_playqueue(playqueue_id)
        except NotFound as err:
            raise MediaNotFound(
                f"PlayQueue '{playqueue_id}' could not be found"
            ) from err
        return PlexMediaSearchResult(playqueue, content)

    search_query = content.copy()
    shuffle = search_query.pop("shuffle", 0)

    media = plex_server.lookup_media(content_type, **search_query)

    if supports_playqueues and (isinstance(media, list) or shuffle):
        playqueue = plex_server.create_playqueue(
            media, includeRelated=0, shuffle=shuffle
        )
        return PlexMediaSearchResult(playqueue, content)

    return PlexMediaSearchResult(media, content)
<|endoftext|>


<commit_before>"""
Support for tracking which astronomical or meteorological season it is.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor/season/
"""
import logging
from datetime import datetime

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_TYPE
from homeassistant.helpers.entity import Entity
from homeassistant import util

REQUIREMENTS = ['ephem==3.7.6.0']

_LOGGER = logging.getLogger(__name__)

NORTHERN = 'northern'
SOUTHERN = 'southern'
EQUATOR = 'equator'
STATE_SPRING = 'spring'
STATE_SUMMER = 'summer'
STATE_AUTUMN = 'autumn'
STATE_WINTER = 'winter'
TYPE_ASTRONOMICAL = 'astronomical'
TYPE_METEOROLOGICAL = 'meteorological'
VALID_TYPES = [TYPE_ASTRONOMICAL, TYPE_METEOROLOGICAL]

HEMISPHERE_SEASON_SWAP = {STATE_WINTER: STATE_SUMMER,
                          STATE_SPRING: STATE_AUTUMN,
                          STATE_AUTUMN: STATE_SPRING,
                          STATE_SUMMER: STATE_WINTER}

SEASON_ICONS = {
    STATE_SPRING: 'mdi:flower',
    STATE_SUMMER: 'mdi:sunglasses',
    STATE_AUTUMN: 'mdi:leaf',
    STATE_WINTER: 'mdi:snowflake'
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_TYPE, default=TYPE_ASTRONOMICAL): vol.In(VALID_TYPES)
})

def setup_platform(hass, config, add_entities, discovery_info=None):
    """Display the current season."""
    if None in (hass.config.latitude, hass.config.longitude):
        _LOGGER.error("Latitude or longitude not set in Home Assistant config")
        return False

    latitude = util.convert(hass.config.latitude, float)
    _type = config.get(CONF_TYPE)

    if latitude < 0:
        hemisphere = SOUTHERN
    elif latitude > 0:
        hemisphere = NORTHERN
    else:
        hemisphere = EQUATOR

    _LOGGER.debug(_type)
    add_entities([Season(hass, hemisphere, _type)])

    return True

def get_season(date, hemisphere, season_tracking_type):
    """Calculate the current season."""
    import ephem

    if hemisphere == 'equator':
        return None

    if season_tracking_type == TYPE_ASTRONOMICAL:
        spring_start = ephem.next_equinox(str(date.year)).datetime()
        summer_start = ephem.next_solstice(str(date.year)).datetime()
        autumn_start = ephem.next_equinox(spring_start).datetime()
        winter_start = ephem.next_solstice(summer_start).datetime()
    else:
        spring_start = datetime(2017, 3, 1).replace(year=date.year)
        summer_start = spring_start.replace(month=6)
        autumn_start = spring_start.replace(month=9)
        winter_start = spring_start.replace(month=12)

    if spring_start <= date < summer_start:
        season = STATE_SPRING
    elif summer_start <= date < autumn_start:
        season = STATE_SUMMER
    elif autumn_start <= date < winter_start:
        season = STATE_AUTUMN
    elif winter_start <= date or spring_start > date:
        season = STATE_WINTER

    # If user is located in the southern hemisphere swap the season
    if hemisphere == NORTHERN:
        return season
    return HEMISPHERE_SEASON_SWAP.get(season)

class Season(Entity):
    """Representation of the current season."""

    def __init__(self, hass, hemisphere, season_tracking_type):
        """Initialize the season."""
        self.hass = hass
        self.hemisphere = hemisphere
        self.datetime = datetime.now()
        self.type = season_tracking_type
        self.season = get_season(self.datetime, self.hemisphere, self.type)

    @property
    def name(self):
        """Return the name."""
        return "Season"

    @property
    def state(self):
        """Return the current season."""
        return self.season

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return SEASON_ICONS.get(self.season, 'mdi:cloud')

    def update(self):
        """Update season."""
        self.datetime = datetime.now()
        self.season = get_season(self.datetime, self.hemisphere, self.type)
<commit_msg>Change .now() to .utcnow() (#22233)<commit_after>"""
Support for tracking which astronomical or meteorological season it is.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor/season/
"""
import logging
from datetime import datetime

import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_TYPE
from homeassistant.helpers.entity import Entity
from homeassistant import util

REQUIREMENTS = ['ephem==3.7.6.0']

_LOGGER = logging.getLogger(__name__)

NORTHERN = 'northern'
SOUTHERN = 'southern'
EQUATOR = 'equator'
STATE_SPRING = 'spring'
STATE_SUMMER = 'summer'
STATE_AUTUMN = 'autumn'
STATE_WINTER = 'winter'
TYPE_ASTRONOMICAL = 'astronomical'
TYPE_METEOROLOGICAL = 'meteorological'
VALID_TYPES = [TYPE_ASTRONOMICAL, TYPE_METEOROLOGICAL]

HEMISPHERE_SEASON_SWAP = {STATE_WINTER: STATE_SUMMER,
                          STATE_SPRING: STATE_AUTUMN,
                          STATE_AUTUMN: STATE_SPRING,
                          STATE_SUMMER: STATE_WINTER}

SEASON_ICONS = {
    STATE_SPRING: 'mdi:flower',
    STATE_SUMMER: 'mdi:sunglasses',
    STATE_AUTUMN: 'mdi:leaf',
    STATE_WINTER: 'mdi:snowflake'
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Optional(CONF_TYPE, default=TYPE_ASTRONOMICAL): vol.In(VALID_TYPES)
})

def setup_platform(hass, config, add_entities, discovery_info=None):
    """Display the current season."""
    if None in (hass.config.latitude, hass.config.longitude):
        _LOGGER.error("Latitude or longitude not set in Home Assistant config")
        return False

    latitude = util.convert(hass.config.latitude, float)
    _type = config.get(CONF_TYPE)

    if latitude < 0:
        hemisphere = SOUTHERN
    elif latitude > 0:
        hemisphere = NORTHERN
    else:
        hemisphere = EQUATOR

    _LOGGER.debug(_type)
    add_entities([Season(hass, hemisphere, _type)])

    return True

def get_season(date, hemisphere, season_tracking_type):
    """Calculate the current season."""
    import ephem

    if hemisphere == 'equator':
        return None

    if season_tracking_type == TYPE_ASTRONOMICAL:
        spring_start = ephem.next_equinox(str(date.year)).datetime()
        summer_start = ephem.next_solstice(str(date.year)).datetime()
        autumn_start = ephem.next_equinox(spring_start).datetime()
        winter_start = ephem.next_solstice(summer_start).datetime()
    else:
        spring_start = datetime(2017, 3, 1).replace(year=date.year)
        summer_start = spring_start.replace(month=6)
        autumn_start = spring_start.replace(month=9)
        winter_start = spring_start.replace(month=12)

    if spring_start <= date < summer_start:
        season = STATE_SPRING
    elif summer_start <= date < autumn_start:
        season = STATE_SUMMER
    elif autumn_start <= date < winter_start:
        season = STATE_AUTUMN
    elif winter_start <= date or spring_start > date:
        season = STATE_WINTER

    # If user is located in the southern hemisphere swap the season
    if hemisphere == NORTHERN:
        return season
    return HEMISPHERE_SEASON_SWAP.get(season)

class Season(Entity):
    """Representation of the current season."""

    def __init__(self, hass, hemisphere, season_tracking_type):
        """Initialize the season."""
        self.hass = hass
        self.hemisphere = hemisphere
        self.datetime = datetime.now()
        self.type = season_tracking_type
        self.season = get_season(self.datetime, self.hemisphere, self.type)

    @property
    def name(self):
        """Return the name."""
        return "Season"

    @property
    def state(self):
        """Return the current season."""
        return self.season

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return SEASON_ICONS.get(self.season, 'mdi:cloud')

    def update(self):
        """Update season."""
        self.datetime = datetime.utcnow()
        self.season = get_season(self.datetime, self.hemisphere, self.type)
<|endoftext|>


<commit_before>from collections import defaultdict

from django.contrib.auth.models import User
from django.db import models

from .managers import EventManager, EventInviteManager

class Event(models.Model):
    PUBLIC = 'PB'
    PRIVATE = 'PR'
    FRIENDS = 'FR'

    PRIVACY_CHOICES = (
        (PUBLIC, 'Public'),
        (PRIVATE, 'Private'),
        (FRIENDS, 'Friends only')
    )

    created_by = models.ForeignKey(User, related_name='created_events', on_delete=models.CASCADE)
    title = models.CharField(max_length=128)
    description = models.TextField(max_length=500, blank=True)
    start_date = models.DateTimeField()
    creation_date = models.DateTimeField(auto_now_add=True)
    privacy = models.CharField(max_length=2, choices=PRIVACY_CHOICES, default=PUBLIC)
    views = models.IntegerField(default=0)
    latitude = models.DecimalField(max_digits=10, decimal_places=7, blank=True, null=True)
    longitude = models.DecimalField(max_digits=10, decimal_places=7, blank=True, null=True)

    objects = EventManager()

    class Meta:
        ordering = ['start_date']

    def __str__(self):
        return self.title

    def incr_views(self):
        Event.objects.filter(pk=self.pk).update(views=models.F('views') + 1)

    def get_invites(self):
        if hasattr(self, 'invites_list'):
            return self.invites_list
        else:
            return self.invites.select_related('to_user').all()

    def invited_by_status(self):
        invited_by_status = defaultdict(list)
        invites = self.get_invites()
        for invite in invites:
            invited_by_status[invite.get_status_display()].append(invite.to_user)
        return invited_by_status

    def _get_user_invite(self, user_pk):
        invites = self.get_invites()
        for invite in invites:
            if invite.to_user_id == user_pk:
                return invite

    def get_permission_and_invite(self, user):
        invite = self._get_user_invite(user.pk)
        if invite or self.privacy == self.PUBLIC or self.created_by_id == user.id:
            return True, invite
        elif self.privacy == self.FRIENDS and user.profile.are_friends_by_filter(self.created_by_id):
            return True, None
        return False, None

    def self_invite_exists(self, user_pk):
        return EventInvite.objects.filter(event=self, to_user_id=user_pk, status=EventInvite.SELF).exists()

class EventInvite(models.Model):
    ACCEPTED = 'A'
    REJECTED = 'R'
    PENDING = 'P'
    SELF = 'S'

    STATUS_CHOICES = (
        (ACCEPTED, 'accepted'),
        (REJECTED, 'rejected'),
        (PENDING, 'pending'),
        (SELF, 'self'),
    )

    event = models.ForeignKey(Event, related_name='invites', on_delete=models.CASCADE)
    from_user = models.ForeignKey(User, related_name='event_invites_sent', on_delete=models.CASCADE)
    to_user = models.ForeignKey(User, related_name='event_invites_received', on_delete=models.CASCADE)
    status = models.CharField(choices=STATUS_CHOICES, max_length=1, default=PENDING)

    objects = EventInviteManager()

    class Meta:
        unique_together = ('event', 'to_user')

    def __str__(self):
        return 'Event: {}; From: {}; To: {}; S: {}'.format(self.event_id,
                                                           self.from_user_id,
                                                           self.to_user_id,
                                                           self.status)
<commit_msg>bug fix add constraints when user is not authenticated<commit_after>from collections import defaultdict

from django.contrib.auth.models import User
from django.db import models

from .managers import EventManager, EventInviteManager

class Event(models.Model):
    PUBLIC = 'PB'
    PRIVATE = 'PR'
    FRIENDS = 'FR'

    PRIVACY_CHOICES = (
        (PUBLIC, 'Public'),
        (PRIVATE, 'Private'),
        (FRIENDS, 'Friends only')
    )

    created_by = models.ForeignKey(User, related_name='created_events', on_delete=models.CASCADE)
    title = models.CharField(max_length=128)
    description = models.TextField(max_length=500, blank=True)
    start_date = models.DateTimeField()
    creation_date = models.DateTimeField(auto_now_add=True)
    privacy = models.CharField(max_length=2, choices=PRIVACY_CHOICES, default=PUBLIC)
    views = models.IntegerField(default=0)
    latitude = models.DecimalField(max_digits=10, decimal_places=7, blank=True, null=True)
    longitude = models.DecimalField(max_digits=10, decimal_places=7, blank=True, null=True)

    objects = EventManager()

    class Meta:
        ordering = ['start_date']

    def __str__(self):
        return self.title

    def incr_views(self):
        Event.objects.filter(pk=self.pk).update(views=models.F('views') + 1)

    def get_invites(self):
        if hasattr(self, 'invites_list'):
            return self.invites_list
        else:
            return self.invites.select_related('to_user').all()

    def invited_by_status(self):
        invited_by_status = defaultdict(list)
        invites = self.get_invites()
        for invite in invites:
            invited_by_status[invite.get_status_display()].append(invite.to_user)
        return invited_by_status

    def _get_user_invite(self, user_pk):
        invites = self.get_invites()
        for invite in invites:
            if invite.to_user_id == user_pk:
                return invite

    def get_permission_and_invite(self, user):
        is_user_auth = user.is_authenticated
        invite = None if not is_user_auth else self._get_user_invite(user.pk)
        if invite or self.privacy == self.PUBLIC or self.created_by_id == user.id:
            return True, invite
        elif is_user_auth and self.privacy == self.FRIENDS and user.profile.are_friends_by_filter(self.created_by_id):
            return True, None
        return False, None

    def self_invite_exists(self, user_pk):
        return EventInvite.objects.filter(event=self, to_user_id=user_pk, status=EventInvite.SELF).exists()

class EventInvite(models.Model):
    ACCEPTED = 'A'
    REJECTED = 'R'
    PENDING = 'P'
    SELF = 'S'

    STATUS_CHOICES = (
        (ACCEPTED, 'accepted'),
        (REJECTED, 'rejected'),
        (PENDING, 'pending'),
        (SELF, 'self'),
    )

    event = models.ForeignKey(Event, related_name='invites', on_delete=models.CASCADE)
    from_user = models.ForeignKey(User, related_name='event_invites_sent', on_delete=models.CASCADE)
    to_user = models.ForeignKey(User, related_name='event_invites_received', on_delete=models.CASCADE)
    status = models.CharField(choices=STATUS_CHOICES, max_length=1, default=PENDING)

    objects = EventInviteManager()

    class Meta:
        unique_together = ('event', 'to_user')

    def __str__(self):
        return 'Event: {}; From: {}; To: {}; S: {}'.format(self.event_id,
                                                           self.from_user_id,
                                                           self.to_user_id,
                                                           self.status)
<|endoftext|>


<commit_before>"""Sensor for Shelly."""
from __future__ import annotations

from typing import Final, cast

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONCENTRATION_PARTS_PER_MILLION,
    DEGREE,
    ELECTRIC_CURRENT_AMPERE,
    ELECTRIC_POTENTIAL_VOLT,
    ENERGY_KILO_WATT_HOUR,
    LIGHT_LUX,
    PERCENTAGE,
    POWER_WATT,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
    TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS
from .entity import (
    BlockAttributeDescription,
    RestAttributeDescription,
    RpcAttributeDescription,
    ShellyBlockAttributeEntity,
    ShellyRestAttributeEntity,
    ShellyRpcAttributeEntity,
    ShellySleepingBlockAttributeEntity,
    async_setup_entry_attribute_entities,
    async_setup_entry_rest,
    async_setup_entry_rpc,
)
from .utils import get_device_entry_gen, get_device_uptime, temperature_unit

SENSORS: Final = {
    ("device", "battery"): BlockAttributeDescription(
        name="Battery",
        unit=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
        removal_condition=lambda settings, _: settings.get("external_power") == 1,
        available=lambda block: cast(int, block.battery) != -1,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("device", "deviceTemp"): BlockAttributeDescription(
        name="Device Temperature",
        unit=temperature_unit,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("emeter", "current"): BlockAttributeDescription(
        name="Current",
        unit=ELECTRIC_CURRENT_AMPERE,
        value=lambda value: value,
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("light", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    ("device", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("emeter", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("device", "voltage"): BlockAttributeDescription(
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    ("emeter", "voltage"): BlockAttributeDescription(
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("emeter", "powerFactor"): BlockAttributeDescription(
        name="Power Factor",
        unit=PERCENTAGE,
        value=lambda value: round(value * 100, 1),
        device_class=SensorDeviceClass.POWER_FACTOR,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("relay", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("roller", "rollerPower"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("device", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("emeter", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("emeter", "energyReturned"): BlockAttributeDescription(
        name="Energy Returned",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("light", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
        default_enabled=False,
    ),
    ("relay", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("roller", "rollerEnergy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("sensor", "concentration"): BlockAttributeDescription(
        name="Gas Concentration",
        unit=CONCENTRATION_PARTS_PER_MILLION,
        icon="mdi:gauge",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("sensor", "extTemp"): BlockAttributeDescription(
        name="Temperature",
        unit=temperature_unit,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.extTemp) != 999,
    ),
    ("sensor", "humidity"): BlockAttributeDescription(
        name="Humidity",
        unit=PERCENTAGE,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.humidity) != 999,
    ),
    ("sensor", "luminosity"): BlockAttributeDescription(
        name="Luminosity",
        unit=LIGHT_LUX,
        device_class=SensorDeviceClass.ILLUMINANCE,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.luminosity) != -1,
    ),
    ("sensor", "tilt"): BlockAttributeDescription(
        name="Tilt",
        unit=DEGREE,
        icon="mdi:angle-acute",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("relay", "totalWorkTime"): BlockAttributeDescription(
        name="Lamp Life",
        unit=PERCENTAGE,
        icon="mdi:progress-wrench",
        value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1),
        extra_state_attributes=lambda block: {
            "Operational hours": round(cast(int, block.totalWorkTime) / 3600, 1)
        },
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("adc", "adc"): BlockAttributeDescription(
        name="ADC",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("sensor", "sensorOp"): BlockAttributeDescription(
        name="Operation",
        icon="mdi:cog-transfer",
        value=lambda value: value,
        extra_state_attributes=lambda block: {"self_test": block.selfTest},
    ),
}

REST_SENSORS: Final = {
    "rssi": RestAttributeDescription(
        name="RSSI",
        unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        value=lambda status, _: status["wifi_sta"]["rssi"],
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "uptime": RestAttributeDescription(
        name="Uptime",
        value=lambda status, last: get_device_uptime(status["uptime"], last),
        device_class=SensorDeviceClass.TIMESTAMP,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
}

RPC_SENSORS: Final = {
    "power": RpcAttributeDescription(
        key="switch",
        sub_key="apower",
        name="Power",
        unit=POWER_WATT,
        value=lambda status, _: round(float(status), 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "voltage": RpcAttributeDescription(
        key="switch",
        sub_key="voltage",
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda status, _: round(float(status), 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    "energy": RpcAttributeDescription(
        key="switch",
        sub_key="aenergy",
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda status, _: round(status["total"] / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    "temperature": RpcAttributeDescription(
        key="switch",
        sub_key="temperature",
        name="Temperature",
        unit=TEMP_CELSIUS,
        value=lambda status, _: round(status["tC"], 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    "rssi": RpcAttributeDescription(
        key="wifi",
        sub_key="rssi",
        name="RSSI",
        unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "uptime": RpcAttributeDescription(
        key="sys",
        sub_key="uptime",
        name="Uptime",
        value=get_device_uptime,
        device_class=SensorDeviceClass.TIMESTAMP,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
}

async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up sensors for device."""
    if get_device_entry_gen(config_entry) == 2:
        return await async_setup_entry_rpc(
            hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor
        )

    if config_entry.data[CONF_SLEEP_PERIOD]:
        await async_setup_entry_attribute_entities(
            hass, config_entry, async_add_entities, SENSORS, BlockSleepingSensor
        )
    else:
        await async_setup_entry_attribute_entities(
            hass, config_entry, async_add_entities, SENSORS, BlockSensor
        )
        await async_setup_entry_rest(
            hass, config_entry, async_add_entities, REST_SENSORS, RestSensor
        )

class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
    """Represent a block sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return cast(str, self._unit)

class RestSensor(ShellyRestAttributeEntity, SensorEntity):
    """Represent a REST sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return self.description.unit

class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
    """Represent a RPC sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return self.description.unit

class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
    """Represent a block sleeping sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        if self.block is not None:
            return self.attribute_value

        return self.last_state

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return cast(str, self._unit)
<commit_msg>Add missing entity category for gen2 devices (#62812)<commit_after>"""Sensor for Shelly."""
from __future__ import annotations

from typing import Final, cast

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONCENTRATION_PARTS_PER_MILLION,
    DEGREE,
    ELECTRIC_CURRENT_AMPERE,
    ELECTRIC_POTENTIAL_VOLT,
    ENERGY_KILO_WATT_HOUR,
    LIGHT_LUX,
    PERCENTAGE,
    POWER_WATT,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
    TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType

from .const import CONF_SLEEP_PERIOD, SHAIR_MAX_WORK_HOURS
from .entity import (
    BlockAttributeDescription,
    RestAttributeDescription,
    RpcAttributeDescription,
    ShellyBlockAttributeEntity,
    ShellyRestAttributeEntity,
    ShellyRpcAttributeEntity,
    ShellySleepingBlockAttributeEntity,
    async_setup_entry_attribute_entities,
    async_setup_entry_rest,
    async_setup_entry_rpc,
)
from .utils import get_device_entry_gen, get_device_uptime, temperature_unit

SENSORS: Final = {
    ("device", "battery"): BlockAttributeDescription(
        name="Battery",
        unit=PERCENTAGE,
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
        removal_condition=lambda settings, _: settings.get("external_power") == 1,
        available=lambda block: cast(int, block.battery) != -1,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("device", "deviceTemp"): BlockAttributeDescription(
        name="Device Temperature",
        unit=temperature_unit,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("emeter", "current"): BlockAttributeDescription(
        name="Current",
        unit=ELECTRIC_CURRENT_AMPERE,
        value=lambda value: value,
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("light", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    ("device", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("emeter", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("device", "voltage"): BlockAttributeDescription(
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    ("emeter", "voltage"): BlockAttributeDescription(
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("emeter", "powerFactor"): BlockAttributeDescription(
        name="Power Factor",
        unit=PERCENTAGE,
        value=lambda value: round(value * 100, 1),
        device_class=SensorDeviceClass.POWER_FACTOR,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("relay", "power"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("roller", "rollerPower"): BlockAttributeDescription(
        name="Power",
        unit=POWER_WATT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("device", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("emeter", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("emeter", "energyReturned"): BlockAttributeDescription(
        name="Energy Returned",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("light", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
        default_enabled=False,
    ),
    ("relay", "energy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("roller", "rollerEnergy"): BlockAttributeDescription(
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda value: round(value / 60 / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    ("sensor", "concentration"): BlockAttributeDescription(
        name="Gas Concentration",
        unit=CONCENTRATION_PARTS_PER_MILLION,
        icon="mdi:gauge",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("sensor", "extTemp"): BlockAttributeDescription(
        name="Temperature",
        unit=temperature_unit,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.extTemp) != 999,
    ),
    ("sensor", "humidity"): BlockAttributeDescription(
        name="Humidity",
        unit=PERCENTAGE,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.humidity) != 999,
    ),
    ("sensor", "luminosity"): BlockAttributeDescription(
        name="Luminosity",
        unit=LIGHT_LUX,
        device_class=SensorDeviceClass.ILLUMINANCE,
        state_class=SensorStateClass.MEASUREMENT,
        available=lambda block: cast(int, block.luminosity) != -1,
    ),
    ("sensor", "tilt"): BlockAttributeDescription(
        name="Tilt",
        unit=DEGREE,
        icon="mdi:angle-acute",
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("relay", "totalWorkTime"): BlockAttributeDescription(
        name="Lamp Life",
        unit=PERCENTAGE,
        icon="mdi:progress-wrench",
        value=lambda value: round(100 - (value / 3600 / SHAIR_MAX_WORK_HOURS), 1),
        extra_state_attributes=lambda block: {
            "Operational hours": round(cast(int, block.totalWorkTime) / 3600, 1)
        },
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    ("adc", "adc"): BlockAttributeDescription(
        name="ADC",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda value: round(value, 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    ("sensor", "sensorOp"): BlockAttributeDescription(
        name="Operation",
        icon="mdi:cog-transfer",
        value=lambda value: value,
        extra_state_attributes=lambda block: {"self_test": block.selfTest},
    ),
}

REST_SENSORS: Final = {
    "rssi": RestAttributeDescription(
        name="RSSI",
        unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        value=lambda status, _: status["wifi_sta"]["rssi"],
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "uptime": RestAttributeDescription(
        name="Uptime",
        value=lambda status, last: get_device_uptime(status["uptime"], last),
        device_class=SensorDeviceClass.TIMESTAMP,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
}

RPC_SENSORS: Final = {
    "power": RpcAttributeDescription(
        key="switch",
        sub_key="apower",
        name="Power",
        unit=POWER_WATT,
        value=lambda status, _: round(float(status), 1),
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    "voltage": RpcAttributeDescription(
        key="switch",
        sub_key="voltage",
        name="Voltage",
        unit=ELECTRIC_POTENTIAL_VOLT,
        value=lambda status, _: round(float(status), 1),
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
    ),
    "energy": RpcAttributeDescription(
        key="switch",
        sub_key="aenergy",
        name="Energy",
        unit=ENERGY_KILO_WATT_HOUR,
        value=lambda status, _: round(status["total"] / 1000, 2),
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
    ),
    "temperature": RpcAttributeDescription(
        key="switch",
        sub_key="temperature",
        name="Temperature",
        unit=TEMP_CELSIUS,
        value=lambda status, _: round(status["tC"], 1),
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "rssi": RpcAttributeDescription(
        key="wifi",
        sub_key="rssi",
        name="RSSI",
        unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
        device_class=SensorDeviceClass.SIGNAL_STRENGTH,
        state_class=SensorStateClass.MEASUREMENT,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
    "uptime": RpcAttributeDescription(
        key="sys",
        sub_key="uptime",
        name="Uptime",
        value=get_device_uptime,
        device_class=SensorDeviceClass.TIMESTAMP,
        default_enabled=False,
        entity_category=EntityCategory.DIAGNOSTIC,
    ),
}

async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up sensors for device."""
    if get_device_entry_gen(config_entry) == 2:
        return await async_setup_entry_rpc(
            hass, config_entry, async_add_entities, RPC_SENSORS, RpcSensor
        )

    if config_entry.data[CONF_SLEEP_PERIOD]:
        await async_setup_entry_attribute_entities(
            hass, config_entry, async_add_entities, SENSORS, BlockSleepingSensor
        )
    else:
        await async_setup_entry_attribute_entities(
            hass, config_entry, async_add_entities, SENSORS, BlockSensor
        )
        await async_setup_entry_rest(
            hass, config_entry, async_add_entities, REST_SENSORS, RestSensor
        )

class BlockSensor(ShellyBlockAttributeEntity, SensorEntity):
    """Represent a block sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return cast(str, self._unit)

class RestSensor(ShellyRestAttributeEntity, SensorEntity):
    """Represent a REST sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return self.description.unit

class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
    """Represent a RPC sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        return self.attribute_value

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return self.description.unit

class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, SensorEntity):
    """Represent a block sleeping sensor."""

    @property
    def native_value(self) -> StateType:
        """Return value of sensor."""
        if self.block is not None:
            return self.attribute_value

        return self.last_state

    @property
    def state_class(self) -> str | None:
        """State class of sensor."""
        return self.description.state_class

    @property
    def native_unit_of_measurement(self) -> str | None:
        """Return unit of sensor."""
        return cast(str, self._unit)
<|endoftext|>


<commit_before>__version__ = '4.0.1dev0'
<commit_msg>Increased version number.<commit_after>__version__ = '4.1.0rc0'
<|endoftext|>


<commit_before>import datetime
import json
import requests

import statscache.plugins

class Plugin(statscache.plugins.BasePlugin):
    name = "releng, amis"
    summary = "Build logs for successful upload/test of cloud images"
    description = """
    Latest build logs for successful upload/test of cloud images
    """
    topics = [
        'org.fedoraproject.prod.fedimg.image.upload',
        'org.fedoraproject.prod.fedimg.image.test'
    ]

    def __init__(self, *args, **kwargs):
        super(Plugin, self).__init__(*args, **kwargs)
        self._seen = {}

    def handle(self, session, timestamp, messages):
        rows = []
        for message in messages:
            if not (message['topic'] in self.topics and
                    message['msg'].get('status') == 'completed'):
                continue
            status = 'completed'
            arch = message['msg']['image_name'].split('.')[1]
            tokens = message['msg']['image_name'].split('.')[0].split('-')
            flavour = tokens[2].lower()
            version = tokens[3].lower()
            tstamp = tokens[4]
            ec2_region = message['msg']['destination'].split('(')[1][:-1]
            msg_timestamp = datetime.datetime.fromtimestamp(message['timestamp'])

            branch = version if version == 'rawhide' else 'branched'

            category = 'ami-{}'.format(branch)
            category_constraint = '{}-{}'.format(
                message['msg']['image_name'], ec2_region)
            last_seen_key = '{}:{}'.format(category, category_constraint)
            last_seen = self._seen.get(last_seen_key)
            if last_seen and msg_timestamp <= last_seen:
                continue
            self._seen[last_seen_key] = msg_timestamp
            msg = json.dumps({
                'name': message['msg']['image_name'],
                'extra_text': '{region}'.format(
                    region=message['msg']['destination']),
                'extra_link': (
                    "https://redirect.fedoraproject.org/console.aws."
                    "amazon.com/ec2/v2/home?region={region}"
                    "#LaunchInstanceWizard:ami={name}".format(
                        name=message['msg']['extra']['id'],
                        region=message['msg']['destination'])
                ),
                'status': status
            })
            result = session.query(self.model).filter(
                self.model.category == category,
                self.model.category_constraint == category_constraint)
            row = result.first()
            if row:
                row.timestamp = msg_timestamp
                row.message = msg
            else:
                row = self.model(
                    timestamp=msg_timestamp,
                    message=msg,
                    category=category,
                    category_constraint=category_constraint
                )
            rows.append(row)
        return rows

    def initialize(self, session, datagrepper_endpoint=None):
        latest = session.query(self.model).filter(
            self.model.category.startswith('ami-')).order_by(
                self.model.timestamp.desc()).first()
        delta = 31536000
        if latest:
            delta = int(
                (datetime.datetime.now() - latest.timestamp).total_seconds())
        for topic in self.topics:
            resp = requests.get(
                datagrepper_endpoint,
                params={
                    'delta': delta,
                    'rows_per_page': 100,
                    'order': 'desc',
                    'meta': 'link',
                    'topic': topic,
                    'contains': 'completed'
                }
            )
            rows = self.handle(session, datetime.datetime.now(),
                               resp.json().get('raw_messages', []))
            session.add_all(rows)
            session.commit()

<commit_msg>Fix sqlalchemy query that wasn't working before.<commit_after>import datetime
import json
import requests

import statscache.plugins

class Plugin(statscache.plugins.BasePlugin):
    name = "releng, amis"
    summary = "Build logs for successful upload/test of cloud images"
    description = """
    Latest build logs for successful upload/test of cloud images
    """
    topics = [
        'org.fedoraproject.prod.fedimg.image.upload',
        'org.fedoraproject.prod.fedimg.image.test'
    ]

    def __init__(self, *args, **kwargs):
        super(Plugin, self).__init__(*args, **kwargs)
        self._seen = {}

    def handle(self, session, timestamp, messages):
        rows = []
        for message in messages:
            if not (message['topic'] in self.topics and
                    message['msg'].get('status') == 'completed'):
                continue
            status = 'completed'
            arch = message['msg']['image_name'].split('.')[1]
            tokens = message['msg']['image_name'].split('.')[0].split('-')
            flavour = tokens[2].lower()
            version = tokens[3].lower()
            tstamp = tokens[4]
            ec2_region = message['msg']['destination'].split('(')[1][:-1]
            msg_timestamp = datetime.datetime.fromtimestamp(message['timestamp'])

            branch = version if version == 'rawhide' else 'branched'

            category = 'ami-{}'.format(branch)
            category_constraint = '{}-{}'.format(
                message['msg']['image_name'], ec2_region)
            last_seen_key = '{}:{}'.format(category, category_constraint)
            last_seen = self._seen.get(last_seen_key)
            if last_seen and msg_timestamp <= last_seen:
                continue
            self._seen[last_seen_key] = msg_timestamp
            msg = json.dumps({
                'name': message['msg']['image_name'],
                'extra_text': '{region}'.format(
                    region=message['msg']['destination']),
                'extra_link': (
                    "https://redirect.fedoraproject.org/console.aws."
                    "amazon.com/ec2/v2/home?region={region}"
                    "#LaunchInstanceWizard:ami={name}".format(
                        name=message['msg']['extra']['id'],
                        region=message['msg']['destination'])
                ),
                'status': status
            })
            result = session.query(self.model)\
                .filter(self.model.category == category)\
                .filter(self.model.category_constraint == category_constraint)
            row = result.first()
            if row:
                row.timestamp = msg_timestamp
                row.message = msg
            else:
                row = self.model(
                    timestamp=msg_timestamp,
                    message=msg,
                    category=category,
                    category_constraint=category_constraint
                )
            rows.append(row)
        return rows

    def initialize(self, session, datagrepper_endpoint=None):
        latest = session.query(self.model).filter(
            self.model.category.startswith('ami-')).order_by(
                self.model.timestamp.desc()).first()
        delta = 31536000
        if latest:
            delta = int(
                (datetime.datetime.now() - latest.timestamp).total_seconds())
        for topic in self.topics:
            resp = requests.get(
                datagrepper_endpoint,
                params={
                    'delta': delta,
                    'rows_per_page': 100,
                    'order': 'desc',
                    'meta': 'link',
                    'topic': topic,
                    'contains': 'completed'
                }
            )
            rows = self.handle(session, datetime.datetime.now(),
                               resp.json().get('raw_messages', []))
            session.add_all(rows)
            session.commit()

<|endoftext|>


<commit_before># -*- encoding: utf-8 -*-

import unittest
import os
import shutil
from . import userinfo
from . import portalexample
from . import peerinfo
from yamlns import namespace as ns

myuseryaml=u"""\
originpeer: somillusio
lang: es
nif: 12345678Z
name: Bunny, Bugs
peerroles:
- member
- worker
innerid: 666
address: Golf Club, 5th hole
city: Murcia
state: Murcia
postalcode: '01022'
country: ES
email:
- bugsbunny@loonietoons.com
phone:
- '555121232'
proxynif:
proxyname:
"""

somacmeyaml=u"""\
intercoopVersion: 1.0
peerVersion: 1
peerid: somacme
name: Som Acme, SCCL
url:
  es: http://somacme.coop/es
logo: http://www.linpictures.com/images/indevimgs/acme.jpg
privacyPolicyUrl:
  es: http://www.wallpapersonly.net/wallpapers/thats-all-folks-1680x1050.jpg
description:
  es: >
    La cooperativa para atrapar correcaminos
targetUrl: http://localhost:5001
services:
  explosives:
    name:
      es: Comprar explosivos
    description:
      es: >
        Puedes comprar explosivos éticos de la mejor calidad.
  anvil:
    name:
      es: Comprar yunques
    description:
      es: >
        Yunques garantizados, siempre caen en una cabeza
    fields:
    - originpeer
    - innerid
fields:
- originpeer
- nif
"""

sombogusyaml=u"""\
intercoopVersion: 1.0
peerVersion: 1
peerid: sombogus
name: Som Bogus, SCCL
url:
  es: https://es.sombogus.coop
logo: http://www.linpictures.com/images/indevimgs/acme.jpg
privacyPolicyUrl:
    es: http://www.wallpapersonly.net/wallpapers/thats-all-folks-1680x1050.jpg
description:
    es: >
      Productos inútiles pero muy éticos
targetUrl: http://localhost:5002/intercoop
services:
  contract:
    name:
        es: Contrata
    description:
        es: >
          Productos con marcas tipo Panone, Grifons, Pas Natural, Reacciona...
    fields:
    - originpeer
    - name
"""

header= u"""\

<meta encoding='utf-8' />
<title>Portal somillusio: Intercooperación</title>
<link rel="stylesheet" type="text/css" href="/intercoop.css">

somillusio: \xc1rea de usuario

Validado como myuser

Intercooperación

"""

footer = u"""\

"""

acmePeerHeader = u"""\

Som Acme, SCCL

La cooperativa para atrapar correcaminos

"""

acmeService = u"""\

Comprar explosivos

Puedes comprar explosivos éticos de la mejor calidad.

Activa

"""

acmeService2 = u"""\

Comprar yunques

Yunques garantizados, siempre caen en una cabeza

Activa

"""

bogusPeerHeader = u"""\

Som Bogus, SCCL

Productos inútiles pero muy éticos

"""

bogusService = u"""\

Contrata

Productos con marcas tipo Panone, Grifons, Pas Natural, Reacciona...

Activa

"""

peerFooter = u"""\

"""

acmeExplosivesHeader = u"""\

<meta encoding='utf-8' />
<title>somillusio: Activación del servicio 'Comprar explosivos' en 'Som Acme, SCCL'</title>
<link rel="stylesheet" type="text/css" href="/intercoop.css">

somillusio: \xc1rea de usuario

Validado como myuser

Autorización de transferencia de datos personales a Som Acme, SCCL

Para activar el servicio Comprar explosivos
en Som Acme, SCCL,
transferiremos a dicha entidad los siguientes datos:

"""

acmeExplosivesFooter = u"""\

Dicha entidad tratar\xe1 dichos datos seg\xfan su propia
pol\xedtica de privacidad.

Acepto

"""

nameField = u"""\

NIF:

12345678Z

"""
originField = u"""\

Entidad de procedencia:

somillusio

"""

class IntercoopCatalog_Test(unittest.TestCase):

    def write(self, filename, content, folder=None):
        fullname = os.path.join(folder or self.peerdatadir,filename)
        with open(fullname, 'wb') as f:
            try:
                f.write(content.encode('utf-8'))
            except UnicodeDecodeError:
                f.write(content)

    def setUp(self):
        self.maxDiff=None
        self.peerid= 'somillusio'
        self.keyfile = 'testkey.pem'
        self.peerdatadir='peerdatadir'
        self.userdatadir='userdatadir'
        self.cleanUp()
        self.peers = peerinfo.PeerInfo(self.peerdatadir)
        self.users = userinfo.UserInfo(self.userdatadir)
        os.system("mkdir -p "+self.peerdatadir)
        os.system("mkdir -p "+self.userdatadir)
        self.write('myuser.yaml', myuseryaml, self.userdatadir)

    def setupPortal(self):
        return portalexample.IntercoopCatalog(
            #peerid = self.peerid,
            #keyfile = self.keyfile,
            peers = self.peers,
            #users = self.users,
            )

    def tearDown(self):
        self.cleanUp()

    def cleanUp(self):
        try: shutil.rmtree(self.peerdatadir)
        except: pass
        try: shutil.rmtree(self.userdatadir)
        except: pass

    def test_requiredFields_justInService_useService(self):
        self.write("sombogus.yaml",sombogusyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer','name'],
            p.requiredFields("sombogus","contract")
        )

    def test_requiredFields_justInPeer_usePeer(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer','nif'],
            p.requiredFields("somacme","explosives")
        )

    def test_requiredFields_inServiceAndPeer_useService(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer', 'innerid'],
            p.requiredFields("somacme","anvil")
        )

    def test_requiredFields_noFields(self):
        bogus = ns.loads(sombogusyaml.encode('utf8'))
        del bogus.services.contract.fields
        self.write("sombogus.yaml",bogus.dump())
        p = self.setupPortal()
        with self.assertRaises(Exception) as ctx:
            p.requiredFields("sombogus","contract")

        self.assertEqual(str(ctx.exception),
            "Peer 'sombogus' does not specify fields for service 'contract'")

    def test_requiredFields_badPeer(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        with self.assertRaises(Exception) as ctx: # TODO: Maybe custom error
            p.requiredFields("badpeer","explosives")
        self.assertEqual(ctx.exception.message,
            "Not such peer 'badpeer'")

    def test_requiredFields_badService(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        with self.assertRaises(KeyError) as ctx: # TODO: Maybe custom error
            p.requiredFields("somacme","badservice")
        self.assertEqual(ctx.exception.message,
            "badservice")

class Portal_Test(unittest.TestCase):

    def write(self, filename, content, folder=None):
        fullname = os.path.join(folder or self.peerdatadir,filename)
        with open(fullname, 'wb') as f:
            try:
                f.write(content.encode('utf-8'))
            except UnicodeDecodeError:
                f.write(content)

    def setUp(self):
        self.maxDiff=None
        self.peerid= 'somillusio'
        self.keyfile = 'testkey.pem'
        self.peerdatadir='peerdatadir'
        self.userdatadir='userdatadir'
        self.cleanUp()
        os.system("mkdir -p "+self.peerdatadir)
        os.system("mkdir -p "+self.userdatadir)
        self.write('myuser.yaml', myuseryaml, self.userdatadir)

    def setupPortal(self):
        return portalexample.Portal("Example Portal",
            peerid = self.peerid,
            keyfile=self.keyfile,
            peers = peerinfo.PeerInfo(self.peerdatadir),
            users = userinfo.UserInfo(self.userdatadir),
            )

    def setupApp(self):
        self.portal = self.setupPortal()
        self.app =  self.portal.app
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()

    def tearDown(self):
        self.cleanUp()

    def cleanUp(self):
        try: shutil.rmtree(self.peerdatadir)
        except: pass
        try: shutil.rmtree(self.userdatadir)
        except: pass

    def test_index_whenEmpty(self):
        self.setupApp()
        self.assertMultiLineEqual(
            header+footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_serviceDescription(self):
        peer = ns.loads(somacmeyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.serviceDescription(peer, 'explosives'),
            acmeService)

    def test_peerDescription_singleService(self):
        peer = ns.loads(sombogusyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.peerDescription(peer),
            bogusPeerHeader + bogusService + peerFooter)

    def test_peerDescription_manyServices(self):
        peer = ns.loads(somacmeyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.peerDescription(peer),
            acmePeerHeader + acmeService + acmeService2 + peerFooter)

    def test_index_onePeer(self):
        self.setupApp()
        self.write('somacme.yaml',somacmeyaml)
        self.assertMultiLineEqual(
            header+
            acmePeerHeader+
            acmeService+
            acmeService2+
            peerFooter+
            footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_index_many(self):
        self.setupApp()
        self.write('somacme.yaml',somacmeyaml)
        self.write('sombogus.yaml',sombogusyaml)
        self.assertMultiLineEqual(
            header+
            acmePeerHeader+
            acmeService+
            acmeService2+
            peerFooter+
            bogusPeerHeader+
            bogusService+
            peerFooter+
            footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_renderField(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field="Nombre", value="Bunny, Bugs"),
            "
\n"
            "
Nombre:
\n"
            "
Bunny, Bugs
\n"
            "
\n"
            )

    def test_renderField_list(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field=u"Teléfono", value=["5550000", "5554455"]),
            "
\n"
            u"
Teléfono:
\n"
            "
\n"
                "
\n"
                "
5550000
\n"
                "
5554455
\n"
                "
\n"
            "
\n"
            "
\n"
            )

    def test_renderField_none(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field=u"Attribute label", value=None),
            "
\n"
            "
Attribute label:
\n"
            "
---
\n"
            "
\n"
            )

    def test_activateService(self):
        self.setupApp()
        self.write("somacme.yaml",somacmeyaml)
        self.assertMultiLineEqual(
            acmeExplosivesHeader+
            originField+
            nameField+
            acmeExplosivesFooter,
            self.client.get("/activateservice/somacme/explosives").data.decode('utf-8'))

# vim: ts=4 sw=4 et
<commit_msg>reordered tests<commit_after># -*- encoding: utf-8 -*-

import unittest
import os
import shutil
from . import userinfo
from . import portalexample
from . import peerinfo
from yamlns import namespace as ns

myuseryaml=u"""\
originpeer: somillusio
lang: es
nif: 12345678Z
name: Bunny, Bugs
peerroles:
- member
- worker
innerid: 666
address: Golf Club, 5th hole
city: Murcia
state: Murcia
postalcode: '01022'
country: ES
email:
- bugsbunny@loonietoons.com
phone:
- '555121232'
proxynif:
proxyname:
"""

somacmeyaml=u"""\
intercoopVersion: 1.0
peerVersion: 1
peerid: somacme
name: Som Acme, SCCL
url:
  es: http://somacme.coop/es
logo: http://www.linpictures.com/images/indevimgs/acme.jpg
privacyPolicyUrl:
  es: http://www.wallpapersonly.net/wallpapers/thats-all-folks-1680x1050.jpg
description:
  es: >
    La cooperativa para atrapar correcaminos
targetUrl: http://localhost:5001
services:
  explosives:
    name:
      es: Comprar explosivos
    description:
      es: >
        Puedes comprar explosivos éticos de la mejor calidad.
  anvil:
    name:
      es: Comprar yunques
    description:
      es: >
        Yunques garantizados, siempre caen en una cabeza
    fields:
    - originpeer
    - innerid
fields:
- originpeer
- nif
"""

sombogusyaml=u"""\
intercoopVersion: 1.0
peerVersion: 1
peerid: sombogus
name: Som Bogus, SCCL
url:
  es: https://es.sombogus.coop
logo: http://www.linpictures.com/images/indevimgs/acme.jpg
privacyPolicyUrl:
    es: http://www.wallpapersonly.net/wallpapers/thats-all-folks-1680x1050.jpg
description:
    es: >
      Productos inútiles pero muy éticos
targetUrl: http://localhost:5002/intercoop
services:
  contract:
    name:
        es: Contrata
    description:
        es: >
          Productos con marcas tipo Panone, Grifons, Pas Natural, Reacciona...
    fields:
    - originpeer
    - name
"""

header= u"""\

<meta encoding='utf-8' />
<title>Portal somillusio: Intercooperación</title>
<link rel="stylesheet" type="text/css" href="/intercoop.css">

somillusio: \xc1rea de usuario

Validado como myuser

Intercooperación

"""

footer = u"""\

"""

acmePeerHeader = u"""\

Som Acme, SCCL

La cooperativa para atrapar correcaminos

"""

acmeService = u"""\

Comprar explosivos

Puedes comprar explosivos éticos de la mejor calidad.

Activa

"""

acmeService2 = u"""\

Comprar yunques

Yunques garantizados, siempre caen en una cabeza

Activa

"""

bogusPeerHeader = u"""\

Som Bogus, SCCL

Productos inútiles pero muy éticos

"""

bogusService = u"""\

Contrata

Productos con marcas tipo Panone, Grifons, Pas Natural, Reacciona...

Activa

"""

peerFooter = u"""\

"""

acmeExplosivesHeader = u"""\

<meta encoding='utf-8' />
<title>somillusio: Activación del servicio 'Comprar explosivos' en 'Som Acme, SCCL'</title>
<link rel="stylesheet" type="text/css" href="/intercoop.css">

somillusio: \xc1rea de usuario

Validado como myuser

Autorización de transferencia de datos personales a Som Acme, SCCL

Para activar el servicio Comprar explosivos
en Som Acme, SCCL,
transferiremos a dicha entidad los siguientes datos:

"""

acmeExplosivesFooter = u"""\

Dicha entidad tratar\xe1 dichos datos seg\xfan su propia
pol\xedtica de privacidad.

Acepto

"""

nameField = u"""\

NIF:

12345678Z

"""
originField = u"""\

Entidad de procedencia:

somillusio

"""

class IntercoopCatalog_Test(unittest.TestCase):

    def write(self, filename, content, folder=None):
        fullname = os.path.join(folder or self.peerdatadir,filename)
        with open(fullname, 'wb') as f:
            try:
                f.write(content.encode('utf-8'))
            except UnicodeDecodeError:
                f.write(content)

    def setUp(self):
        self.maxDiff=None
        self.peerid= 'somillusio'
        self.keyfile = 'testkey.pem'
        self.peerdatadir='peerdatadir'
        self.userdatadir='userdatadir'
        self.cleanUp()
        self.peers = peerinfo.PeerInfo(self.peerdatadir)
        self.users = userinfo.UserInfo(self.userdatadir)
        os.system("mkdir -p "+self.peerdatadir)
        os.system("mkdir -p "+self.userdatadir)
        self.write('myuser.yaml', myuseryaml, self.userdatadir)

    def setupPortal(self):
        return portalexample.IntercoopCatalog(
            #peerid = self.peerid,
            #keyfile = self.keyfile,
            peers = self.peers,
            #users = self.users,
            )

    def tearDown(self):
        self.cleanUp()

    def cleanUp(self):
        try: shutil.rmtree(self.peerdatadir)
        except: pass
        try: shutil.rmtree(self.userdatadir)
        except: pass

    def test_requiredFields_badPeer(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        with self.assertRaises(Exception) as ctx: # TODO: Maybe custom error
            p.requiredFields("badpeer","explosives")
        self.assertEqual(ctx.exception.message,
            "Not such peer 'badpeer'")

    def test_requiredFields_badService(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        with self.assertRaises(KeyError) as ctx: # TODO: Maybe custom error
            p.requiredFields("somacme","badservice")
        self.assertEqual(ctx.exception.message,
            "badservice")

    def test_requiredFields_justInService_useService(self):
        self.write("sombogus.yaml",sombogusyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer','name'],
            p.requiredFields("sombogus","contract")
        )

    def test_requiredFields_justInPeer_usePeer(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer','nif'],
            p.requiredFields("somacme","explosives")
        )

    def test_requiredFields_inServiceAndPeer_useService(self):
        self.write("somacme.yaml",somacmeyaml)
        p = self.setupPortal()
        self.assertEqual(
            ['originpeer', 'innerid'],
            p.requiredFields("somacme","anvil")
        )

    def test_requiredFields_noFields(self):
        bogus = ns.loads(sombogusyaml.encode('utf8'))
        del bogus.services.contract.fields
        self.write("sombogus.yaml",bogus.dump())
        p = self.setupPortal()
        with self.assertRaises(Exception) as ctx:
            p.requiredFields("sombogus","contract")

        self.assertEqual(ctx.exception.message,
            "Peer 'sombogus' does not specify fields for service 'contract'")

class Portal_Test(unittest.TestCase):

    def write(self, filename, content, folder=None):
        fullname = os.path.join(folder or self.peerdatadir,filename)
        with open(fullname, 'wb') as f:
            try:
                f.write(content.encode('utf-8'))
            except UnicodeDecodeError:
                f.write(content)

    def setUp(self):
        self.maxDiff=None
        self.peerid= 'somillusio'
        self.keyfile = 'testkey.pem'
        self.peerdatadir='peerdatadir'
        self.userdatadir='userdatadir'
        self.cleanUp()
        os.system("mkdir -p "+self.peerdatadir)
        os.system("mkdir -p "+self.userdatadir)
        self.write('myuser.yaml', myuseryaml, self.userdatadir)

    def setupPortal(self):
        return portalexample.Portal("Example Portal",
            peerid = self.peerid,
            keyfile=self.keyfile,
            peers = peerinfo.PeerInfo(self.peerdatadir),
            users = userinfo.UserInfo(self.userdatadir),
            )

    def setupApp(self):
        self.portal = self.setupPortal()
        self.app =  self.portal.app
        self.app.config['TESTING'] = True
        self.client = self.app.test_client()

    def tearDown(self):
        self.cleanUp()

    def cleanUp(self):
        try: shutil.rmtree(self.peerdatadir)
        except: pass
        try: shutil.rmtree(self.userdatadir)
        except: pass

    def test_index_whenEmpty(self):
        self.setupApp()
        self.assertMultiLineEqual(
            header+footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_serviceDescription(self):
        peer = ns.loads(somacmeyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.serviceDescription(peer, 'explosives'),
            acmeService)

    def test_peerDescription_singleService(self):
        peer = ns.loads(sombogusyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.peerDescription(peer),
            bogusPeerHeader + bogusService + peerFooter)

    def test_peerDescription_manyServices(self):
        peer = ns.loads(somacmeyaml.encode('utf-8'))
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.peerDescription(peer),
            acmePeerHeader + acmeService + acmeService2 + peerFooter)

    def test_index_onePeer(self):
        self.setupApp()
        self.write('somacme.yaml',somacmeyaml)
        self.assertMultiLineEqual(
            header+
            acmePeerHeader+
            acmeService+
            acmeService2+
            peerFooter+
            footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_index_many(self):
        self.setupApp()
        self.write('somacme.yaml',somacmeyaml)
        self.write('sombogus.yaml',sombogusyaml)
        self.assertMultiLineEqual(
            header+
            acmePeerHeader+
            acmeService+
            acmeService2+
            peerFooter+
            bogusPeerHeader+
            bogusService+
            peerFooter+
            footer,
            self.client.get("/").data.decode('utf-8')
            )

    def test_renderField(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field="Nombre", value="Bunny, Bugs"),
            "
\n"
            "
Nombre:
\n"
            "
Bunny, Bugs
\n"
            "
\n"
            )

    def test_renderField_list(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field=u"Teléfono", value=["5550000", "5554455"]),
            "
\n"
            u"
Teléfono:
\n"
            "
\n"
                "
\n"
                "
5550000
\n"
                "
5554455
\n"
                "
\n"
            "
\n"
            "
\n"
            )

    def test_renderField_none(self):
        p = self.setupPortal()
        self.assertMultiLineEqual(
            p.renderField(field=u"Attribute label", value=None),
            "
\n"
            "
Attribute label:
\n"
            "
---
\n"
            "
\n"
            )

    def test_activateService(self):
        self.setupApp()
        self.write("somacme.yaml",somacmeyaml)
        self.assertMultiLineEqual(
            acmeExplosivesHeader+
            originField+
            nameField+
            acmeExplosivesFooter,
            self.client.get("/activateservice/somacme/explosives").data.decode('utf-8'))

# vim: ts=4 sw=4 et
<|endoftext|>


<commit_before>import paddle.v2.framework.core as core
from paddle.v2.framework.framework import OpProtoHolder, Variable, Program, \
    Operator
<commit_msg>rm unused dir framework (#5652)<commit_after><|endoftext|>


<commit_before>from mezzanine.pages.page_processors import processor_for
from models import RasterResource
from crispy_forms.layout import Layout, HTML
from forms import CellInfoForm, BandInfoForm, BaseBandInfoFormSet, OriginalCoverageSpatialForm, \
    BandInfoLayoutEdit
from hs_core import page_processors
from django.forms.models import formset_factory
from hs_core.views import add_generic_context
from functools import partial, wraps

# page processor to populate raster resource specific metadata into my-resources template page
@processor_for(RasterResource)
# TODO: problematic permissions
def landing_page(request, page):
    content_model = page.get_content_model()
    edit_resource = page_processors.check_resource_mode(request)

    context = page_processors.get_page_context(page, request.user, resource_edit=edit_resource,
                                               extended_metadata_layout=None, request=request)
    extended_metadata_exists = False
    if content_model.metadata.cellInformation or content_model.metadata.bandInformations:
        extended_metadata_exists = True

    context['extended_metadata_exists'] = extended_metadata_exists
    if not edit_resource:
        # get the context from content_model
        if content_model.metadata.originalCoverage:
            ori_coverage_data_dict = {}
            ori_coverage_data_dict['units'] = content_model.metadata.originalCoverage.value['units']
            ori_coverage_data_dict['projection'] = content_model.metadata.originalCoverage.\
                value.get('projection', None)
            ori_coverage_data_dict['northlimit'] = content_model.metadata.originalCoverage.\
                value['northlimit']
            ori_coverage_data_dict['eastlimit'] = content_model.metadata.originalCoverage.\
                value['eastlimit']
            ori_coverage_data_dict['southlimit'] = content_model.metadata.originalCoverage.\
                value['southlimit']
            ori_coverage_data_dict['westlimit'] = content_model.metadata.originalCoverage.\
                value['westlimit']
            ori_coverage_data_dict['projection_string'] = content_model.metadata.originalCoverage.\
                value.get('projection_string', None)
            ori_coverage_data_dict['datum'] = content_model.metadata.originalCoverage.\
                value.get('datum', None)
            context['originalCoverage'] = ori_coverage_data_dict
        context['cellInformation'] = content_model.metadata.cellInformation
        context['bandInformation'] = content_model.metadata.bandInformations
    else:
        # cellinfo_form
        cellinfo_form = CellInfoForm(instance=content_model.metadata.cellInformation,
                                     res_short_id=content_model.short_id,
                                     allow_edit=True,
                                     element_id=content_model.metadata.cellInformation.id
                                     if content_model.metadata.cellInformation else None)
        cellinfo_layout = HTML("
 "
                               '{% load crispy_forms_tags %} '
                               '{% crispy cellinfo_form %} '
                               '
')
        # bandinfo_formset
        BandInfoFormSetEdit = formset_factory(wraps(BandInfoForm)
                                              (partial(BandInfoForm, allow_edit=edit_resource)),
                                              formset=BaseBandInfoFormSet, extra=0)
        bandinfo_formset = BandInfoFormSetEdit(
            initial=content_model.metadata.bandInformations.values(),
            prefix='BandInformation')

        for form in bandinfo_formset.forms:
            if len(form.initial) > 0:
                form.action = "/hsapi/_internal/%s/bandinformation/%s/update-metadata/" % \
                              (content_model.short_id, form.initial['id'])
                form.number = form.initial['id']

        # original coverage_form
        ori_cov_obj = content_model.metadata.originalCoverage
        ori_coverage_data_dict = {}
        if ori_cov_obj:
            ori_coverage_data_dict['units'] = ori_cov_obj.value['units']
            ori_coverage_data_dict['projection'] = ori_cov_obj.value.get('projection', None)
            ori_coverage_data_dict['northlimit'] = ori_cov_obj.value['northlimit']
            ori_coverage_data_dict['eastlimit'] = ori_cov_obj.value['eastlimit']
            ori_coverage_data_dict['southlimit'] = ori_cov_obj.value['southlimit']
            ori_coverage_data_dict['westlimit'] = ori_cov_obj.value['westlimit']
            ori_coverage_data_dict['projection_string'] = ori_cov_obj.value.\
                get('projection_string', None)
            ori_coverage_data_dict['datum'] = ori_cov_obj.value.get('datum', None)
        else:
            ori_cov_obj = None

        ori_coverage_form = OriginalCoverageSpatialForm(initial=ori_coverage_data_dict,
                                                        res_short_id=content_model.short_id,
                                                        allow_edit=edit_resource,
                                                        element_id=ori_cov_obj.id
                                                        if ori_cov_obj else None)
        ori_coverage_layout = HTML("""

                              {% load crispy_forms_tags %}
                              {% crispy ori_coverage_form %}

                                """)

        # update context
        ext_md_layout = Layout(HTML("
"),
                               ori_coverage_layout,
                               cellinfo_layout,
                               BandInfoLayoutEdit,
                               HTML("
")
                               )
        context = page_processors.get_page_context(page, request.user, resource_edit=edit_resource,
                                                   extended_metadata_layout=ext_md_layout,
                                                   request=request)
        context['ori_coverage_form'] = ori_coverage_form
        context['cellinfo_form'] = cellinfo_form
        context['bandinfo_formset'] = bandinfo_formset

    hs_core_context = add_generic_context(request, page)
    context.update(hs_core_context)

    return context
<commit_msg>Fix layout getting cutoff for Geographic Raster resources in edit mode<commit_after>from mezzanine.pages.page_processors import processor_for
from models import RasterResource
from crispy_forms.layout import Layout, HTML
from forms import CellInfoForm, BandInfoForm, BaseBandInfoFormSet, OriginalCoverageSpatialForm, \
    BandInfoLayoutEdit
from hs_core import page_processors
from django.forms.models import formset_factory
from hs_core.views import add_generic_context
from functools import partial, wraps

# page processor to populate raster resource specific metadata into my-resources template page
@processor_for(RasterResource)
# TODO: problematic permissions
def landing_page(request, page):
    content_model = page.get_content_model()
    edit_resource = page_processors.check_resource_mode(request)

    context = page_processors.get_page_context(page, request.user, resource_edit=edit_resource,
                                               extended_metadata_layout=None, request=request)
    extended_metadata_exists = False
    if content_model.metadata.cellInformation or content_model.metadata.bandInformations:
        extended_metadata_exists = True

    context['extended_metadata_exists'] = extended_metadata_exists
    if not edit_resource:
        # get the context from content_model
        if content_model.metadata.originalCoverage:
            ori_coverage_data_dict = {}
            ori_coverage_data_dict['units'] = content_model.metadata.originalCoverage.value['units']
            ori_coverage_data_dict['projection'] = content_model.metadata.originalCoverage.\
                value.get('projection', None)
            ori_coverage_data_dict['northlimit'] = content_model.metadata.originalCoverage.\
                value['northlimit']
            ori_coverage_data_dict['eastlimit'] = content_model.metadata.originalCoverage.\
                value['eastlimit']
            ori_coverage_data_dict['southlimit'] = content_model.metadata.originalCoverage.\
                value['southlimit']
            ori_coverage_data_dict['westlimit'] = content_model.metadata.originalCoverage.\
                value['westlimit']
            ori_coverage_data_dict['projection_string'] = content_model.metadata.originalCoverage.\
                value.get('projection_string', None)
            ori_coverage_data_dict['datum'] = content_model.metadata.originalCoverage.\
                value.get('datum', None)
            context['originalCoverage'] = ori_coverage_data_dict
        context['cellInformation'] = content_model.metadata.cellInformation
        context['bandInformation'] = content_model.metadata.bandInformations
    else:
        # cellinfo_form
        cellinfo_form = CellInfoForm(instance=content_model.metadata.cellInformation,
                                     res_short_id=content_model.short_id,
                                     allow_edit=True,
                                     element_id=content_model.metadata.cellInformation.id
                                     if content_model.metadata.cellInformation else None)
        cellinfo_layout = HTML("
 "
                               '{% load crispy_forms_tags %} '
                               '{% crispy cellinfo_form %} '
                               '
')
        # bandinfo_formset
        BandInfoFormSetEdit = formset_factory(wraps(BandInfoForm)
                                              (partial(BandInfoForm, allow_edit=edit_resource)),
                                              formset=BaseBandInfoFormSet, extra=0)
        bandinfo_formset = BandInfoFormSetEdit(
            initial=content_model.metadata.bandInformations.values(),
            prefix='BandInformation')

        for form in bandinfo_formset.forms:
            if len(form.initial) > 0:
                form.action = "/hsapi/_internal/%s/bandinformation/%s/update-metadata/" % \
                              (content_model.short_id, form.initial['id'])
                form.number = form.initial['id']

        # original coverage_form
        ori_cov_obj = content_model.metadata.originalCoverage
        ori_coverage_data_dict = {}
        if ori_cov_obj:
            ori_coverage_data_dict['units'] = ori_cov_obj.value['units']
            ori_coverage_data_dict['projection'] = ori_cov_obj.value.get('projection', None)
            ori_coverage_data_dict['northlimit'] = ori_cov_obj.value['northlimit']
            ori_coverage_data_dict['eastlimit'] = ori_cov_obj.value['eastlimit']
            ori_coverage_data_dict['southlimit'] = ori_cov_obj.value['southlimit']
            ori_coverage_data_dict['westlimit'] = ori_cov_obj.value['westlimit']
            ori_coverage_data_dict['projection_string'] = ori_cov_obj.value.\
                get('projection_string', None)
            ori_coverage_data_dict['datum'] = ori_cov_obj.value.get('datum', None)
        else:
            ori_cov_obj = None

        ori_coverage_form = OriginalCoverageSpatialForm(initial=ori_coverage_data_dict,
                                                        res_short_id=content_model.short_id,
                                                        allow_edit=edit_resource,
                                                        element_id=ori_cov_obj.id
                                                        if ori_cov_obj else None)
        ori_coverage_layout = HTML("""

                              {% load crispy_forms_tags %}
                              {% crispy ori_coverage_form %}

                                """)

        # update context
        ext_md_layout = Layout(HTML("
"),
                               ori_coverage_layout,
                               cellinfo_layout,
                               BandInfoLayoutEdit,
                               HTML("
")
                               )
        context = page_processors.get_page_context(page, request.user, resource_edit=edit_resource,
                                                   extended_metadata_layout=ext_md_layout,
                                                   request=request)
        context['ori_coverage_form'] = ori_coverage_form
        context['cellinfo_form'] = cellinfo_form
        context['bandinfo_formset'] = bandinfo_formset

    hs_core_context = add_generic_context(request, page)
    context.update(hs_core_context)

    return context
<|endoftext|>