Ejemplo n.º 1
0
  def AssignDrink(self, drink, user):
    """Assigns, or re-assigns, a previously-recorded Drink.

    Statistics and session data will be recomputed as a side-effect
    of any change to user assignment.  (If the drink is already assigned
    to the given user, this method is a no-op).

    Args:
        drink: The Drink object (or drink id) to assign.
        user: The User object (or username) to set as the owner,
            or None for anonymous.

    Returns:
        The drink.
    """
    drink = get_drink(drink)
    if drink.user == user:
      return drink

    stats.invalidate(drink)
    drink.user = user
    drink.save()
    stats.generate(drink)

    self.cache.update_generation()

    return drink
Ejemplo n.º 2
0
  def SetDrinkVolume(self, drink, volume_ml):
    """Updates the drink volume."""
    if volume_ml == drink.volume_ml:
      return

    drink.volume_ml = volume_ml
    drink.save()

    stats.invalidate(drink)
    drink.session.Rebuild()
    stats.generate(drink)
Ejemplo n.º 3
0
    def handle(self, **options):
        stats.invalidate_all()

        drinks = models.Drink.objects.all().order_by('id')
        num_drinks = len(drinks)
        pos = 0
        for d in drinks:
            pos += 1
            progbar('regenerating stats', pos, num_drinks)
            stats.generate(d, invalidate_first=False)

        print ''
        print 'done!'
Ejemplo n.º 4
0
  def handle(self, **options):
    stats.invalidate(None)

    drinks = models.Drink.objects.all().order_by('id')
    num_drinks = len(drinks)
    pos = 0
    for d in drinks:
      pos += 1
      progbar('regenerating stats', pos, num_drinks)
      stats.generate(d, invalidate_first=False)

    print ''
    print 'done!'
Ejemplo n.º 5
0
    def CancelDrink(self, drink, spilled=False):
        """Permanently deletes a Drink from the system.

        Associated data, such as SystemEvent, PourPicture, and other data, will
        be destroyed along with the drink. Statistics and session data will be
        recomputed after the drink is destroyed.

        Args:
            drink_id: The Drink, or id of the Drink, to cancel.
            spilled: If True, after canceling the Drink, the drink's volume will
                be added to its Keg's "spilled" total.  This is is typically useful
                for removing test pours from the system while still accounting for
                the lost volume.

        Returns:
            The deleted drink.
        """
        if not isinstance(drink, models.Drink):
            drink = models.Drink.objects.get(id=drink)

        session = drink.session
        drink_id = drink.id
        keg = drink.keg
        volume_ml = drink.volume_ml

        keg_update_fields = ['served_volume_ml']
        keg.served_volume_ml -= volume_ml

        # Transfer volume to spillage if requested.
        if spilled and volume_ml and drink.keg:
            keg.spilled_ml += volume_ml
            keg_update_fields.append('spilled_ml')

        keg.save(update_fields=keg_update_fields)

        # Invalidate all statistics.
        stats.invalidate(drink)

        # Delete the drink, including any objects related to it.
        drink.delete()

        session.Rebuild()

        # Regenerate stats for any drinks following the just-deleted one.
        for drink in models.Drink.objects.filter(
                id__gt=drink_id).order_by('id'):
            stats.generate(drink)

        self.cache.update_generation()

        return drink
Ejemplo n.º 6
0
    def cancel_drink(self, drink, spilled=False):
        """Permanently deletes a Drink from the system.

        Associated data, such as SystemEvent, PourPicture, and other data, will
        be destroyed along with the drink. Statistics and session data will be
        recomputed after the drink is destroyed.

        Args:
            drink_id: The Drink, or id of the Drink, to cancel.
            spilled: If True, after canceling the Drink, the drink's volume will
                be added to its Keg's "spilled" total.  This is is typically useful
                for removing test pours from the system while still accounting for
                the lost volume.

        Returns:
            The deleted drink.
        """
        if not isinstance(drink, models.Drink):
            drink = models.Drink.objects.get(id=drink)

        session = drink.session
        drink_id = drink.id
        keg = drink.keg
        volume_ml = drink.volume_ml

        keg_update_fields = ['served_volume_ml']
        keg.served_volume_ml -= volume_ml

        # Transfer volume to spillage if requested.
        if spilled and volume_ml and drink.keg:
            keg.spilled_ml += volume_ml
            keg_update_fields.append('spilled_ml')

        keg.save(update_fields=keg_update_fields)

        # Invalidate all statistics.
        stats.invalidate(drink)

        # Delete the drink, including any objects related to it.
        drink.delete()

        session.Rebuild()

        # Regenerate stats for any drinks following the just-deleted one.
        for drink in models.Drink.objects.filter(id__gt=drink_id).order_by('id'):
            stats.generate(drink)

        self.cache.update_generation()

        return drink
Ejemplo n.º 7
0
    def set_drink_volume(self, drink, volume_ml):
        """Updates the drink volume."""
        if volume_ml == drink.volume_ml:
            return

        difference = volume_ml - drink.volume_ml
        drink.volume_ml = volume_ml
        drink.save(update_fields=['volume_ml'])

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

        stats.invalidate(drink)
        drink.session.Rebuild()

        # Regenerate stats for this drink and all subsequent.
        for drink in models.Drink.objects.filter(id__gte=drink.id).order_by('id'):
            stats.generate(drink)

        self.cache.update_generation()
Ejemplo n.º 8
0
    def SetDrinkVolume(self, drink, volume_ml):
        """Updates the drink volume."""
        if volume_ml == drink.volume_ml:
            return

        difference = volume_ml - drink.volume_ml
        drink.volume_ml = volume_ml
        drink.save(update_fields=['volume_ml'])

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

        stats.invalidate(drink)
        drink.session.Rebuild()

        # Regenerate stats for this drink and all subsequent.
        for drink in models.Drink.objects.filter(
                id__gte=drink.id).order_by('id'):
            stats.generate(drink)

        self.cache.update_generation()
Ejemplo n.º 9
0
    def AssignDrink(self, drink, user):
        """Assigns, or re-assigns, a previously-recorded Drink.

        Statistics and session data will be recomputed as a side-effect
        of any change to user assignment.  (If the drink is already assigned
        to the given user, this method is a no-op).

        Args:
            drink: The Drink object (or drink id) to assign.
            user: The User object (or username) to set as the owner,
                or None for anonymous.

        Returns:
            The drink.
        """
        drink = get_drink(drink)
        user = get_user(user)
        if drink.user == user:
            return drink

        drink.user = user
        drink.save()

        stats.invalidate(drink)

        for e in drink.events.all():
            e.user = user
            e.save()
        for p in drink.pictures.all():
            p.user = user
            p.save()

        drink.session.Rebuild()
        stats.generate(drink)

        self.cache.update_generation()
        return drink
Ejemplo n.º 10
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