在settings.py文件中,settings文件中顶部的INSTALLED_APPS
设置项。它列出了所有的项目中被激活的Django应用(app)。你必须将你自定义的app注册在这里。每个应用可以被多个项目使用,并且可以打包和分发给其他人在他们的项目中使用。
默认情况,INSTALLED_APPS
中会自动包含下列条目,它们都是Django自动生成的:
- django.contrib.admin:admin管理后台站点
- django.contrib.auth:身份认证系统
- django.contrib.contenttypes:内容类型框架
- django.contrib.sessions:会话框架
- django.contrib.messages:消息框架
- django.contrib.staticfiles:静态文件管理框架
这些应用文件需要建立一些数据库表,我们需要在终端中使用命令创建数据库表:
$ python manage.py migrate
migrate命令将遍历INSTALLED_APPS
设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息
Django模型model
模型本质上就是数据库表的布局,再附加一些元数据。
Django通过自定义Python类的形式来定义具体的模型,每个模型的物理存在方式就是一个Python的类Class,每个模型代表数据库中的一张表,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。Django通过模型,将Python代码和数据库操作结合起来,实现对SQL查询语言的封装。
# 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)
这个例子中,每一个字段都是Field
类的一个实例,例如用于保存字符数据的CharField和用于保存时间类型的DateTimeField,它们告诉Django每一个字段保存的数据类型。
最后请注意,我们使用ForeignKey
定义了一个外键关系。它告诉Django,每一个Choice关联到一个对应的Question(注意要将外键写在‘多’的一方)。Django支持通用的数据关系:一对一,多对一和多对多
启用模型
Django在我们启动命令时,会做两件事:
- 创建该app对应的数据库表结构
- 为Question和Choice对象创建基于Python的数据库访问API
$ python manage.py makemigrations polls
启动命令前应该先makemigrations一下。通过运行makemigrations
命令,相当于告诉Django你对模型有改动,并且你想把这些改动保存为一个“迁移(migration)”。
migrations
是Django保存模型修改记录的文件,这些文件保存在磁盘上。在例子中,它就是polls/migrations/0001_initial.py
,你可以打开它看看,里面保存的都是人类可读并且可编辑的内容,方便你随时手动修改。
$ python manage.py sqlmigrate polls 0001
继续运行指令
$ python manage.py migrate
migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。
migrations的功能非常强大,允许你随时修改你的模型,而不需要删除或者新建你的数据库或数据表,在不丢失数据的同时,实时动态更新数据库。记住修改模型时的操作分三步:
- 在models.py中修改模型;
- 运行
python manage.py makemigrations
为改动创建迁移记录; - 运行
python manage.py migrate
,将操作同步到数据库。
之所以要将创建和实施迁移的动作分成两个命令两步走是因为你也许要通过版本控制系统(例如github,svn)提交你的项目代码,如果没有一个中间过程的保存文件(migrations),那么github如何知道以及记录、同步、实施你所进行过的模型修改动作呢?毕竟,github不和数据库直接打交道,也没法和你本地的数据库通信。但是分开之后,你只需要将你的migration文件(例如上面的0001)上传到github,它就会知道一切。
使用模型的API
$ python manage.py shell
输入如上指令即可进入Python中的Django shell界面,调用manage.py
参数能将DJANGO_SETTINGS_MODULE
环境变量导入,它将自动按照mysite/settings.py
中的设置,配置好python shell环境。这样,我们就可以导入和调用任何项目内的模块了。
进入shell中,尝试下面的API:
>>> from polls.models import Question, Choice # 导入我们写的模型类 # 现在系统内还没有questions对象 >>> Question.objects.all() <QuerySet []> # 创建一个新的question对象 # Django推荐使用timezone.now()代替python内置的datetime.datetime.now() # 这个timezone就来自于Django唯一的依赖库pytz from django.utils import timezone >>> q = Question(question_text="What's new?", pub_date=timezone.now()) # 你必须显式的调用save()方法,才能将对象保存到数据库内 >>> q.save() # 默认情况,你会自动获得一个自增的名为id的主键 >>> q.id 1 # 通过python的属性调用方式,访问模型字段的值 >>> q.question_text "What's new?" >>> q.pub_date datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>) # 通过修改属性来修改字段的值,然后显式的调用save方法进行保存。 >>> q.question_text = "What's up?" >>> q.save() # objects.all() 用于查询数据库内的所有questions >>> Question.objects.all() <QuerySet [<Question: Question object>]>
这里等一下:上面的<Question: Question object>
是一个不可读的内容展示,你无法从中获得任何直观的信息,为此我们需要一点小技巧,让Django在打印对象时显示一些我们指定的信息。
返回polls/models.py
文件,修改一下question和Choice这两个类,代码如下:
from django.db import models from django.utils.encoding import python_2_unicode_compatible @python_2_unicode_compatible # 当你想支持python2版本的时候才需要这个装饰器 class Question(models.Model): # ... def __str__(self): # 在python2版本中使用的是__unique__ return self.question_text @python_2_unicode_compatible class Choice(models.Model): # ... def __str__(self): return self.choice_text
这个技巧不但对你打印对象时很有帮助,在你使用Django的admin站点时也同样有帮助。这样提高了Django的版本兼容性
另外,这里我们自定义一个模型的方法,用于判断问卷是否最近时间段内发布度的:
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)
这里在return中实现了条件筛选返回。在实际开发中可以更灵活的使用来产生更高的开发效率。
保存修改后,我们重新启动一个新的python shell,再来看看其他的API:
>>> from polls.models import Question, Choice # 先看看__str__()的效果,直观多了吧? >>> Question.objects.all() <QuerySet [<Question: What's up?>]> # Django提供了大量的关键字参数查询API >>> Question.objects.filter(id=1) <QuerySet [<Question: What's up?>]> >>> Question.objects.filter(question_text__startswith='What') <QuerySet [<Question: What's up?>]> # 获取今年发布的问卷 >>> from django.utils import timezone >>> current_year = timezone.now().year >>> Question.objects.get(pub_date__year=current_year) <Question: What's up?> # 查询一个不存在的ID,会弹出异常 >>> Question.objects.get(id=2) Traceback (most recent call last): ... DoesNotExist: Question matching query does not exist. # Django为主键查询提供了一个缩写:pk。下面的语句和Question.objects.get(id=1)效果一样. >>> Question.objects.get(pk=1) <Question: What's up?> # 看看我们自定义的方法用起来怎么样 >>> q = Question.objects.get(pk=1) >>> q.was_published_recently() True # 让我们试试主键查询 >>> q = Question.objects.get(pk=1) # 显示所有与q对象有关系的choice集合,目前是空的,还没有任何关联对象。 >>> q.choice_set.all() <QuerySet []> # 创建3个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对象可通过API访问和他们关联的Question对象 >>> c.question <Question: What's up?> # 同样的,Question对象也可通过API访问关联的Choice对象 >>> q.choice_set.all() <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> >>> q.choice_set.count() 3 # API会自动进行连表操作,通过双下划线分割关系对象。连表操作可以无限多级,一层一层的连接。 # 下面是查询所有的Choices,它所对应的Question的发布日期是今年。(重用了上面的current_year结果) >>> Choice.objects.filter(question__pub_date__year=current_year) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> # 使用delete方法删除对象 >>> c = q.choice_set.filter(choice_text__startswith='Just hacking') >>> c.delete()
模型使用是实现动态网站和数据库交互的核心,更是Django项目的核心。要重点理解。
part4 表单和类视图部分
1.表单
先贴上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 %} {% 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>
要点总结:
1.在form表单中属性,action指要发送的目标url,method表示要提交数据的方式(get或post)
2.对于form表单发送post请求,必须考虑一个跨站请求伪造的安全问题,要在form表单内添加一条{% csrf_token %}标签,标签名不可更改,固定格式,位置任意,只要是在form表单内。这个方法对form表单的提交方式方便好使,但如果是用ajax的方式提交数据,那么就不能用这个方法了。
小问题:ajax方式提交数据如何防范CSRF攻击?
3.forloop.counter是Django提供的一个用于计算当前循环次数的变量,还有给循环项目添加有序数标的功能。
4.表单:上面的模板显示一系列单选按钮,按钮的值是选项的ID,按钮的名字是字符串"choice"。这意味着,当你选择了其中某个按钮,并提交表单,一个包含数据choice=#
的POST请求将被发送到指定的url,#
是被选择的选项的ID。这就是HTML表单的基本概念。
对于vote视图函数(polls/views.py)源码:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse from .models import Choice, Question # ... 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): # 发生choice未找到异常时,重新返回表单页面,并给出提示信息 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.这里的request.POST是一个类似字典的对象。允许我们通过通过key访问对应的值。本例中,request.POST['choice']返回的值是一个string字符串,即使是一个数字,也不是整形,都是字符串类型。
2.当处理用户的动作,计数器加一后,通过HttpResponseRedirect跳转到一个新的页面。为何这里不用Httpresponse?因为这样用户对是否完成动作没有好的体验。
HttpResponseRedirect中需要一个目标url参数。一个良好的编程习惯:当处理完POST请求后,应用HttpResponseRedirect返回一个响应,不仅仅对Django来说是这样。
3.Django中处理http请求-响应流程:都是通过路由分发,视图处理,模板渲染,模型这几个流程。
如何利用类视图减少重复代码?
为了处理views.py中类似的功能代码,我们利用Django提供的偷懒方式:类视图
先将urls.py中的代码修改下,改成类似这样的
urlpatterns = [ url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
接下来修改views.py中的函数,将def function()函数改为class function(object)类函数。需要先调用django.views中的generic函数。generic函数中还有两个子函数,DetailView与ListView,在类函数中是需要作为父类继承来的。
关于静态文件
我试着按照教程给网页添加背景图片。
首先创建css样式表。在polls目录中创建一个static
目录。Django将在那里查找静态文件,这与Django在polls/templates/中寻找对应的模板文件的方式是一致的。
Django的STATICFILES_FINDERS
设置项中包含一个查找器列表,它们知道如何从各种源中找到静态文件。
在刚才的static
目录中新建一个polls
子目录,再在该子目录中创建一个style.css
文件。换句话说,这个css样式文件应该是polls/static/polls/style.css
。你可以通过书写polls/style.css
在Django中访问这个静态文件,与你如何访问模板的路径类似。这里涉及到了一个知识点:静态文件的命名空间。简单来说就是,Django在项目目录下寻找与导入同名的静态文件。若目录下存在多个不同应用,不同应用中还存在两个同名的静态文件(这很有可能!),那Django将会使用它找到的第一个匹配的静态文件,这样很容易导致加载错误。为了避免这种问题,我们可以在应用下添加命名空间,也就是说,将这些静态文件放进以它们所在的应用的名字同名的另外一个子目录下。这样就解决了问题。
在polls/static/polls/
目录下创建一个用于存放图片的images
子目录,在这个子目录里放入`background.gif文件。换句话说,这个文件的路径是polls/static/polls/images/background.gif。之后再css文件中添加
body { background: white url("images/background.gif") no-repeat right bottom; }
同时在index.html文件中最顶部声明
{% load staticfiles %} <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}" />
但是随后刷新时候出现问题,发现网页显示不出背景图片,先查了下开发者工具里显示的500错误,图片加载失败。我上网查找了下原因,发现可能是setting.py设置的静态文件路径出了问题,原来的静态路径是
STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR ,"static") #实际名 ,即实际文件夹的名字 )
然后我更改成为
STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(os.path.dirname(__file__), '../static/').replace('\','/'), )
刷新后图片显示正常!看来是静态文件的路径设置出了问题,导致并没有按照预定找到路径。
part7 通过admin页面更好的管理
分开显示:
polls/admin.py
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)
把一对多关联显示,这里额外显示3个
polls/admin.py
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)
choice列展示换位横向展示
class ChoiceInline(admin.TabularInline):
在数据列表里面展示更多的信息,字段和方法都可以
list_display = ('question_text', 'pub_date', 'was_published_recently')
点击字段名是自带排序的,除了方法,没有确定的返回值,不能排序,我们可以这样
定义某字段的排序值为别的值,
改变值为布尔值
改变字段显示名称
class Question(models.Model): # ... def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now was_published_recently.admin_order_field = 'pub_date' was_published_recently.boolean = True was_published_recently.short_description = 'Published recently?'
添加一个筛选条件
list_filter = ['pub_date']
因pub_date是一个DateTimeField字段,所以Django自动添加了几个时间过滤器。
添加一个搜索条件,似乎只能添加一个字符串的搜索条件
search_fields = ['question_text']
查找import包的路径,比如django
python -c "import django; print(django.__path__)"
设定项目自己的templates
mysite/settings.py
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(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', ], }, }, ]
在manage.py同目录下创建templates/admin。复制django自带的目录下的base_site.html复制到admin文件下,改变内容
{% block branding %} <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1> {% endblock %}
项目会自动覆盖原django的文件,而展示这个文件内容,一般最重要的是admin下的index.html,也可以像这样覆盖。
关于反向查询
django表结构中,可以设置OneToOne,ManyToMany,ManyToOne多种关联关系。这是ORM的强大之处。
一对多反向查询用__set,一对一反查用句点属性标识法加上小写的表的名字,多对多反查可以用select_related或.all。
还有一点,在views视图函数胡中能从request对象中获取到的数据,在前端模板中也都能获取到。
参考:http://www.liujiangblog.com/course/django/88 很好的Django学习教程!