class PageData(Entity): pagename = Field(Unicode(40), primary_key=True) data = Field(Unicode(10 * 1024)) creator = Field(Unicode(40))
class Artigo(Entity): using_options(tablename='artigo_lei') lei = ManyToOne('Lei') numero = Field(Unicode(32)) paragrafos = OneToMany('Paragrafo') incisos = OneToMany('Inciso')
class DataChecklist(Entity): using_options(tablename='checklists') title = Field(Unicode(255)) items = OneToMany('DataChecklistItem', order_by='index', inverse='checklist', collection_class=ordering_list('index')) card = ManyToOne('DataCard') author = ManyToOne('DataUser') index = Field(Integer) @classmethod def get_by_card(cls, card): q = cls.query q = q.filter_by(card=card) q = q.order_by(cls.index) return q.all() def update(self, other): self.title = other.title self.index = other.index for item in other.items: self.items.append( DataChecklistItem(title=item.title, index=item.index, done=False)) database.session.flush() def __unicode__(self): titles = [item.title for item in self.items if item.title] if self.title: titles.insert(0, self.title) return u'\n'.join(titles) def to_indexable(self): return unicode(self) def add_item_from_str(self, text): item = DataChecklistItem.new_from_str(text) return self.add_item(item) def add_item(self, item): self.items.append(item) return item def insert_item(self, index, item): self.items.insert(index, item) def remove_item(self, item): self.items.remove(item) def delete_item(self, item): self.remove_item(item) item.delete() def purge(self): for item in self.items: item.delete() @staticmethod def total_items(card): return DataChecklistItem.total_items(card) @staticmethod def total_items_done(card): return DataChecklistItem.total_items_done(card)
class DataHistory(Entity): using_options(tablename='history', order_by='-when') when = Field(DateTime) action = Field(Unicode(255)) data = Field(JSONType) board = ManyToOne('DataBoard', ondelete='cascade') card = ManyToOne('DataCard', ondelete='cascade', required=True) user = ManyToOne('DataUser', ondelete='cascade', required=True) def to_string(self): data = self.data.copy() data['author'] = self.user.fullname or self.user.username return render_event(self.action, data) @classmethod def add_history(cls, board, card, user, action, data): data.update(action=action) when = datetime.utcnow() data = cls(when=when, action=action, board=board, card=card, user=user, data=data) database.session.flush() @classmethod def get_events(cls, board, hours=None): '''board to None means "everything".''' since = datetime.utcnow() - timedelta(hours=hours) q = cls.query if board: q = q.filter_by(board=board) q = q.filter(cls.when >= since) q = q.order_by(cls.board_id, cls.action, cls.when) return q.all() @classmethod def get_history(cls, board, cardid=None, username=None): q = cls.query q = q.filter_by(board=board) if cardid: q = q.filter(cls.card.has(id=cardid)) if username: q = q.filter(cls.user.has(username=username)) return q @classmethod def get_last_activity(cls, board): q = database.session.query(cls.when) q = q.filter(cls.board == board) q = q.order_by(cls.when.desc()) q = q.limit(1) return q.scalar() @classmethod def purge(cls, card): q = database.session.query(cls).filter(cls.card == card) for log in q: log.delete()
class Recipe(Entity, DeepCopyMixin, ShallowCopyMixin): TYPES = ('MASH', 'EXTRACT', 'EXTRACTSTEEP', 'MINIMASH') MASH_METHODS = ('SINGLESTEP', 'TEMPERATURE', 'DECOCTION', 'MULTISTEP') STATES = ('DRAFT', 'PUBLISHED') type = Field(Enum(*TYPES, native_enum=False), default='MASH', index=True) name = Field(Unicode(256), index=True) gallons = Field(Float, default=5) boil_minutes = Field(Integer, default=60) notes = Field(UnicodeText) creation_date = Field(DateTime, default=datetime.utcnow) last_updated = Field(DateTime, default=datetime.utcnow, index=True) # Cached statistics _og = Field(Float, colname='og') _fg = Field(Float, colname='fg') _abv = Field(Float, colname='abv') _srm = Field(Integer, colname='srm') _ibu = Field(Integer, colname='ibu') mash_method = Field(Enum(*MASH_METHODS, native_enum=False), default='SINGLESTEP') mash_instructions = Field(UnicodeText) state = Field(Enum(*STATES, native_enum=False), default='DRAFT') current_draft = ManyToOne('Recipe', inverse='published_version') published_version = OneToOne('Recipe', inverse='current_draft', order_by='creation_date') copied_from = ManyToOne('Recipe', inverse='copies') copies = OneToMany('Recipe', inverse='copied_from', order_by='creation_date') views = OneToMany('RecipeView', inverse='recipe', cascade='all, delete-orphan') additions = OneToMany('RecipeAddition', inverse='recipe', cascade='all, delete-orphan') fermentation_steps = OneToMany('FermentationStep', inverse='recipe', order_by='step', cascade='all, delete-orphan') slugs = OneToMany('RecipeSlug', inverse='recipe', order_by='id', cascade='all, delete-orphan') style = ManyToOne('Style', inverse='recipes') author = ManyToOne('User', inverse='recipes') __ignored_properties__ = ('current_draft', 'published_version', 'copies', 'views', 'creation_date', 'state', '_og', '_fg', '_abv', '_srm', '_ibu') def __init__(self, **kwargs): super(Recipe, self).__init__(**kwargs) if kwargs.get('name') and not kwargs.get('slugs'): self.slugs.append(entities.RecipeSlug(name=kwargs['name'])) def duplicate(self, overrides={}): """ Used to duplicate a recipe. An optional hash of `overrides` can be specified to override the default copied values, e.g., dupe = user.recipes[0].duplicate({'author': otheruser}) assert dupe.author == otheruser """ # Make a deep copy of the instance copy = deepcopy(self) # For each override... for k, v in overrides.items(): # If the key is already defined, and is a list (i.e., a ManyToOne) if isinstance(getattr(copy, k, None), list): # # Delete each existing entity, because we're about to # override the value. # for i in getattr(copy, k): i.delete() # Set the new (overridden) value setattr(copy, k, v) return copy def draft(self): """ Used to create a new, unpublished draft of a recipe. """ if self.current_draft: self.current_draft.delete() self.current_draft = None return self.duplicate({'published_version': self}) def publish(self): """ Used to publish an orphan draft as a new recipe. """ assert self.state == 'DRAFT', "Only drafts can be published." # If this recipe is a draft of another, merge the changes back in if self.published_version: return self.merge() # Otherwise, just set the state to PUBLISHED self.state = 'PUBLISHED' # Store cached values self._og = self.calculations.og self._fg = self.calculations.fg self._abv = self.calculations.abv self._srm = self.calculations.srm self._ibu = self.calculations.ibu # Generate a new slug if the existing one hasn't changed. existing = [slug.slug for slug in self.slugs] if entities.RecipeSlug.to_slug(self.name) not in existing: self.slugs.append(entities.RecipeSlug(name=self.name)) def merge(self): """ Used to merge a drafted recipe's changes back into its source. """ # Make sure this is a draft with a source recipe assert self.state == 'DRAFT', "Only drafts can be merged." source = self.published_version assert source is not None, \ "This recipe doesn't have a `published_version`." # Clone the draft onto the published version self.__copy_target__ = self.published_version deepcopy(self) # Delete the draft self.delete() @property def calculations(self): return Calculations(self) @property def efficiency(self): if self.author: return self.author.settings['brewhouse_efficiency'] return .75 @property def unit_system(self): if request.context['metric'] is True: return 'METRIC' return 'US' @property def metric(self): return self.unit_system == 'METRIC' @property def liters(self): liters = to_metric(*(self.gallons, "GALLON"))[0] return round(liters, 3) @liters.setter # noqa def liters(self, v): gallons = to_us(*(v, "LITER"))[0] self.gallons = gallons def _partition(self, additions): """ Partition a set of recipe additions by ingredient type, e.g.,: _partition([grain, grain2, hop]) {'Fermentable': [grain, grain2], 'Hop': [hop]} """ p = {} for a in additions: p.setdefault(a.ingredient.__class__, []).append(a) return p def _percent(self, partitions): """ Calculate percentage of additions by amount within a set of recipe partitions. e.g., _percent({'Fermentable': [grain, grain2], 'Hop': [hop]}) {grain : .75, grain2 : .25, hop : 1} """ percentages = {} for type, additions in partitions.items(): total = sum([addition.amount for addition in additions]) for addition in additions: if total: percentages[addition] = float( addition.amount) / float(total) else: percentages[addition] = 0 return percentages @property def mash(self): return self._partition([a for a in self.additions if a.step == 'mash']) @property def boil(self): return self._partition([a for a in self.additions if a.step == 'boil']) @property def fermentation(self): return self._partition( [a for a in self.additions if a.step == 'fermentation']) def contains(self, ingredient, step): if step not in ('mash', 'boil', 'fermentation'): return False additions = getattr(self, step) for a in sum(additions.values(), []): if a.ingredient == ingredient: return True return False @property def next_fermentation_step(self): """ The next available fermentation step for a recipe. e.g., if temperature/length is already defined for "PRIMARY" a fermentation period, returns "SECONDARY". If "SECONDARY" is already defined, returns "TERTIARY". Always returns one of `model.FermentationStep.STEPS`. """ total = len(self.fermentation_steps) return {1: 'SECONDARY', 2: 'TERTIARY'}.get(total, None) def url(self, public=True): """ The URL for a recipe. """ return '/recipes/%s/%s/%s' % ( ('%x' % self.id).lower(), self.slugs[-1].slug, '' if public else 'builder') @property def printable_type(self): return { 'MASH': 'All Grain', 'EXTRACT': 'Extract', 'EXTRACTSTEEP': 'Extract w/ Steeped Grains', 'MINIMASH': 'Mini-Mash' }[self.type] def touch(self): self.last_updated = datetime.utcnow() @property def og(self): return self._og @property def fg(self): return self._fg @property def abv(self): return self._abv @property def srm(self): return self._srm @property def ibu(self): return self._ibu def to_xml(self): from draughtcraft.lib.beerxml import export kw = { 'name': self.name, 'type': { 'MASH': 'All Grain', 'MINIMASH': 'Partial Mash' }.get(self.type, 'Extract'), 'brewer': self.author.printed_name if self.author else 'Unknown', 'batch_size': self.liters, 'boil_size': self.liters * 1.25, 'boil_time': self.boil_minutes, 'notes': self.notes, 'fermentation_stages': len(self.fermentation_steps), } hops = [a.to_xml() for a in self.additions if a.hop] fermentables = [a.to_xml() for a in self.additions if a.fermentable] yeast = [a.to_xml() for a in self.additions if a.yeast] extras = [a.to_xml() for a in self.additions if a.extra] kw['hops'] = hops kw['fermentables'] = fermentables kw['yeasts'] = yeast kw['miscs'] = extras kw['mash'] = [] kw['waters'] = [] if self.style is None: kw['style'] = export.Style(name='', category='No Style Chosen', type='None', category_number=0, style_letter='', og_min=0, og_max=0, ibu_min=0, ibu_max=0, color_min=0, color_max=0, fg_min=0, fg_max=0) else: kw['style'] = self.style.to_xml() if self.type != 'EXTRACT': kw['efficiency'] = self.efficiency * 100.00 for stage in self.fermentation_steps: if stage.step == 'PRIMARY': kw['primary_age'] = stage.days kw['primary_temp'] = stage.celsius if stage.step == 'SECONDARY': kw['secondary_age'] = stage.days kw['secondary_temp'] = stage.celsius if stage.step == 'TERTIARY': kw['tertiary_age'] = stage.days kw['tertiary_temp'] = stage.celsius return export.Recipe(**kw).render() def __json__(self): from draughtcraft.templates.helpers import alphanum_key def inventory(cls, types=[]): return sorted([ f.__json__() for f in cls.query.all() if not types or (types and f.type in types) ], key=lambda f: alphanum_key(f['name'])) # # Attempt to look up the preferred calculation method for the # recipe's author. # ibu_method = 'tinseth' user = self.author if user: ibu_method = user.settings.get('default_ibu_formula', 'tinseth') return { # Basic attributes 'name': self.name, 'author': self.author.username if self.author else '', 'style': self.style.id if self.style else None, 'gallons': self.gallons, # Ingredients 'mash': filter(lambda a: a.step == 'mash', self.additions), 'boil': filter(lambda a: a.step == 'boil', self.additions), 'fermentation': filter(lambda a: a.step == 'fermentation', self.additions), 'ibu_method': ibu_method, 'efficiency': self.efficiency, # Inventory 'inventory': { 'malts': inventory(entities.Fermentable, ('MALT', 'GRAIN', 'ADJUNCT', 'SUGAR')), 'extracts': inventory(entities.Fermentable, ('EXTRACT', )), 'hops': inventory(entities.Hop), 'yeast': inventory(entities.Yeast), 'extras': inventory(entities.Extra) }, # Extras 'mash_method': self.mash_method, 'mash_instructions': self.mash_instructions, 'boil_minutes': self.boil_minutes, 'fermentation_steps': self.fermentation_steps, 'notes': self.notes, 'metric': self.metric }
class DataUser(Entity): """Label mapper """ using_options(tablename='user') # VARCHAR(binary=True) here is a hack to make MySQL case sensitive # like the other DBMS. # No consequences on regular databases. username = Field(VARCHAR(255, binary=True), unique=True, primary_key=True, nullable=False) source = Field(Unicode(255), nullable=False, primary_key=True) fullname = Field(Unicode(255), nullable=False) email = Field(Unicode(255), nullable=True) picture = Field(Unicode(255), nullable=True) language = Field(Unicode(255), default=u"en", nullable=True) email_to_confirm = Field(Unicode(255)) _salt = Field(Unicode(255), colname='salt', nullable=False) _password = Field(Unicode(255), colname='password', nullable=True) registration_date = Field(DateTime, nullable=False) last_login = Field(DateTime, nullable=True) display_week_numbers = Field(Boolean, default=False) board_members = OneToMany('DataBoardMember') boards = AssociationProxy( 'board_members', 'board', creator=lambda board: DataBoardMember(board=board)) board_managers = OneToMany('DataBoardManager') managed_boards = AssociationProxy( 'board_managers', 'board', creator=lambda board: DataBoardManager(board=board)) last_board = OneToOne('DataBoard', inverse='last_users') cards = ManyToMany('DataCard', inverse='members', lazy='dynamic') my_cards = OneToMany('DataCard', inverse='author') history = OneToMany('DataHistory') votes = OneToMany('DataVote') def __init__(self, username, password, fullname, email, source=u'application', picture=None, **kw): """Create a new user with an unconfirmed email""" super(DataUser, self).__init__(username=username, fullname=fullname, email=None, email_to_confirm=email, source=source, picture=picture, registration_date=datetime.datetime.utcnow(), **kw) # Create password if source is local if source == "application": self.change_password(password) else: # External authentication self.change_password('passwd') self.email_to_confirm = None def update(self, fullname, email, picture=None): self.fullname = fullname if email: self.email = email self.picture = picture def check_password(self, clear_password): """Check the user password. Return True if the password is valid for this user""" encrypted_password = self._encrypt_password(self._salt, clear_password) return encrypted_password == self._password def change_password(self, clear_password): """Change the user password""" self._salt = self._create_random_salt() self._password = self._encrypt_password(self._salt, clear_password) def set_email_to_confirm(self, email_to_confirm): if email_to_confirm: self.email_to_confirm = email_to_confirm def is_validated(self): return self.email_to_confirm is None def confirm_email(self): """Called when a user confirms his email address""" # already confirmed if self.email_to_confirm is None: return self.email = self.email_to_confirm self.email_to_confirm = None def add_board(self, board, role="member"): """Add board to user's board lists In: - ``board`` -- DataBoard instance to add - ``role`` -- user is member or manager """ boards = set(dbm.board for dbm in self.board_members) if board not in boards: self.board_members.append(DataBoardMember(board=board)) if role == "manager" and board not in self.managed_boards: self.managed_boards.append(board) def get_picture(self): return self.picture @classmethod def get_confirmed_users(cls): return cls.query.filter(cls.email is not None) @staticmethod def _create_random_salt(length=32): allowed_chars = string.ascii_letters + string.digits return u''.join(random.choice(allowed_chars) for _ in range(length)) @staticmethod def _encrypt_password(salt, password): secret = "NzlSszmvDNY2e2lVMwiKJwgWjNGFCP1a" secret_salt = hashlib.sha512(secret + salt).hexdigest() utf8_password = password.encode('utf-8') return unicode(hashlib.sha512(secret_salt + utf8_password).hexdigest()) @classmethod def get_unconfirmed_users(cls, before_date=None): q = cls.query.filter(cls.email is None) if before_date: q = q.filter(cls.registration_date < before_date) return q @classmethod def get_by_username(cls, username): return cls.get_by(username=username) @classmethod def get_by_email(cls, email): return cls.get_by(email=email) @classmethod def search(cls, value): return cls.query.filter( cls.fullname.ilike('%' + value + '%') | cls.email.ilike('%' + value + '%')) def best_friends(self, exclude_list=(), size=None): from kansha.board.models import DataBoard cls = self.__class__ bm2 = aliased(DataBoardMember) cnt = func.count(DataBoardMember.board_id) query = database.session.query(cls, cnt) query = query.join( (DataBoardMember, and_(DataBoardMember.user_source == cls.source, DataBoardMember.user_username == cls.username))) query = query.join( (DataBoard, DataBoard.id == DataBoardMember.board_id)) query = query.join((bm2, bm2.board_id == DataBoard.id)) query = query.filter(bm2.member == self) if exclude_list: query = query.filter(~cls.email.in_(exclude_list)) query = query.group_by(cls) query = query.order_by(cnt.desc(), cls.fullname) if size: query = query.limit(size) return [res[0] for res in query]
class User(Entity, ShallowCopyMixin): first_name = Field(Unicode(64), index=True) last_name = Field(Unicode(64), index=True) username = Field(Unicode(64), unique=True, index=True) _password = Field(Unicode(64), colname='password', synonym='password') email = Field(Unicode(64), index=True) bio = Field(Unicode(512)) signup_date = Field(DateTime, default=datetime.utcnow) location = Field(Unicode(256)) recipes = OneToMany('Recipe', inverse='author', order_by='-last_updated') user_settings = OneToMany( 'UserSetting', cascade='all, delete-orphan', collection_class=attribute_mapped_collection('name')) settings = AssociationProxy( 'user_settings', 'value', creator=lambda name, value: UserSetting(name=name, value=value)) def __init__(self, *args, **kwargs): super(User, self).__init__(*args, **kwargs) UserSetting.init_defaults(self) @property def full_name(self): return "%s %s" % (self.first_name or '', self.last_name or '') @property def printed_name(self): if self.full_name.strip(): return self.full_name.strip() return self.username @property def printed_first_name(self): if self.first_name and self.first_name.strip(): return self.first_name.strip() return self.username @property def abbreviated_name(self): if self.first_name and self.last_name: return "%s %s." % (self.first_name, self.last_name[0]) return self.username @property def password(self): return self._password @password.setter # noqa def password(self, v): self._password = self.__hash_password__(v) @property def published_recipes(self): return filter(lambda r: r.state == "PUBLISHED", self.recipes) @property def drafts(self): return filter( lambda r: r.state == "DRAFT" and r.published_version is None, self.recipes) @property def gravatar(self): return 'https://www.gravatar.com/avatar/%s?d=https://draughtcraft.com/images/glass-square.png' % ( md5(self.email.strip().lower()).hexdigest()) @classmethod def __hash_password__(cls, plain, salt=None): if salt is None: salt = b64encode(urandom(6)) if ':' not in salt: salt = '%s:' % salt salt = salt.split(':')[0] return '%s:%s' % ( salt, b64encode(PBKDF2(plain, salt, iterations=16000).read(32))) @classmethod def validate(cls, username, password): # Lookup the user user = cls.get_by(username=username) if user: salt = user.password.split(':')[0] pbk = cls.__hash_password__(password, salt) # If PBKDF2 matches... match = cls.query.filter( and_(cls.username == username, cls.password == pbk)).first() if match is not None: return match # Otherwise the user might have a sha256 password salt = getattr(getattr(conf, 'session', None), 'password_salt', 'example') sha = sha256(password + salt).hexdigest() # If sha256 matches... match = cls.query.filter( and_(cls.username == username, cls.password == sha)).first() if match is not None: # Overwrite to use PBKDF2 in the future user.password = password return match
class Style(Entity, ShallowCopyMixin): TYPES = [ 'LAGER', 'ALE', 'MEAD', 'CIDER' ] uid = Field(Unicode(32), unique=True) name = Field(Unicode(256), index=True) url = Field(Unicode(256)) # Gravities min_og = Field(Float) max_og = Field(Float) min_fg = Field(Float) max_fg = Field(Float) # IBU min_ibu = Field(Integer) max_ibu = Field(Integer) # SRM min_srm = Field(Integer) max_srm = Field(Integer) # ABV min_abv = Field(Float) max_abv = Field(Float) category = Field(Unicode(64)) category_number = Field(Integer) style_letter = Field(Unicode(1)) type = Field(Enum(*TYPES, native_enum=False)) recipes = OneToMany('Recipe', inverse='style') def defined(self, statistic): if statistic not in ( 'og', 'fg', 'abv', 'srm', 'ibu' ): raise InvalidStatistic('Invalid statistic, %s' % statistic) minimum = getattr(self, 'min_%s' % statistic) maximum = getattr(self, 'max_%s' % statistic) return minimum is not None and maximum is not None def matches(self, recipe, statistic): if statistic not in ( 'og', 'fg', 'abv', 'srm', 'ibu' ): raise InvalidStatistic('Invalid statistic, %s' % statistic) minimum = getattr(self, 'min_%s' % statistic) maximum = getattr(self, 'max_%s' % statistic) if minimum is None or maximum is None: return False actual = getattr(recipe.calculations, statistic) if actual <= maximum and actual >= minimum: return True return False def to_xml(self): from draughtcraft.lib.beerxml import export kw = { 'name': self.name, 'category': self.category, 'category_number': self.category_number, 'style_letter': self.style_letter, 'style_guide': 'BJCP', 'type': self.type.capitalize(), 'og_min': self.min_og, 'og_max': self.max_og, 'fg_min': self.min_fg, 'fg_max': self.max_fg, 'ibu_min': self.min_ibu, 'ibu_max': self.max_ibu, 'color_min': self.min_srm, 'color_max': self.max_srm, 'abv_min': self.min_abv, 'abv_max': self.max_abv } return export.Style(**kw)
class PasswordResetRequest(Entity): code = Field(Unicode(64), primary_key=True) datetime = Field(DateTime) user = ManyToOne('User')
class Permission(Entity): permission_id = Field(Integer, primary_key=True) permission_name = Field(Unicode(16), unique=True) description = Field(Unicode(255)) groups = ManyToMany('Group', inverse='permissions') using_options(tablename='permission')
class Actor(Entity): name = Field(Unicode(60)) movies = ManyToMany('Movie', inverse='actors', tablename='movie_casting') using_options(tablename='actors')
class Director(Entity): name = Field(Unicode(60)) movies = OneToMany('Movie', inverse='director') using_options(tablename='directors')
class Node(BaseEntityMixin, Entity): ip_address = Field(Unicode(15)) uuid = Field(Unicode(64), nullable=False, unique=True, primary_key=True) port = Field(Integer) supernode = Field(Boolean, default=False) primary_supernode = ManyToOne('Node') rtt = Field(Integer) upstream = Field(Integer) # KB/s downstream = Field(Integer) # KB/s using_table_options(UniqueConstraint('ip_address', 'port')) API_FIELDS = [ 'ip_address', 'uuid', 'port', 'supernode', ] RTT_STEP = 0.2 BANDWIDTH_STEP = 0.2 @classmethod def from_dict(cls, data): node = Node.get_by(uuid=data['uuid']) if not node: node = cls(ip_address=data['ip_address'], uuid=data['uuid'], port=data['port']) if 'supernode' in data: node.supernode = data['supernode'] if 'primary_supernode_uuid' in data: node.primary_supernode = Node.get_by( uuid=data['primary_supernode_uuid']) return node def update_rtt(self): try: sampled_rtt = NodeAPI(self.uri()).ping() except RequestError: log.warning( "Unable to connect to %s for updating RTT -- " "leaving it at %s", self, self.rtt) else: self.rtt = self._weighted_average(self.rtt, self.RTT_STEP, sampled_rtt) return self.rtt def update_downstream(self): byte_count, transfer_time = NodeAPI(self.uri()).downstream_check() self.downstream = self._weighted_average( self.downstream, self.BANDWIDTH_STEP, byte_count / transfer_time / 1000.0) return self.downstream def update_upstream(self, url=None): byte_count, transfer_time = NodeAPI(url or self.uri()).upstream_check() self.upstream = self._weighted_average( self.upstream, self.BANDWIDTH_STEP, byte_count / transfer_time / 1000.0) return self.upstream def _weighted_average(self, estimated, step, sample): if not estimated: return sample return (1 - step) * estimated + step * sample @classmethod def update_supernode_rtt(cls): for supernode in cls.query.filter_by(supernode=True).filter( Node.uuid != Node.me().uuid): try: supernode.update_rtt() except RequestError: supernode.delete() @classmethod def supernodes(cls): return cls.query.filter_by(supernode=True) @classmethod def closest_supernode(cls): closest = cls.supernodes().filter( Node.uuid != Node.me().uuid).order_by('rtt').first() if not Node.me().supernode and not closest: log.warn("No supernodes in the database") return closest @classmethod def me(cls, uuid_override=None, refresh=False): desired_uuid = uuid_override or unicode(uuid.getnode()) node = Node.get_by(uuid=desired_uuid) if not node: node = Node() node.uuid = desired_uuid log.info("Using %s for this node's unique ID", node.uuid) if refresh: try: node.ip_address = RemoteIP().get() except RequestError, e: log.debug("Couldn't connect to the web: %s", e) node.ip_address = '127.0.0.1' log.info("Using %s for this node's IP address", node.ip_address) node.port = settings.PORT log.info("Using %s for this node's API port", node.port) return node