deep-dive into django #1

115

Upload: avik-das

Post on 15-Apr-2017

74 views

Category:

Documents


2 download

TRANSCRIPT

A model is blueprint.

from django.db import models

class User(models.Model):

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

name = models.CharField(max_length=50)

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

name = models.CharField(max_length=50) phone = models.CharField(max_length=20, null=True, blank=True)

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

name = models.CharField(max_length=50) phone = models.CharField(max_length=20, null=True, blank=True)

STATUS_PAID = 'paid' STATUS_TRIALING = 'trialing'

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

name = models.CharField(max_length=50) phone = models.CharField(max_length=20, null=True, blank=True)

STATUS_PAID = 'paid' STATUS_TRIALING = 'trialing'

STATUS_CHOICES = ( (STATUS_PAID, 'Paid'), (STATUS_TRIALING, 'Trialing'), )

from django.db import models

class User(models.Model): email = models.EmailField( max_length=254, unique=True, help_text='Used as the username')

name = models.CharField(max_length=50) phone = models.CharField(max_length=20, null=True, blank=True)

STATUS_PAID = 'paid' STATUS_TRIALING = 'trialing'

STATUS_CHOICES = ( (STATUS_PAID, 'Paid'), (STATUS_TRIALING, 'Trialing'), ) status = models.CharField( max_length=20, unique=True, choices=STATUS_CHOICES)

$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=>

$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> \d accounts_user

$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> \d accounts_user

Table "public.accounts_user" Column | Type | Modifiers--------+------------------------+---------------------- id | integer | not null default ... email | character varying(254) | not null name | character varying(50) | not null phone | character varying(20) | status | character varying(20) | not null

class Message(models.Model):

class Message(models.Model): sender = models.ForeignKey( User)

$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> \d messaging_message

$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> \d messaging_message

Table "public.messaging_message" Column | Type | Modifiers-----------+---------+---------------------- id | integer | not null default ... sender_id | integer | not null

$ ./manage.py shell_plus

$ ./manage.py shell_plusIn [1]: user = ...

$ ./manage.py shell_plusIn [1]: user = ...

In [2]: user.message_set.all()Out[2]: [<Message: ...>, <Message: ...>]

class Message(models.Model): sender = models.ForeignKey( User)

class Message(models.Model): sender = models.ForeignKey( User, related_name='messages_sent')

$ ./manage.py shell_plusIn [1]: user = ...

In [2]: user.message_set.all()Out[2]: [<Message: ...>, <Message: ...>]

$ ./manage.py shell_plusIn [1]: user = ...

In [2]: user.messages_sent.all()Out[2]: [<Message: ...>, <Message: ...>]

Object-relational mapping

ModelUser

ModelUser

ManagerUser.objects

ModelUser

ManagerUser.objects

QuerysetUser.objects.all()

User.objects.filter(...)

ModelUser

ManagerUser.objects

QuerysetUser.objects.all()

User.objects.filter(...)

eval via DB

ModelUser

ManagerUser.objects

QuerysetUser.objects.all()

User.objects.filter(...)

Python Listlist(User.objects

.all())

eval via DB

User.objects.all() # => queryset

User.objects.all() # => querysetUser.objects.filter(email='...') # => queryset

User.objects.all() # => querysetUser.objects.filter(email='...') # => querysetUser.objects.filter(email='...').count() # DB access

User.objects.filter(email='...')

User.objects.filter(email='...')User.objects.filter(email='...', first_name='...')

User.objects.filter(email='...')User.objects.filter(email='...', first_name='...')User.objects.filter(email='...').filter(first_name='...')

User.objects.filter(email='...')User.objects.filter(email='...', first_name='...')User.objects.filter(email='...').filter(first_name='...')

first call creates

QuerySet from Manager

User.objects.filter(email='...')User.objects.filter(email='...', first_name='...')User.objects.filter(email='...').filter(first_name='...')

second call transforms

QuerySet into another

QuerySet

first call creates

QuerySet from Manager

User.objects.filter(email='...')

User.objects.filter(email='...').delete()

