Exemple #1
0
def registerValidation(mapped, exclude=None):
    '''
    Register to mapped class all the validations that can be performed based on the SQL alchemy mapping.
    
    @param mapped: class
        The mapped model class.
    @param exclude: list[string]|tuple(string)
        A list of column names to be excluded from automatic validation.
    @return: Property
        The property id of the model.
    '''
    assert isclass(mapped), 'Invalid class %s' % mapped
    mapper, typeModel = mappingFor(mapped), typeFor(mapped)
    assert isinstance(mapper, Mapper), 'Invalid mapped class %s' % mapped
    assert isinstance(typeModel, TypeModel), 'Invalid model class %s' % mapped
    assert not exclude or isinstance(exclude, (list, tuple)), 'Invalid exclude %s' % exclude
    model = typeModel.container
    assert isinstance(model, Model)

    properties = set(model.properties)
    for cp in mapper.iterate_properties:
        if not isinstance(cp, ColumnProperty): continue

        assert isinstance(cp, ColumnProperty)
        if cp.key:
            prop = cp.key
            try: properties.remove(prop)
            except KeyError: continue

            if not (exclude and prop in exclude):
                propRef = getattr(mapped, prop)
                column = getattr(mapper.c, cp.key, None)
                assert isinstance(column, Column), 'Invalid column %s' % column
                if __debug__:
                    propType = typeFor(propRef)
                    assert isinstance(propType, TypeModelProperty), 'Invalid property %s with type %s' % (prop, propType)

                if column.primary_key and column.autoincrement:
                    if prop != model.propertyId:
                        raise MappingError('The primary key is expected to be %s, but got SQL primary key for %s' % 
                                           (model.propertyId, prop))
                    validateAutoId(propRef)
                elif not column.nullable:
                    validateRequired(propRef)

                if isinstance(column.type, String) and column.type.length:
                    validateMaxLength(propRef, column.type.length)
                if column.unique:
                    validateProperty(propRef, partial(onPropertyUnique, mapped))
                if column.foreign_keys:
                    for fk in column.foreign_keys:
                        assert isinstance(fk, ForeignKey)
                        try: fkcol = fk.column
                        except AttributeError:
                            raise MappingError('Invalid foreign column for %s, maybe you are not using the meta class'
                                               % prop)
                        validateProperty(propRef, partial(onPropertyForeignKey, mapped, fkcol), index=INDEX_PROP_FK)

    for prop in properties:
        if not (exclude and prop in exclude): validateManaged(getattr(mapped, prop))
Exemple #2
0
    def testValidation(self):
        Entity._ally_listeners = {}

        validateAutoId(Entity.Id)
        validateRequired(Entity.Required)
        validateMaxLength(Entity.WithLength, 5)
        validateManaged(Entity.Managed)

        dummyService = DummyServiceEntity()
        proxySrvNonValid = proxyWrapFor(dummyService)
        proxySrv = proxyWrapFor(dummyService)
        bindValidations(proxySrv)
        assert isinstance(proxySrv, IServiceEntity)

        e = Entity()
        self.assertRaisesRegex(InputError, "(Entity.Required='Expected a value')", proxySrv.insert, e)
        self.assertEqual(proxySrvNonValid.insert(e), 'inserted')
        self.assertRaisesRegex(InputError, "(Entity.Id='Expected a value')", proxySrv.update, e)
        self.assertEqual(proxySrvNonValid.update(e), 'updated')

        e.Id = 'custom id'
        self.assertRaisesRegex(InputError, "(Entity.Id='No value expected')", proxySrv.insert, e)
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'Provided a value'
        self.assertTrue(proxySrv.insert(e) == 'inserted')
        e.Id = 'id'
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'required'
        e.WithLength = 'This is a longer text then 5'
        self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.insert, e)
        e.WithLength = 'hello'
        self.assertTrue(proxySrv.insert(e) == 'inserted')
        e.WithLength = 'This is a longer text then 5'
        e.Id = 'id'
        self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.update, e)
        e.WithLength = 'hello'
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'required'
        e.Managed = 'should not have value'
        self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.insert, e)
        e.Id = 'id'
        self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.update, e)

        self.assertRaises(AttributeError, getattr, proxySrv, '_hidden')
