What is Django ?

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

  • MVT (Model View Template)

  • Flexible template language that can be used to generate HTML, CSV, Email or any other format.

  • Includes ORM that supports many databases – Postgresql, MySQL, Oracle, SQLite.

  • Lots of extras included – middleware, csrf protections, sessions, caching, authentication

What is Model?

  • Models contains anything and everything about the data: how to access it, how to validate it.

  • Each model is a Python class that subclasses django.db.models.Model.

  • Each attribute of the model represents a database field.

  • Django gives you an automatically-generated database-access API

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

What is Field?

  • The most important part of a model – and the only required part of a model – is the list of database fields.

  • Field types

  • Each field in your model should be an instance of the appropriate Field class.

  • The column type, which tells the database what kind of data to store
    e.g. INTEGER, VARCHAR.

  • The default HTML widget to use when rendering a form field
     e.g. <input type="text">, <select>.

  • The minimal validation requirements, used in Django’s admin and in automatically-generated forms.

  • Available field types in  django

What is Field?

  • Field options

  • Each field takes a certain set of field-specific arguments .
    For example,
    CharField require a max_length argument.

  • Common field options:   null, black , choices, default, help_text, primary key, unique

  • Available field options in  django

  • PostgreSQL specific fields
    For example:  ArrayField, JSONField

Relationships

  • Many-To-One(Foreign Key)                                                                                      

  • Primary key of other model.

from django.db import models

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE
    )
    # ...

Relationships

  • Many-to-many

  • A row in a table in a database can be associated with one or (likely) more rows in another table.

  • It doesn’t matter which model has the ManyToManyField, but you should only put it in one of the models – not both.

  • Add extra fields on many-to-many relationships

from django.db import models

class Topping(models.Model):
    # ...
    pass

class Pizza(models.Model):
    # ...
    toppings = models.ManyToManyField(Topping)

Relationships

  • One-to-one relationships                                                                                          

  • Extending parent class                                                        
from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(models.Model):
    place = models.OneToOneField(Place,
    on_delete=models.CASCADE,
    primary_key=True,
    )
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Meta Options

  • Model metadata is “anything that’s not a field”, such as ordering options

from django.db import models

class Ox(models.Model):
    horn_length = models.IntegerField()

class Meta:
    db_table = 'ox'
    ordering = ["horn_length"]
    verbose_name_plural = "oxen"

Model Methods

  • Define custom methods on a model to add custom “row-level” functionality to your objects

  • This is a valuable technique for keeping business logic in one place – the model.
from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    @staticmethod
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

Model Inheritance

Abstract Model

  • This model will then not be used to create any database table. Instead, when it is used as a base class for other models

  • write your base class and put abstract=True in the Meta class

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
     	abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Proxy Model

  • When using multi-table inheritance, a new database table is created for each subclass of a model.

  • Sometimes, however, you only want to change the Python behavior of a model – perhaps to change the default manager, or add a new method
class Person(models.Model):
   first_name = models.CharField(max_length=30)
   last_name = models.CharField(max_length=30)

class MyPerson(Person):
   class Meta:
       proxy = True

    def do_something(self):
       	# ...
       	pass

Multiple Inheritance

  • To inherit from multiple parent models                                                                    

from django.db import models

class Article(models.Model):
    article_id = models.AutoField(primary_key=True)
    ...

class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    ...

class BookReview(Book, Article):
    pass

Making Queries

  • Creating objects

  • An instance of that class represents a particular record in the database table.

from blog.models import 

b = Blog(name='Beatles Blog', 
        tagline='All the latest Beatles news.'
    )
b.save()

Blog.objects.create(
    name='Beatles Blog',
    tagline='All the latest Beatles news.'
    )

Making Queries

Entry.objects.get(id=1)
Entry.objects.filter(name='ggk')
Entry.objects.all()
Entry.objects.all()[:5]
  • Limiting querysets                                                                                                        

  • Retrieving objects

  • A QuerySet represents a collection of objects from your database. It can have zero, one or many filters.

Filed Lookups

Entry.objects.filter(pub_date__lte='2006-01-01') #lessthan
Blog.objects.get(name__iexact="beatles blog")
  • A case-insensitive match.                                                                                             

  • An “exact” match                                                                                                          
Entry.objects.get(headline__exact="Cat bites dog")
  • Case-sensitive containment test                                                                                
    Icontains, startswith, endswith, istartswith, iendswith

Entry.objects.get(headline__contains='Lennon')

Filed Lookups

Entry.objects.filter(blog__name='Beatles Blog')
  • Django offers a powerful and intuitive way to “follow” relationships in lookups, taking care of the SQL JOINs for you automatically.

  • F expressions, to find a list of all blog entries that have had more comments than pingbacks                                                                                                          
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Entry.objects.filter(authors__name=F('blog__name'))
  • It supports time objects also                                                                                      

Entry.objects.filter(mod_date__gt=F('pub_date') + \
     timedelta(days=3))

Filed Lookups

