Slide 1

Slide 1 text

Stupid ORM Tricks

Slide 2

Slide 2 text

"[Django has] an ORM with a beautiful interface - for very basic things. But once you try to do the extraordinary with it… it's frustrating to say the least." — Adam Gomaa

Slide 3

Slide 3 text

CUSTOM MODEL FIELDS (1)

Slide 4

Slide 4 text

AD HOC MODELS (2)

Slide 5

Slide 5 text

USING VIEWS AS MODELS (3)

Slide 6

Slide 6 text

Warning:

Slide 7

Slide 7 text

When it breaks, you get to keep all the pieces.

Slide 8

Slide 8 text

Hi, Internets! If you were here in person, you'd hear me say that most code in this talk is not production-worthy. But since you're not here, this'll have to do. The code presented here is untested, and uses internal, undocumented interfaces. At best, it's a hack; at worst, it'll bring your site down. It's not the kind of code you can just copy and paste! Please don't reuse code from this talk in production without considering it carefully! Only once you complete understand how it can fail — it will — should you consider using this code in your own projects. Thank you!

Slide 9

Slide 9 text

CUSTOM MODEL FIELDS (1)

Slide 10

Slide 10 text

class Contact(models.Model): name = models.CharField(max_length=200) phone = models.CharField(max_length=30)

Slide 11

Slide 11 text

what am I supposed to type here? what am I supposed to type here?

Slide 12

Slide 12 text

>>> c = Contact.objects.get(...) >>> c.phone u'yo, call me, bro!'

Slide 13

Slide 13 text

PROGRAMMER Y U NO USE PROPER TYPES?

Slide 14

Slide 14 text

from internationalphone.models import \ InternationalPhoneNumberField class Contact(models.Model): name = models.CharField(max_length=200) phone = InternationalPhoneNumberField( default_region='US')

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

>>> Contact.objects.get(name='Jacob').phone PhoneNumber(country_code=1, national_number=2125551212L, extension=None, italian_leading_zero=False, country_code_source=None, preferred_domestic_carrier_code=None)

Slide 17

Slide 17 text

Requirements: phonenumbers iso3166

Slide 18

Slide 18 text

from django.db import models class InternationalPhoneNumberField(models.Field): __metaclass__ = models.SubfieldBase description = "An international phone number." def __init__(self, *args, **kwargs): ... def to_python(self, value): ... def get_prep_value(self, value): ... def formfield(self, **kwargs): ... def get_internal_type(self): ...

Slide 19

Slide 19 text

Required:

Slide 20

Slide 20 text

class InternationalPhoneNumberField(models.Field): ... def to_python(self, value): if not value: return None return utils.parse_number(value, self.default_region)

Slide 21

Slide 21 text

class InternationalPhoneNumberField(models.Field): ... def get_prep_value(self, value): if not isinstance(value, phonenumbers.PhoneNumber): value = self.to_python(value) if value: return utils.format_number(value) else: return None

Slide 22

Slide 22 text

class InternationalPhoneNumberField (models.Field): ... def get_internal_type(self): return 'CharField'

Slide 23

Slide 23 text

Optional:

Slide 24

Slide 24 text

class InternationalPhoneNumberField(models.Field): ... def formfield(self, **kwargs): kwargs.setdefault('form_class', forms.InternationalPhoneNumberField) kwargs.setdefault('default_region', self.default_region) return super(InternationalPhoneNumberField, self).formfield(**kwargs)

Slide 25

Slide 25 text

