models.py 2.8 KB

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