Exemple #3
0
    def testValidation(self):
        Entity._ally_listeners = {}

        validateAutoId(Entity.Id)
        validateRequired(Entity.Required)
        validateMaxLength(Entity.WithLength, 5)
        validateManaged(Entity.Managed)

        dummyService = DummyServiceEntity()
        proxySrvNonValid = proxyWrapFor(dummyService)
        proxySrv = proxyWrapFor(dummyService)
        bindValidations(proxySrv)
        assert isinstance(proxySrv, IServiceEntity)

        e = Entity()
        self.assertRaisesRegex(InputError, "(Entity.Required='Expected a value')", proxySrv.insert, e)
        self.assertEqual(proxySrvNonValid.insert(e), 'inserted')
        self.assertRaisesRegex(InputError, "(Entity.Id='Expected a value')", proxySrv.update, e)
        self.assertEqual(proxySrvNonValid.update(e), 'updated')

        e.Id = 'custom id'
        self.assertRaisesRegex(InputError, "(Entity.Id='No value expected')", proxySrv.insert, e)
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'Provided a value'
        self.assertTrue(proxySrv.insert(e) == 'inserted')
        e.Id = 'id'
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'required'
        e.WithLength = 'This is a longer text then 5'
        self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.insert, e)
        e.WithLength = 'hello'
        self.assertTrue(proxySrv.insert(e) == 'inserted')
        e.WithLength = 'This is a longer text then 5'
        e.Id = 'id'
        self.assertRaisesRegex(InputError, "(Entity.WithLength='Maximum length allowed is 5)", proxySrv.update, e)
        e.WithLength = 'hello'
        self.assertTrue(proxySrv.update(e) == 'updated')

        e = Entity()
        e.Required = 'required'
        e.Managed = 'should not have value'
        self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.insert, e)
        e.Id = 'id'
        self.assertRaisesRegex(InputError, "(Entity.Managed='No value expected')", proxySrv.update, e)

        self.assertRaises(AttributeError, getattr, proxySrv, '_hidden')
Exemple #4
0
        return self.author.Name

    # Non REST model attributes --------------------------------------
    typeId = Column('fk_type_id', ForeignKey(PostTypeMapped.id, ondelete='RESTRICT'), nullable=False)
    type = relationship(PostTypeMapped, uselist=False, lazy='joined')
    author = relationship(CollaboratorMapped, uselist=False, lazy='joined')
    creator = relationship(UserMapped, uselist=False, lazy='joined')

    # Expression for hybrid ------------------------------------
    @classmethod
    @IsModified.expression
    def _IsModified(cls):
        return case([(cls.UpdatedOn != None, True)], else_=False)
    @classmethod
    @IsPublished.expression
    def _IsPublished(cls):
        return case([(cls.PublishedOn != None, True)], else_=False)
    @classmethod
    @AuthorName.expression
    def _AuthorName(cls):
        return case([(cls.Author == None, UserMapped.Name)], else_=CollaboratorMapped.Name)

validateRequired(PostMapped.Type)
validateManaged(PostMapped.Type, key=EVENT_PROP_UPDATE)
validateManaged(PostMapped.Author, key=EVENT_PROP_UPDATE)

validateManaged(PostMapped.CreatedOn)
validateManaged(PostMapped.PublishedOn)
validateManaged(PostMapped.UpdatedOn)
validateManaged(PostMapped.DeletedOn)
Exemple #5
0
from livedesk.meta.blog_type import BlogTypeMapped

# --------------------------------------------------------------------

