Example #1
0
    def attach_keg(self, tap, keg):
        """Activates a keg at the given tap (with existing choices).

        The tap must be inactive (tap.current_keg == None), otherwise a
        ValueError will be thrown.

        Args:
            tap: The KegTap or meter name to tap against.
            keg_id: The Keg ID to map to tap.

        Returns:
            The keg instance.
        """
        tap = self._get_tap(tap)

        if tap.is_active():
            raise ValueError('Tap is already active, must end keg first.')

        keg.start_time = timezone.now()
        keg.online = True
        keg.save()

        old_keg = tap.current_keg
        if old_keg:
            self.end_keg(tap)

        tap.current_keg = keg
        tap.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        signals.keg_attached.send_robust(sender=self.__class__, keg=keg, tap=tap)
        return keg
Example #2
0
    def end_keg(self, tap):
        """Takes the current Keg offline at the given tap.

        Args:
            tap: The KegTap object to tap against, or a string matching
                the tap's meter name.

        Returns:
            The old keg.

        Raises:
            ValueError: if the tap is missing or already offline.
        """
        tap = self._get_tap(tap)

        if not tap.current_keg:
            raise ValueError('Tap is already offline.')

        keg = tap.current_keg
        keg.online = False
        keg.end_time = timezone.now()
        keg.finished = True
        keg.save()

        tap.current_keg = None
        tap.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        signals.keg_ended.send_robust(sender=self.__class__, keg=keg)
        return keg
Example #3
0
    def end_keg(self, tap):
        """Takes the current Keg offline at the given tap.

        Args:
            tap: The KegTap object to tap against, or a string matching
                KegTap.meter_name.

        Returns:
            The old keg.

        Raises:
            ValueError: if the tap is missing or already offline.
        """
        if not isinstance(tap, models.KegTap):
            tap = models.KegTap.objects.get(meter_name=tap)

        if not tap.current_keg:
            raise ValueError('Tap is already offline.')

        keg = tap.current_keg
        keg.online = False
        keg.save()

        tap.current_keg = None
        tap.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        return keg
Example #4
0
    def end_keg(self, keg, when=None):
        """Takes the given keg offline."""
        if not when:
            when = timezone.now()

        if keg.status == keg.STATUS_ON_TAP:
            keg = self.disconnect_keg(keg)

        keg.status = keg.STATUS_FINISHED
        keg.end_time = when
        keg.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        signals.keg_ended.send_robust(sender=self.__class__, keg=keg)
        return keg
Example #5
0
    def end_keg(self, keg, when=None):
        """Takes the given keg offline."""
        if not when:
            when = timezone.now()

        if keg.status == keg.STATUS_ON_TAP:
            keg = self.disconnect_keg(keg)

        keg.status = keg.STATUS_FINISHED
        keg.end_time = when
        keg.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        signals.keg_ended.send_robust(sender=self.__class__, keg=keg)
        return keg
Example #6
0
    def attach_keg(self, tap, keg):
        """Activates a new keg at the given tap (with existing choices).

        The tap must be inactive (tap.current_keg == None), otherwise a
        ValueError will be thrown.

        Since the keg already exists in the database, it is assumed that
        "add_keg" took care of all the error conditions and checking.

        Args:
            tap: The KegTap or meter name to tap against.
            keg_id: The Keg ID to map to tap.

        Returns:
            The new keg instance.
        """
        with transaction.atomic():
            tap = self._get_tap(tap)

            if tap.is_active():
                raise ValueError('Tap is already active, must end keg first.')

            keg.start_time = timezone.now()
            keg.online = True
            keg.save()

            old_keg = tap.current_keg
            if old_keg:
                self.end_keg(tap)

            tap.current_keg = keg
            tap.save()

            events = models.SystemEvent.build_events_for_keg(keg)

        with transaction.atomic():
            tasks.schedule_tasks(events)

        return keg
