编写你的第一个 Django 应用——总结

urls.py:

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

urlpatterns = [
    path('polls/', include('polls.urls')),
    path('admin/', admin.site.urls),
]
复制代码

docs.djangoproject.com/zh-hans/3.2…

  • 这里将admin/转到admin.site.urls(自带的),这是
  • polls/转到polls.urls(polls文件夹的urls.py),这是截断转入

然后来看一下polls/urls.py:

from django.urls import path

from . import views

# 添加命名空间(比如另一个app里面也有一个views.detail,为了确保不会错,用app_name来确定是哪个app)
app_name = 'polls'  


urlpatterns = [
    # ex: /polls/  [问题索引页——展示最近的几个投票问题。]
    path('', views.index, name='index'),

    # ex: /polls/5/ [问题详情页——展示某个投票的问题和不带结果的选项列表]
    path('<int:question_id>/', views.detail, name='detail'),

    # ex: /polls/5/results/ [问题结果页——展示某个问题的所有答案的数据(答案名和投票数)。]
    path('<int:question_id>/results/', views.results, name='results'),

    # ex: /polls/5/vote/    [投票处理器——用于响应用户为某个问题的特定选项投票的操作]
    path('<int:question_id>/vote/', viewews.vote, name='vote'),
]
复制代码

可以看到

  • /polls/传到index,即问题索引
  • /polls/5/传到detail,即问题详情(问题文本+所有的选项文本)
  • /polls/5/vote/传到vote,即投票处理程序(将票+1,然后重定向到results)
  • /polls/5/results传到results,即最终结果(问题+所有选项文本+票数)

页面展示如下:

image.png

image.png

image.png
先看一下它的models,即polls/models.py:

from django.db import models
# 每个模型被表示为 django.db.models.Model 类的子类

class Question(models.Model):   #Question问题表
    question_text = models.CharField(max_length=200)    #字符字段(CharField):question_text问题
    pub_date = models.DateTimeField('date published')   #时间字段(DateTimeField):pub_data日期
    def __str__(self):
        return self.question_text

class Choice(models.Model): #Choice投票表
    question = models.ForeignKey(Question, on_delete=models.CASCADE) 
    #问题:是问题表的外键,这表明 每个Choice对象都关联到一个Question对象
    choice_text = models.CharField(max_length=200)  #choice_text:投票文本
    votes = models.IntegerField(default=0)  #投票票数
    def __str__(self):
        return self.choice_text
复制代码

我们在命令行运行:

python manage.py makemigrations polls //检测并储存对模型文件的修改(类似于git add.放到暂存区)
python manage.py migrate  //根据修改信息,产生新的模型(数据库)
复制代码

我们执行后的数据库,其实是下面这样:

BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL,
    "question_id" integer NOT NULL
);
ALTER TABLE "polls_choice"
  ADD CONSTRAINT "polls_choice_question_id_c5b4b260_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");

COMMIT;
复制代码

再来看看Django的admin管理员功能
运行:python manage.py createsuperuser后,输入用户名和密码等,就成功创建了一个admin账户,此时就可以进入admin界面了。

如果要允许在admin界面中修改数据库,那么需要在对应的application中的admin.py中注册数据库的权限:

polls/admin.py:

from django.contrib import admin

from .models import Question,Choice

# admin/ 中注册表的增删改权限
admin.site.register(Question)
admin.site.register(Choice)
复制代码

这样,我们就注册了Question表和Choice表了,我们就可以在admin界面去增删该数据库了,很方便,如下图所示:

image.png


ok,下面逐个分析我们的view函数.

1.index

# 展示数据库里以发布日期排序的最近 5 个投票问题,以空格分割:
''' 写法1:这种写法没有利用模板文件,不是一个前后端分离的,所以想改界面的话就真的得在这里改,很麻烦
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)
'''