try: import south.modelsinspector rules = [ # Field classes this rule applies to [InternationalPhoneNumberField], # Positional arguments (none). [], # Keyword arguments. { 'max_length': ["max_length", {"default": 20}], 'default_region': [ "default_region", {"default": utils.guess_default_region()} ], }, ] south.modelsinspector.add_introspection_rules( [rules], ["^internationalphone\.models"] ) except ImportError: pass

Slide 26

Slide 26 text

MORE: django.me/custom-model-fields

Slide 27

Slide 27 text

AUTO-GENERATED MODELS (2)

Slide 28

Slide 28 text

Background:

Slide 29

Slide 29 text

In Python, classes are just syntactic sugar!

Slide 30

Slide 30 text

>>> class Foo(object): ... pass ... >>> f = Foo() >>> type(f) >>> type(Foo) >>> isinstance(Foo, type) True

Slide 31

Slide 31 text

>>> help(type) Help on class type in module __builtin__: class type(object) | type(object) -> the object's type | type(name, bases, dict) -> a new type

Slide 32

Slide 32 text

>>> help(type) Help on class type in module __builtin__: class type(object) | type(object) -> the object's type | type(name, bases, dict) -> a new type

Slide 33

Slide 33 text

class Foo(object): x = 1

Slide 34

Slide 34 text

Foo = type('Foo', (object,), {'x': 1})

Slide 35

Slide 35 text

class Person(models.Model): first_name = models.CharField(...) last_name = models.CharField(...) def __unicode__(self): return self.first_name

Slide 36

Slide 36 text

Person = type("Person", (models.Model), { 'first_name' : models.CharField(...), 'last_name': models.CharField(...), '__unicode__': lambda self: self.first_name, })

Slide 37

Slide 37 text

What does this mean?

Slide 38

Slide 38 text

settings.DATABASES "A dictionary containing the settings for all databases to be used with Django."

Slide 39

Slide 39 text

HARDCODE ALL THE SETTINGS!

Slide 40

Slide 40 text

But… what if…

Slide 41

Slide 41 text

Ad hoc models:

Slide 42

Slide 42 text

>>> from adhocdb.models import DB >>> lightroom = DB(alias='lightroom', ... path='.../Lightroom 3 Catalog.lrcat') >>> lightroom.model_names() ['Adobe_AdditionalMetadata', 'Adobe_imageDevelopBeforeSettings', 'Adobe_imageDevelopSettings', 'Adobe_imageProperties', 'Adobe_images', ... ] >>> Image = lightroom.model('Adobe_images') >>> Image.objects.count() 4775

Slide 43

Slide 43 text

from django import db from django.conf import settings from django.db import models class DB(object): def __init__(self, alias, path): self.alias = alias settings.DATABASES[self.alias] = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': path, } if self.alias in db.connections._connections: del db.connections._connections[self.alias] self._model_cache = None

Slide 44

Slide 44 text

from django import db from django.conf import settings from django.db import models class DB(object): ... def model_names(self): conn = db.connections[self.alias] introspection = conn.introspection return map(str, introspection.table_names())

Slide 45

Slide 45 text

from django import db from django.conf import settings from django.db import models class DB(object): ... def model_for_table(self, table_name, **namespace): introspection = db.connections[self.alias].introspection cursor = db.connections[self.alias].cursor() namespace.update({ '__module__': __name__, 'Meta': type("Meta", (object,), {'db_table': table_name}), '_adhocdb_alias': self.alias, }) for row in introspection.get_table_description(cursor, table_name): rname, rtype, _, _, _, _, nullok = row try: classname = introspection.DATA_TYPES_REVERSE[rtype] fieldclass = getattr(models, classname) except (KeyError, AttributeError): fieldclass = models.TextField namespace[rname] = fieldclass(null=nullok) return type(table_name, (models.Model,), namespace)

Slide 46

Slide 46 text

class AdHocRouter(object): def db_for_read(self, model, **hints): return getattr(model, '_adhocdb_alias', None) db_for_write = db_for_read

Slide 47

Slide 47 text

MORE: gist.github.com/1337664 Pro Django by Marty Alchin code.djangoproject.com/wiki/DynamicModels

Slide 48

Slide 48 text

USING VIEWS AS MODELS (3)

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

#  \d  wiki            Table  "public.wiki"    Column    |    Type      |  Modifiers   -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐  name          |  text        |  not  null  version    |  integer  |  not  null  time          |  bigint    |    author      |  text        |    ipnr          |  text        |    text          |  text        |    comment    |  text        |    readonly  |  integer  |   Indexes:        "wiki_pk"  PRIMARY  KEY,  btree  (name,  version)        "wiki_time_idx"  btree  ("time")

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

FFFFFFF UUUUUU UUUUUU

Slide 53

Slide 53 text

There's gotta be a way…

Slide 54

Slide 54 text

CREATE VIEW "wiki_django_view" AS SELECT "name" || '.' || "version" AS "django_id", * FROM wiki;

Slide 55

Slide 55 text

#  select  django_id,  author  from  wiki_django_view  limit  5;              django_id              |              author                 -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐+-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐  CMSAppsComparison.73    |  [email protected]  DevelopersForHire.694  |  knlg2011  Tutorials.60                    |  anonymous  DevelopersForHire.725  |  jwilk  SummerOfCode2011.25      |  ramiro

Slide 56

Slide 56 text

class Wiki(models.Model): django_id = models.TextField(primary_key=True) ... class Meta: db_table = u'wiki_django_view' managed = False

Slide 57

Slide 57 text

class Wiki(models.Model): django_id = models.TextField(primary_key=True) ... class Meta: db_table = u'wiki_django_view' managed = False

Slide 58

Slide 58 text

MORE: django.me/managed github.com/django/djangoproject.com

Slide 59

Slide 59 text

Would you do this in the real world?

Slide 60

Slide 60 text

"Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design."

Slide 61

Slide 61 text

rapid development clean, pragmatic design

Slide 62

Slide 62 text

rapid development clean, pragmatic design

Slide 63

Slide 63 text

Sometimes "do it right now" has to win over "do it right."

Slide 64

Slide 64 text

(but please be careful!)

Slide 65

Slide 65 text

[email protected] Thank you!