0
点赞
收藏
分享

微信扫一扫

开发JSP应用程序

Java旺 2024-02-10 阅读 13

Django从入门到放弃

安装Django

  1. 使用anaconda创建环境
conda create -n django_env python=3.10
conda activate django_env
  1. 使用pip安装django
python -m pip install Django
  1. 查看安装的django版本
python
>>> import django
>>> print(django.get_version())
5.0.1

编写你的第一个Django应用

  1. 创建项目
# 在当前目录下创建一个mysite目录
django-admin startproject mysite
  1. 切换到mysite目录,启动django开发服务器,这个服务器不要用于生产
# 默认8000端口,会自动重新加载的服务器runserver
python manage.py runserver
# 更换端口
python manage.py runserver 8080
  1. 创建投票应用
python manage.py startapp polls
  1. 编写第一个视图

(1)打开polls/views.py,添加以下代码。

from django.shortcuts import render
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

(2)在polls目录中创建一个URL配置,创建urls.py,添加以下代码。

from django.urls import path 
from . import views

urlpatterns = [
    path("", views.index, name="index"),
]

(3)在根URLconf文件中指定创建的polls.urls模块,在mysite/urls.py文件中添加以下代码。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # include允许引用其他URLConfs
    path("polls/", include("polls.urls")),
    path('admin/', admin.site.urls),
]

(4)函数path有四个参数,两个必传参数route和view,两个可选参数kwargs和name。

  • route:route是一个匹配URL的准则,类似正则表达式。当Django响应一个请求时,它会从urlpatterns的第一项开始按顺序依次匹配列表中的项,直到找到匹配的项。这些准则不会匹配GET和POST参数或域名。
  • view:当Django找到了一个匹配的准则,就会调用这个特定的视图函数,并传入HttpRequest对象作为第一个参数,被捕获的参数以关键词参数的形式传入。
  • kwarg:任意个关键词参数可以作为一个字典传递给目标视图函数。
  • name:为你的URL取名能使你在Django的任意地方唯一地引用它。
  1. 数据库配置

(1)打开mysite/setting.py,该配置包含了Django项目设置的Python模块。

  • 通常配置文件使用SQLite作为默认数据库。