@validate(exclude=('CreatedOn',))
class BlogMapped(Base, Blog):
    '''
    Provides the mapping for Blog.
    '''
    __tablename__ = 'livedesk_blog'
    __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8')

    Id = Column('id', INTEGER(unsigned=True), primary_key=True)
    Type = Column('fk_blog_type', ForeignKey(BlogTypeMapped.Id), nullable=False)
    Language = Column('fk_language_id', ForeignKey(LanguageEntity.Id), nullable=False)
    Creator = Column('fk_creator_id', ForeignKey(UserMapped.Id), nullable=False)
    Title = Column('title', String(255), nullable=False)
    Description = Column('description', Text)
    OutputLink = Column('output_link', Text)
    CreatedOn = Column('created_on', DateTime, nullable=False)
    LiveOn = Column('live_on', DateTime)
    ClosedOn = Column('closed_on', DateTime)

validateManaged(BlogMapped.CreatedOn)

# --------------------------------------------------------------------

from livedesk.meta.blog_post import BlogPostMapped
BlogMapped.UpdatedOn = column_property(select([func.max(BlogPostMapped.UpdatedOn)]).
                                       where(BlogPostMapped.Blog == BlogMapped.Id))
Exemple #6
0
'''

from ..api.user import User
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import String, DateTime
from superdesk.person.meta.person import PersonMapped
from ally.support.sqlalchemy.mapper import validate
from ally.container.binder_op import validateManaged, validateRequired

# --------------------------------------------------------------------

@validate(exclude=('Password', 'CreatedOn', 'DeletedOn'))
class UserMapped(PersonMapped, User):
    '''
    Provides the mapping for User entity.
    '''
    __tablename__ = 'user'
    __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8')

    Name = Column('name', String(20), nullable=False)
    CreatedOn = Column('created_on', DateTime, nullable=False)
    DeletedOn = Column('deleted_on', DateTime)
    # Non REST model attribute --------------------------------------
    userId = Column('fk_person_id', ForeignKey(PersonMapped.Id, ondelete='CASCADE'), primary_key=True)
    password = Column('password', String(255), nullable=False)
    # Never map over the inherited id

validateRequired(UserMapped.Password)
validateManaged(UserMapped.CreatedOn)
validateManaged(UserMapped.DeletedOn)
    BlogType = declared_attr(lambda cls: Column(
        'fk_blog_type_id', ForeignKey(BlogTypeMapped.Id), nullable=False))
    Name = declared_attr(
        lambda cls: Column('name', String(190), nullable=False, unique=True))
    Order = declared_attr(lambda cls: Column('ordering', REAL))
    # Non REST model attribute --------------------------------------
    blogTypePostId = declared_attr(
        lambda cls: Column('fk_post_id',
                           ForeignKey(PostMapped.Id, ondelete='CASCADE'),
                           primary_key=True))
    # Never map over the inherited id


class BlogTypePostEntry(Base, BlogTypePostDefinition):
    '''
    Provides the mapping for BlogPost table where it keeps the connection between the post and the blog.
    '''


@validate(exclude=('Order', ))
class BlogTypePostMapped(BlogTypePostDefinition, PostMapped, BlogTypePost):
    '''
    Provides the mapping for BlogPost in the form of extending the Post.
    '''
    __table_args__ = dict(BlogTypePostDefinition.__table_args__,
                          extend_existing=True)


validateManaged(BlogTypePostMapped.Order)
Exemple #8
0
    LiveOn = Column('live_on', DateTime)
    ClosedOn = Column('closed_on', DateTime)

    @hybrid_property
    def IsLive(self):
        return self.LiveOn is not None and self.ClosedOn is None

    # Expression for hybrid ------------------------------------
    @classmethod
    @IsLive.expression
    def _IsLive(cls):
        return case([((cls.LiveOn != None) & (cls.ClosedOn == None), True)],
                    else_=False)


validateManaged(BlogMapped.CreatedOn)

# --------------------------------------------------------------------

from livedesk.meta.blog_post import BlogPostMapped

BlogMapped.UpdatedOn = column_property(
    select([func.max(BlogPostMapped.UpdatedOn)
            ]).where(BlogPostMapped.Blog == BlogMapped.Id))

# --------------------------------------------------------------------


class BlogSourceDB(Base):
    '''
    Provides the mapping for BlogSource.
Exemple #9
0
from superdesk.person.meta.person import PersonMapped
from sqlalchemy.dialects.mysql.base import INTEGER

