Related:
Learning Python

Ref: This is combined learning notes of:

  1. basic level sentdex's youtube tutorial, the corresponding video+text one is here.
  2. middle level another tutorial: Django Tutorials for Beginners
  3. book "O'Reilly 2014 - Test-Driven Development with Python (django 1.7)".
  4. official docs: DOCs.DjangoProject.com.

Install

python 3 (inc pip), make sure added to PATH,

then pip3 install django or pip3 install django==1.7

Start a Project/Site

start

mkdir myProject
cd myProject
django-admin startproject mySite
tree
>└─djangoSunnySite # container folder
>    └─djangoSunnySite # settings folder

There will be a file 'db.sqlite3' in the container folder, because sql-lite is the default DB for python.

set git to ignore 'secret_key'

see: git -- Ignore Sensitive/Secret Lines

run server

cd <container folder>
manage.py runserver

check naming convention (naming rules)

ref

Start an App (Under a Site)

start

manage.py startapp <appName>
the folders should look like this now:

└─djangoSunnySite       # container folder
    ├─app111            # one app folder 
    │  └─migrations
    ├─appSunny2         # another app folder
    │  └─migrations
    ├─app_underline     # another app folder
    │  └─migrations
    ├─webapp            # another app folder
    │  └─migrations
    └─djangoSunnySite   # settings folder
        └─__pycache__

// (i use 'app111' as the 'personal' app in the video)

Warn: we should START an app before registration!!!. Otherwize, manager.py will give errors: ImportError: No module named '<appName>'.
Warn: after creating db tables, table's name is fixed together with app name, so renaming app means also renaming db's table name (or copy the table if we should not delete the table).

register app in settings.py

config route to the app in the project's main urls.py

vim <settings folder>/urls.py

Note: 'webapp' is the name of app, 'urls' is the file name of 'urls.py' without extension. If the being called urls.py file does not exist, there will be exception.

register a (function-based) view in app's urls.py

cd <app folder>
touch urls.py
vim urls.py
from django.conf.urls import url
from . import views # . means import from current package

urlpatterns = [
    url(r'^$', views.index, name='index')] # "r" means regular expression, "view" is file name, "index" is the function in the file

add the (function-based) view, in app's views.py

cd <app folder>
vim views.py
from django.shortcuts import render
from django.http import HttpResponse

def index(request):
    return HttpResponse("<h2> Hello! == Hallå! <h2>")

run server


and:

Jinja Templates

// i created another app "app111"

add route to the page "mypage.html"

vim <app folder>/urls.py
# the same as the previous urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.index, name='index')]
vim  <app folder>/views.py
from django.shortcuts import render
# this time, we use render() instead of HttpResponse()
def index(request):
    return render(request, '<app name>/mypage.html')

create a template for header "header.html"

cd <app folder>
mkdir templates # plural
cd templates
mkdir <app name> 

OBS: all apps' templates' content are shared, so it is necessary to have another layer to distinguish apps, and usually, it has the same name as the app.

touch templates/<app name>/header.html
vim   templates/<app name>/header.html
<!DOCTYPE html>
<html>

    <head><title> a title </title></head>

    <body>
        <div>
            {% block content %}
            {% endblock %}
        </div>
    </body>

</html>

use the template in "mypage.html"

touch templates/<app name>/mypage.html
vim   templates/<app name>/mypage.html
{% extends "<app name>/header.html" %}

{% block content %}
this is sunny's test page. 
{% endblock %}

create another smaller snippet-template to be included

mkdir templates/<app name>/includes/
touch templates/<app name>/includes/small.html
vim   templates/<app name>/includes/small.html
{% block content %}
<p>i am a small snippet, and i have been included multi times.  </p>
{% endblock %}

modify "mypage.html" to use this snippet:

{% extends "<app name>/header.html" %}

{% block content %}
<p>this is sunny's test page.  </p>
{% include "<app name>/includes/small.html" %}
{% include "<app name>/includes/small.html" %}
{% include "<app name>/includes/small.html" %}
{% endblock %}

run server

notice, obs

files like "header.html" /shared templates ?/ are usually used everywhere and every page for a site, not so many files.
files as snippets are used somewhere, but the amount of files are more than "shared templates".

reading list

Simple Logic & Loop Control by sentdex / Harrison
Quick Examples, Super Blocks, Macros

Boostrap

install

getbootstrap > getting-started > download
mkdir <app folder>/static/
Unzip bootstrap and put the folders (css, fonts, js ...) to static.
OBS 1: The folder name "static" a default value and can be changed by editing the site's settings.py.
OBS 2: Similar as "templates", the "static" folder is also shared by all apps. However, this is usually general for the whole site, so we didn't create another layer of folder.

