Example #1
0
 class C(self.Entity):
     b = ManyToOne('B', primary_key=True)
Example #2
0
class BatchJob(Entity, type_and_status.StatusMixin):
    """Information the batch job that is planned, running or has run"""
    using_options(tablename='batch_job', order_by=['-id'])
    host = Field(sqlalchemy.types.Unicode(256),
                 required=True,
                 default=hostname)
    type = ManyToOne('BatchJobType',
                     required=True,
                     ondelete='restrict',
                     onupdate='cascade')
    status = type_and_status.Status(batch_job_statusses)
    message = Field(camelot.types.RichText())

    @classmethod
    def create(cls, batch_job_type=None, status='running'):
        """Create a new batch job object in a session of its
        own.  This allows flushing the batch job independent from
        other objects.
        
        :param batch_job_type: an instance of type 
            :class:`camelot.model.batch_job.BatchJobType`
        :param status: the status of the batch job
        :return: a new BatchJob object
        """
        batch_session = BatchSession()
        batch_job = BatchJob(type=batch_job_type)
        batch_job.change_status('running')
        session = orm.object_session(batch_job)
        batch_session_batch_job = batch_session.merge(batch_job)
        if session:
            session.expunge(batch_job)
        batch_session.commit()
        return batch_session_batch_job

    def is_canceled(self):
        """Verifies if this Batch Job is canceled.  Returns :keyword:`True` if 
        it is.  This method is thus suiteable to call inside a running batch job 
        to verifiy if another user has canceled the running job.  Create a
        batch job object through the :meth:`create` method to make sure
        requesting the status does not interfer with the normal session.
        
        :return: :keyword:`True` or :keyword:`False`
        """
        orm.object_session(self).expire(self, ['status'])
        return self.current_status == 'canceled'

    def add_exception_to_message(self,
                                 exc_type=None,
                                 exc_val=None,
                                 exc_tb=None):
        """If an exception occurs in a batch job, this method can be used to add
        the stack trace of an exception to the message.
        
        If no arguments are given, `sys.exc_traceback` is used.
        
        :param exc_type: type of the exception, such as in `sys.exc_type`
        :param exc_val: value of the exception, such as in `sys.exc_value`
        :param exc_tb: a traceback object, such as in `sys.exc_traceback`
        """
        import traceback, cStringIO
        sio = cStringIO.StringIO()
        traceback.print_exception(exc_type or sys.exc_type, exc_val
                                  or sys.exc_value, exc_tb
                                  or sys.exc_traceback, None, sio)
        traceback_print = sio.getvalue()
        sio.close()
        self.add_strings_to_message([unicode(exc_type or sys.exc_type)],
                                    color='red')
        self.add_strings_to_message(traceback_print.split('\n'), color='grey')

    def add_strings_to_message(self, strings, color=None):
        """Add strings to the message of this batch job.
        
        :param strings: a list or generator of strings
        :param color: the html color to be used for the strings (`'red'`, 
        `'green'`, ...), None if the color needs no change. 
        """
        if color:
            strings = [u'<font color="%s">' % color] + strings + [u'</font>']
        session = orm.object_session(self)
        # message might be changed in the orm
        session.commit()
        batch_table = self.__table__
        update = batch_table.update().where(batch_table.c.id == self.id)
        update = update.values(
            message=sql.func.coalesce(batch_table.c.message, '') +
            sql.bindparam('line'))
        for line in strings:
            session.execute(update, params={'line': line + '<br/>'})
        session.commit()

    def __enter__(self):
        self.change_status('running')
        orm.object_session(self).commit()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type != None:
            self.add_exception_to_message(exc_type, exc_val, exc_tb)
            self.change_status('errors')
        elif self.current_status == 'running':
            self.change_status('success')
        orm.object_session(self).commit()
        return True

    class Admin(EntityAdmin):
        verbose_name = _('Batch job')
        list_display = ['host', 'type', 'current_status']
        list_filter = ['current_status', filters.ComboBoxFilter('host')]
        form_display = forms.TabForm([(_('Job'), list_display + ['message']),
                                      (_('History'), ['status'])])
        form_actions = [type_and_status.ChangeStatus('canceled', _('Cancel'))]
Example #3
0
 class B(self.Entity):
     a = ManyToOne('A', primary_key=True)
Example #4
0
 class B(self.Entity):
     name = Field(String(60))
     a = ManyToOne(A)