# --------------------------------------------------------------------

@validate(exclude=('Password', 'CreatedOn', 'Active', 'Type'))
class UserMapped(PersonMapped, User):
    '''
    Provides the mapping for User entity.
    '''
    __tablename__ = 'user'
    __table_args__ = (UniqueConstraint('name', name='uix_user_name'),
                      dict(mysql_engine='InnoDB', mysql_charset='utf8'))

    Name = Column('name', String(150), nullable=False, unique=True)
    Uuid = Column('uuid', String(32), unique=True, nullable=True)
    Cid = Column('cid', INTEGER(unsigned=True), nullable=True, default=0)
    CreatedOn = Column('created_on', DateTime, nullable=False)
    Active = Column('active', Boolean, nullable=False, default=True)
    Type = association_proxy('type', 'Key')
    # Non REST model attribute --------------------------------------
    userId = Column('fk_person_id', ForeignKey(PersonMapped.Id, ondelete='CASCADE'), primary_key=True)
    password = Column('password', String(255), nullable=False)
    # Never map over the inherited id
    typeId = Column('fk_type_id', ForeignKey(UserTypeMapped.id, ondelete='RESTRICT'), nullable=False)
    type = relationship(UserTypeMapped, uselist=False, lazy='joined')

validateRequired(UserMapped.Password)
validateManaged(UserMapped.CreatedOn)
validateManaged(UserMapped.Active)
Exemple #10
0
class MetaInfoMapped(Base, MetaInfo):

    def validate(self):
        o sa fie un singur media data service archive care foloseste file systzem:
            salveaza fisierul local in pathul cunoscut
            notifica data handlerurile de noul fisier dand si locatia fisireului in file system.
        o sa fie un singur thumbnail service unde faci upload la thumbnail si care se ocupa de resizing foloseste file systzem
        fara sa foloseasca un CDM si face resizing on demand bazat pe o lista cunoscuta de resizing. o sa fie un URL
        atasat de meta data ptr thumb si cat se cere thumbu o sa se specifice thumb size, eventual de facut o entitate
        pentru thumb care sa aiba un id unique si sa poate fi folosti ca referinta la upload, asta pentru cazul in care
        folosesti acelasi thumb pentru mai multe meta data si inclusiv daca se face sql pentru selectarea unui thumb
        tot merita, deci ideea ii sa folosim REST aproach si sa nu complicam lucrurile cu tot felu de procese.

        Ideea i ca daca folosim CDM ii complicat procseul si atunci ii mai usor ca serviciul the data si thumbs sa lucreze
        direct cu file system, ele putand fi distrbuite daca ii cazul sau facute implementari pe baza la altfel de
        structuri.



        de facut validatra astfel incat sa nu poti insera meta info pentru alte meta data types, tre facut cu
        declarative base
        de continuat cu image media si dupa aia de facut resize

table = Table('archive_image_info', meta,
              Column('fk_meta_info_id', ForeignKey(MetaInfo.Id), primary_key=True, key='Id'),
              Column('caption', String(255), nullable=False, key='Caption'),
              mysql_engine='InnoDB', mysql_charset='utf8')

ImageInfo = mapperModel(ImageInfo, table, exclude=['MetaData'], inherits=MetaInfo)
validateManaged(ImageInfo.MetaData)
Exemple #11
0
from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint
from sqlalchemy.types import String, DateTime
from superdesk.person.meta.person import PersonMapped

# --------------------------------------------------------------------


@validate(exclude=('Password', 'CreatedOn', 'DeletedOn'))
class UserMapped(PersonMapped, User):
    '''
    Provides the mapping for User entity.
    '''
    __tablename__ = 'user'
    __table_args__ = (UniqueConstraint('name', name='uix_user_name'),
                      dict(mysql_engine='InnoDB', mysql_charset='utf8'))

    Name = Column('name', String(150), nullable=False, unique=True)
    CreatedOn = Column('created_on', DateTime, nullable=False)
    DeletedOn = Column('deleted_on', DateTime)
    # Non REST model attribute --------------------------------------
    userId = Column('fk_person_id',
                    ForeignKey(PersonMapped.Id, ondelete='CASCADE'),
                    primary_key=True)
    password = Column('password', String(255), nullable=False)
    # Never map over the inherited id