use

Edit <app folder>/templates/<app name>/headerBootstrap.html to include bootstrap:

...
    <head>
        <title> a title </title>
        {% load staticfiles %}
        <link rel='stylesheet' href="{% static 'css/bootstrap.min.css' %}"></link>
    </head>
...

After adding some content, the header file is bigger, the full header html content is here or origin as backup here.
Let's modify <app folder>/views.py to re-route the default home page to this file.

from django.shortcuts import render

def index(request):
    return render(request, '<app name>/mypageBootstrap.html')

run server

Passing Variable from Python to Jinja

add a vew

add a new function-based view in views.py:

vim <app folder>/views.py
...
def contact(request):
    return render(request, 'app111/basic.html', {'content':['if u wanna contact me, plz email me.', '[email protected]']})
# The function-based view contact() has a dictionary as the last parameter. The dictionary has 2 strings.

add a template html file for the new view:

touch <app folder>/templates/<app name>/<file name: basic>.html
vim <the above file>
{%extends 'app111/headerBootstrap.html' %}

{%block content%}
    {% for tcString in content %} 
    <!-- this content is the variable passed from views.py's function-based view contact(), and it is different with "block content", and, there is no conflict!!! (sunny: i prefer use another varialbe name) -->
        <p>{{tcString}}</p>
    {% endfor %}
{% endblock %}

Tip 1: The spaces at the beginning and end of {% %} don't matter.
Tip 2: Flow control: "for loop".
Tip 3: To print a varialbe in jinja: {{ varName }}, spaces do not matter.

run server

Class-Based Generic View

Will use a new app "blog" to do this.

start a new app (e.g. blog)