Example #7
0
class KegbotBackend:
    """Provides high-level operations against the Kegbot system."""
    def __init__(self):
        self._logger = logging.getLogger('backend')
        self.cache = KegbotCache()

    @transaction.commit_on_success
    def CreateNewUser(self, username):
        """Creates and returns a User for the given username."""
        return models.User.objects.create(username=username)

    @transaction.commit_on_success
    def CreateTap(self, name, meter_name, relay_name=None, ml_per_tick=None):
        """Creates and returns a new KegTap.

        Args:
          name: The human-meaningful name for the tap, for instance, "Main Tap".
          meter_name: The unique sensor name for the tap, for instance,
              "kegboard.flow0".
          relay_name: If the tap is connected to a relay, this specifies its
              name, for instance, "kegboard.relay0".
          ml_per_tick: The number of milliliters per flow meter tick on this
              tap's meter.

        Returns:
            The new KegTap instance.
        """
        tap = models.KegTap.objects.create(name=name,
                                           meter_name=meter_name,
                                           relay_name=relay_name,
                                           ml_per_tick=ml_per_tick)
        tap.save()
        return tap

    @transaction.commit_on_success
    def CreateAuthToken(self, auth_device, token_value, username=None):
        """Creates a new AuthenticationToken.

        The combination of (auth_device, token_value) must be unique within the
        system.

        Args:
            auth_device: The name of the authentication device, for instance,
                "core.rfid".
            token_value: The opaque string value of the token, which is typically
                unique within the `auth_device` namespace.
            username: The User with which to associate this Token.

        Returns:
            The newly-created AuthenticationToken.
        """
        token = models.AuthenticationToken.objects.create(
            auth_device=auth_device, token_value=token_value)
        if username:
            user = get_user(username)
            token.user = user
        token.save()
        return token

    @transaction.commit_on_success
    def RecordDrink(self,
                    tap_name,
                    ticks,
                    volume_ml=None,
                    username=None,
                    pour_time=None,
                    duration=0,
                    shout='',
                    tick_time_series='',
                    do_postprocess=True):
        """Records a new drink against a given tap.

        The tap must have a Keg assigned to it (KegTap.current_keg), and the keg
        must be active.

        Args:
            tap_name: The tap's meter_name.
            ticks: The number of ticks observed by the flow sensor for this drink.
            volume_ml: The volume, in milliliters, of the pour.  If specifed, this
                value is saved as the Drink's actual value.  If not specified, a
                volume is computed based on `ticks` and the KegTap's
                `ml_per_tick` setting.
            username: The username with which to associate this Drink, or None for
                an anonymous Drink.
            pour_time: The datetime of the drink.  If not supplied, the current
                date and time will be used.
            duration: Number of seconds it took to pour this Drink.  This is
                optional information not critical to the record.
            shout: A short comment left by the user about this Drink.  Optional.
            tick_time_series: Vector of flow update data, used for diagnostic
                purposes.
            do_postprocess: Set to False during bulk inserts.

        Returns:
            The newly-created Drink instance.
        """

        tap = self._GetTapFromName(tap_name)
        if not tap:
            raise BackendError("Tap unknown")
        if not tap.is_active or not tap.current_keg:
            raise BackendError("No active keg at this tap")

        if volume_ml is None:
            volume_ml = float(ticks) * tap.ml_per_tick

        user = None
        if username:
            user = get_user(username)
        else:
            user = models.SiteSettings.get().default_user

        if not pour_time:
            pour_time = timezone.now()

        keg = tap.current_keg

        if tick_time_series:
            try:
                # Validate the time series by parsing it; canonicalize it by generating
                # it again.  If malformed, just junk it; it's non-essential information.
                tick_time_series = time_series.to_string(
                    time_series.from_string(tick_time_series))
            except ValueError, e:
                self._logger.warning(
                    'Time series invalid, ignoring. Error was: %s' % e)
                tick_time_series = ''

        d = models.Drink(ticks=ticks,
                         keg=keg,
                         user=user,
                         volume_ml=volume_ml,
                         time=pour_time,
                         duration=duration,
                         shout=shout,
                         tick_time_series=tick_time_series)
        models.DrinkingSession.AssignSessionForDrink(d)
        d.save()

        keg.served_volume_ml += volume_ml
        keg.save(update_fields=['served_volume_ml'])

        self.cache.update_generation()

        if do_postprocess:
            stats.generate(d)
            events = models.SystemEvent.ProcessDrink(d)
            tasks.schedule_tasks(events)

        return d
