class C(self.Entity): b = ManyToOne('B', primary_key=True)
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'))]
class B(self.Entity): a = ManyToOne('A', primary_key=True)
class B(self.Entity): name = Field(String(60)) a = ManyToOne(A)
class B(self.Entity): name = Field(String(60)) a = ManyToOne(A, target_column=['name'])
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 ''
class Table3( self.Entity ): t3id = Field(Integer, primary_key=True) name = Field(String(30)) tbl1 = ManyToOne(Table1)
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
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())
class Person( self.Entity ): name = Field(String(30)) father = ManyToOne('Person', inverse='children') children = OneToMany('Person', inverse='father')
class Address( self.Entity ): user = ManyToOne('User') street = Field(Unicode(255)) city = Field(Unicode(255))
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