validateRequired(UserMapped.Password)
validateManaged(UserMapped.CreatedOn)
validateManaged(UserMapped.DeletedOn)
Exemple #12
0
    # Non REST model attributes --------------------------------------
    typeId = Column('fk_type_id',
                    ForeignKey(PostTypeMapped.id, ondelete='RESTRICT'),
                    nullable=False)
    type = relationship(PostTypeMapped, uselist=False, lazy='joined')
    author = relationship(CollaboratorMapped, uselist=False, lazy='joined')
    creator = relationship(UserMapped, uselist=False, lazy='joined')

    # Expression for hybrid ------------------------------------
    @classmethod
    @IsModified.expression
    def _IsModified(cls):
        return case([(cls.UpdatedOn != None, True)], else_=False)

    @classmethod
    @AuthorName.expression
    def _AuthorName(cls):
        return case([(cls.Author == None, UserMapped.Name)],
                    else_=CollaboratorMapped.Name)


validateRequired(PostMapped.Type)
validateManaged(PostMapped.Type, key=EVENT_PROP_UPDATE)
validateManaged(PostMapped.Author, key=EVENT_PROP_UPDATE)

validateManaged(PostMapped.CreatedOn)
validateManaged(PostMapped.PublishedOn)
validateManaged(PostMapped.UpdatedOn)
validateManaged(PostMapped.DeletedOn)
Exemple #13
0
# --------------------------------------------------------------------

class BlogTypePostDefinition:
    '''
    Provides the mapping for BlogCollaborator definition.
    '''
    __tablename__ = 'livedesk_blog_type_post'
    __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8')

    BlogType = declared_attr(lambda cls: Column('fk_blog_type_id', ForeignKey(BlogTypeMapped.Id), nullable=False))
    Name = declared_attr(lambda cls: Column('name', String(190), nullable=False, unique=True))
    Order = declared_attr(lambda cls: Column('ordering', REAL))
    # Non REST model attribute --------------------------------------
    blogTypePostId = declared_attr(lambda cls: Column('fk_post_id', ForeignKey(PostMapped.Id, ondelete='CASCADE'), primary_key=True))
    # Never map over the inherited id

class BlogTypePostEntry(Base, BlogTypePostDefinition):
    '''
    Provides the mapping for BlogPost table where it keeps the connection between the post and the blog.
    '''

@validate(exclude=('Order',))
class BlogTypePostMapped(BlogTypePostDefinition, PostMapped, BlogTypePost):
    '''
    Provides the mapping for BlogPost in the form of extending the Post.
    '''
    __table_args__ = dict(BlogTypePostDefinition.__table_args__, extend_existing=True)