manage.py startapp blog
vim <setting folder>/settings.py
...
INSTALLED_APPS = [
    'blog', 
...
vim <setting folder>/urls.py
...
urlpatterns = [
    url(r'^blog/', include('blog.urls')),
...

We will use two types of view (list view & detail view). ref: doc: Generic display views
Thus:

vim <app folder>/urls.py
from django.conf.urls import url, include
from django.views.generic import ListView, DetailView
from blog.models import Post # we need a DB table/model named "Post"


urlpatterns = [
    url(r'^$', ListView.as_view(queryset = Post.objects.all().order_by("-date")[:25],
                                template_name = "blog/blog.html"))
# Tip: order by DESC, limit 25.
...

models.py

Django uses models to avoid direct writing sql strings.

vim <app folder>/models.py

models.py == the entire DB
a class == a table
a variable == a column (a column named id will be added by default, as key)
More info here: django doc: Model field reference

from django.db import models


class Post(models.Model): # this will create a table "blog_post" in small letter format: "<app_name>_<class_name>"
    title = models.CharField(max_length = 140) # optional max_length
    body  = models.TextField()
    date  = models.DataTimeField()

    def __str__(self):
        return self.title

??? what does __str__ mean?

a more generic view

see video

Migration

  • Migration is used to initialize project's database.
  • Similar like registering an app is 1st thing to od in settings (after starting an app), the most important thing for model is migration.
  • One part of migration is for the app admin, this is a default app that comes with all projects.
  • As it is not able to undo it, so it is good to check the sql strings (before it will be run) using sqlmigrate.
manage.py makemigrations # make migration-s
# `makemigrations` can be followed by: [optional: app name] 

# optional: check.
# after finding files' prefix in: <app folder>/migrations/digital-prefix_initial.py
manage.py sqlmigrate <app name> <digital-prefix e.g. 0001> 

# if `sqlmigrate` gives good sql query strings, run 
manage.py migrate

The file "db.sqlite3" will become bigger now.

The Default Admin App

To use the admin app, we need to create a super user first.

create a super user

manage.py createsuperuser
manage.py runserver

Go to http://127.0.0.1:8000/admin and log in.

Now, there are "admin.py" files in each <app folder>.
Make sure the models which we want to use is registered in admin.py, otherwise the /admin cannot manipulate the corresponding tables.

vim <app folder>/admin.py
from django.contrib import admin
from blog.models import <table name, eg: Post>

# Register your models here.
admin.site.register(<table name, eg: Post>)

The site will have one more thing:

Display

add blog posts via admin app

We can add a new post using this GUI:

Thus, we have something to show:

add view to display a single post

register a deailed-view:

vim <app folder>/urls.py
...
urlpatterns = [
    url(r'^(?P<pk>\d+)$', DetailView.as_view(model = Post,
                                             template_name = 'blog/post.html')),
...

Tip: '?P' is a named (capturing) group. 'pk' means primary-key. doc: urls:named-groups

add the deailed-view:

touch <app folder>/templates/<app name>/post.html
vim <the above file>
{% extends "app111/headerBootstrap.html" %}

{% block content %}
    <h3>{{post.title}}</h3>
    <h6>{{post.date}}</h6>
    {{post.body|safe|linebreaks}} <!-- the "safe" filter tells jinja that the string is safe (no need to escape html tags), so we can have html tags taking effect (come into operation) instead of escaped and displayed as plain text -->
{% endblock %}

OBS: I cannot change the word "post", why and where is that word from???

Model Form

It would be necessary to have seperate forms for normal users instead of uing admin to submit.
Model Form's entire commit diff.

add a form for a model/db-table

add html files:

Add form html file, must be <model name>_form.html (commit).
Besides, form-group is suggested to have another file, e.g. form-group-template.html (commit).

add a view in views.py:

...
from django.views.generic.edit import CreateView

class PostCreate(CreateView):
     model = Post
     fields = ['title', 'body', 'date']

commit

add route in app urls.py:

...
urlpatterns = [
    url(r'^post/create$', views.PostCreate.as_view(), name='post-create')
...

success insert-db redirection in models.py via reverse()

...
from django.core.urlresolvers import reverse

    def get_absolute_url(self):
        return reverse('blog2:detail', kwargs={'pk': self.pk}) # form inserted into db, then redirect to this url.

add corresponding namespace in main urls.py:

...
urlpatterns = [
    url(r'^blog2/', include('blog2.urls', namespace='blog2')),
...

add corresponding name in app urls.py:

...
urlpatterns = [
    url(r'^(?P<pk>\d+)$', views.DetailView.as_view(), name='detail'),
...

update & delete

code see commit diff
OBS: delete via "GET" requires confirmation, but "POST" does NOT.

ref: video

File Uplad

upload to mysql file-field column

see video

upload to folder with filename in db

+ function-based view
see sunny's implementation, (ref: minimal-django-file-upload-example)
+ class-based view
//to be cont.

multi filenames in mysql's one record/row's

//to be cont.

Put Online

Situation 1: we already have a project online.
We just need to copy the apps and db file to the existing project container folder, then modify settings.py to register the new apps. This is what the tutorial video did.
Situation 2: new project.
//to be cont.

Restful API with Angular - A Shopping List Demo

(Very Simple Demo with Json)

  • George Kappel 2013 video (backed up as: django-rest-framework and angularjs.mp4)
  • code with db See Shopping List Demo for the rest Angular part. Below is the API part. George's video is not giving clear explaination for beginners, see Chris Hawkes 2015 video for clear explaination to understand. However, Chris did not provide code, we can continue to use the app "blog". (video backed up as: how-to-create-a-RESTful-API-with-Django-in-20-minutes.mp4)

Virtualenv
OBS: This demo is old, and needs Python 2.7.
If we run using py3: NameError: name 'unicode' is not defined.
We can use virtual env to solve this problem.

Migrate Database
The original code does not contain a db file (I added to my github repo), so we need to do migration which requires django 1.7 or higher. It is different to the freezed 1.5, we can do this with another venv. See Learning Django for more info.

C:\Users\user_name\Envs\py27_rest_demo\Scripts\python.exe manage.py makemigrations
C:\Users\user_name\Envs\py27_rest_demo\Scripts\python.exe manage.py migrate

rest_framework install & settings

pip install djangorestframework
vim <project setting folder>/settings.py
...
INSTALLED_APPS = [
    'rest_framework',
...

# add the following control (otherwise, default settings allow: GET, POST, HEAD, OPTIONS)
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly']
}
...

serializers in views.py (app)

vim views.py
from rest_framework import generics, serializers
from .models import Post


class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post # model/table name
        field = ('id', 'title', 'body', 'date') # some/all columns in the corresponding table


class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer


class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Tip: Chris Hawkes has many serializers, so he created a dedicated file "serializers.py" for serializers, then import it in views.py.

urls.py (project & app)

vim <project setting folder>/urls.py
urlpatterns = [
    url(r'^api/', include('<app name>.urls', namespace='rest_framework')),
...
vim <app folder>/urls.py
urlpatterns = [
    url(r'^posts/$', PostList.as_view()),
    url(r'^post/(?P<pk>\d+)$', PostDetail.as_view()),
...