Friday, October 17, 2014

Use django with Mysql on Ubuntu 12.04 LTS


1. Dependency

    1-1 mysql

        $ sudo apt-get install mysql-server

    1-2 phpmyadmin

        $ sudo apt-get install phpmyadmin

    1-3 python 2.7 up

        // system build in: python 2.7

    1-4 pip

        $ sudo apt-get install python-pip

    1-5 python-mysqldb

        $ sudo apt-get install python-mysqldb



2. Install django

    2-1 Install pip

        $ sudo apt-get install python-pip

    2-2 Install django

        $ sudo pip install Django

    2-3 Verifying // optional

        $ python
Python 2.7.3 (default, Feb 27 2014, 20:00:17)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import django
>>> django.VERSION
(1, 7, 0, 'final', 0)


3. First django project (with mysql database)

    3.1 Create a folder to put the project [1]

        $ mkdir ~/nthuojv2

    3.2 Create a project [1]

        $ cd ~/nthuojv2
        $ django-admin.py startproject nthuoj
       /* --------------------------Folder structure--------------------------
         *     nthuojv2/nthuoj/
         *         manage.py
         *         nthuoj/
         *             __init__.py
         *             settings.py
         *             urls.py
         *             wsgi.py
         * --------------------------End of structure-------------------------- */

    3.3 Database configuration [2]

        $ vim nthuoj/settings.py
...
DATABASES = {
    'default': {
        'ENGINE': 'djago.db.backends.mysql',
        'OPTIONS':{
            'read_default_file': '~/nthuojv2/nthuoj.ini',
        },
    }
}
...
        $ vim nthuoj.ini
[client]
host = 127.0.0.1
database = newnthuoj
user = root
password = *********
default-character-set = utf8

    3.4 Create database

        $ mysql -u root -p*********
mysql> CREATE DATABASE newnthuoj character set utf8;
mysql> exit

    3.5 Run server [1]

        $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying sessions.0001_initial... OK 
    $ python manage.py runserver <IP>:<port>
    // without argument, default ip: 127.0.0.1, default port: 8000
    // another version: only one argument <port>, default ip: 127.0.0.1
Performing system checks...
System check identified no issues (0 silenced).
October 15, 2014 - 09:26:04
Django version 1.7, using settings 'nthuoj.settings'
Starting development server at http://<IP>:<port>/
Quit the server with CONTROL-C.

    3.6 Verifying //optional

        open the browser and visit <IP>:<port>


4. Create models [1]

    4.1 Create a new app

        $ python manage.py startapp <app_name> // app_name: polls
       /* --------------------------Folder structure--------------------------
         *     nthuojv2/polls/
         *         admin.py
         *         __init__.py
         *         migrations/
         *             __init__.py
         *         models.py
         *         tests.py
         *         views.py
         * --------------------------End of structure-------------------------- */

    4.2 Finish contents for the app

        $ vim polls/models.py
import datetime

from django.db import models
from django.utils import timezone

# Create your own models here.
class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    # <blanks>def instead of <tab>def
    def __unicode__(self): # python 3 up use __str__
        # <blanks>return instead of <tab><tab>return
        return self.question_text

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

class Choice(models.Model):
    question = models.ForeignKey(Question)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __unicode__(seof): # python 3 up use __str__
        return self.choice_text
        $ vim nthuoj/settings.py
...
INSTALLED_APPS = (
    ...
    'polls',
)
...
        $ python manage.py makemigrations polls
Migrations for 'polls':
  0001_initial.py:
    - Create model Choice
    - Create model Question
    - Add field question to choice

    4.3 See what django want to do on your database // optional

        $ python manage.py sqlmigrate polls 0001
BEGIN;
CREATE TABLE `polls_choice` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `choice_text` varchar(200) NOT NULL,
    `votes` integer NOT NULL
);
CREATE TABLE `polls_question` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `question_text` varchar(200) NOT NULL,
    `pub_date` datetime NOT NULL
);
ALTER TABLE `polls_choice` ADD COLUMN `question_id` integer NOT NULL;
ALTER TABLE `polls_choice` ALTER COLUMN `question_id` DROP DEFAULT;
CREATE INDEX polls_choice_7aa0f6ee ON `polls_choice` (`question_id`);
ALTER TABLE `polls_choice`
    ADD CONSTRAINT polls_choice_question_id_40fbbd72_fk_polls_question_id
        FOREIGN KEY (`question_id`)
        REFERENCES `polls_question` (`id`);
COMMIT;

    4.4 Create tables in database

        $ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, polls, auth, sessions
Running migrations:
  Applying polls.0001_initial... OK


5. Use python shell to insert data [1]

    5.1 Open python shell

        $ python manage.py shell

    5.2 Setting environment

        >>> import django
        >>> django.setup()
        >>> from polls.models import Question, Choice
        >>> from django.utils import timezone

    5.3 Add a question

        >>> q = Question(question_text="What's up?", pub_date=timezone.now())
        >>> q.save()

    5.4 Add choices for the question

        >>> q.choice_set.create(choice_text='Not much', votes=0)
        >>> q.choice_set.create(choice_text='The sky', votes=0)

    // for more APIs, refer to [1]


6. Create views [3]

    6.1 Edit the content

        $ vim polls/views.py
...
from django.http import HttpResponse
def index(request):
    return HttpResponse("Hello, world.")

    6.2 Create urls.py

        $ vim polls/url.py
from django.conf.urls import patterns, url
from polls import views
urlpatterns = patterns('',
    url(r'^$', views.index, name='index'),
)

    6.3 Edit nthuoj/urls.py

        $ vim nthuoj/urls.py