DATABASES = {
    'default': {
        # ENGINE的可选值:'django.db.backends.sqlite3','django.db.backends.postgresql',
        # 'django.db.backends.mysql',或 'django.db.backends.oracle'
        'ENGINE': 'django.db.backends.sqlite3',
        # NAME是数据库名称,如果使用SQLite,就是文件的完整的绝对路径
        # 如果不适用SQLite,需要添加USER、PASSWORD、HOST等
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
  • 如果使用SQLite以外的数据库,确认在使用前已经创建了数据库。

(2)设置TIME_ZONE为自己的时区。

LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'

(3)设置应用,默认开启的应用需要至少一个数据表,使用前需要创建一些表

# 这个 migrate 命令查看 INSTALLED_APPS 配置,并根据 mysite/settings.py 文件中的数据库配置和随应用提供的数据库迁移文件,创建任何必要的数据库表。你会看到它应用的每一个迁移都有一个消息。
python manage.py migrate
INSTALLED_APPS = [
    # 默认包含了以下自带应用    
    # 管理员站点
    'django.contrib.admin',
    # 认证授权系统
    'django.contrib.auth',
    # 内容类型框架
    'django.contrib.contenttypes',
    # 会话框架
    'django.contrib.sessions',
    # 消息框架
    'django.contrib.messages',
    # 管理静态文件的框架
    'django.contrib.staticfiles',    
    # 添加应用配置
    'polls.apps.PollsConfig',
]
  1. 模型

(1)一个模型就是单个定义你的数据的信息源。模型中包含了不可缺少的数据区域和你存储数据的行为。

(2)创建两个模型

  • 问题Question:包括问题描述和发布时间;

  • 选项Choice:包括选项描述和当前得票数。

  • 打开polls/models.py文件,添加以下内容。

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

# 每个模型都是django.db.models.Model类的子类
class Question(models.Model):
    # 模型的变量表示一个数据库字段,每个字段都是Field类的实例
    # question_text也是字段名
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")
	
    def __str__(self):
        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, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)
    
    def __str__(self):
        return self.choice_text

(3)激活模型

# 通过运行 makemigrations 命令,Django 会检测你对模型文件的修改,并且把修改的部分储存为一次迁移。
# migrate是自动执行数据库迁移并同步管理你的数据库结构的命令。
python manage.py makemigrations polls
# 查看迁移命令会执行哪些SQL语句
# 主键id会被自动创建,也可以自定义;数据库表名由应用名polls和模型名如question连接而来;
# 默认Django会在外键字段名后追加字符串"_id"
python manage.py sqlmigrate polls 0001
# 检查项目中的问题
python manage.py check
# 再次运行migrate在数据库里创建新定义的模型的数据表
python manage.py migrate
  1. 初始API

(1)进入交互式Python命令行

# 我们使用这个命令而不是简单的使用“python”是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。
python manage.py shell
# 进入shell后就可以探索数据库API
>>> from polls.models import Choice, Question
>>> Question.objects.all()
>>> from django.utils import timezone
# 添加记录
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()
>>> q.id 
>>> q.question_text
>>> q.pub_date
# 修改字段值
>>> q.question_text = "What's up?"
>>> q.save()
# 关键词查找
>>> Question.objects.filter(id=1)
>>> Question.objects.filter(question_text__startswith="What")
# 获取今年发布的问题
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
# 请求不存在的记录会抛出一个异常
>>> Question.objects.get(id=2)
# 查找主键值
>>> Question.objects.get(pk=1)
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
>>> q.choice_set.all()
# 创建三个选项
>>> q.choice_set.create(choice_text="Not much", votes=0)
>>> q.choice_set.create(choice_text="The sky", votes=0)
>>> c = q.choice_set.create(choice_text="Just hacking again", votes=0)
>>> c.question
>>> q.choice_set.count()
# 删除选项
>>> c = q.choice_set.filter(choice_text__startswith="Just hacking")
>>> c.delete()
  1. Django管理界面

(1)创建一个管理员账号

# 创建一个能登录管理页面的用户,添加用户admin/admin
python manage.py createsuperuser

(2)启动开发服务器

(3)向管理界面中加入投票应用

# 打开polls/admin.py
from django.contrib import admin
from .models import Question

admin.site.register(Question)
  1. 视图

(1)投票应用中需要的视图

  • 问题索引页——展示最近的几个投票问题。
  • 问题详情页——展示某个投票的问题和不带结果的选项列表。
  • 问题结果页——展示某个投票的结果。
  • 投票处理器——用于响应用户为某个问题的特定选项投票的操作。

(2)URLconf将URL模式映射到视图

(3)编写更多的视图

# 向polls/views.py中添加更多视图
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)

# 把新视图加入到polls/urls.py文件
urlpatterns = [
    path("", views.index, name="index"),
    # 使用尖括号获得网址部分后发送给视图函数作为一个关键字参数
    # question_id部分定义了要使用的名字,用来识别相匹配的模式
    # int部分是一种转换形式,用来确定应该匹配网址路径的什么模式
    # 冒号用来分隔转换形式和模式名
    path("<int:question_id>/", views.detail, name="detail"),
    path("<int:question_id>/results/", views.results, name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

(4)重构index方法

from django.shortcuts import render
from .models import Question

# Create your views here.
from django.http import HttpResponse
# 展示数据库里以发布日期排序的最近5个投票问题
def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    output = ", ".join([q.question_text for q in latest_question_list])    
    return HttpResponse(output)

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)

(5)使用Django的模板系统

# polls/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
<!-- polls/templates/polls/index.html -->
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <!-- 硬编码连接 -->
        <!-- <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> -->
        <!-- 在polls.urls模块的URL定义中寻具有指定名字的条目 -->
        <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
# 更新index视图
from django.template import loader

# 载入模板文件并传递一个上下文
def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    template = loader.get_template("polls/index.html")
    context = {
        "latest_question_list": latest_question_list
    }
    return HttpResponse(template.render(context, request))

(6)快捷函数render

# 更新index视图,我们不在需要导入loader和HttpResponse
from django.shortcuts import render
from .models import Question

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

(7)抛出404错误

# 更新detail视图
from django.http import Http404
from django.shortcuts import render
from .models import Question

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, "polls/detail.html", {"question":question})
<!-- polls/templates/polls/detail.html -->
<!-- 模板系统使用点符号访问变量的属性 -->
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