User.objects.filter(email='...').delete()User.objects.delete()

User.objects.filter(email='...').delete()User.objects.delete()

User.objects.filter(email='...').delete()User.objects.delete()User.objects.all().delete()

User.objects.filter(email='...').delete()User.objects.delete()User.objects.all().delete()

.delete() is only defined on QuerySet, not on Manager (for safety)

from django.db.models import Q

from django.db.models import Q

User.objects.filter(Q(email='...'))

from django.db.models import Q

User.objects.filter(Q(email='...') | Q(first_name='...'))

from django.db.models import Q

is_user_query = Q(email='...') | Q(first_name='...')User.objects.filter(is_user_query)

from django.db.models import Q

is_user_query = Q(email='...') | Q(first_name='...')User.objects.filter(is_user_query)

transform search criteria into

first-class value

User.objects.filter(customer_profile=cust_prof_instance)

User.objects.filter(customer_profile__id=123)

User.objects.filter(customer_profile__id__in=[123, 456])

User.objects.filter(customer_profile__isnull=False)

User.objects.filter(invitecodeusage__invite__code='...')

User.objects.filter(invitecodeusage__invite__code='...')

One-to-many: at least one matching

users = Users.objects.filter(...)

for user in users: for tm in user.tracked_metrics.all(): print tm.metric.key

users = Users.objects.filter(...)

for user in users: for tm in user.tracked_metrics.all(): print tm.metric.key

Assume 10 users and 16 distinct metrics

users = Users.objects.filter(...)

for user in users: for tm in user.tracked_metrics.all(): print tm.metric.key

1 - users10 - tracked metrics16 - metrics

users = (Users.objects.filter(...) .prefetch_related('tracked_metrics'))

for user in users: for tm in user.tracked_metrics.all(): print tm.metric.key

1 - users 1 - tracked metrics16 - metrics

users = (Users.objects.filter(...) .prefetch_related('tracked_metrics__metric'))

for user in users: for tm in user.tracked_metrics.all(): print tm.metric.key

1 - users 1 - tracked metrics 1 - metrics

users = (User.objects .filter(customer_profile__isnull=False))

for user in users: print user.customer_profile.uuid

users = (User.objects .filter(customer_profile__isnull=False))

for user in users: print user.customer_profile.uuid

Assume 10 users

users = (User.objects .filter(customer_profile__isnull=False))

for user in users: print user.customer_profile.uuid

1 - users10 - customer profiles

users = (User.objects .filter(customer_profile__isnull=False) .prefetch_related('customer_profile'))

for user in users: print user.customer_profile.uuid

1 - users 1 - customer profiles

users = (User.objects .filter(customer_profile__isnull=False) .select_related('customer_profile'))

for user in users: print user.customer_profile.uuid

1 - users 0 - customer profiles using JOIN

$ ./manage.py makemigrations

$ ./manage.py makemigrations

current model

definition

$ ./manage.py makemigrations

current model

definition

model definition based on applied

migrations

$ ./manage.py makemigrations

current model

definition

model definition based on applied

migrations

DIFF

$ ./manage.py makemigrations$ ./manage.py migrate

$ ./manage.py makemigrations$ ./manage.py migrate$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=>

$ ./manage.py makemigrations$ ./manage.py migrate$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> SELECT * FROM django_migrations; id | app | name | applied----+--------------+--------------+-------------------------- 1 | accounts | 0001_initial | 2016-08-30 11:56:29.798795-07(1 row)

$ ./manage.py migrate accounts 0002 # to specific version

$ ./manage.py migrate accounts 0002 # to specific version$ ./manage.py migrate accounts 0001 # rollback

$ ./manage.py migrate accounts 0002 # to specific version$ ./manage.py migrate accounts 0001 # rollback$ ./manage.py migrate accounts 0002 --fake # only add to table

vida=> -- faking a migrationvida=>

vida=> -- faking a migrationvida=> INSERT INTO django_migrations (app, name, applied) VALUES ('accounts', '0003_faked', current_timestamp);

