Пример #1
0
class PageData(Entity):
    pagename = Field(Unicode(40), primary_key=True)
    data = Field(Unicode(10 * 1024))
    creator = Field(Unicode(40))
Пример #2
0
class Artigo(Entity):
    using_options(tablename='artigo_lei')
    lei = ManyToOne('Lei')
    numero = Field(Unicode(32))
    paragrafos = OneToMany('Paragrafo')
    incisos = OneToMany('Inciso')
Пример #3
0
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)
Пример #4
0
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()
Пример #5
0
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
        }
Пример #6
0
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]
Пример #7
0
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
Пример #8
0
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)
Пример #9
0
class PasswordResetRequest(Entity):
    code = Field(Unicode(64), primary_key=True)
    datetime = Field(DateTime)

    user = ManyToOne('User')
Пример #10
0
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')
Пример #11
0
class Actor(Entity):
    name = Field(Unicode(60))
    movies = ManyToMany('Movie', inverse='actors', tablename='movie_casting')
    using_options(tablename='actors')
Пример #12
0
class Director(Entity):
    name = Field(Unicode(60))
    movies = OneToMany('Movie', inverse='director')
    using_options(tablename='directors')
Пример #13
0
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