''' 写法2:这种写法利用了模板文件(templates\polls\index.html)
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]  #选择最近的5个问题组成list
    template = loader.get_template('polls/index.html') #从[遍历选取配置文件INSTALLED_APP的某个dir]/templates/polls/index.html
    # 细节:我们创建的templates文件夹后面跟着这个APP的文件夹名字(polls),这样就避免了错误导入template,因为template的机制是遍历所有app,选择正确的那个
    context = {
        'latest_question_list': latest_question_list,
    }
    # 将上下文context(该例子中是最近的Question list)传入该模板,并将request传入该模板,然后就去执行index.html里的程序,然后将结果返回给服务端
    return HttpResponse(template.render(context, request))
'''

#「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数
# 写法3:这种写法是写法2的简化版,不用去loader.get_template了,...,一次性用render去完成模板的调用
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)
    # 这个语句集成了前面的get_template(模板名)+template.render(context,request)+HttpResponse
复制代码

在index中,我们用去执行模板polls/index.html:

{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">

<h2>Quizs to vote:</h2>
{% if latest_question_list %}   <!--如果Question的list非空,就去遍历...--> 
    <ul>
    {% for question in latest_question_list %}  <!--遍历Question list,用question得到该元素--> 
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
        <!--输出一个到/polls/{{question.id}}的超链接,并显示文字为这个问题的question_text--> 
    {% endfor %}
    </ul>
{% else %}  <!--如果Question的list空,就输出这个提示语--> 
    <p>No polls are available.</p>
{% endif %}
复制代码

这个html就是显示最近的5个问题列表中的所有的问题,并超链接到polls/question.id/detail中,结果如下图所示

image.png

2.detail

#像这个question_id是在urls.py的path函数的参数1中定义的变量,将这个路径的一部分看作变量
''' 写法1:普通的try-except 写法
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id) #pk的意思是primary-key,即id
    except Question.DoesNotExist:   #如果没有得到对应的question,抛出404错误
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question}) 
    # 如果得到了question,那么将这个question对象传入这个detail.html进行处理,将结果返回
'''
# 写法2:利用django内置的get_object_or_404()函数,如果得不到就抛出404,如果得到就继续执行
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})
复制代码

这里就是去执行template/polls/detail.html模板:

<form action="{% url 'polls:vote' question.id %}" method="post"> <!--post的表单:转到polls的views.vote的url-->
    {% csrf_token %}    <!--所有的URL的POST表单都要加这个,来避免跨站点伪造表单-->
    <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 %}<!--遍历该问题的所有答案-->
            <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
            <!--提交表单时:发送choice=#,其中# 为选择的 Choice 的 ID-->
            <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
        {% endfor %}
    </fieldset>
    <input type="submit" value="Vote"><!--提交按钮-->
</form>
复制代码

可以看到,这是一个表单,显示这个问题,让所有的选项允许打勾,并有一个提交的vote按钮,如下图所示:

image.png

点完vote按钮之后,会用post去访问/polls/question.id/vote/界面,跳转到views.vote函数:

3.vote

views.vote函数:

''' 最最简陋的写法:返回这个问题编号...
def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
'''
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)  #question=Question中pk=question_id的对象
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
        # 该question的choice编号为request.POST['choice'],得到该对象为selected_choice
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1  #该答案的投票数+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=(question.id,)))
        # Http**rect的参数reverse('',args=()))为:'/polls/question.id(如3)/results'
        # HttpResponseRedirect是重定向,这里会重定向到/polls/3(例如)/results这个url,这里会返回结果
复制代码

这里所做的主要就是将vote+=1,并提交该事务。提交之后,重定向/polls/question.id/results界面,即执行view.results函数

4.results

results函数:

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})
复制代码

可以看到,这里所做的就是找到Question表中id为传入的question_id的一个question对象,并把该question对象做成一个字典,传入到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>
<!--重新投的超链接:该问题的detail-->
复制代码

可以看到,就是输出这个问题的文本+输出各选项的文本和票数,如下图

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享