Poll.objects.get( 
    Q(question__startswith='Who'),
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
  • Q objects - queries with or statements                                                                      

  • Comparing objects                                                                                                        
some_entry == other_entry or some_entry.id == other_entry.id

Deleting Objects

e.delete()
  • This method immediately deletes the object and returns the number of objects deleted and a dictionary with the number of deletions per object type.

  • Coping model instances                                                                                              
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
  • Updating objects, You can do this with the update() method                                
Entry.objects.filter(pub_date__year=2007).update(
    headline='Everything is the same')

Related objects

e = Entry.objects.get(id=2)
e.blog = some_blog
e.save()
  • If a model has a ForeignKey, instances of that model will have access to the related (foreign) object via a simple attribute of the model.

  • Note that the select_related() QuerySet method recursively pre populates the cache of all one-to-many relationships ahead of time.
e = Entry.objects.select_related().get(id=2)
print(e.blog)  # Doesn't hit the database; uses cached version.
print(e.blog)  # Doesn't hit the database; uses cached version.
  • One-to-One relations                                                                                                    
ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

Aggregation

Book.objects.all().aggregate(Avg('price'))
 #{'price__avg': 34.35}
Book.objects.aggregate(average_price=Avg('price'))
 #{'average_price': 34.35}
Book.objects.aggregate(Avg('price'),Max('price'),Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'),
     'price__min': Decimal('12.99')}
  • aggregate() is used to calculate summary values over the objects                      

  • Per-object summaries can be generated using the annotate()                            
q = Book.objects.annotate(Count('authors'))
q = Book.objects.annotate(Count('authors'), Count('store'))
  • We can use filter to aggregations                                                                              
  • We can use values with aggregations
Book.objects.filter(name__startswith="Django").annotate(
    num_authors=Count('authors'))
Author.objects.annotate(average_rating=Avg('book__rating'))
.values('name', 'average_rating')

Search

Author.objects.filter(name__contains='Terry')
  • A common task for web applications is to search some data in the database with user input.
    Basic Example

Author.objects.filter(name__unaccent__icontains='Helen')
  • PostgreSQL support                                                                                                      
Ahthor.objects.filter(name__search='author')

Managers

  • A Manager is the interface through which database query operations are provided to Django models. At least one Manager exists for every model in a Django application.

  • By default, Django adds a Manager with the name objects to every Django model class. However, if you want to use objects as a field name, or if you want to use a name other than objects for the Manager, you can rename it on a per-model basis

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

Person.people.all()

Custom Managers

  • Adding extra Manager methods is the preferred way to add “table-level” functionality to your models.

class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).
            get_queryset().filter(author='Roald Dahl')

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    # The Dahl-specific manager.
    dahl_objects = DahlBookManager() 

Book.objects.all()
Book.dahl_objects.all()

Custom QuerySet

  • Adding custom queryset methods.                                                                            

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')



class Student(models.Model):
    '''person'''
    name = models.CharField(max_length=50)

    objects = PersonQuerySet.as_manager()


Person.objects.authors()

Performing raw SQL 

  • The raw() manager method can be used to perform raw SQL queries that return model instances:​                           
 Person.objects.raw('''SELECT id, first_name, last_name,
         birth_date FROM myapp_person''')
  • you can use SQL’s AS clauses to map fields in the query to model fields
Person.objects.raw('''SELECT first AS first_name,
                             last AS last_name,
                             bd AS birth_date,
                             pk AS id,
                       FROM some_other_table''')
  • you can use SQL’s AS clauses to map fields in the query to model fields
name_map = {'first': 'first_name', 'last': 'last_name',
     'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table',
     translations=name_map)

Performing raw SQL 

  • Passing parameters to raw()                                                                                        
 lname = 'Doe'
Person.objects.raw(
    'SELECT * FROM myapp_person WHERE last_name = %s',
         [lname])
  • Executing SQL's directly                                                                                            
from django.db import connection

def my_custom_sql(self):
    with connection.cursor() as cursor:
        cursor.execute("SELECT foo FROM bar WHERE baz = %s",
             [self.baz])
        row = cursor.fetchone()

    return row

Database tranctions

  • Django’s default behavior is to run in autocommit mode. Each query is immediately committed to the database, unless a transaction is active.
  • A common way to handle transactions on the web is to wrap each request in a transaction.
     Eg: Set ATOMIC_REQUESTS to True in the configuration of each databas
  • When ATOMIC_REQUESTS is enabled, it’s still possible to prevent views from running in a transaction.
  • Django provides a single API to control database transactions
from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

Database tranctions

  • Under the hood, Django’s transaction management code:

  • opens a transaction when entering the outermost atomic block;
  • creates a savepoint when entering an inner atomic block;
  • releases or rolls back to the savepoint when exiting an inner block;
  • commits or rolls back the transaction when exiting the outermost block.
     
  • Performing actions after commit
from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

Multiple databases

  • configure databases in settings.py                                                                            
DATABASES = {
    'default': {
        'NAME': 'user_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'superS3cret'
    },
    'customers': {
        'NAME': 'customer_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_cust',
        'PASSWORD': 'veryPriv@ate'
    }
}

Multiple databases

  • Create a router                                                                                                               
class AuthRouter(object):
    """
    A router to control all database operations on models in the
    auth application.
    """
    def db_for_read(self, model, **hints):
        """
        Attempts to read auth models go to auth_db.
        """
        if model._meta.app_label == 'auth':
            return 'auth_db'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write auth models go to auth_db.
        """
        if model._meta.app_label == 'auth':
            return 'auth_db'
        return None

Multiple databases

  • Using keyword to specify database                                                                           
p = Person(name='Fred')
p.save(using='first')  # (statement 1)
p.save(using='second')