Django动态网页 Python项目

0.0 规范

我要编写一个名为“学习笔记”的 Web 应用程序,让用户能够记录感兴趣的主题,并在学习每个主题的过程中添加日志条目。“学习笔记”的主页对这个网站进行描述,并邀请用户注册或登录。用户登录后,可以创建新主题、添加新条目以及阅读既有的条目。

目录 ll_project 包含 4 个文件, 其中最重要的是 settings.pyurls.pywsgi.py。文件 settings.py 指定 Django 如何与系统交互以及如何管理项目。
在开发项目的过程中,我们将修改其中的一些设置,并添加一些设置。
文件 urls.py 告诉 Django,应创建哪些网页来响应浏览器请求。
文件 wsgi.py 帮助 Django 提供它创建的文件,名称是 web server gateway interface(Web 服务器网关接口)的首字母缩写。

1.0 创建环境

1.1 创建虚拟环境

在独立的项目文件夹下运行

python -m venv ll_env

python -m ensurepip --default-pip # 重装pip

将会创造一个ll_env文件夹
可以使用下面的命令激活虚拟环境。激活虚拟环境后,安装的模组将只在虚拟环境中生效,而不干扰到电脑上Python的模组

source ll_env/bin/activate # Linux系统
source ll_env/Scripts/activate # Windows系统

(ll_env)
work_directory$ # 命令提示符前会显示(ll_env)

使用deactive取消激活虚拟环境

1.2 安装配置Django

pip install --upgrade pip # 更新pip
pip install django # 安装Django
django-admin startproject ll_project . # 初始化Django
python manage.py migrate # 生成数据库
python manage.py runserver # 生成预览,可以在本地8000端口看到网页
Django手册: https://docs.djangoproject.com/en/4.1/ref/models/fields/

1.3 创建和配置App

  • 创建app
python manage.py startapp learning_logs(app_name)
  • 保存更改
# 更改了.py文件就要重新执行下面的命令保存,否则会出错
python manage.py makemigrations learning_logs(app name)
python manage.py migrate

每当需要修改“学习笔记”管理的数据时,都采取如下三个步骤:修改
models.py,对 learning_logs 调用 makemigrations,以及让 Django迁移项目。

1.4 创建管理员及注册模型到admin页面

  • 创建管理员
python manage.py createsuperuser
ll_admin, Abc123
  • 注册模型,打开admin.py并注册模型
from django.contrib import admin

from .models import Model1, Model2 # improt models with their name

admin.site.register(Model1)

1.5 定义模型Models

  1. 打开app_name/models.py
# your models:
class Model_name1(models.Model):
"""Document"""
text = models.TextField() # field attribute

def __str__(self):
"""Return a string representation of the model."""
return self.text
# will be used as output

class Model_name2(models.Model):
"""Document"""
content = models.TextField(max_length=200) # parameter is optional
something_else = models.ForeignKey(Model_name1, on_delete=models.CASCADE)
# foreign key, cascading delete级联删除

def __str__(self):
"""Return a string representation of the model."""
return self.content

1.6 激活模型

  • settings.py下找到INSTALLED_APPS并添加App名称
INSTALLED_APPS = [
# My apps.
'app_name', # the app's file name

# Default django apps.
'django.contrib.admin',
--snip--
]

1.7 Shell命令行

$ python manage.py shell
from learning_logs.models import Topic
Topic.objects.all() # 返回Queryset, 查询结果集

topics = Topic.objects.all()
# 列出topic的id与名称
for topic in topics:
print(topic.id, topic)

# 知道id后可以调用方法查看相应内容
t = Topic.objects.get(id=1)
t.text
t.date_added

t.entry_set.all() # 查看entries具体内容

2.0 制作网页

使用 Django 创建网页的过程分为三个阶段:定义 URL,编写视图,以及编写模板。按什么顺序完成这三个阶段无关紧要,但在本项目中,总是先定义 URL 模式。URL 模式描述了 URL 的构成,让 Django 知道如何将浏览器请求与网站 URL 匹配,以确定返回哪个网页。

2.1 制作主页

  1. 增加配置文件,在ll_project中编辑urls.py
from django.urls import include # 导入方法

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app_folder_name.urls')), # 添加app文件夹的名称
]
  1. 创建并编辑app_folder_name/urls.
"""Defines URL patterns for learning_logs."""

from django.urls import path

from . import views

app_name = 'learning_logs' # app文件夹名称
urlpatterns = [
# Home page
path('', views.index, name='index'),
]
  1. 编写views.py
from django.shortcuts import render

def index(request):
"""The home page for Learning Log."""
return render(request, 'app_floder_name/index.html') # template路径
  1. 创建tmplates/app_floder_name文件夹,并创建index.html文件
<p>Learning Log</p>

