1. Django常用命令
- 创建django项目
django-admin startproject mysite
mysite/ #项目的容器,可以任意改名 manage.py #管理该项目的工具 mysite/ #一个python包i,容纳该项目 __init__.py #空文件,python的规则中,代表这是一个包 settings.py #该项目的配置文件 urls.py #项目的URL请求映射规则 asgi.py #项目和ASGI兼容的WEB服务器的入口 wsgi.py #项目和WSGI兼容的WEB服务器的入口
- 开启项目
python manage.py runserver
python manage.py runserver 8080
python manage.py runserver 0.0.0.0:8000
- 创建一个应用(一个项目包含多个应用)
python manage.py startapp polls
- 数据库迁移:
python manage.py migrate
- 根据APP中定义的模型生产迁移事项清单(迁移:根据APP的模型定义models.py,在数据库中创建对应的数据表和字段)
python manage.py makemigrations polls
- 查看迁移所要执行的SQL语句
python manage.py sqlmigrate polls 0001
- 自动检查项目中的问题
python manage.py check
- 打开操作当前项目的命令行(可操作数据库API)
python manage.py shell
- 创建能够登录后台管理页面的管理员账户
python manage.py createsuperuser
2. URL映射
polls/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
mysite/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
每当django遇到include时,会截断匹配到的url部分,然后将剩余的url部分发送到include的参数所指向的URLConf以供进一步处理
path(route, view, kwargs=None, name=None)
参数 | 用法 |
---|---|
route | 字符串,类似于正则表达式,URL匹配规则 |
view | 视图函数或者是一个Include |
kwargs | 向视图函数或方法传递附加参数 |
name | 该条url的名字,用于在django任意地方引用这个url,主要用于模板 |
3. 安装项目
mysite/settings.py
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
4. 数据库
mysite/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
字段 | 内容 |
---|---|
ENGINE | 可选的值有: ‘django.db.backends.sqlite3’, ‘django.db.backends.postgresql’, ‘django.db.backends.mysql’, ‘django.db.backends.oracle’ |
NAME | 数据库名称,SQLite数据库时,是一个数据库文件的绝对路径 |
数据库迁移:python manage.py migrate
该命令会查看settings.py中的INSTALLED_APPS配置,并根据数据库配置来迁移这些APP的数据库文件至新的数据库中
5. 模型
polls/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
- 每个模型都是django.db.models.Model类的子类
- 每个模型的类变量都是模型的一个数据库字段
- 每个类变量都是Field类的实例。如,字符字段表示为CharField,日期时间字段表示为DateTimeField
- 每个类变量的名字就是数据库中代表该模型表的一个列名。
- django支持常用数据库关系,多对一,一对多,一对一。如,ForeignKey代表一个关联关系
编辑模型三步骤:
- 编辑Models.py文件
- 运行 python manage.py makemigrations 为模型的改变生成迁移文件
- 运行 python manage.py migrate 来应用数据库迁移
6. 数据库操作
打开当前项目的交互命令行:python manage.py shell
>>> from polls.models import Choice, Question # Import the model classes we just wrote.
# No questions are in the system yet.
>>> Question.objects.all()
<QuerySet []>
# Create a new Question.
# Support for time zones is enabled in the default settings file, so
# Django expects a datetime with tzinfo for pub_date. Use timezone.now()
# instead of datetime.datetime.now() and it will do the right thing.
>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# Save the object into the database. You have to call save() explicitly.
>>> q.save()
# Now it has an ID.
>>> q.id
1
# Access model field values via Python attributes.
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)
# Change values by changing the attributes, then calling save().
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() displays all the questions in the database.
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
为了调用Question.objects.all()
时便于展示已经创建出来的对象,添加__str__()方法
polls/models.py
from django.db import models
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
给模型添加自定以的方法,添加
polls/models.py
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
>>> from polls.models import Choice, Question
# Make sure our __str__() addition worked.
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django provides a rich database lookup API that's entirely driven by
# keyword arguments.
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# Get the question that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# Request an ID that doesn't exist, this will raise an exception.
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Question.objects.get(id=1).
>>> Question.objects.get(pk=1)
<Question: What's up?>
# Make sure our custom method worked.
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# Give the Question a couple of Choices. The create call constructs a new
# Choice object, does the INSERT statement, adds the choice to the set
# of available choices and returns the new Choice object. Django creates
# a set to hold the "other side" of a ForeignKey relation
# (e.g. a question's choice) which can be accessed via the API.
>>> q = Question.objects.get(pk=1)
# Display any choices from the related object set -- none so far.
>>> q.choice_set.all()
<QuerySet []>
# Create three choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice objects have API access to their related Question objects.
>>> c.question
<Question: What's up?>
# And vice versa: Question objects get access to Choice objects.
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# The API automatically follows relationships as far as you need.
# Use double underscores to separate relationships.
# This works as many levels deep as you want; there's no limit.
# Find all Choices for any question whose pub_date is in this year
# (reusing the 'current_year' variable we created above).
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# Let's delete one of the choices. Use delete() for that.
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
7. 后台管理
创建后台管理员:python manage.py createsuperuser
默认管理员地址:http://127.0.0.1:8000/admin/
管理页面添加应用
<polls/admin.py>
from django.contrib import admin
from .models import Question, Choice
admin.site.register(Question)
admin.site.register(Choice)
8. 视图
添加其他视图
<polls/views.py>
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Choice, Question
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):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST["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
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,)))
HttpResponseRedirect
对象只需要一个参数,即重定向后的路径。处理表单post数据时,应当总是返回一个 HttpResponseRedirect
对象
可以通过reverse
函数来动态生成路径,避免硬编码
将这些视图添加到应用的URL映射模块中
<polls/urls.py>
from django.urls import path
from . import views
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/', views.vote, name='vote'),
]
URL映射模块中的<int:question_id>
中的int代表类型,quesiton_id是该参数的命名
该question_id命名所代表的参数将会被传递给视图函数对应的同名参数
每个视图必须要做的只有两件事:
- 返回一个包含请求页面内容的
HttpResponse
对象 - 或者抛出一个异常,比如
Http404
视图函数从数据库中取出数据,并展示
<polls/views.py>
from django.http import HttpResponse
from .models import Question
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)
# Leave the rest of the views (detail, results, vote) unchanged
8. 模板
配置模板:在应用下创建下创建名为templates
的文件夹,再创建应用同名的文件夹,模板放在该目录下
即:polls/templates/polls/index.html
- 项目的 TEMPLATES 配置项描述了 Django 如何载入和渲染模板。
- 默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 “templates” 子目录。
使用模板,通过给模板传入一个context字典:
<polls/views.py>
from django.http import HttpResponse
from django.template import loader
from .models import Question
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))
快捷函数:render()
,会返回一个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)
<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>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
django模板文档:https://docs.djangoproject.com/zh-hans/4.1/topics/templates/
{{ question.question_text }}
模板系统会首先对question使用字典查找,即question.get(‘question_text’)
如果字典查找失败,会尝试属性查找,即question.question
如果属性查找失败,会尝试列表查找,即question[数字]操作
{% for choice in question.choice_set.all %}
question.choice_set.all会被解释为python代码question.choice_set.all(),返回一个可迭代的Choice对象,这一对象可在{% for %}标签内部使用
{% url %}
去除模板中的硬编码URL
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
改写为:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
#或者为了区分不同的App具有同样名叫detail的url命名,可采用命名空间
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
#采用命名空间时,需要在urls.py中添加
app_name = "polls"
<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>
<polls/templates/polls/detail.html>
<h1>{{ question.question_text }}</h1>
<hr />
<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 %}
<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="Vote">
</form>
9. 抛出404错误
<polls/views.py>
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})
快捷函数:get_object_or_404()
,先尝试用get获取一个对象,如果不存在就抛出Http404错误
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
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>
10. 表单
<polls/templates/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 %}
<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="Vote">
</form>
11. 静态文件
- 创建
polls/static/polls
目录 - 在
polls/static/polls
目录下创建style.css
- style.css中填写内容
li a { color: green; }
- 模板
polls/templates/polls/index.html
头文件中添加引用该css{% load static %} <link rel="stylesheet" href="{% static 'polls/style.css' %}">
- 重启django服务器
12. 自定义后台管理界面
- 表单排序
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 = [ (None, {"fields": ["question_text"]}), ("Date information", {"fields": ["pub_date"]}), ] admin.site.register(Question, QuestionAdmin).
- 创建Question时,自动创建Choice
from django.contrib import admin from .models import Choice, Question class ChoiceInline(admin.StackedInline): model = Choice extra = 3 class QuestionAdmin(admin.ModelAdmin): fieldsets = [ (None, {"fields": ["question_text"]}), ("Date information", {"fields": ["pub_date"], "classes": ["collapse"]}), ] inlines = [ChoiceInline] admin.site.register(Question, QuestionAdmin)
- 创建Querstion时,自动创建Choice,横向展示
class ChoiceInline(admin.TabularInline): ...
- 展示Question列表时,额外展示其他字段
class QuestionAdmin(admin.ModelAdmin): # ... list_display = ["question_text", "pub_date", "was_published_recently"]
- 使用装饰器改进展示效果
#<polls/models.py> from django.contrib import admin class Question(models.Model): # ... @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
- 添加一个过滤器便于查找
class QuestionAdmin(admin.ModelAdmin): # ... list_filter = ["pub_date"]
- 添加搜索功能
class QuestionAdmin(admin.ModelAdmin): # ... search_fields = ["question_text"]
- 自定义后台界面和风格
- 修改settings.py
#<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", ], }, }, ]
- 创建
mysite/templates/admin
目录 - 从django安装目录中复制
django/contrib/admin/templates/admin/base_site.html
至mysite/templates/admin
目录中 - 修改
base_site.html
内容{% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1> {% endblock %}
- 修改settings.py