(8)快捷函数get_object_or_404

from django.shortcuts import render, get_object_or_404
# 更新detail视图
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question":question})

(9)为URL名称添加命名空间

# 在根URLconf中添加命名空间,修改polls/urls.py
from django.urls import path 
from . import views

app_name = "polls"
urlpatterns = [
    path("", views.index, name="index"),
    path("<int:question_id>/", views.detail, name="detail"),
    path("<int:question_id>/results/", views.results, name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]
<!-- polls/templates/polls/index.html -->
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
  1. 表单处理

(1)编写一个简单的表单

<!-- 更新polls/detail.html模板 -->
<form action="{% url 'polls:vote' question.id %}" method="post">
<!-- 防止跨站请求伪造 -->
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
    	<!-- 在每个选项前加上一个单选按钮,value属性对应选项的ID -->
    	<!-- 当有人选择一个单选按钮并提交表单,会发送一个POST数据choice=ID -->
    	<!-- forloop.counter指示for标签已经循环多少次 -->
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="投票">
</form>

(2)更新vote和result视图

# polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from .models import Question, Choice 

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try: 
        # request.POST是一个类字典对象,通过关键字的名字获取提交的字符串数据        
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        return render(request, "polls/detail.html", {
            "question":question,
            "error_message":"You didn't select a choice."
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 增加选项的投票后重定向到结果页
        # reverse函数避免在视图函数中硬编码URL,需要传递的是要跳转的视图名字和参数
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))   
    
def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/results.html", {"question":question})    

(3)创建一个结果页面

<!-- 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>

(4)使用通用视图精简代码

  • 转换URLconf;
  • 删除一些旧的、不再需要的视图;
  • 基于Django的通用视图引入新的视图。

(5)改良URLconf

# polls/urls.py
from django.urls import path 
from . import views

app_name = "polls"
urlpatterns = [
    path("", views.IndexView.as_view(), name="index"),
    path("<int:pk>/", views.DetailView.as_view(), name="detail"),
    path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]

(6)改良视图

# polls/views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.views import generic
from .models import Question, Choice 

class IndexView(generic.ListView):
    template_name = "polls/index.html"
    # 替代默认提供的对象
    context_object_name = "latest_question_list"
    def get_queryset(self):
        return Question.objects.order_by("-pub_date")[:5] 

class DetailView(generic.DetailView):
    # 每个通用模型都需要知道它要操作的模型
    # 通过model属性提供这个信息或者定义get_queryset()方法来实现
    model = Question
    # 默认情况下,DetailView会使用<app name>/<model name>_detail.html的模板
    # template_name属性是用来告诉Django使用一个指定的模板名字,而不是自动生成的默认名字
    template_name = "polls/detail.html"

class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"
    
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try: 
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        return render(request, "polls/detail.html", {
            "question":question,
            "error_message":"You didn't select a choice."
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
  1. 自动化测试

(1)为什么你需要写测试

  • 测试帮你节约你的时间;
  • 测试不仅能发现错误,而且能够预防错误;
  • 测试使你的代码更有吸引力;
  • 测试有利于团队协作。

(2)基础测试策略

(3)开始写一个测试

# 打开shell,查看这个问题
python manage.py shell
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> future_question.was_published_recently()
True
# 测试需要写在polls/tests.py中,测试系统会自动寻找以test开头的测试函数并执行
import datetime
import datetime 
from django.test import TestCase
from django.utils import timezone
from .models import Question

class QuestionModelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)
# 在终端运行测试,会自动寻找polls应用中的测试代码,找到TestCase的子类创建一个特殊的数据库供测试使用,寻找类中的test开头的方法执行
python manage.py test polls
# 修复这个问题,更新models.py中方法后再次运行测试问题解决
def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

(4)测试视图

# setup_test_environment()安装了一个模板渲染器,使我们能够检查响应上的一些额外属性
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
# 导入测试客户端类
>>> from django.test import Client
>>> client = Client()
# 获取'/'的响应
>>> response = client.get("/")
>>> response.status_code
404
>>> from django.urls import reverse
>>> response = client.get(reverse("polls:index"))
>>> response.status_code
200
>>> response.content
>>> response.context['latest_question_list']
from django.utils import timezone
# 更新ListView视图类
class IndexView(generic.ListView):
    template_name = "polls/index.html"
    context_object_name = "latest_question_list"

    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now())
    		  				   .order_by("-pub_date")[:5]
# 更新polls/tests.py
import datetime 
from django.test import TestCase
from django.utils import timezone
from .models import Question
from django.urls import reverse

# 封装了创建投票的流程,减少了重复代码
def create_question(question_text, days):
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text, pub_date=time)

class QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        response = self.client.get(reverse("polls:index"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "No polls are available.")
        self.assertQuerySetEqual(response.context["latest_question_list"], [])
    def test_past_question(self):        
        question = create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse("polls:index"))
        self.assertQuerySetEqual(
            response.context["latest_question_list"],
            [question],
        )

class QuestionModelTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)
# 更新DetailView视图,排除还未发布的问题
class DetailView(generic.DetailView):
    model = Question
    template_name = "polls/detail.html"
    
    def get_queryset(self):
        return Question.objects.filter(pub_date__lte=timezone.now())
  1. 添加样式表和图像

(1)自定义应用的界面和风格

/* 在polls目录下创建static目录,在创建polls目录,然后添加style.css样式表
 polls/static/polls/style.css */
li a {
    color: green;
}
<!-- 在polls/templates/polls/index.html中添加样式表 -->
<!-- static模板标签生成静态文件的绝对路径 -->
{% load static %}
<link rel="stylesheet" href="{% static 'polls/style.css' %}">

(2)添加背景图

  1. Django自动生成的后台

(1)自定义后台表单

# polls/admin.py
from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    fields = ["pub_date", "question_text"]
# 创建一个模型后台类,接着将其作为第二个参数传递给函数
admin.site.register(Question, QuestionAdmin)
# 将表单分为几个字段集
from django.contrib import admin
from .models import Question

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        # fieldsets元组第一个元素是字段集的标题
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"]}),
    ]

admin.site.register(Question, QuestionAdmin)

(2)添加关联的对象

  • 向后台注册Choice;
  • 在创建投票对象时直接添加好几个选项。
# polls/admin.py
from django.contrib import admin
from .models import Question, Choice

# 使用admin.TabularInline可以使关联对象以一种表格的方式展示
class ChoiceInline(admin.StackedInline):
    model = Choice
    extra = 3

class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"]}),
    ]
    inlines = [ChoiceInline]

admin.site.register(Question, QuestionAdmin)

(3)自定义后台更改列表

# polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
    class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["question_text"]}),
        ("Date information", {"fields": ["pub_date"]}),
    ]
    inlines = [ChoiceInline]
    list_display = ["question_text", "pub_date", "was_published_recently"]
	# 优化过滤器,添加了一个过滤器侧边栏
    list_filter = ["pub_date"]
    # 列表顶部添加搜索框
    search_fields = ["question_text"]
# polls/models.py
import datetime 
from django.db import models
from django.utils import timezone
from django.contrib import admin

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")

    def __str__(self):
        return self.question_text
    # 通过display装饰器给方法添加排序字段
    @admin.display(
        boolean=True,
        ordering="pub_date",
        description="Published recently?",
    )
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now

(4)自定义后台界面和风格

# mysite/settings.py中添加DIRS选项
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
# 获取Django源码的位置
python -c "import django;print(django.__path__)"
<!-- 修改base_site.html内容 -->
{% extends "admin/base.html" %}

{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}

{% block branding %}
<div id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></div>
{% if user.is_anonymous %}
  {% include "admin/color_theme_toggle.html" %}
{% endif %}
{% endblock %}

{% block nav-global %}{% endblock %}

(5)自定义你应用的模板

(6)自定义后台主页

  1. 使用第三方包

(1)安装

python -m pip install django-debug-toolbar

(2)安装其他第三方包

举报

相关推荐

0 条评论