Example #5
0
 class B(self.Entity):
     name = Field(String(60))
     a = ManyToOne(A, target_column=['name'])
Example #6
0
class Movie(Entity):

    __tablename__ = 'movies'

    title = Column(sqlalchemy.types.Unicode(60), nullable=False)
    short_description = Column(sqlalchemy.types.Unicode(512))
    releasedate = Column(sqlalchemy.types.Date)
    genre = Column(sqlalchemy.types.Unicode(15))
    rating = Column(camelot.types.Rating())
    #
    # All relation types are covered with their own editor
    #
    director = ManyToOne('Person')
    cast = OneToMany('Cast')
    visitor_reports = OneToMany('VisitorReport', cascade='delete')
    tags = ManyToMany('Tag',
                      tablename='tags_movies__movies_tags',
                      local_colname='tags_id',
                      remote_colname='movies_id')
    # end short movie definition
    #
    # Camelot includes custom sqlalchemy types, like Image, which stores an
    # image on disk and keeps the reference to it in the database.
    #
    # begin image definition
    cover = Column(camelot.types.Image(upload_to='covers'))
    # end image definition
    #
    # Or File, which stores a file in the upload_to directory and stores a
    # reference to it in the database
    #
    script = Column(camelot.types.File(upload_to='script'))
    description = Column(camelot.types.RichText)
    #
    # Normal python properties can be used as well, but then the
    # delegate needs be specified in the Admin.field_attributes
    #
    @property
    def visitors_chart(self):
        #
        # Container classes are used to transport chunks of data between
        # the model the gui, in this case a chart
        #
        from camelot.container.chartcontainer import BarContainer
        return BarContainer(range(len(self.visitor_reports)),
                            [vr.visitors for vr in self.visitor_reports])

# begin column_property

    @ColumnProperty
    def total_visitors(self):
        return sql.select([sql.func.sum(VisitorReport.visitors)],
                          VisitorReport.movie_id == self.id)


# end column_property

#
# Each Entity subclass can have a subclass of EntityAdmin as
# its inner class.  The EntityAdmin class defines how the Entity
# class will be displayed in the GUI.  Its behavior can be steered
# by specifying some class attributes
#
# To fully customize the way the entity is visualized, the EntityAdmin
# subclass should overrule some of the EntityAdmin's methods
#

    class Admin(EntityAdmin):
        # the list_display attribute specifies which entity attributes should
        # be visible in the table view
        list_display = [
            'cover',
            'title',
            'releasedate',
            'rating',
        ]
        lines_per_row = 5
        # define filters to be available in the table view
        list_filter = ['genre', ComboBoxFilter('director.full_name')]
        # if the search function needs to look in related object attributes,
        # those should be specified within list_search
        list_search = ['director.full_name']
        # begin list_actions
        #
        # the action buttons that should be available in the list view
        #
        list_actions = [ChangeRatingAction()]
        # end list_actions
        drop_action = DropAction()
        # the form_display attribute specifies which entity attributes should be
        # visible in the form view
        form_display = TabForm([
            ('Movie',
             Form([
                 HBoxForm(
                     [WidgetOnlyForm('cover'), ['title', 'rating',
                                                Stretch()]]),
                 'short_description',
                 'releasedate',
                 'director',
                 'script',
                 'genre',
                 'description',
             ],
                  columns=2)), ('Cast', WidgetOnlyForm('cast')),
            ('Visitors', WidgetOnlyForm('visitors_chart')),
            ('Tags', WidgetOnlyForm('tags'))
        ])

        # begin form_actions
        #
        # create a list of actions available for the user on the form view
        #
        form_actions = [BurnToDisk()]
        # end form_actions
        #
        # additional attributes for a field can be specified in the
        # field_attributes dictionary
        #
        field_attributes = dict(
            cast=dict(create_inline=True),
            genre=dict(choices=genre_choices,
                       editable=lambda o: bool(o.title and len(o.title))),
            releasedate=dict(background_color=lambda o: ColorScheme.orange_1
                             if o.releasedate and o.releasedate < datetime.
                             date(1920, 1, 1) else None),
            visitors_chart=dict(delegate=delegates.ChartDelegate),
            rating=dict(tooltip='''<table>
                                                          <tr><td>1 star</td><td>Not that good</td></tr>
                                                          <tr><td>2 stars</td><td>Almost good</td></tr>
                                                          <tr><td>3 stars</td><td>Good</td></tr>
                                                          <tr><td>4 stars</td><td>Very good</td></tr>
                                                          <tr><td>5 stars</td><td>Awesome !</td></tr>
                                                       </table>'''),
            smiley=dict(delegate=delegates.SmileyDelegate),
            script=dict(remove_original=True))

    def __unicode__(self):
        return self.title or ''