...
urlpatterns = patterns('',
    url(r'^polls/', include('polls.urls')),
    ...
)

    6.4 Verifying // optional

        run server, open the browser and visit <IP>:<port>/polls

    6.5 Passing parameters to view

        $ vim polls/views.py // create more views
...
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
        $ vim polls/urls.py // link views and urls
...
urlpatterns = patterns('',
    ...
    url(r'^(?P<question_id>\d+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>\d+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>\d+)/vote/$', views.vote, name='vote'),
)

    6.6 Verifying // optional

        run server, open the browser
        visit <IP>:<port>/polls/34
        visit <IP>:<port>/polls/34/results/
        visit <IP>:<port>/polls/34/vote/


7. Get data and show on web by view and template [3]

    7.1 Create the template folder and polls templates folder

        $ mkdir polls/templates
        $ mkdir polls/templates/polls // for namespace, not necessery, but recommand

    7.2 Edit template file

        $ vim polls/templates/polls/index.html
<!-- views.py will pass content: 'lastest_question_list' -->
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
        $ vim polls/templates/polls/detail.html
<!-- views.py will pass content: 'question' -->
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

    7.3 Edit the movement (in views.py) // show latest 5 questions (sorted by public time, dec order)

        $ vim polls/views.py
...
from django.template import RequestContext, loader
from polls.models import Question
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = RequestContext(request, {
        'latest_question_list': latest_question_list,
    })
    return HttpResponse(template.render(context))
...

    7.4 Shortcut: render() // no more HttpResponse is needed

        $ vim polls/views.py
from django.shortcuts import render
from polls.models import Question

def index(request):
    latest_question_list = Question.objects.all().order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

    7.5 Handing exception // query a not exist object

        $ vim polls/views
...
from django.http import Http404
...
def detail(request, question_id):
    try:
        question = Question.objects.get(id=question_id)
    except Question.DoesNotExist:
        raise Http404 # or redirect to borken page
    return render(request, 'polls/detail.html', {'question': question})
        // use shortcut get_object_or_404() to replace try except raise Http404

    7.6 Reomve hardcoded URLs in templates

        $ vim polls/templates/polls/index.html
...
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
...

    7.7 Namespace of template // optionary, but recommand

        $ vim nthuoj/urls.py
...
url(r'^polls/', include('polls.urls', namespace="polls")),
...
        $ vim polls/templates/polls/index.html
...
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
...


8. Modify data in database [4]

    8.1 Complete version of detail.html

        $ vim polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
{% if error_message %}
    <p><strong>{{ error_message }}</strong></p>
{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %} # Prevent Cross Site Request Forgeries [5]
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
    {% endfor %}
    <input type="submit" value="Vote" />
</form>

    8.2 Complete version of vote and result

        $ vim polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from polls.models import Choice, Question
...
def vote(request, question_id):
    p = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': p,
            'error_message': "You didn't select a choice.",
        })
    else:
        # simply use += 1 and save to modify the value in database
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

    8.3 Template for results

        $ vim polls/templates/polls/results.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

    8.4 Verifying // optional

        run server, open the browser
        vote it! and check the value in the database       


9. Generic views [4][6] // To avoid redundant code

    9.1 Change polls/urls.py

        $ vim polls/urls.py
...
urlpatterns = patterns('',
    url(r'^$', views.IndexView.as_view(), name='index'),
    # The DetailView generic view expects the primary key value captured from the URL to be called "pk"
    url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>\d+)/results/$', views.ResultsView.as_view(), name='results'),
    ...
)

    9.2 Change polls/views.py

        $ vim polls/views.py
from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views import generic
from polls.models import Choice, Question
class IndexView(generic.ListView):
    # default template: <app_name>/<model_name>_list.html, ie. polls/question_list.html
    template_name = 'polls/index.html'
    # default context variable: question_list
    context_object_name = 'latest_question_list'
    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
    model = Question
    # default template: <app_name>/<model_name>_detail.html, ie. polls/question_detail.html
    template_name = 'polls/detail.html'
class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'
    def vote(request, question_id):
        # same as section 8

    9.3 Verifying // optional

        run server, open the browser
        the appearence should be the same as in section 8

    // for more details on generic views, refer to [6]


Murmur

An app is a Web application that does something
A project is a collection of configuration and apps for a particular Web site
A project can contain multiple apps. An app can be in multiple projects.

The “view” describes the data that gets presented to the user. It’s not necessarily how the data looks, but which data is presented. The view describes which data you see, not how you see it.
In Django, a “view” describes which data is presented, but a view normally delegates to a template, which describes how the data is presented.

By running makemigrations, you’re telling Django that you’ve made some changes to your models (in this case, you’ve made new ones) and that you’d like the changes to be stored as a migration.
There’s a command that will run the migrations for you and manage your database schema automatically - that’s called migrate,
The sqlmigrate command takes migration names and returns their SQL:
The sqlmigrate command doesn’t actually run the migration on your database - it just prints it to the screen so that you can see what SQL Django thinks is required.
Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema.

You should always return an HttpResponseRedirect after successfully dealing with POST data. This tip isn’t specific to Django; it’s just good Web development practice.


Reference

[1] https://docs.djangoproject.com/en/1.7/intro/tutorial01/
[2] https://docs.djangoproject.com/en/1.5/ref/databases/
[3] https://docs.djangoproject.com/en/1.7/intro/tutorial03/
[4] https://docs.djangoproject.com/en/1.7/intro/tutorial04/
[5] http://www.dotblogs.com.tw/joysdw12/archive/2013/09/16/asp-net-cross-site-request-forgery.aspx
[6] https://docs.djangoproject.com/en/1.7/topics/class-based-views/

No comments:

Post a Comment