<p>Learning Log helps you keep track of your learning, for any topic you're
interested in.</p>

2.2 制作网页模板

  1. 创建base.html
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a>
</p>

{% block content %}{% endblock content %}
  1. 使用base.html模板
{% extends 'learning_logs/base.html' %} <!-- 'extends 表示继承哪个模板 -->

{% block content %}
<p>Learning Log helps you keep track of your learning, for any topic you're
interested in.</p>
{% endblock content %}

2.3 制作Topic与Entry网页

2.3.1 Topics

  1. 修改urls.py,创建topics界面
urlpatterns = [
# Home page
path('', views.index, name='index'),
# Page that shows all topics.
path('topics/', views.topics, name='topics'),
# 标注网址、调用方法、名称
]
  1. 修改views.py,创建topics方法
from .models import Topic # 导入Topic类
def topics(request):
"""Show all topics."""
topics = Topic.objects.order_by('date_added') # 顺序
context = {'topics': topics} # 内容组织方式
return render(request, 'learning_logs/topics.html', context) # 指出html文件名,传入内容
  1. 创建topics.html界面
{% extends 'learning_logs/base.html' %} # 模板

{% block content %}

<p>Topics</p>

<ul> # 无序列表 unordered list
{% for topic in topics %} # for循环
<li>{{ topic.text }}</li> # 有序列表 list item, text是models.py的Topic类中定义的属性
{% empty %}
<li>No topics have been added yet.</li>
{% endfor %}
</ul>

{% endblock content %}
  1. 修改base.html,添加链接
<p>
<a href="{% url 'learning_logs:index' %}">Learning Log</a> -
<a href="{% url 'learning_logs:topics' %}">Topics</a> # Topics链接
</p>

2.3.2 Topic

  • 同样是4步走
  1. 修改urls.py,创建topic界面
    1. 修改views.py,创建topic方法
  2. 创建topic.html界面
  3. 修改topics.html,添加链接(不是修改base.html
# urls.py
urlpatterns = [
--snip--
# 特定主题的详细页面
path('topics/<int:topic_id>/', views.topic, name='topic'),
# <int:topic_id>变量匹配每个单独topic的id
]

# views.py
def topic(request, topic_id):
"""显示单个主题及其所有的条目"""
topic = Topic.objects.get(id=topic_id) # query检索id
entries = topic.entry_set.order_by('-date_added') # 负date,按时间倒序,新的在前
context = {'topic': topic, 'entries': entries} # 组织方式
return render(request, 'learning_logs/topic.html', context) # 指定html文件名,返回内容
<!-- topic.html -->
{% extends 'learning_logs/base.html' %}

{% block content %}

<p>Topic: {{ topic.text }}</p>
<p>Entries:</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.date_added|date:'M d, Y H:i' }}</p> <!-- |设置时间格式 -->
<p>{{ entry.text|linebreaks }}</p>
<!-- |textlinebreaks将包含换行符的长条目转换为浏览器能够理解的格式,
以免显示为不间断的文本块。-->
</li>
{% empty %}
<li>There are no entries for this topic yet.</li>
{% endfor %}
</ul>

{% endblock content %}

<!-- topics.html -->
{% for topic in topics %}
<li>
<a href="{% url 'learning_logs:topic' topic.id %}">
{{ topic.text }}</a>
</li>
{% empty %}

1.1 第一阶段总结

  • 构建learning_logs的过程基本上是在抄代码,只有到做课后练习,完成pizzas_project的时候才真正在学习。例如为什么import Topic而不是Entry,在我课后练习的时候就给我带来了一个无法运行网页的代价:不理解subsequence_set是怎么来的,就不能够成功地导入Pizza模组并成功设置toppings参数;不理解.order_by()方法建立在什么基础上,不阅读手册,就想不到在不是博客,而是菜单的pizza项目中使用.all()
  • 理解是那么的重要,它几乎占据了学习的全部:理解过后就只剩计算了。而学习的乐趣也大多在于这个理解和融会贯通。
  • 最终我明白:正是在错误中我得以学习,不要畏惧错误。正如CSAPP所说:错误地使用内存,最好的结果是程序马上崩溃,最坏的结果是程序运行一段时间才出现不知道哪里冒出来的问题。让我多多崩溃,这是相对最好的结果。
  • 2024/6/25:有一些小细节也会导致错误,例如DateField和DateTimeField是不一样的
  • 2024/6/26:html -> views.py字典 -> models.py定义字段

3.0 用户账户

3.1 让用户添加Topics

  • 添加新主题,四步走
    1. 创建app_folder/forms.py,添加表单
    2. 修改urls.py,添加网页
    3. 修改views.py,添加定义的函数
    4. 创建html模板
    5. 链接到页面