Example #7
0
 class Table3( self.Entity ):
     t3id = Field(Integer, primary_key=True)
     name = Field(String(30))
     tbl1 = ManyToOne(Table1)  
Example #8
0
class BatchJob( Entity, type_and_status.StatusMixin ):
    """A batch job is a long running task that is scheduled by
    the user or started periodically.  The BatchJob objects can be used
    to store information on such running task so the end user can review
    them
    """
    
    __tablename__ = 'batch_job'
    
    host    = schema.Column( sqlalchemy.types.Unicode(256), nullable=False, default=hostname )
    type    = ManyToOne( 'BatchJobType', nullable=False, ondelete = 'restrict', onupdate = 'cascade' )
    status  = type_and_status.Status( batch_job_statusses )
    message = orm.column_property(schema.Column(camelot.types.RichText())
                                  , deferred=True)

    @classmethod
    def create( cls, batch_job_type = None, status = 'running' ):
        """Create a new batch job object in a session of its
        own.  This allows to flus the batch job independent from
        other objects, as well as to begin/end/rollback it's session without
        affecting other objects.
        
        :param batch_job_type: an instance of type 
            :class:`camelot.model.batch_job.BatchJobType`
        :param status: the status of the batch job
        :return: a new BatchJob object
        """
        batch_session = BatchSession()
        with batch_session.begin():
            batch_job = BatchJob(type=batch_job_type)
            batch_job.change_status( 'running' )
            session = orm.object_session( batch_job )
            batch_session_batch_job = batch_session.merge( batch_job )
            if session:
                session.expunge( batch_job )
            return batch_session_batch_job

    def is_canceled( self ):
        """Verifies if this Batch Job is canceled.  Returns :const:`True` if 
        it is.  This method is thus suiteable to call inside a running batch job 
        to verifiy if another user has canceled the running job.  Create a
        batch job object through the :meth:`create` method to make sure
        requesting the status does not interfer with the normal session.
        
        This method executes within it's own transaction, to make sure the state
        of the session is rolled back on failure.
        
        :return: :const:`True` or :const:`False`
        """
        session = orm.object_session( self )
        with session.begin():
            session.expire( self, ['status'] )
            return self.current_status == 'canceled'
        
    def add_exception_to_message( self, 
                                  exc_type = None, 
                                  exc_val = None, 
                                  exc_tb = None ):
        """If an exception occurs in a batch job, this method can be used to add
        the stack trace of an exception to the message.
        
        If no arguments are given, `sys.exc_traceback` is used.
        
        :param exc_type: type of the exception, such as in `sys.exc_type`
        :param exc_val: value of the exception, such as in `sys.exc_value`
        :param exc_tb: a traceback object, such as in `sys.exc_traceback`
        """
        import traceback
        sio = six.StringIO()
        traceback.print_exception( exc_type or sys.exc_info()[0], 
                                   exc_val or sys.exc_info()[1],
                                   exc_tb or sys.exc_info()[2],
                                   None, 
                                   sio )
        traceback_print = sio.getvalue()
        sio.close()
        self.add_strings_to_message([six.text_type(exc_type or sys.exc_info()[0])],
                                    color = 'red' )
        self.add_strings_to_message(traceback_print.split('\n'),
                                    color = 'grey' )
        
    def add_strings_to_message( self, strings, color = None ):
        """Add strings to the message of this batch job.

        This method executes within it's own transaction, to make sure the state
        of the session is rolled back on failure.

        :param strings: a list or generator of strings
        :param color: the html color to be used for the strings (`'red'`, 
        `'green'`, ...), None if the color needs no change. 
        """
        if color:
            strings = [u'<font color="%s">'%color] + strings + [u'</font>']
        session = orm.object_session( self )
        with session.begin():
            # message might be changed in the orm
            session.flush()
            batch_table = self.__table__
            update = batch_table.update().where( batch_table.c.id == self.id )
            update = update.values( message = sql.func.coalesce( batch_table.c.message, '' ) + sql.bindparam('line') )
            for line in strings:
                session.execute( update, params = {'line':line + '<br/>'} )
        
    def __enter__( self ):
        batch_session = orm.object_session( self )
        with batch_session.begin():
            if self.current_status != 'running':
                self.change_status( 'running' )
        return self
    
    def __exit__( self, exc_type, exc_val, exc_tb ):
        new_status = None
        if exc_type != None:
            self.add_exception_to_message( exc_type, exc_val, exc_tb )
            new_status = 'errors'
            LOGGER.info( 'batch job closed with exception', 
                         exc_info = (exc_type, exc_val, exc_tb) )
        batch_session = orm.object_session( self )
        with batch_session.begin():
            if new_status is not None:
                self.change_status(new_status)
            elif self.current_status in (None, 'running'):
                self.change_status('success')
        return True
        
    class Admin(EntityAdmin):
        verbose_name = _('Batch job')
        list_display = ['host', 'type', 'current_status']
        list_filter = ['current_status', list_filter.ComboBoxFilter('host')]
        form_display = forms.TabForm( [ ( _('Job'), list_display + ['message'] ),
                                        ( _('History'), ['status'] ) ] )
        form_actions = [ type_and_status.ChangeStatus( 'canceled',
                                                       _('Cancel') ) ]

        def get_query(self, *args, **kwargs):
            query = EntityAdmin.get_query(self, *args, **kwargs)
            query = query.order_by(self.entity.id.desc())
            query = query.options(orm.subqueryload('status'))
            return query