Example #8
0
    def record_drink(self, tap, ticks, volume_ml=None, username=None,
            pour_time=None, duration=0, shout='', tick_time_series='', photo=None,
            spilled=False):
        """Records a new drink against a given tap.

        The tap must have a Keg assigned to it (KegTap.current_keg), and the keg
        must be active.

        Args:
            tap: A KegTap or matching meter name.
            ticks: The number of ticks observed by the flow sensor for this drink.
            volume_ml: The volume, in milliliters, of the pour.  If specifed, this
                value is saved as the Drink's actual value.  If not specified, a
                volume is computed based on `ticks` and the meter's
                `ticks_per_ml` setting.
            username: The username with which to associate this Drink, or None for
                an anonymous Drink.
            pour_time: The datetime of the drink.  If not supplied, the current
                date and time will be used.
            duration: Number of seconds it took to pour this Drink.  This is
                optional information not critical to the record.
            shout: A short comment left by the user about this Drink.  Optional.
            tick_time_series: Vector of flow update data, used for diagnostic
                purposes.
            spilled: If drink is recorded as spill, the volume is added to spilled_ml
                and the "drink" is not saved as an event.

        Returns:
            The newly-created Drink instance.
        """
        tap = self._get_tap(tap)
        if not tap.is_active or not tap.current_keg:
            raise exceptions.BackendError("No active keg at this tap")

        keg = tap.current_keg

        if spilled:
            keg.spilled_ml += volume_ml
            keg.save(update_fields=['spilled_ml'])
            return None

        if volume_ml is None:
            meter = tap.current_meter()
            if not meter:
                raise exceptions.BackendError("Tap has no meter, can't compute volume")
            volume_ml = float(ticks) / meter.ticks_per_ml

        user = None
        if username:
            user = self.get_user(username)
        else:
            user = models.User.objects.get(username='******')

        if not pour_time:
            pour_time = timezone.now()

        if tick_time_series:
            try:
                # Validate the time series by parsing it; canonicalize it by generating
                # it again.  If malformed, just junk it; it's non-essential information.
                tick_time_series = time_series.to_string(time_series.from_string(tick_time_series))
            except ValueError as e:
                self._logger.warning('Time series invalid, ignoring. Error was: %s' % e)
                tick_time_series = ''

        d = models.Drink(ticks=ticks, keg=keg, user=user,
            volume_ml=volume_ml, time=pour_time, duration=duration,
            shout=shout, tick_time_series=tick_time_series)
        models.DrinkingSession.AssignSessionForDrink(d)
        d.save()

        keg.served_volume_ml += volume_ml
        keg.save(update_fields=['served_volume_ml'])

        if photo:
            pic = models.Picture.objects.create(
                image=photo,
                user=d.user,
                keg=d.keg,
                session=d.session
            )
            d.picture = pic
            d.save()

        events = models.SystemEvent.build_events_for_drink(d)
        tasks.schedule_tasks(events)
        self.build_stats(d.id)

        signals.drink_recorded.send_robust(sender=self.__class__, drink=d)
        return d