class User(BaseModel): email = models.EmailField(max_length=254, unique=True)- phone_number = models.CharField(max_length=50)

class User(BaseModel): email = models.EmailField(max_length=254, unique=True)- phone_number = models.CharField(max_length=50)

+class Migration(migrations.Migration):+ operations = [+ migrations.RemoveField(+ model_name='user',+ name='phone_number',+ ),+ ]

User

emailphone_number

User

emailphone_number

Run migrations

1

User

email

User

emailphone_number

Run migrations

1

User

email

User

emailphone_number

Run migrations

Deploy code

1

2

User

email

User

email

class User(BaseModel): email = models.EmailField(max_length=254, unique=True)- phone_number = models.CharField(max_length=50)

+class Migration(migrations.Migration):+ operations = [+ migrations.RemoveField(+ model_name='user',+ name='phone_number',+ ),+ ]

class User(BaseModel): email = models.EmailField(max_length=254, unique=True)- phone_number = models.CharField(max_length=50)

+class Migration(migrations.Migration):+ operations = [+ migrations.RemoveField(+ model_name='user',+ name='phone_number',+ ),+ ]

Commit and deploy first.

class User(BaseModel): email = models.EmailField(max_length=254, unique=True)- phone_number = models.CharField(max_length=50)

+class Migration(migrations.Migration):+ operations = [+ migrations.RemoveField(+ model_name='user',+ name='phone_number',+ ),+ ]

Commit and deploy first.

Then commit and deploy.

User

emailphone_number

User

emailphone_number

User

emailphone_number

User

emailphone_number

Run migrations(None)

1

Deploy code(model change only)2

User

emailphone_number

User

email

Run migrations(None)

1

Deploy code(model change only)

Run migrations(remove field)

2

3

User

email

User

email

Run migrations(None)

1

Deploy code(model change only)

Run migrations(remove field)

2

3

User

email

User

email

Run migrations(None)

1

Deploy code(None)

4

class User(BaseModel):- first_name = models.CharField(max_length=50)- first_name = models.CharField(max_length=50, db_index=True)

class User(BaseModel):- first_name = models.CharField(max_length=50)- first_name = models.CharField(max_length=50, db_index=True)

+class Migration(migrations.Migration):+ operations = [+ migrations.AlterField(+ model_name='user',+ name='first_name',+ field=models.CharField(max_length=50, db_index=True),+ ),+ ]

$ ./manage.py sqlmigrate accounts 0004

$ ./manage.py sqlmigrate accounts 0004BEGIN;CREATE INDEX "accounts_user_first_name_a03f23825d126a2_uniq" ON "accounts_user" ("first_name");

COMMIT;

$ ./manage.py sqlmigrate accounts 0004BEGIN;CREATE INDEX "accounts_user_first_name_a03f23825d126a2_uniq" ON "accounts_user" ("first_name");

COMMIT;

Locks the entire table!

CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");

CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");

Running this is appropriate.

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");CREATE_INDEX

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");CREATE_INDEX

vida=> \d accounts_user

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");CREATE_INDEX

vida=> \d accounts_user

vida=> INSERT INTO django_migrations (app, name, applied) VALUES ('accounts', '0004_auto_20160830_1124', current_timestamp);INSERT 0 1

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");CREATE_INDEX

vida=> \d accounts_user

vida=> INSERT INTO django_migrations (app, name, applied) VALUES ('accounts', '0004_auto_20160830_1124', current_timestamp);INSERT 0 1

vida=> SELECT * FROM django_migrations;

$ aptible ssh --app vida-webserver$ ./manage.py dbshellpsql (9.4.1)Type "help" for help.

vida=> CREATE INDEX CONCURRENTLY "accounts_user_first_name" ON "accounts_user" ("first_name");CREATE_INDEX

vida=> \d accounts_user

vida=> INSERT INTO django_migrations (app, name, applied) VALUES ('accounts', '0004_auto_20160830_1124', current_timestamp);INSERT 0 1

vida=> SELECT * FROM django_migrations; Now it’s safe to deploy.