Example #9
0
 class User(self.Entity):
     name = Field(String(16))
     category = ManyToOne('Category')
     tags = OneToMany('Tag', lazy=False)
     score = ColumnProperty(lambda c: select([func.sum(
         Tag.score)], Tag.user_id == c.id).as_scalar())
Example #10
0
        class Person( self.Entity ):
            name = Field(String(30))

            father = ManyToOne('Person', inverse='children')
            children = OneToMany('Person', inverse='father')
Example #11
0
 class Address( self.Entity ):
     user = ManyToOne('User')
     street = Field(Unicode(255))
     city = Field(Unicode(255))
Example #12
0
class PartyAddress(Entity):
    using_options(tablename='party_address')
    party = ManyToOne(Party,
                      required=True,
                      ondelete='cascade',
                      onupdate='cascade',
                      lazy='subquery')
    address = ManyToOne(Address,
                        required=True,
                        ondelete='cascade',
                        onupdate='cascade',
                        lazy='subquery')
    from_date = Field(Date(),
                      default=datetime.date.today,
                      required=True,
                      index=True)
    thru_date = Field(Date(), default=end_of_times, required=True, index=True)
    comment = Field(Unicode(256))

    def _get_address_field(self, name):
        if self.address:
            return getattr(self.address, name)

    def _set_address_field(self, name, value):
        if not self.address:
            self.address = Address()
        setattr(self.address, name, value)

    @hybrid.hybrid_property
    def street1(self):
        return self._get_address_field(u'street1')

    @street1.setter
    def street1_setter(self, value):
        return self._set_address_field(u'street1', value)

    @street1.expression
    def street1_expression(self):
        return Address.street1

    @hybrid.hybrid_property
    def street2(self):
        return self._get_address_field(u'street2')

    @street2.expression
    def street2_expression(self):
        return Address.street2

    @street2.setter
    def street2_setter(self, value):
        return self._set_address_field(u'street2', value)

    @hybrid.hybrid_property
    def city(self):
        return self._get_address_field(u'city')

    @city.setter
    def city_setter(self, value):
        return self._set_address_field(u'city', value)

    def party_name(self):
        return sql.select([sql.func.coalesce(Party.full_name, '')],
                          whereclause=(Party.id == self.party_id))

    party_name = ColumnProperty(party_name, deferred=True)

    def __unicode__(self):
        return '%s : %s' % (unicode(self.party), unicode(self.address))

    class Admin(EntityAdmin):
        verbose_name = _('Address')
        verbose_name_plural = _('Addresses')
        list_search = [
            'party_name',
            'street1',
            'street2',
        ]
        list_display = ['party_name', 'street1', 'street2', 'city', 'comment']
        form_display = [
            'party', 'street1', 'street2', 'city', 'comment', 'from_date',
            'thru_date'
        ]
        form_size = (700, 200)
        field_attributes = dict(party_name=dict(
            editable=False, name='Party', minimal_column_width=30))

        def get_compounding_objects(self, party_address):
            if party_address.address:
                yield party_address.address