Example #9
0
    def start_keg(self, tap, beverage=None, keg_type=keg_sizes.HALF_BARREL,
            full_volume_ml=None, beverage_name=None, beverage_type=None,
            producer_name=None, style_name=None, when=None):
        """Activates a new keg at the given tap.

        The tap must be inactive (tap.current_keg == None), otherwise a
        ValueError will be thrown.

        A beverage must be specified, either by providing an existing
        Beverage instance as `beverage`, or by specifying values for
        `beverage_type`, `beverage_name`, `producer_name`,
        and `style_name`.

        When using the latter form, the system will attempt to match
        the string type parameters against an already-existing Beverage.
        Otherwise, a new Beverage will be created.

        Args:
            tap: The KegTap object to tap against, or a string matching
                KegTap.meter_name.
            beverage: The type of beverage, as a Beverage object.
            keg_type: The type of physical keg, from keg_sizes.
            full_volume_ml: The keg's original unserved volume.  If unspecified,
                will be interpreted from keg_type.  It is an error to omit this
                parameter when keg_type is OTHER.
            beverage_name: The keg's beverage name.  Must be given with
                `producer_name` and `style_name`;
                `beverage` must be None.
            beverage_type: The keg beverage type.
            producer_name: The brewer or producer of this beverage.
            style_name: The style of this beverage.
            when: Keg activation date and time. If not specified, current
                time will be used.

        Returns:
            The new keg instance.
        """
        if not isinstance(tap, models.KegTap):
            tap = models.KegTap.objects.get(meter_name=tap)

        if tap.is_active():
            raise ValueError('Tap is already active, must end keg first.')

        if beverage:
            if beverage_type or beverage_name or producer_name or style_name:
                raise ValueError(
                    'Cannot give beverage_type, beverage_name, producer_name, or style_name with beverage')
        else:
            if not beverage_type:
                raise ValueError('Must supply beverage_type when beverage is None')
            if not beverage_name:
                raise ValueError('Must supply beverage_name when beverage is None')
            if not producer_name:
                raise ValueError('Must supply producer_name when beverage is None')
            if not style_name:
                raise ValueError('Must supply style_name when beverage is None')
            producer = models.BeverageProducer.objects.get_or_create(name=producer_name)[0]
            beverage = models.Beverage.objects.get_or_create(name=beverage_name, beverage_type=beverage_type,
                    producer=producer, style=style_name)[0]

        if keg_type not in keg_sizes.DESCRIPTIONS:
            raise ValueError('Unrecognized keg type: %s' % keg_type)
        if full_volume_ml is None:
            full_volume_ml = keg_sizes.VOLUMES_ML[keg_type]

        if not when:
            when = timezone.now()

        keg = models.Keg.objects.create(type=beverage, keg_type=keg_type,
                online=True, full_volume_ml=full_volume_ml,
                start_time=when)

        old_keg = tap.current_keg
        if old_keg:
            self.end_keg(tap)

        tap.current_keg = keg
        tap.save()

        events = models.SystemEvent.build_events_for_keg(keg)
        tasks.schedule_tasks(events)

        return keg