# forms.py
from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
class Meta:
model = Topic
fields = ['text']
labels = {'text': ''} # 不要为字段text生成标签
widgets = {'text': forms.Textarea(attrs={'cols': 80})} # 使用80列宽(默认是40列)

# urls.py
urlpatterns = [
# 用于添加新主题的网页
path('new_topic/', views.new_topic, name='new_topic'),
]

# views.py
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm

def new_topic(request):
"""添加新主题"""
if request.method != 'POST':
# 未提交数据:创建一个新表单
form = TopicForm()
else:
# POST 提交的数据:对数据进行处理
form = TopicForm(data=request.POST)
if form.is_valid(): # 检查是否有效
form.save() # 写入数据库
return redirect('learning_logs:topics')
# 显示空表单或指出表单数据无效
context = {'form': form}
return render(request, 'learning_logs/new_topic.html',
context)
<!-- new_topic.html -->
{% extends "learning_logs/base.html" %}
{% block content %}

<p>Add a new topic:</p>
<form action="{% url 'learning_logs:new_topic' %}"
method='post'>
{% csrf_token %} <!-- web安全,防止攻击 -->
{{ form.as_div }} <!-- 自动创建表单 -->
<button name="submit">Add topic</button>
</form>

{% endblock content %}

<!-- topics.html添加链接 -->
{% extends "learning_logs/base.html" %}
{% block content %}

<p>Topics</p>
<ul>
--snip--
</ul>
<a href="{% url 'learning_logs:new_topic' %}">Add a new topic</a>

{% endblock content %}

3.2 新增、修改entries

  • 新增
# forms.py
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
--snip--
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = ['text']
labels = {'text': ''}
widgets = {'text': forms.Textarea(attrs={'cols': 80})}

# urls.py
urlpatterns = [
--snip--
# 用于添加新条目的页面
path('new_entry/<int:topic_id>/', views.new_entry,
name='new_entry'),
]

# views.py
from django.shortcuts import render, redirect
from .models import Topic
from .forms import TopicForm, EntryForm
--snip--
def new_entry(request, topic_id):
"""在特定主题中添加新条目"""
topic = Topic.objects.get(id=topic_id)
if request.method != 'POST':
# 未提交数据:创建一个空表单
form = EntryForm()
else:
# POST 提交的数据:对数据进行处理
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry = form.save(commit=False)
new_entry.topic = topic
new_entry.save()
return redirect('learning_logs:topic',
topic_id=topic_id)
# 显示空表单或指出表单数据无效
context = {'topic': topic, 'form': form}
return render(request, 'learning_logs/new_entry.html',
context)
<!-- new_entry.html -->
{% extends "learning_logs/base.html" %}
{% block content %}

<p>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</p>
<p>Add a new entry:</p>
<form action="{% url 'learning_logs:new_entry' topic.id %}" method='post'>
{% csrf_token %}
{{ form.as_div }}
<button name='submit'>Add entry</button>
</form>

{% endblock content %}

<!-- topic.html -->
<p>Entries:</p>
<p>
<a href="{% url 'learning_logs:new_entry' topic.id %}">Add new entry</a>
</p>
  • 修改
# urls.py
urlpatterns = [
--snip--
# 用于编辑条目的页面
path('edit_entry/<int:entry_id>/', views.edit_entry,
name='edit_entry'),
]

# views.py
from django.shortcuts import render, redirect
from .models import Topic, Entry
from .forms import TopicForm, EntryForm
--snip--
def edit_entry(request, entry_id):
"""编辑既有的条目"""
entry = Entry.objects.get(id=entry_id)
topic = entry.topic
if request.method != 'POST':
# 初次请求:使用当前的条目填充表单
form = EntryForm(instance=entry)
else:
# POST 提交的数据:对数据进行处理
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return redirect('learning_logs:topic', topic_id=topic.id)

context = {'entry': entry, 'topic': topic, 'form': form}
return render(request, 'learning_logs/edit_entry.html',
context)
{% extends "learning_logs/base.html" %}
{% block content %}

<p>
<a href="{% url 'learning_logs:topic' topic.id %}">{{ topic }}</a>
</p>
<p>Edit entry:</p>
<form action="{% url 'learning_logs:edit_entry' entry.id %}" method='post'>
{% csrf_token %}
{{ form.as_div }}
<button name="submit">Save changes</button>
</form>

{% endblock content %}

3.3 让用户登录、注册

3.4 让用户只能看见自己的topic

4.0 美化页面

4.1 安装并启用bootstrap5

(ll_env)learning_log$ pip install django-bootstrap5

settings.py

INSTALLED_APPS = [
#
我的应用程序
'learning_logs',
'accounts',
#
第三方应用程序
'django_bootstrap5',

5.0 部署

生成配置文件requirements.txt

$ pip freeze > requirements.txt
$ pip install -r requirements.txt

YAML (YAML Ain’t Markup Language)