validateManaged(BlogTypePostMapped.Order)
Exemple #14
0
def registerValidation(mapped, exclude=None):
    '''
    Register to mapped class all the validations that can be performed based on the SQL alchemy mapping.
    
    @param mapped: class
        The mapped model class.
    @param exclude: list[string]|tuple(string)
        A list of column names to be excluded from automatic validation.
    @return: Property
        The property id of the model.
    '''
    assert isclass(mapped), 'Invalid class %s' % mapped
    mapper, typeModel = mappingFor(mapped), typeFor(mapped)
    assert isinstance(mapper, Mapper), 'Invalid mapped class %s' % mapped
    assert isinstance(typeModel, TypeModel), 'Invalid model class %s' % mapped
    assert not exclude or isinstance(
        exclude, (list, tuple)), 'Invalid exclude %s' % exclude
    model = typeModel.container
    assert isinstance(model, Model)

    properties = set(model.properties)
    for cp in mapper.iterate_properties:
        if not isinstance(cp, ColumnProperty): continue

        assert isinstance(cp, ColumnProperty)
        if cp.key:
            prop = cp.key
            try:
                properties.remove(prop)
            except KeyError:
                continue

            if not (exclude and prop in exclude):
                propRef = getattr(mapped, prop)
                column = getattr(mapper.c, cp.key, None)
                assert isinstance(column, Column), 'Invalid column %s' % column
                if __debug__:
                    propType = typeFor(propRef)
                    assert isinstance(
                        propType, TypeModelProperty
                    ), 'Invalid property %s with type %s' % (prop, propType)

                if column.primary_key and column.autoincrement:
                    if prop != model.propertyId:
                        raise MappingError(
                            'The primary key is expected to be %s, but got SQL primary key for %s'
                            % (model.propertyId, prop))
                    validateAutoId(propRef)
                elif not column.nullable:
                    validateRequired(propRef)

                if isinstance(column.type, String) and column.type.length:
                    validateMaxLength(propRef, column.type.length)
                if column.unique:
                    validateProperty(propRef, partial(onPropertyUnique,
                                                      mapped))
                if column.foreign_keys:
                    for fk in column.foreign_keys:
                        assert isinstance(fk, ForeignKey)
                        try:
                            fkcol = fk.column
                        except AttributeError:
                            raise MappingError(
                                'Invalid foreign column for %s, maybe you are not using the meta class'
                                % prop)
                        validateProperty(propRef,
                                         partial(onPropertyForeignKey, mapped,
                                                 fkcol),
                                         index=INDEX_PROP_FK)

    for prop in properties:
        if not (exclude and prop in exclude):
            validateManaged(getattr(mapped, prop))
Exemple #15
0
from ally.container.binder_op import validateManaged
from sqlalchemy.ext.hybrid import hybrid_property
from superdesk.user.meta.user import UserMapped
from superdesk.person.meta.person import PersonMapped

# --------------------------------------------------------------------

@validate(exclude=['Item', 'PublishedOn'])
class ArticleMapped(Base, Article):
    '''
    Provides the mapping for Article.
    '''
    __tablename__ = 'article'
    __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8')

    Id = Column('id', INTEGER(unsigned=True), primary_key=True)
    Item = Column('fk_item_id', ForeignKey(ItemMapped.GUId, ondelete='RESTRICT'), nullable=False)
    Creator = Column('fk_creator_id', ForeignKey(UserMapped.Id, ondelete='RESTRICT'), nullable=False)
    Author = Column('fk_author_id', ForeignKey(PersonMapped.Id, ondelete='RESTRICT'), nullable=False)
    Content = Column('content', TEXT, nullable=False)
    PublishedOn = Column('published_on', DateTime)
    @hybrid_property
    def IsPublished(self):
        return self.PublishedOn is not None

    # Expression for hybrid ------------------------------------
    IsPublished.expression(lambda cls: cls.PublishedOn != None)

validateManaged(ArticleMapped.Item)
validateManaged(ArticleMapped.PublishedOn)
Exemple #16
0
class ArticleMapped(Base, Article):
    '''
    Provides the mapping for Article.
    '''
    __tablename__ = 'article'
    __table_args__ = dict(mysql_engine='InnoDB', mysql_charset='utf8')

    Id = Column('id', INTEGER(unsigned=True), primary_key=True)
    Item = Column('fk_item_id',
                  ForeignKey(ItemMapped.GUId, ondelete='RESTRICT'),
                  nullable=False)
    Creator = Column('fk_creator_id',
                     ForeignKey(UserMapped.Id, ondelete='RESTRICT'),
                     nullable=False)
    Author = Column('fk_author_id',
                    ForeignKey(PersonMapped.Id, ondelete='RESTRICT'),
                    nullable=False)
    Content = Column('content', TEXT, nullable=False)
    PublishedOn = Column('published_on', DateTime)

    @hybrid_property
    def IsPublished(self):
        return self.PublishedOn is not None

    # Expression for hybrid ------------------------------------
    IsPublished.expression(lambda cls: cls.PublishedOn != None)


validateManaged(ArticleMapped.Item)
validateManaged(ArticleMapped.PublishedOn)