Example #10
0
class KegbotBackend(object):
    """Provides high-level operations against the Kegbot system."""

    def __init__(self):
        self._logger = logging.getLogger('backend')
        self.cache = KegbotCache()

    @transaction.atomic
    def create_new_user(self, username, email, password=None, photo=None):
        """Creates and returns a User for the given username."""
        try:
            user = get_auth_backend().register(username=username, email=email,
                    password=password, photo=photo)
            return user
        except UserExistsException as e:
            raise UserExistsError(e)
        except AuthException as e:
            raise BackendError(e)

    @transaction.atomic
    def create_tap(self, name, meter_name, toggle_name=None, ticks_per_ml=None):
        """Creates and returns a new KegTap.

        Args:
          name: The human-meaningful name for the tap, for instance, "Main Tap".
          meter_name: The unique sensor name for the tap, for instance,
              "kegboard.flow0".
          toggle_name: If the tap is connected to a relay, this specifies its
              name, for instance, "kegboard.relay0".
          ticks_per_ml: The number of flow meter ticks per milliliter of fluid
              on this tap's meter.

        Returns:
            The new KegTap instance.
        """
        tap = models.KegTap.objects.create(name=name)
        tap.save()

        meter = models.FlowMeter.get_or_create_from_meter_name(meter_name)
        meter.tap = tap
        if ticks_per_ml:
            meter.ticks_per_ml = ticks_per_ml
        meter.save()

        if toggle_name:
            toggle = models.FlowToggle.get_or_create_from_toggle_name(toggle_name)
        return tap

    @transaction.atomic
    def create_auth_token(self, auth_device, token_value, username=None):
        """Creates a new AuthenticationToken.

        The combination of (auth_device, token_value) must be unique within the
        system.

        Args:
            auth_device: The name of the authentication device, for instance,
                "core.rfid".
            token_value: The opaque string value of the token, which is typically
                unique within the `auth_device` namespace.
            username: The User with which to associate this Token.

        Returns:
            The newly-created AuthenticationToken.
        """
        token = models.AuthenticationToken.objects.create(
            auth_device=auth_device, token_value=token_value)
        if username:
            user = get_user(username)
            token.user = user
        token.save()
        return token

    def record_drink(self, tap, ticks, volume_ml=None, username=None,
        pour_time=None, duration=0, shout='', tick_time_series='', photo=None,
        do_postprocess=True, spilled=False):
        """Records a new drink against a given tap.

        The tap must have a Keg assigned to it (KegTap.current_keg), and the keg
        must be active.

        Args:
            tap: A KegTap or matching meter name.
            ticks: The number of ticks observed by the flow sensor for this drink.
            volume_ml: The volume, in milliliters, of the pour.  If specifed, this
                value is saved as the Drink's actual value.  If not specified, a
                volume is computed based on `ticks` and the meter's
                `ticks_per_ml` setting.
            username: The username with which to associate this Drink, or None for
                an anonymous Drink.
            pour_time: The datetime of the drink.  If not supplied, the current
                date and time will be used.
            duration: Number of seconds it took to pour this Drink.  This is
                optional information not critical to the record.
            shout: A short comment left by the user about this Drink.  Optional.
            tick_time_series: Vector of flow update data, used for diagnostic
                purposes.
            do_postprocess: Set to False during bulk inserts.
            spilled: If drink is recorded as spill, the volume is added to spilled_ml
                and the "drink" is not saved as an event.

        Returns:
            The newly-created Drink instance.
        """

        with transaction.atomic():
            tap = self._get_tap(tap)
            if not tap.is_active or not tap.current_keg:
                raise BackendError("No active keg at this tap")

            keg = tap.current_keg

            if spilled:
                keg.spilled_ml += volume_ml
                keg.save(update_fields=['spilled_ml'])
                return None

            if volume_ml is None:
                meter = tap.current_meter()
                if not meter:
                    raise BackendError("Tap has no meter, can't compute volume")
                volume_ml = float(ticks) / meter.ticks_per_ml

            user = None
            if username:
                user = get_user(username)
            else:
                user = models.KegbotSite.get().default_user
                if not user:
                    user = models.User.objects.get(username='******')

            if not pour_time:
                pour_time = timezone.now()

            if tick_time_series:
                try:
                    # Validate the time series by parsing it; canonicalize it by generating
                    # it again.  If malformed, just junk it; it's non-essential information.
                    tick_time_series = time_series.to_string(time_series.from_string(tick_time_series))
                except ValueError, e:
                    self._logger.warning('Time series invalid, ignoring. Error was: %s' % e)
                    tick_time_series = ''

            d = models.Drink(ticks=ticks, keg=keg, user=user,
                volume_ml=volume_ml, time=pour_time, duration=duration,
                shout=shout, tick_time_series=tick_time_series)
            models.DrinkingSession.AssignSessionForDrink(d)
            d.save()

            keg.served_volume_ml += volume_ml
            keg.save(update_fields=['served_volume_ml'])

        self.cache.update_generation()

        if photo:
            with transaction.atomic():
                pic = models.Picture.objects.create(
                    image=photo,
                    user=d.user,
                    keg=d.keg,
                    session=d.session
                )
                d.picture = pic
                d.save()

        if do_postprocess:
            self.build_stats(d.id)
            with transaction.atomic():
                events = models.SystemEvent.build_events_for_drink(d)
                tasks.schedule_tasks(events)

        return d