Django(三):进阶------扩充Form、ModelForm、Model、事务、query
66第1节,67第2节
一 、model
1.创建books项目
创建app:python manage.py startapp books
在mysite/setting.py中,添加app:
INSTALLED_APPS = [ 'books.apps.booksConfig',
数据表结构设计:
一个作者,可能出版多本书;一本书可能有多个作者。因此作者与书之间的关系,是-多对多关系;存放作者的数据表和存放书的数据表,设计为多对多关系ManyToMany。
一本书,通常来说,只由一个出版社出版;一个出版社出版多本书。因此某出版社与书之间的关系,是一对多的关系;某本书与出版社的关系,是一对一的关系;存放书的数据表和存放出版社的数据表,设计为多对一的关系,用外键ForeignKey。
如下图:原始数据表的关系中,一本书对应两个作者;在关系型数据库中,数据库是不能维护这样的关系的,除非在代码层去维护,将两个作者处理为一个。对这种一对多或多对多的关系,数据库会另外生成一张关系型数据表,来维护这种关系。
=>多对多或一对多的关系,会生成关系表=>一对一关系,不会生成额外的关系表。用外键即可
编写数据结构models.py:
# books/models.py from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=50) website = models.URLField() def __str__(self): return self.name class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() def __str__(self): return '%s %s' % (self.first_name, self.last_name) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) def __str__(self): return self.title
查询python manage.py支持哪些命令: python manage.py
查看数据库版本控制记录:# ls books/migrations 返回__init__.py,说明无任何版本记录
生成数据库版本控制记录:# python manage.py makemigrations 可以看到,仅有books更新了记录;其它数据库没有变动,所以没有更新
再次查看版本记录:# ls books/migrations 可以看到刚才生成的记录,0001_initial.py
查看记录的数据结构:# python manage.py sqlmigrate books 0001_initial
BEGIN; -- -- Create model Author -- CREATE TABLE "books_author" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "first_name" varchar(30) NOT NULL, "last_name" varchar(40) NOT NULL, "email" varchar(254) NOT NULL); -- -- Create model Book -- CREATE TABLE "books_book" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT NULL); CREATE TABLE "books_book_authors" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "book_id" integer NOT NULL REFERENCES "books_book" ("id"), "author_id" integer NOT NULL REFERENCES "books_author" ("id")); -- -- Create model Publisher -- CREATE TABLE "books_publisher" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(30) NOT NULL, "address" varchar(50) NOT NULL, "city" varchar(60) NOT NULL, "state_province" varchar(50) NOT NULL, "website" varchar(200) NOT NULL); -- -- Add field publisher to book -- ALTER TABLE "books_book" RENAME TO "books_book__old"; CREATE TABLE "books_book" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT NULL, "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id")); INSERT INTO "books_book" ("publisher_id", "id", "title") SELECT NULL, "id", "title" FROM "books_book__old"; DROP TABLE "books_book__old"; CREATE INDEX "books_book_2604cbea" ON "books_book" ("publisher_id"); COMMIT;
根据数据库版本控制记录,将它更新到数据库中:# python manage.py migrate
写一个添加出版社的view:
# books/templates/books/publisher_add.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post" action=""> {% csrf_token %} <label>Name: </label> <input type="text" name="name"> <br> <label>Address: </label> <input type="text" name="address"> <br> <label>City: </label> <input type="text" name="city"> <br> <label>state_province: </label> <input type="text" name="state_province"> <br> <label>country: </label> <input type="text" name="country"> <br> <label>website: </label> <input type="text" name="website"> <br> <input type="submit" value="Submit"> </form> </body> </html>
# books/views.py from django.shortcuts import render, redirect def publisher_add(request): if request.method == 'POST': name = request.POST.get('name', '') address = request.POST.get('address', '') city = request.POST.get('city', '') state_province = request.POST.get('state_province', '') country = request.POST.get('country', '') website = request.POST.get('website', '') error_message = [] if not name: error_message.append('Name is required') if len(name) > 100: error_message.append('Name should be short than 100') if not address: error_message.append('Address is required') if error_message: return render(request, 'books/book_add.html', {'error_message': error_message}) else: publisher = Publisher(name=name, address=address, city=city, state_province=state_province, country=country, website=website) return redirect('books:publisher-detail', kwargs={'publisher_id': publisher.id}) else: return render(request, 'books/publis_add.html')
# books/urls.py from django.conf.urls import url from . import views urlpatterns = [ url('^publisher_add/', views.publisher_add), ] # mysite/urls.py from django.conf.urls import url, include urlpatterns = [ url(r'^books/', include('books.urls')), ]
以上代码说明:验证用记输入;返回用户error;html构造form;验证输入和model的约束重复;代码冗长。用django form解决这些问题。
二、Form
1.Form表单的功能:
- 自动生成HTML表单元素
- 检查表单数据的合法性
- 如果验证错误,重新显示表单(数据不会重置,保留上次的数据)
- 数据类型转换(字符类型的数据转换成相应的Python类型)
创建一个Form表单有两种方式:第一种方式,继承于forms.Form;第二种方式,结合Model,继承forms.ModelForm
第一种方式,继承forms.Form,定义form:
from django import forms class NameForm(forms.Form): your_name = forms.CharField(label='Your name', max_length=100)
form = NameForm()
form.is_bound #Out[5]: False,form是否实例化
form.is_valid() #Out[8]: False,验证form是否合法
form = NameForm({'name': 'jerry'})
form.is_bound #Out[7]: True
form.is_valid() #Out[8]: True
form.cleaned_data #获得验证后的,已转化为python类型的数据。在此之前,必须先执行is_valid()验证数据,否则抛异常。Out[9]: {'name': 'jerry'}
form.cleaned_data['name'] #python类型的数据。Out[10]: 'jerry'
#将form生成HTML标签
form.as_p() # '<p><label for="id_name">Your_name:</label> <input id="id_name" maxlength="45" name="name" type="text" value="jerry" required /></p>'
form.as_ul() # '<li><label for="id_name">Your_name:</label> <input id="id_name" maxlength="45" name="name" type="text" value="jerry" required /></li>'
#生成HTML标签(表单)
form.as_table() # '<tr><th><label for="id_name">Your_name:</label></th><td><input id="id_name" maxlength="45" name="name" type="text" value="jerry" required /></td></tr>'
form = NameForm({'Your_name': 'candy'})
form.is_valid() # False
form = NameForm({'name': 'bob' *100})
form.is_valid() # False
form.cleaned_data # 没有验证合格的数据,返回空字典{}
form = NameForm({'name': 'jerry'})
f = form['name']
#查看field属性和方法
f.name #字段名,Out[43]: 'name'
f.label #字段的label,Out[44]: 'Your_name'
f.label_tag() #字段的label_tag,Out[45]: '<label for="id_name">Your_name:</label>'
f.id_for_label #字段的id_for_label,Out[46]: 'id_name'
f.value #字段的值,Out[48]: 'jerry'
f.html_name # 字段的HTML名字,Out[49]: 'name',如input里面的name
f.help_text #字段的帮助信息Out[50]: ''
f.errors #字段的错误信息,Out[51]: []
示例,发送邮件
# books/forms.py from django import forms class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField(widget=forms.Textarea) sender = forms.EmailField() cc_myself = forms.BooleanField(required=False) # bookw/views.py from django.http import HttpResponse from django.shortcuts import render from django.core.mail import send_mail from .forms import ContactForm def send_email(request): if request.method == 'GET': form = ContactForm() return render(request, 'sendmail.html', {'form': form}) else: form = ContactForm(request.POST) if form.is_valid(): subject = form.cleaned_data['subject'] message = form.cleaned_data['message'] sender = form.cleaned_data['sender'] cc_myself = form.cleaned_data['cc_myself'] recipients = ['info@example.com'] if cc_myself: recipients.append(sender) send_mail(subject, message, sender, recipients) return HttpResponse('subject: {} message: {} sender: {} cc_myself {}'.format(subject, message, sender, cc_myself)) else: return render(request, 'sendmail.html', {'form': form}) # books/tepmlates/sendmail.html 模板1 <form action="", method="post"> {% csrf_token %} {{ form.errors }} {{ form.as_p }} <input type="submit" value="Submit" /> </form> # books/templates/sendmail.html 模板2,手写模板 <form action="", method="post"> {{ csrf_token }} {{ form.non_field_errors }} <div class="fieldWrapper"> {{ form.subject.errors }} <label for="{{ form.subject.id_for_label }}">{{ form.subject.label }}: </label> {{ form.subject }} </div> <div class="fieldWrapper"> {{ form.message.errors }} <label for="{{ form.message.id_for_label }}">Your message: </label> {{ form.message }} </div> <div class="fieldWrapper"> {{ form.sender.errors }} <label for="{{ form.sender.id_for_label }}">Your email address: </label> {{ form.sender }} </div> <div class="fieldWrapper"> {{ form.cc_myself.errors }} <label for="{{ form.cc_myself.id_for_label }}">CC yourself?</label> </div> <div> <input type="submit", value="Submit"/> </div> </form> # books/urls.py urlpatterns = [url(r'^sendmail/$', views.send_email), ] # 项目/urls.py urlpatterns = [url(r'^books/', include('books.urls')),]
form和field的一性属性见下文。
示例,第一部分的示例为例,编写form:
# books/forms.py from django import forms class PublisherForm(forms.Form): name = forms.CharField(max_length=10) address = forms.CharField(max_length=20) city = forms.CharField(max_length=20)
state_province = forms.CharField(max_length=20)
website = forms.URLField(max_length=20)
使用form简化以上示例的代码,使用form改写publisher_add方法
# books/views.py from django.http import HttpResponse from django.shortcuts import render from .forms import PublisherForm from .models import Publisher def publisher_add(request): if request.method == 'GET': form = PublisherForm() #PublisherForm(initial={'name': '出版社A'},initial给定初始值 return render(request, 'books/publisher_add.html', {'form': form}) elif request.method == 'POST': form = PublisherForm(request.POST) if form.is_valid(): form.save() return HttpResponse("ok!") else: return render(request, 'books/publisher_add.html', {'form': form}) def publisher_add(request): if request.method == 'GET': form = PublisherForm() #forms get its required data return render(request, 'books/publisher_add.html', {'form': form}) elif request.method == 'POST': form = PublisherForm(request.POST) if form.is_valid(): name = form.cleaned_data['name'] address = form.cleaned_data['address'] city = form.cleaned_data['city'] state_province = form.cleaned_data['state_province'] website = form.cleaned_data['website'] publisher = Publisher(name=name, address=address, city=city, state_province=state_province, website=website) publisher.save() #对数据表实例化,提交到数据库。 return HttpResponse("ok!") else: return render(request, 'books/publisher_add.html', {'form': form})
使用后端改写后的publisher_add方法,简化前端publisher_add.html代码:
# books/templates/books/publisher_add.html <!DOCTYPE html> <html> <head> <title>books</title> </head> <body> <form action="/your-name" method="post">
{% if form.errors %}
{{ form.errors }}
{% endif %} {% csrf_token %} {{ form.as_ul }} <input type="submit" value="Submit"> </form> </body> </html>
Form相关的对象包括
- Widget挂件:用来渲染成HTML元素的工具,如:forms.Textarea对应HTML中的<textarea>标签。如没有指明,默认是Textinput对应HTML中的input标签。input是一行,Textarea是多行。
- Field字段:Form对象中的一个字段,如:EmailField表示email字段,如果这个字段不是有效的email格式,就会产生错误。
- Form:一系列Field对象的集合,负责验证和显示HTML元素
- Form Media:用来渲染表单的CSS和JavaScript资源。
widge内置挂件:
Textarea ----> HTML: <textarea>...</textarea>
TextInput ----> HTML:<input type="text" ...>
NumberInput --> HTML: <input type="number" ...>
DateTimeInput --> HTML: <input type=DateTime ....>
PasswordInput --> HTML: <input type='password' ...>
选择类挂件:
Select ---> HTML: <select><option ...>...</select>
SelectMultiple
---> HTML: <select multiple='multiple'>...</select>
....更多详情,参见官网:https://docs.djangoproject.com/en/1.10/ref/forms/widgets
Field类型:Form有众多的字段,对应model里的field,如CharField、DateTimeField。更多field类型文档:https://docs.djangoproject.com/en/1.10/ref/forms/fields/
一部分常见的Field设置:
required=True 是否为空。默认为True,不可以为空。
max_length=20 最大长度
min_length=4 最小长度
label=None
erros......
Form和field的一些属性:
{{ form.non_field_errors }} 不是某个字段引起的错误,多个字段联合在一起所引起的错误。或者通过非form自身的功能,如自定义的方法等,校验field时,所产生的错误放在此变量中。
{{ form.errors }} 通过form自身的校验功能,校验field时,产生的所有错误放在此变量中。
{{ field.initial }} 为字段赋于初始值
{{ field.required }} 字段是否可以为空。字段有required属性,是不能为空;没有required属性,可以为空
{{ field.label }} label的名字
{{ field.labeL_tag }} label标签
{{ field.id_for_lable }} label中的id
{{ field.value }} label中的value
{{ field.is_hidden }} 隐藏label
{{ field.html_name }}
{{ field.error }} 某个字段的错误
{{ field.help_text }} 提示信息
常见的form、field方法:
form.is_bound # form是否已经实例化
field.is_valid #校验form是否成功
field.clean() # 校验某个字段,如forms.EmailField().clean('ad@163.com')
更多关于form、field属性参见官方文档:https://docs.djangoproject.com/en/1.10/topics/forms/#looping-over-the-form-s-fields
三、ModelForm
ModelForm结合了form和model,将model的field类型映射成form的field类型,复用了model和model的验证,写更少的代码,并且还实现了存储数据库的简单方法。
Model Field类型与Form Field类弄的映射关系:https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#field-types
创建表单form的第二种方式,结合model,继承forms.ModelForm,定义form:
1).建立模型(model),即数据表
# clound_app/models.py from django.db import models
class Server(models.Model):
name = models.CharField(u"名称", max_length=30, null=True, blank=True, unique=True)
ip = models.IPAddressField(u"IP地址", max_length=20,unique=True)
port = models.IntegerField()
2).根据model,创建表单form:
# clound_app/forms.py
from django.forms import ModelForm
from cloud_app.models import Server
class ServerForm(ModelForm):
cpunum = forms.ChoiceField(label="CPU核数",required=True, choices=cpunum_choice) #重写字段。修改某字段的属性。如不重新定义,继承原model字段的属性。
class Meta:
model = Server # model = Server,表明这个表单来自名叫Server的模型
型。
fields = ['name', 'ip', 'prot', 'cpunum', 'mem'] #限制字段。列出的,将继承;没有列出的,前端将不展示。
labels = {
'name': 'Writer',
'ip': 'IPAdrress',
} #重写lable。修改某些字段的label属性。如不重写,继承原字段的label
widgets = {
...
} #重写插件。修改某些字段的插件属性。如不重写,继承原字段的widget
help_texts = {
'name': _('some useful help text'),
'website': '......',
} #重写提示信息........
error_message = {
'name': 'some error message',
'website': '......',
} #重写错误信息........
#....重写其它。
fields = '__all__' 取出所有字段
创建model、form:
# books/models.py from django.db import models TITLE_CHOICE = ( ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ) class Author(models.Model): name = models.CharField(max_length=45) title = models.CharField(max_length=3, choices=TITLE_CHOICE) birth_date = models.DateField(blank=True, null=True) def __str__(self): return self.name class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) def __str__(self): return self.name
# books/forms.py from django import forms from .models import Author, Book class AuthorForm(forms.ModelForm): class Meta: model = Author fields = ['name', 'title', 'birth_date'] class BookForm(forms.ModelForm): class Meta: model = Book fields = ['name', 'authors']
# books/views def author_add(request): if request.method == 'GET': form = AuthorForm() return render(request, 'author_add.html', {'form': form})
choices=TITLE_CHOICES,其中TITLE_CHOICES是一个二维元组,二维中的第一个元素存放于数据库中,第二个元素用于前端展示。
ModelForm和Form的使用方法,相同。
ModelForm比Form,多了save(),可以直接提交数据库。因为用forms.Form定义的form,和数据库是没关系的;用forms.ModelForm定义的form,和数据库是关联的,所以可以直接向数据库提交。示例,save()方法:
from books.models import *
from books.forms import *
form = AuthorForm({'name': 'jerry', 'title': 'MR'}) form.is_valid() # True form.save() #将form直接保存到数据库中; save之前最好is_valid()
#多对多关系的form保存到数据库,同上面一样,直接save即可。会自动保存多对多关系。 authors = Author.objects.all() authors_id = [author.id for author in authors] form = BookForm({'name': 'Django Book', 'authors': authors_id}) form.is_valid() # True
#多对多关系时,如果使用了save(commit=False),再次save时需要分别用save保存单表,用save_m2m保存多对多关系
form = BookForm({'name': 'Python Book, 'authors': authors_id})
book = form.save(commit=False) #commit=False,form没有向数据库提交保存
book.name = 'New Python Book'
book.save() #将form保存到数据库;但因为前面用了commit=False,再次保存时,不会保存多对多关系
book.authors.all() # <QuerySet []>
form.save_m2m() #这种情况,须使用form.save_m2m()保存多对多关系
book.authors.all() # 返回,<QuerySet [<Author: jerry>, <Author: jason>, <Author: dave>, <Author: bob>]>
在定时modelform时,fields= '__all__',表示列出所有字段。exclude = ('某字段'),表示排除某字段以外的所有。
在view视图和template模板中,使用form:
用ModelForm重写第一、二部分的示例:
# books/forms.py from django import forms from .models import Publisher class PublisherForm(forms.ModelForm): class Meta: model = Publisher fields = ['name', 'address', 'city', 'state_province', 'website']
# books/forms.py
from django.http import HttpResponse from django.shortcuts import render from .forms import PublisherForm def publisher_add(request): if request.method == 'GET': form = PublisherForm() #一个只有初始值的,没有实例化的form,给到GET页面
return render(request, 'books/publisher_add.html', {'form': form}) elif request.method == 'POST': form = PublisherForm(request.POST) if form.is_valid(): form.save() #form直接提交到数据库 return HttpResponse("ok!")
#return redirect('some view name') else: return render(request, 'books/publisher_add.html', {'form': form})
改写以后,(ModeForm的form)form的field的校验功能,来自于model的field校验。第二部分的form(Form的form)的field校验,来自于form的field字段定义。
# books/views.py def publisher_add(request): if request.method == 'POST': form = PublisherForm(request.POST) if form.is_valid(): publisher = form.save() return HttpResponse('Add ok') #return redirect('some view name') else: form = PublisherForm(initial={'name': "O'Reilly", 'city': 'ShangHai'}) return render(request, 'publisher_add.html', {'form': form}) def publisher_update(request, publisher_id): publisher = get_object_or_404(Publisher, id=publisher_id) if request.method == 'GET': form = PublisherForm(instance=publisher) return render(request, 'publisher_add.html', {'form': form}) elif request.method == 'POST': form = PublisherForm(request.POST, instance=publisher) if form.is_valid: form.save() return HttpResponse('Update success') #return redirect('some view name') return HttpResponse('Invalid') # books/urls.py urlpatterns = [ url(r'^publisher_add/$', views.publisher_add), url(r'^publisher/(?P<publisher_id>[0-9]+)/update/$', views.publisher_update), ]
以上的publisher_add方法是创建新的publisher实例。如果要修改、更新已经存在的publisher实例,使用此方法并不行,需要使用Publisher()。代码如下:
更新用户提交的form用instance,渲染form给用户用initial。initial给表单一个初始值。instance常见的用法,如上面示例的用法,获取到用户GET时得到的值,做为实例,有点类似于初始值的意思;再用rquest.POST的值去更新实例的值,生成新的form。
如果要使form样式更美观,参考:django form bootstrap插件 : https://github.com/tzangms/django-bootstrap-form 。示例:
# settings.py
INSTALLED_APPS = ['bootstrapform', ]
# books/templates/publisher_add.html
{% load bootstrap %}
<form action="" method="post">
{% csrf_token %}
<br />
{{ form|bootstrap_horizontal }}
<hr />
<div>
<input type="submit" value="Submit">
</div>
</form>
自定义校验:
from django import forms from django.forms import ValidationError from .models import Publisher class PublisherForm(forms.ModelForm): class Meta: model = Publisher fields = ['name', 'address', 'city', 'state_province', 'website'] def clean_name(self): # 定义某字段的校验方法,一般以clean_某字段为方法名称 name = self.cleaned_data['name'] # 从cleaned_data校验后,取数据。相当于多了一层校验 if len(name) < 5: raise ValidationError('Length must be more than 5.') return name #最后需要返回校验后的字段,因为后面的程序可能会再用到 def clean(self): # 重写clean方法,如两次密码的校验。clean方法是最后一层的校验,不需要返回,clean校验后的数据会自动保存。 cleaned_data = super(AuthorForm, self).clean() # 继承原clean方法 name = cleaned_data.get('name', '') title = cleaned_data.get('title') if len(name) < 40 and title == 'MR': raise ValidationError('xxxx')
使用了“clean_字段名”,验证该字段时会自动调用;重写了clean方法,验证所有字段时都会自动调用。不需要手动调用。验证:
In [1]: from book.forms import *
In [5]: form = AuthorForm({'name': 'tom', 'title': 'MR'})
In [6]: form.is_valid() Out[6]: False
In [7]: form.errors Out[7]: {'name': ['Length must be more than 5.']} #这里,自动调用了clean_name的校验方法。
In [8]: form = AuthorForm({'name': 'tome'*10, 'title': 'MR'})
In [9]: form.is_valid() Out[9]: False
In [10]: form.errors Out[10]: {'__all__': ['Length Must be Less than 30']} #这里,自动调用了clean的校验方法
In [11]: form = AuthorForm({'name': 'to'*10, 'title': 'MR'})
In [12]: form.is_valid() Out[12]: True
四、model模型
使用model的前提:settings.py激活app后,才能使用此app的model。
migrations:
1.创建模型:模型的意义在于定义数据模型,使用python操作数据库:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=30)
转换为SQL:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"name" VARCHAR(30) NOT NULL
);
1). python manage.py makemigrations # 告诉Django我的model.py中做了一些改动, 这时其实是在该app下建立 migrations目录,
并记录下你所有的关于modes.py的改动,比如0001_initial.py,数据库文件数据库没有增加新的表"但是这个改动还没有作用到数据库文件,数据库没有增加新的表 2). python manage.py migrate # 这时候才真的把作用到数据库文件,产生对应的表
2.Field
Field类型:字段类型,django有众多的Field类型:CharField、IntegerField、DatetimeField.........
Field类型还有两个作用,一是定义的是什么类型的数据,从数据库取出来以后就是什么类型的数据;二是还可以作类型校验
Field参数:常见的有:null、blank、choices、default、help_text提示信息、primary_key、unique唯一键、verbose_name前端展视的名字(用于modelForm)..........
表之间关系:多对一ManyToOne,使用外键ForeignKey ;多对多ManyToMany,使用ManyToManyField,会自动增加一张关系表;一对一OneToOne,使用OneToOneField
个人理解:如果对于双方,都是一对一的关系,那么是一对一的关系;如果对于一方是多对一,对另一方是一对一,那么是多对一的关系;如果对于双方,都是多对一的关系,那么是多对多的关系。
如:一本书只能由一个出版社出版(一对一),一个出版社可以出版多本书,那么是一对多的关系,通常在小的一方(如书,是更小的一方,即一对一的这方)的model类中定义ForeignKey,如publisher = models.ForeignKey(Publisher)。
一本书可能有多个作者(一对多),一个作者可以写作多本书(一对多),那么是多对多的关系,如果在本的models中定义,则为author=models.ManyToManyField(Author),或者在作者的models 中定义,则为
book=models.ManyToManyField(Book);只需要在任意一个表中定义一个ManyToManyField即可。
一个任务可以包含多个计划,一对多;一个计划只能属于一个任务,一对一,因此是多对一的关系。
一个计划可以包含多个模块,一对多;一个模块只能属于一个计划,一对一,因此是多对一的关系。
在比较两个表的关系的时侯,强调的是一个**对应**,如一个***对应一个***,或者一个**对应多个***。
from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) website = models.URLField() def __unicode__(self): return self.name class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() def __unicode__(self): return '{} {}'.format(self.first_name, self.last_name) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher)
多对多的查询:
# 如果定义了多对多,可以直接用ORM反查 book=Book.objects.get(id=1) # 获取一本书 book.authors.all() # 获取一本书对应的所有作者。ManyToMany定义在book类,多对多的field为authors,所以这里是通过book.authors来获取 author = author.objects.get(id=1) # 获取一个作者 author.book_set.all() #获取一个作者对应的所有书。ManyToMany未定义在本类author中,因此不通过多对多字段来查询,但可以通过author.book_set来反查。只要定义了多对多,是可以这样反查的,djanog是知道它们的多对多的关系的。
多对多的关系,可以拆分:
新建一张关系表,如AuthorBook,拆分为两个多对一关系,一本书可以有多个作者,一个作者可以有多本书,即两个外键。同ManyToMany的关系是一样的,但是不能使用ManyToMany的ORM。
class AuthorBook(models.Model): book = models.ForeignKey(Book) # 多对一关系之一:一本书可以对应多个作者 author = models.ForeignKey(Author) # 多对一关系之二:一个作者可以对应多本书
此时,怎么查?
class Car(models.Model): #一辆车只能属于一个品牌,但一个品牌有多辆车;多对一关系
manufacturer = models.ForeignKey(关联表对应的类)
class User(models.Model): #一个组包含多个用户,一个用户属于多个组;多对多关系
groups = ManyToManyField(关联表对应的类) class Restaurant(models.Model): #一个位置一个餐馆;一对一关系 place = OneToOneField(Place, primary_key=True)
3.model中的Meta:
db_table = '自定义表名'
get_latest_by按最后更新时间排序、ordering按什么字段排序、permissions权限、unique_together联合唯一键、index_together
重定save()方法:
class BLog(models.Models):
name = models.CharField(max_length=100)
tagline = models.TextField()
def save(self, *args, **kwargs): #重写save方法
#do_something() before save()
super(Blog, self).save(*args, **kwargs) #调用父类的save方法。在save前后做一些事情
#do_something() after save()
如果不用ORM,要在python中使用原生的SQL,使用格式:数据表的类.objects.raw(原生的SQL的SELECT语句);只支持SELECT开头的语句。
3.2 重写model的某方法:
def save(self, *args, **kwargs): do_something() super(Model类名, self).save(*args, **kwargs) do_something()
3.3原生sql:
1.raw只支持select:
modelObj.objects.raw("select....."),结果不是query_set,是一个迭代器。
2..使用extra方法
解释:结果集修改器,一种提供额外查询参数的机制
说明:依赖model模型
用在where后:
Book.objects.filter(publisher_id="1").extra(where=["title='python学习1'"])
用在select后
Book.objects.filter(publisher_id="1").extra(select={"count":"select count(*) from hello_book"})
3.执行自定义SQL
解释:利用游标执行
导入:from django.db import connection
说明:不依赖model
用法:
from django.db import connection
cursor = connection.cursor()
#插入
cursor.execute("insert into hello_author(name) values('xiaol')")
#更新
cursor.execute("update hello_author set name='xiaol' where id=1")
#删除
cursor.execute("delete from hello_author where name='xiaol'")
#查询
cursor.execute("select * from hello_author")
#返回一行
raw = cursor.fetchone()
print(raw)
# #返回所有
# cursor.fetchall()
4.继承
1).抽象基类,abstract=True
如果只想使用父类来持有一些信息,不想在每个子模型中都敲一遍。这个类永远不会单独使用,使用abstract表明基类,数据库不会建该表。如以下示例中的CommonInfo就是抽象基类,数据库不创建此表,不能实例化:
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
oredering = ['name']
class Teacher(CommonInfo):pass
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
class Meta:
db_table = 'student_info'
如果子类没有实现Meta则默认继承。
2).多表继承:
这里的示例说的是一对一关系!!觉得应该叫继承,不知为何叫多表继承?
如果继承一个已经存在的模型且想让每个模型具有它自己的数据库表,那么应该使用多表继承。每个层级下的每个model都是一个真正意义上完整的model。每个model都有专属的数据表,都可以查询和创建数据表。继承关系在子model和它的每个父类之间都添加一个链接(通过一个自动创建的OneToOneField来实现)。
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=100)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
Place.objects.creat(name='Tsinghua', address='BJ')
Restaurant.objects.create(name='Bishengke', address='x', serves_hot_dogs=True, serves_pizza=True)
p0 = Place.objects.get(name='Tsing')
p1 = Place.objects.get(name='Bishengke')
p2 = Restaurant.objects.get(name='Bishengke')
p3 = Restaurant.objects.get(name='Tsing') #抛出异常
以上model,在数据库中表现为两张表。restaurant表还包括一个OneToOneField字段,即继承父类的关系。
可见,抽象基类和多表继承都实现了继承,实现了扩展,但抽象基类并不创建数据表,而多表继承父类和子类都有数据表。
以上实例,p1正常,p3异常,是否可以理解为:子类继承父类的属性和方法,可以使用父类的属性和方法,但是不继承父类的value或者说实例,即不能使用父类的valud或实例;父类不可使用子类的属性的方法,但是父类可以使用子类的继承父类的那些属性或方法而得到的valud或实例?
3).代理继承,proxy=True
场景,不希望改变某表,但又想给此表增加一些方法时,使用。目的,额外增加方法。不会创建表。
使用多表继承时,父类model的每个子类都会创建一张数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包括在基类中的字段数据。
但有时,可能只想更改父类model在python层的行为实现,并不想更改父类的数据表结构 。比如:更改默认的manger,或者添加一个新方法。而这,正是代理继承要做的:为原始模型创建一个代理。你可以创建、删除、更新代理model的补全,而且所有的数据都可以像使用原始model一样被保存。
不同之处在于:你可以在代理model中改变默认manager,更不会对原始model产生影响。即代理继承,不会创建一张新的数据表。
class Person(models.Model):
name = models.CharField(max_length=45)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):pass
说明:MyPerson只是一个代理继承的类,数据库中不会创建该表。MyPerson类和它的父类Person操作同一个数据表。特别是,Person的任何实例,也可以通过MyPerson访问、操作。
4).多重继承
多重继承和多表继承没什么大的差别,会自动添加两个OneToOneField。
多重继承,需要注意的是:两个基类的默认自增id要重新命名,否则添加OneToOneField会有问题。因为两个基类的默认自增id,字段名都叫id,所以多重继承将会产生冲突。
也不允许子类重写父类字段
class Article(models.Model):
article_id = models.AutoField(primary_key=True)
...
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
...
class BookReview(Book, Article):
pass
4).事务:
默认情况下,在Django中事务是自动提交的。当我们运行Django内置的模板修改函数时,例如调用model.save()或model.delete()时,事务将被立即提交。这种机制和数据库的自动提交事务机制类似。记住这里没有默认的回滚机制。
在HTTP请求上加事务
对于Web请求,Django官方推荐使用中件间TransactionMiddleware来处理请求和响应中的事务。它的工作原理是这样的:当一个请求到来时,Django开始一个事务,如果响应没有出错,Django提交这期间所有的事务,如果view中的函数抛出异常,那么Django会回滚这之间的事务。
为了实现这个特性,需要在MIDDLEWARE_CLASSES setting中添加TransactionMiddleware:
MIDDLEWARE_CLASSES = (
'django.middleware.cache.UpdateCacheMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
)
顺序很重要,TransactionMiddleware中间件会将置于其后的中间件都包含在事务的范围之中(用于缓存的中间件除外,他们不受影响,例如CacheMiddleware,UpdateCacheMiddleware和FetchFromCacheMiddleware)。
另外需要注意的是,TransactionMiddleware只会影响DATABASES设置中的默认的数据库,对于其它的数据库,如果我们实现事务控制的话只能用别的方案了。
在View中实现事务控制
如果想在更细粒度的条件下(例如在一个view函数中)控制事务,我们可以使用django.db.transaction。有两种用法:
1.使用装饰器
from django.db import transaction
@transaction.commit_on_success
def viewfunc(request):
# ...
# this code executes inside a transaction
# ...
2.使用context manager
from django.db import transaction
def viewfunc(request):
# ...
# this code executes using default transaction management
# ...
with transaction.commit_on_success():
# ...
# this code executes inside a transaction
# ...
这两种方法都可以正常工作。不过如果使用的Python版本为2.5并且要使用with语法的话,还需加一句
from __future__ import with_statement。
所以为了最大的兼容性,下面的示例使用装饰器来实现事务。
autocommit()
使用autocommit装饰器可以将view函数中的事务还原成Django默认的自动提交模式,无视全局事务的设置。
示例:
from django.db import transaction
@transaction.autocommit
def viewfunc(request):
....
@transaction.autocommit(using="my_other_database")
def viewfunc2(request):
....
commit_on_success()
顾名思义,view函数成功则提交事务,否则回滚。用法同上。
commit_manually()
告诉Django我们将自己控制函数中的事务处理。并且要注意,如果在视图函数中改变了数据库的数据并且没有调用commit() 或rollback(),那么将抛出TransactionManagementError异常。
示例:
from django.db import transaction
@transaction.commit_manually
def viewfunc(request):
...
# You can commit/rollback however and whenever you want
transaction.commit()
...
# But you've got to remember to do it yourself!
try:
...
except:
transaction.rollback()
else:
transaction.commit()
@transaction.commit_manually(using="my_other_database")
def viewfunc2(request):
....
五、model querys
增:方法一: Model类.objects.create(title='abc', status=1)
或 Model类.objects.create(**dic)
方法二:Model类(**dic).save()
分解:1.生成对象:obj = Model类(**dic)
2. obj.save()
或者:obj =Model类()
obj.name = 'asdf'
obj.status = 1
obj.save()
删: 方法一:QuerySet.delete()
方法二:object.delete()
改: 方法一:object.attr = value
object.save()
方法二:QuerySet.update(**dic),如:Model类.objects.filter(id=1).update(**dic)
分解: 1.返回Query_set:q = Model类.objects.filter(id=1)
2. q.update(**dic)
查:方法一:Model.objects.all(**dic)
方法二:Model.objects.filter(**dic)
方法三:Model.objects.get(**dict)
高级查询的方法,示例:
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
1). exclude,排除符合条件的,取出不符合条件的。
Author.objects.exclude(name='jerry')
b = Blog.objects.get(name='Beates Blog', tagline='all the latest Beates news')
a1 = Author.objects.create(name='alice', email='ad@ad.com')
Author.objects.exclude(name='alice')
2).关联对象查询
一对多,正向查询:
e = entry.objects.get(id=2)
e.blog #blog为外键,关联到Blog表。
e.blog = None #删除对应的blog实例
e.save()
一对多,反向查询:
b = Blog.objects.get(id=1)
b.entry_set.all() # returns all Entry objects related to Blog.
b.entry_set.all.filter(headline_contains='Lennon')
b.entry_set.count() # entry_set is a Manager that returns QuerySets.
3).Q对象
以上方法中的关键字参数查询,都是一起进行‘AND'的。如果需要执行更复杂的查询,如OR语句,可以使用Q对象,用|分隔;如果多个Q对象AND,使用","隔开。
Q对象(django.db.models.Q)用于封装一组关键字参数。这些关键字参数就是上文“字段查询”中所提及的所有方法。
from django.db.models import Q
Author.objects.filter(Q(name='jerry')|Q(name='jason'))
每个接受关键字参数的查询函数(如filter(),exclude(),get())都可以传递一个或多个Q对象,作为位置参数;如果一个查询函数有多个Q对象参数,这些参数的逻辑关系为’AND'。
查询函数可以混合使用Q对象和关键字参数,所有提供给查询函数的参数都将以'AND'关系连在一起。但是Q对象,必须位于所有关键字参数前面。这和python的位置参数、关键字参数是一样的。
Poll.objects.get(
Q(question_startswith='Who'),
Q(choice_x='Yes'),
Q(pub_date=date(2005, 5 , 2)) | Q(pub_date=date( 2005, 5, 6))
)
Poll.objects.get(
Q(choice_x='Yes'),
Q(pub_date=date(2005, 5 , 2)) | Q(pub_date=date( 2005, 5, 6)),
question_startswith='Who',
)
保存ForeignKey,直接赋值即可;保存ManyToManyToManyField字段,因为是多对多关系,要赋值为列表,或者使用add方法添加多个。
e = Entry.objects.create(blog=b, headline='Hello', bode_text='hello world', pub_date=timzone.now())
e.blog = b #blog字段为外键,直接赋值即可
e.authors = [a1] #authors字段为多对多关系,须使用列表赋值,这是方式一
e.save()
e.authors.add(a1, a2) #authors字段为多对多关系,可以使用add方法增加多个值,这是方式二。更常用
e.save()
e.authors.create(name='bob', email='bob@bob.com') #多对多,方式三
4).QuerySet链式过滤
Entry.objects.filter(
headline_startswith='What'
).exclude(
pub_date_get=datetime.date.today()
).filter(
pub_date_gte=datetime(2005, 1, 30)
)
5).高级条件过滤lookup,常见的lookup有:
大于 gt 大于等于 gte
小于 lt 小于等于 lte
包含contains 精确匹配exact,也是默认方式
开始于startswith 结束于endswith
regex正则匹配
忽略大小写icontains iexact istartswith iendswith iregex
列表中in 范围内range
时间日期类型date year month day
lookup使用格式: 字段名__lookup=lookup的条件
Entry.objects.filter(pub_date__lte='2006-01-01')
Entry.objects.get(headline__exact="Man bits dog")
Blog.objects.get(name__iexact="beatles blog")
Blog.objects.filter(pk__in=[1, 4, 7]
Entry.objects.filter(pub_date__range=(start_date, end_date))
Entry.objects.get(title__regex=r'^(An?|The) +')
跨关联关系查询,表和字段之间使用___
Entry.objects.filter(blog__name='Beatles Blog')
Blog.objects.filter(entry__headline__contains="Lennon") #反向
Blog.objects.filter(entry__authors__name__isnull=True) #多层,博文表entry下面的authors字段对应的name(author表的name字段)。这种太复杂,少用,一般还是分几步
Blog.objects.filter(entry__authors__isnull=False,
entry__authors__name__isnull=True) #多个过滤条件
6).引用模型字段查询F
通常是色走将模型字段与常量比较。如,比较两个字段值
from Django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks)) #同引用的投票数量进行比较
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks))
7). 限制返回个数
Entry.objects.all()[:5] #返回前5个对象(LIMIT 5)
Entry.objects.all()[5:10] # 返回第6个至第10个对象(OFFSET 5 LIMIT 5)
8).查询缓存
查询集不会永远缓存它们的结果。当只对查询集的部分进行求值时会检查缓存,但是如果这个部分不在缓存中,那么接下来查询返回的记录都将不会被缓存。特别地,这意味着使用切片或索引来限制查询集将不会填充缓存。
例如,重复获取查询集对象中一个特定的索引将每次都查询数据库
query = Entry.objects.all() print queryset[5] #不会使用缓,仍然从数据库中查询数据
但是,如果已经对查询集求过值,则将检查缓存
query = Entry.objects.all()
[entry for entry in queryset] #对查询集query进行循环,即已经对查询集求过值。此时,如果再对查询集的部分进行查询,将检查缓存
print queryset[5] #Uses cache
9).查询对象比较
为了比较两个模型实例,只需要使用标准的pyhton比较操作符,即双等于符号 == ,在后台,它会比较两个模型主键的值
some_entry == other_entry
10).所有查询api
所有objects方法,返回queryset:https:/docs.djangoproject.com/en/1.10/ref/models/querysets,常见的几个:
annotate添加注释属性,与聚合函数一起使用,返回聚合值
from Django.db.models import Count
qs = Question.objects.annotate(Count('choices'))
for q in qs:
print(q.choices__count)
qs = Question.objects.annotate(choices_num=Count('choices'))
for q in qs:
print(q.choices_num)
aggregate聚合,与聚合类函数使用,返回结果字典
from django.db.models import Count
q = Blog.objects.aggregate(Count('entry'))
q = Blog.objects.aggregate(number_of_entries=Count('entry'))
聚合类函数:聚合类函数配合聚合函数或注释函数使用,在django.db.models模块中,聚合类函数有以下几种:
class Avg(expression, output_field=FloatField(), **extra)
class Count(expression, distinct=False, **extra) #计数
class Max(expression, output_field=None, **extra)
class Min(expression, output_field=None, **extra)
class StdDev(expression, sample=False, **extra) #标准除
class Sum(expression, output_field=None, **extra)
class Variance(expression, sample=False, **extra) #方差
distinct去重
Author.objects.distinct()
Entry.objects.or_der_by('pub_date').distinct('pub_date')
Entry.objects.or_der_by('blog').distinct('blog')
values,返回ValuesQuerySet类的字典,不是模型实例对象
Blog.objects.filter(name__startswith="Beatles")
Blog.objects.filter(name__startswith="Beatles").values()
Blog.objects.values()
Blog.objects.values('id', 'name')
values_list与values类似,但返回Queryset类的列表,不是字典
Entry.objects.values_list('id').order_by('id')
Entry.objects.values_list('id', flat=True).order_by('id')
defer或only,有时侯模型包含一些含有大量数据的类型,通常模型会将数据库取出的数据转换为python对象,有时我们不需要浪费这性能,可以使用defer来排除某些字段,用only仅转换某些字段
Entry.objects.defer*"headline")
Entry.objects.only("headline")
using使用哪个数据库
Entry.objects.all()
Entry.objects.using('backup')
select_for_update,返回一个queryset,会锁定相关行直到事务结束。在支持的数据库上产生一个SELECT...FOR UPDATE语句
entries = Entry.objects.select_for_update().filter(author=request.user)
raw,执行原始SQL。只能执行SELECT开头.....的语句
fro p in Person.objects.raw('SELECT * FROM myapp_person'):print(p)
以下objects方法,返回的不是queryset,而是直接的数据,对象。
get_or_create,如果查询的数据不存在,则创建
Blog.objects.get_or_create(name='ccc') # 返回 (<Blog: ccc>, True) Blog.objects.get_or_create(name='ccc') # 返回 (<Blog: ccc>, False)
update_or_create,如果修改的数据不存在,则创建
bulk_create,批量创建,接受一个列表
Entry.objects.bulk_create([
Entry(headline='Django 1.0 Released'),
Entry(headline='Django 1.1 Announced'),
Entry(headline=breaking: Django is awesome'),
])
in_bluk,类似in,但返回的数据结果是类字典
Blog.objects.in_bulk([1])
Blog.objects.in_bulk([1, 2])
latest、earliest、first、last,查询最近、最早、第一个、最后一个数据
Entry.objects.first()
Entry.objects.latest('pub_date')
六、model事务
事务是绑定在view中实现的:
from Django.db import transaction
@transaction.non_atomic_requests #不使用事务,即不保存到数据库
def my_views(request):pass
@transaction.atomic #使用事务,当被装饰的函数执行成功的进侯,提交事务,保存到数据库;如果函数执行异常,不会提交事务
def viewfunc(request):pass
七、model管理器
1..添加额外管理器方法
自定义管理器,为管理器添加额外的方法,是对类增加“表”级功能的首先方式。
如果是添加行级功能,比如只对某个模型的实例起作用,应使用模型方法,而不是管理器方法。
自定义管理器方法,可以返回想要的任何数据,而不需要返回一个查询集。
例如,下面这个自定义管理器提供了一个with_counts()方法,它返回所有OpinionPoll对象的列表,列表的每项都有一额外num_responses属性。该属性保存一个聚合查询的结果。(对应SQL查询语句中的COUNT(*)生成的项)。
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connections
cursor = connection.cursor()
cursor.execute("""
SELECT p.id, p.quesiotn, p.poll_date, COUNT(*)
FROM polls_opinionpoll p, polls_response r
WHERE p.id = r.poll_id
GROUP BY p.id, p.question, p.poll_date
ORDER BY p.poll_date DESC
""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], questionrow[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list class OpinionPoll(models.Model): question = models.CharField(max_length=200) poll_date = models.DateField() objects = PollManager() class Response(models.Model): poll = models.ForeignKey(OpinionPoll) person_name = models.CharField(max_length=50) response = models.TextField()
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
poll_date = models.DateField()
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll)
person_name = models.CharField(max_length=50)
response = models.TextField()
2.添加自定义manager。
class AuthorManager(models.Manager):
def get_queryset(self):
return super(AuthorManager, self).get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super(EditorManager, self).get_queryset().filter(role='E')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
people = models.Manager()
authors = AuthorManager()
editors = EditorManager()
Person.authors.all()
Person.editors.all()
Person.people.all()
posted on 2014-06-17 11:16 myworldworld 阅读(321) 评论(0) 编辑 收藏 举报