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
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)
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!'
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!'
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
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
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()
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()
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
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