models.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. from datetime import datetime
  2. import sqlalchemy as sa
  3. from sqlalchemy.util.langhelpers import symbol
  4. class Timestamp(object):
  5. """Adds `created` and `updated` columns to a derived declarative model.
  6. The `created` column is handled through a default and the `updated`
  7. column is handled through a `before_update` event that propagates
  8. for all derived declarative models.
  9. ::
  10. import sqlalchemy as sa
  11. from sqlalchemy_utils import Timestamp
  12. class SomeModel(Base, Timestamp):
  13. __tablename__ = 'somemodel'
  14. id = sa.Column(sa.Integer, primary_key=True)
  15. """
  16. created = sa.Column(sa.DateTime, default=datetime.utcnow, nullable=False)
  17. updated = sa.Column(sa.DateTime, default=datetime.utcnow, nullable=False)
  18. @sa.event.listens_for(Timestamp, 'before_update', propagate=True)
  19. def timestamp_before_update(mapper, connection, target):
  20. # When a model with a timestamp is updated; force update the updated
  21. # timestamp.
  22. target.updated = datetime.utcnow()
  23. NO_VALUE = symbol('NO_VALUE')
  24. NOT_LOADED_REPR = '<not loaded>'
  25. def _generic_repr_method(self, fields):
  26. state = sa.inspect(self)
  27. field_reprs = []
  28. if not fields:
  29. fields = state.mapper.columns.keys()
  30. for key in fields:
  31. value = state.attrs[key].loaded_value
  32. if value == NO_VALUE:
  33. value = NOT_LOADED_REPR
  34. else:
  35. value = repr(value)
  36. field_reprs.append('='.join((key, value)))
  37. return '%s(%s)' % (self.__class__.__name__, ', '.join(field_reprs))
  38. def generic_repr(*fields):
  39. """Adds generic ``__repr__()`` method to a declarative SQLAlchemy model.
  40. In case if some fields are not loaded from a database, it doesn't
  41. force their loading and instead repesents them as ``<not loaded>``.
  42. In addition, user can provide field names as arguments to the decorator
  43. to specify what fields should present in the string representation
  44. and in what order.
  45. Example::
  46. import sqlalchemy as sa
  47. from sqlalchemy_utils import generic_repr
  48. @generic_repr
  49. class MyModel(Base):
  50. __tablename__ = 'mymodel'
  51. id = sa.Column(sa.Integer, primary_key=True)
  52. name = sa.Column(sa.String)
  53. category = sa.Column(sa.String)
  54. session.add(MyModel(name='Foo', category='Bar'))
  55. session.commit()
  56. foo = session.query(MyModel).options(sa.orm.defer('category')).one(s)
  57. assert repr(foo) == 'MyModel(id=1, name='Foo', category=<not loaded>)'
  58. """
  59. if len(fields) == 1 and callable(fields[0]):
  60. target = fields[0]
  61. target.__repr__ = lambda self: _generic_repr_method(self, fields=None)
  62. return target
  63. else:
  64. def decorator(cls):
  65. cls.__repr__ = lambda self: _generic_repr_method(
  66. self,
  67. fields=fields
  68. )
  69. return cls
  70. return decorator