Upgrade to Pro — share decks privately, control downloads, hide ads and more …

I never Meta model I didn't like

I never Meta model I didn't like

For the 2014 Google Summer of Code, Daniel Pyrathon formalized one of the pieces of Django that was most used unstable APIs in Django: the Meta interface. This code is now part Django 1.8. In this talk, Russell Keith-Magee will take you for a walk around the facilities provided by the Meta interface, and show how you can use those interfaces to make any data store quack like a Django-shaped duck.

B91373320dbc3bc52fcd870d3b21748f?s=128

Russell Keith-Magee

September 07, 2015
Tweet

Transcript

  1. I never Meta model I didn't like Dr Russell Keith-Magee

    DjangoCon US 2015
  2. I never Meta model I didn't like Dr Russell Keith-Magee

    DjangoCon US 2015
  3. None
  4. None
  5. None
  6. None
  7. What is meta programming?

  8. An example: Python

  9. Metaprogramming Python class class Point Point: def def __init__ __init__(self,

    x, y): self.x = x self.y = y class class Point3D Point3D(Point): def def __init__ __init__(self, x, y, z): super().__init__(x, y) self.z = z >>> p1 = Point(3, 4) >>> p2 = Point3D(5, 6, 7)
  10. Metaprogramming Python def def output_point output_point(p): print("x = %s" %

    p.x) print("y = %s" % p.y) def def output_point3D output_point3D(p): output_point(p) print("z = %s" % p.z)
  11. Metaprogramming Python >>> output_point(p1) x = 3 y = 4

    >>> output_point3D(p2) x = 5 y = 6 z = 7
  12. Metaprogramming Python def def output_point2 output_point2(p): print("x = %s" %

    p.x) print("y = %s" % p.y) if if isinstance(p, Point3d): print("z = %s" % p.z)
  13. Metaprogramming Python >>> output_point2(p1) x = 3 y = 4

    >>> output_point2(p2) x = 5 y = 6 z = 7
  14. Metaprogramming Python def def output_point3 output_point3(p): print("x = %s" %

    p.x) print("y = %s" % p.y) if if hasattr(p, 'z'): print("z = %s" % p.z)
  15. Metaprogramming Python class class Point4D Point4D(Point3D): def def __init__ __init__(self,

    x, y, z, t): super().__init__(x, y, z) self.t = t
  16. Metaprogramming Python def def output_point4 output_point4(p): for for attr in

    in sorted(p.__dict__): print('%s = %s' % ( attr, getattr(p, attr) ))
  17. Metaprogramming Python def def output_point4 output_point4(p): for for attr in

    in sorted(p.__dict__): print('%s = %s' % ( attr, getattr(p, attr) ))
  18. Metaprogramming Python

  19. Metaprogramming Django

  20. Modelforms

  21. django.contrib.admin

  22. django.contrib.admin

  23. django.contrib.admin

  24. History of Django's Meta

  25. The Old Meta API meta = MyObject._meta meta.get_field(name) meta.get_field_by_name(name) meta.get_fields_with_model()

    meta.get_concrete_fields_with_model() meta.get_m2m_with_model() meta.get_all_related_objects() meta.get_all_related_objects_with_model() meta.get_all_related_many_to_many_objects() meta.get_all_related_m2m_objects_with_model() meta.get_all_field_names()
  26. Unofficially stable

  27. Daniel Pyrathon

  28. The New Meta API

  29. The New Meta API meta = MyObject._meta meta.get_field(field_name) meta.get_fields( include_parents=True

    True, include_hidden=False False )
  30. The New Meta API field.is_relation field.one_to_one field.many_to_one field.one_to_many field.many_to_many field.related_model

    field.auto_created field.concrete
  31. The New Meta API

  32. The New Meta API

  33. So why bother?

  34. High level frameworks and tools

  35. A practical example

  36. A practical example

  37. A practical example

  38. Merging records class class Customer Customer(Model): name = CharField(max_length=100) ...

    class class WorkOrder WorkOrder(Model): customer = ForeignKey(Customer) order_id = CharField(max_length=100) ...
  39. Merging records class class Invoice Invoice(Model): customer = ForeignKey(Customer) invoice_number

    = CharField(max_length=100) ... class class Payment Payment(Model): customer = ForeignKey(Customer) received = DateField() ...
  40. Merging records def def merge_customer merge_customer(orig, dup): dup.workorder_set.update(customer=orig) dup.invoice_set.update(customer=orig) dup.payment_set.update(customer=orig)

    dup.delete()
  41. Time passes...

  42. Adding quotes class class Quote Quote(Model): customer = ForeignKey(Customer) quote_number

    = CharField(max_length=100) ...
  43. Adding quotes def def merge_customer merge_customer(orig, dup): dup.workorder_set.update(customer=orig) dup.invoice_set.update(customer=orig) dup.payment_set.update(customer=orig)

    dup.delete()
  44. Adding quotes def def merge_customer merge_customer(orig, dup): dup.workorder_set.update(customer=orig) dup.invoice_set.update(customer=orig) dup.payment_set.update(customer=orig)

    dup.quote_set.update(customer=orig) dup.delete()
  45. Metaprograming merge def def merge_customer merge_customer(orig, dup): for for f

    in in orig._meta.get_fields(): if if f.one_to_many and and f.auto_created: getattr( dup, f.get_accessor_name() ).update(customer=orig) dup.delete()
  46. Metaprograming merge def def merge_anything merge_anything(orig, dup): for for f

    in in orig._meta.get_fields(): if if f.one_to_many and and f.auto_created: getattr( dup, f.get_accessor_name() ).update(**{ f.field.name: orig }) dup.delete()
  47. Killer app

  48. Wrapping non-relational data stores

  49. What does "How do I use X with Django?" actually

    mean?
  50. What does "How do I use X with Django?" actually

    mean?
  51. Can I get a Django form for my model? and

    Can I query and edit my data in Django's Admin?
  52. Can I get a Django form for my model?

  53. Can I query and edit my MongoDB data in Django's

    Admin?
  54. Can I query and edit my MongoDB data in Django's

    Admin?
  55. Dream the impossible dream

  56. None of this needs to be in core

  57. django-mailer https://github.com/PirosB3/django-mailer

  58. django-mailer https://github.com/PirosB3/django-mailer

  59. Managers class class GmailManager GmailManager(object): def def __init__ __init__(self, model):

    storage = Storage(settings.CREDENTIALS_PATH) self.creds = storage.get() self.model = model ...
  60. Managers class class ThreadManager ThreadManager(GmailManager): def def get_queryset get_queryset(self): ...

    return return ThreadQuerySet(self.creds, ...) class class MessageManager MessageManager(GmailManager): def def get_queryset get_queryset(self): ... return return MessageQuerySet(self.creds, ...)
  61. Queries and QuerySets class class GmailQuery GmailQuery(object): ... class class

    GmailQuerySet GmailQuerySet(list): def def __init__ __init__(self, *args, **kwargs): self.model = kwargs.pop('model') self.creds = kwargs.pop('creds') super().__init__(*args, **kwargs) self.query = GmailQuery()
  62. Queries and QuerySets class class ThreadQuerySet ThreadQuerySet(GmailQuerySet): def def get

    get(self, *args, **kwargs): thread_id = kwargs['id'] thread = get_thread_by_id( self.creds, thread_id ) ... return return thread
  63. Queries and QuerySets class class MessageQuerySet MessageQuerySet(GmailQuerySet): def def get

    get(self, *args, **kwargs): ... def def filter filter(self, *args, **kwargs): ... return return MessageQuerySet( model=self.model, credentials=self.credentials, selected_thread=selected_thread ) def def count count(self, *args, **kwargs): ... ...
  64. Meta class class GmailOptions GmailOptions(Options): ... def def _get_fields _get_fields(self,

    reverse=True, forward=True): ... def def get_field get_field(self, field_name): ... class class ThreadOptions ThreadOptions(GmailOptions): ... class class MessageOptions MessageOptions(GmailOptions): ...
  65. Models class class GmailModel GmailModel(object): _deferred = False False _state

    = ModelState() ... class class constructor constructor(type): def def __new__ __new__(cls, name, bases, attrs): dm = attrs.pop('_default_manager') klass = super().__new__( cls, name, bases, attrs) klass._default_manager = dm(klass) return return klass
  66. Models class class Message Message(GmailModel): __metaclass__ = constructor _default_manager =

    MessageManager _meta = MessageOptions() class class Thread Thread(GmailModel): __metaclass__ = constructor _default_manager = ThreadManager _meta = ThreadOptions()
  67. Admin class class MessageInline MessageInline(admin.TabularInline): model = Message class class

    ThreadAdmin ThreadAdmin(admin.ModelAdmin): inlines = [MessageInline] fields = ('number_of_messages',) list_display = ('id',) ordering = ('id',) admin.site.register(Thread, ThreadAdmin)
  68. Admin class class MessageAdmin MessageAdmin(admin.ModelAdmin): fields = ('sender', 'receiver', 'body',

    'snippet') list_display = ('id',) ordering = ('id',) admin.site.register(Message, MessageAdmin)
  69. (... and breathe ....)

  70. Call to action

  71. Why not meta duck at the sprints?

  72. MongoDB Google App Engine Datastore Cassandra Riak CouchDB (or any

    other non-relational data store)
  73. LDAP Local mailbox directory File system AWS S3/EC2 resources

  74. SQLAlchemy

  75. Caveat emptor

  76. None
  77. Thank you! russell@keith-magee.com @freakboy3742