Django学习(2.2.1版本)
项目技术重难点分析:
模型层:模型是您的数据唯一而且准确的信息来源。它包含您正在储存的数据的重要字段和行为。一般来说,每一个模型都映射一个数据库表。
每各模型都是一个python的类,这些类继承 django.db.models.Model
模型类的每个属性都相当于一个数据库的字段
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) ### 会映射成下面的 sql CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
myapp_person的名字由 app名称_class名称,id字段是一个默认字段,如果有主键则被覆盖,默认是不允许为空。
模型的使用:在setting中的INSTALLED_APPS中注册你的app名称,
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'courses', # 以下4个是注册的app名称 'organization', 'users', 'captcha', ]
python manage.py makemigrations:相当于在该app下建立 migrations目录,并记录下你所有的关于modes.py的改动,比如0001_initial.py, 但是这个改动还没有作用到数据库文件
python manager.py migrate:将该改动作用到数据库文件,比如产生table,修改字段的类型等。
字段:
字段名备注:除了 ForeignKey
,ManyToManyField
和OneToOneField
,任何字段类型都接收一个可选的参数verbose_name
,如果未指定该参数值,Django会自动使用该字段的属性名作为该参数值,并且把下划线转换为空格。
null:字段是否为空,默认为False
blank:字段是否为空,默认为False(该注意选项对话与null不同,null
选项仅仅是数据库层面的设置,然而blank
是涉及表单验证方面。如果一个字段设置为blank=True
,在进行表单验证时,接收的数据该字段值允许为空,而为设置blank=False
时,不允许为空。)
choices:该参数接收一个可迭代的列表或元组(基本单位为二元组)。如果指定了该参数,在实例化该模型时,该字段只能取选项列表中的值。每个二元组的第一个值会储存在数据库中,而第二个值将只会用于显示作用。
from django.db import models class Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
default:该字段的默认值。可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
help_text:使用表单小部件显示的额外“帮助”文本。即使您的字段未在表单上使用,它也对文档很有用。
primary_key:如果设置为True
,将该字段设置为该模型的主键。
unique:如果设置为True
,这个字段必须在整个表中保持值唯一。
多对一关系:使用 django.db.models.Model.ForeignKey 类,
# Car与Manufacturer 是多对一的关系,on_delete = models.CASCADE 是联级删除 from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
多对多关系:建一个多对多的表,显示的写出多对多表时可以记录除2的外键的字段。
from django.db import models class Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
Meta选项:给模型赋予元型态数据,模型的元数据是指“所有不是字段的东西”,比如排序选项,数据库表名,abstract = True表示这个模型是一个抽象基类。
from django.db import models class Ox(models.Model): horn_length = models.IntegerField() class Meta: ordering = ["horn_length"] verbose_name_plural = "oxen"
继承模式:1. 抽象基类 2. 多表继承 3. 代理模型
抽象基类:当您想要将一些公共信息放入许多其他模型时,抽象基类非常有用。你写你的基类,并把abstract=True
在元类。然后,抽象基类模型不会用于创建任何数据库表。相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。(基类不会产生数据表,只会共享数据,而子类产生数据表)
1、抽象基类有的元数据,子类没有的话直接继承
2、抽象基类有的元数据,子类有的话直接覆盖
3、abstract参数不会继承,必须显式写出
from django.db import models
class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True class Student(CommonInfo): home_group = models.CharField(max_length=5)
Meta继承:如果子类没有Meta方法则会继承父类的Meta,也可以扩展父类的Meta方法。
from django.db import models class CommonInfo(models.Model): # ... class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): # ... class Meta(CommonInfo.Meta): db_table = 'student_info'
多表继承:每个模型对应于自己的数据库表,子类不会继承父类的Meta
from django.db import models class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 OneToOneField 来实现)BEGIN;
CREATE TABLE
"myapp_place"
(
"id"
integer NOT NULL PRIMARY KEY,
"name"
varchar(
50
) NOT NULL,
"address"
varchar(
80
) NOT NULL
)
;
CREATE TABLE
"myapp_restaurant"
(
"place_ptr_id"
integer NOT NULL PRIMARY KEY REFERENCES
"myapp_place"
(
"id"
),
"serves_hot_dogs"
bool
NOT NULL,
"serves_pizza"
bool
NOT NULL
)
;
COMMIT;
代理继承:你可能只想更改 model 在 Python 层的行为实现。
子类和父类共同操作同一个表,不会为子类创建表,为父类提供一些方法。
代理 model 从非抽象基类中继承那些未在代理 model 定义的 Meta 选项。
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) class MyPerson(Person): class Meta: proxy = True def do_something(self): # ... pass
ORM:提供数据操作的API
######### 以下是例子中用到的模型样例
from django.db import models 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=200) email = models.EmailField() def __str__(self): return self.name class Entry(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): return self.headline
创建并保存对象:
# 在执行 save 时不会执行数据操作
from blog.models import Blog b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') b.save() # insert 操作 b5.name = 'New name' # b5是Blog的实例 b5.save() # 执行update操作
保存ForeignKey、MarytoMaryFiled字段:
更新ForeignKey字段的工作方式与保存普通字段的方式完全相同。
from blog.models import Blog, Entry entry = Entry.objects.get(pk=1) cheese_blog = Blog.objects.get(name="Cheddar Talk") entry.blog = cheese_blog entry.save()
更新MarytoMaryFiled的方式是使用add方法:
# 只增加一条数据 from blog.models import Author joe = Author.objects.create(name="Joe") entry.authors.add(joe) # 增加多条数据 john = Author.objects.create(name="John") paul = Author.objects.create(name="Paul") george = Author.objects.create(name="George") ringo = Author.objects.create(name="Ringo") entry.authors.add(john, paul, george, ringo)
select:(QuerySet对象是相互独立的,可以嵌套 filter 等)
all_entries = Entry.objects.all() # 获得Entry所有对象,返回包含QuerySet对象的列表 # filter是返回包含给定条件的QuerySet对象 # exclude是返回不包含给定条件的QuerySet对象 Entry.objects.filter(pub_date__year=2006) Entry.objects.all().filter(pub_date__year=2006) Entry.objects.filter( headline__startswith='What').exclude(pub_date__gte=datetime.date.today()).filter(pub_date__gte=datetime.date(2005, 1, 30)) # get 方法与 filter 类似,但是只返回一个数据,如果有0或多条数据则会报错 one_entry = Entry.objects.get(pk=1) # limit 操作 Entry.objects.all()[:5]
Entry.values(blog).distinct()
Entry.values_list(blog) # values:返回包含字典的列表,每行的数据为字典格式,键为字段名;values_list:返回包含元组的列表,每行的数据是元组格式
跨表查找:
Entry.objects.filter(blog__name='Beatles Blog') # 正向查询,外键字段__对应表的字段 Blog.objects.filter(entry__headline__contains='Lennon') # 反向查询,使用模型的小写表名称__字段名;
Blog.objects.filter(name = 'Beatles Blog').entry_set.all() # 反向连表查询:小写表目_set;与上述区别是前者是查询某字段,而后者是查询一系列记录
Blog.objects.filter(entry__authors__name='Lennon') Blog.objects.filter(entry__authors__name__isnull=True) Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True) # F查询,F查询专门对对象中某列值的操作,获取原来值的功能,F(字段名) from django.db.models import F Entry.objects.filter(n_comments__gt=F('n_pingbacks')) Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2) Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks')) Entry.objects.filter(authors__name=F('blog__name')) # 使用双下划线表示法来跨越F()对象中的关系 Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) Entry.objects.filter(headline__contains='%') # 包含关系,类似like # Q查询,Q查询可以组合使用 “&”, “|” 操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象, # Q对象可以用 “~” 操作符放在前面表示否定,也可允许否定与不否定形式的组合。Q对象可以与关键字参数查询一起使用, #不过一定要把Q对象放在关键字参数查询的前面。 Poll.objects.get(Q(question__startswith='Who') & Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))) SELECT * from polls WHERE question LIKE 'Who%',(pub_date = '2005-05-02' OR pub_date = '2005-05-06') Poll.objects.get(Q(pub_date=date(2005, 5, 2)) |Q(pub_date=date(2005, 5, 6)),question__startswith='Who')
# 删除,每一个QuerySet都有delete方法,返回已删除的对象数和具有每个对象类型的删除数的字典 Entry.objects.filter(pub_date__year=2005).delete() Entry.objects.all().delete() # 更新操作 Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same') b = Blog.objects.get(pk=1) Entry.objects.all().update(blog=b) Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same') Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1) Entry.objects.update(headline=F('blog__name'))
聚合函数:
from django.db import models class Author(models.Model): name = models.CharField(max_length=100) age = models.IntegerField() class Publisher(models.Model): name = models.CharField(max_length=300) class Book(models.Model): name = models.CharField(max_length=300) pages = models.IntegerField() price = models.DecimalField(max_digits=10, decimal_places=2) rating = models.FloatField() authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) pubdate = models.DateField() class Store(models.Model): name = models.CharField(max_length=300) books = models.ManyToManyField(Book)
aggregate:aggregate是一个终结子句QuerySet
,当被调用时,返回一个key-value的字典。名称是聚合值的标识符; 该值是计算的聚合。名称将根据字段名称和聚合函数自动生成。如果要手动指定聚合值的名称,可以通过在指定聚合子句时提供该名称来执行此操作:
>>> from django.db.models import Avg >>> Book.objects.aggregate(Avg('price')) {'price__avg': 34.35} >>> Book.objects.aggregate(average_price=Avg('price')) {'average_price': 34.35} >>> from django.db.models import Avg, Max, Min >>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
annotate:返回的是QuerySet对象,只不过多了聚合函数字段,可以与任何 filter、order_by等连用,类似于having,用于分组后的数据。
# Build an annotated queryset >>> from django.db.models import Count >>> q = Book.objects.annotate(Count('authors')) # Interrogate the first object in the queryset >>> q[0] <Book: The Definitive Guide to Django> >>> q[0].authors__count 2
其他:
# order by Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors') # 加 - 是DESC # values,查找那列 Author.objects.values('name').annotate(average_rating=Avg('book__rating'))
Manager(提供数据库查询操作的接口):默认情况下,每个模型都会默认创建一个objects属性,你也可以显示的写在模型内、重命名、增加、修改objects。
from django.db import models class Person(models.Model): #... people = models.Manager()
# First, define the Manager subclass. class DahlBookManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(author='Roald Dahl') # Then hook it into the Book model explicitly. class Book(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=50) objects = models.Manager() # The default manager. dahl_objects = DahlBookManager() # The Dahl-specific manager.
原生sql的查询:
class Person(models.Model): first_name = models.CharField(...) last_name = models.CharField(...) birth_date = models.DateField(...) >>> for p in Person.objects.raw('SELECT * FROM myapp_person'): ... print(p) John Smith Jane Jones >>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]
数据库的设置:default必须存在
不同app使用不同数据库
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'db1': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django_advanced', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'root', } }
设置setting:
DATABASES_APPS_MAPPING = { 'polls': 'default', 'polls2': 'db1', } DATABASE_ROUTERS = ['my_blog.database_app_router.DatabaseAppsRouter'] # 路由文件的路径
路由文件:database_app_router.py
from django.conf import settings class DatabaseAppsRouter(object): def db_for_read(self, model, **hints): app_label = model._meta.app_label # 取Meta下的app_label(app名称)属性 if app_label in settings.DATABASES_APPS_MAPPING: res = settings.DATABASES_APPS_MAPPING[app_label] print(res) return res return None def db_for_write(self, model, **hints): app_label = model._meta.app_label if app_label in settings.DATABASES_APPS_MAPPING: return settings.DATABASES_APPS_MAPPING[app_label] return None def allow_relation(self, obj1, obj2, **hints): # 获取对应数据库的名字 db_obj1 = settings.DATABASES_APPS_MAPPING.get(obj1._mata.app_label) db_obj2 = settings.DATABASES_APPS_MAPPING.get(obj2._mata.app_label) if db_obj1 and db_obj2: if db_obj1 == db_obj2: return True else: return False return None def db_for_migrate(self, db, app_label, model_name=None, **hints): if db in settings.DATABASES_APPS_MAPPING.values(): return settings.DATABASES_APPS_MAPPING.get(app_label) == db elif app_label in settings.DATABASES_APPS_MAPPING: return False return None
同一个app下使用不同的数据库:
class Book2(models.Model): author = models.CharField(max_length=1024, blank=True, null=True) title = models.CharField(max_length=1024) class Meta: app_label = 'polls2'
URL调度:URL到视图函数的映射(查找不包括域名)
1.如果传入的对象HttpRequest具有root_urlconf(setting值的变量)属性,则有这个值代替root_urlconf
2.查找urlpatterns变量,其中有django.urls.path()和
django.urls.re_path()
3.Django依次匹配每个URL模式,在与请求的URL匹配的第一个模式停下来
4.一旦其中一个URL模式匹配,Django就会导入并调用给定的视图,并传参数:HttpRequest实例、如果匹配的URL模式未返回任何命名组,则正则表达式中的匹配将作为位置参数提供(即没有括号的正则匹配)、键字参数由路径表达式匹配的任何命名部分组成,由或者 可选kwargs
参数中指定的任何参数覆盖
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail), ]
str:匹配除路径分隔符之外的任何非空字符串'/'
。如果转换器未包含在表达式中,则这是默认值。
int:匹配零或任何正整数。返回一个int。
slug: 匹配由ASCII字母或数字组成的任何slug字符串,以及连字符和下划线字符。
uuid: 匹配格式化的UUID。要防止多个URL映射到同一页面,必须包含短划线,并且字母必须为小写。
path:匹配任何非空字符串,包括路径分隔符 '/'
。
正则表达式:django.urls.re_path(),(?P<name>pattern)
,name
的名称,并且 pattern
是要匹配的模式。
from django.urls import path, re_path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), # 没有命名的正则表达式组通过圆括号来捕获URL中的值,并以位置参数传递给视图;语法:(?p<name>pattern),name是参数名, re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail), ]
包含其他app的路由:它都会删除与该点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf以进行进一步处理。
from django.urls import include, path urlpatterns = [ # ... snip ... path('community/', include('aggregator.urls')), # 以 . 来表示路径的递进;include表示路由的转发,分发给其他APP的路由函数 path('contact/', include('contact.urls')), # ... snip ... ]
传递额外参数:
# main.py from django.urls import include, path urlpatterns = [ path('blog/', include('inner'), {'blog_id': 3}), ] # inner.py from django.urls import path from mysite import views urlpatterns = [ path('archive/', views.archive), path('about/', views.about), ]
URL的反向解析:
from django.urls import path from . import views urlpatterns = [ #... path('articles/<int:year>/', views.year_archive, name='news-year-archive'), #... ]
在模板中: <a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a> {# Or with the year in a template context variable: #} <ul> {% for yearvar in year_list %} <li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li> {% endfor %} </ul>
在Python中: from django.http import HttpResponseRedirect from django.urls import reverse def redirect_to_year(request): # ... year = 2006 # ... return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
方向解析的命名空间:由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回 我们在开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间。
urlpatterns = [ re_path(r'^admin/', admin.site.urls), re_path(r'^app01/', include("app01.urls",namespace="app01")), re_path(r'^app02/', include("app02.urls",namespace="app02")), ]
视图:它接受Web请求并返回Web响应。此响应可以是网页的HTML内容,重定向,404错误,XML文档等
Http404:当您返回错误时HttpResponseNotFound
,生成的错误页面的HTML(可以是HTML文件)
from django.http import Http404 from django.shortcuts import render from polls.models import Poll def detail(request, poll_id): try: p = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise Http404("Poll does not exist") return render(request, 'polls/detail.html', {'poll': p})
上传文件:
表单:数据存储在request.FILES ['file']中,如果<form>
表单有enctype="multipart/form-data"
属性时request.FILES 才有数据,否则没有数据
from django import forms class UploadFileForm(forms.Form): title = forms.CharField(max_length=50) file = forms.FileField()
from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import UploadFileForm # Imaginary function to handle an uploaded file. from somewhere import handle_uploaded_file def upload_file(request): if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) # 注意我们必须将request.FILES传入到表单的构造方法中,只有这样文件数据才能绑定到表单中。 if form.is_valid(): handle_uploaded_file(request.FILES['file']) return HttpResponseRedirect('/success/url/') else: form = UploadFileForm() return render(request, 'upload.html', {'form': form}) # 处理数据的函数 def handle_uploaded_file(f): with open('some/file/name.txt', 'wb+') as destination: for chunk in f.chunks(): # 使用UploadedFile.chunks()而不是read()是为了确保即使大的英文文件又不会将我们系统内存占满。 destination.write(chunk)
模块处理上传的文件:(负责把上传文件的路径存入数据库中,与form负责上传文件不同)
file = models.FileField(upload_to='demo_files/%Y/%m/%d/%H:%M:%S'),/%Y/%m/%d/%H:%M:%S 是自动调用时间函数。
upload_to为文件路径(MRDIA_ROOT/upload_to/filename),文件名为上传的文件名(不变)
MRDIA_ROOT:setting中的变量,如:os.path.join(BASE_DIR, 'media')
MRDIA_URL:浏览器访问上传文件的前缀,如:MEDIA_URL = '/media/',用于渲染模板
1、使用form类而不使用model类来上传文件
from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import ModelFormWithFileField def upload_file(request): if request.method == 'POST': form = ModelFormWithFileField(request.POST, request.FILES) if form.is_valid(): # 表单符合规范 # file is saved form.save() return HttpResponseRedirect('/success/url/') else: form = ModelFormWithFileField() return render(request, 'upload.html', {'form': form})
.2、使用model类和form类来上传文件;save()的方法代替了之前我自己定义写文件的方法。
def upload_file(request): if request.method == 'POST': form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): #表单数据如果合法 upload_from = FileModel() upload_from.title = form.cleaned_data['title'] # 表单的验证 upload_from.file = form.cleaned_data['file'] # 表单的验证 upload_from.save() #处理上传来的文件 return HttpResponse('文件上传成功!') else: form = UploadFileForm() return render(request, 'blog/upload.html', {'form':form})
上传多个文件:
#这个方法可以定义上传文件的目录和名字。 def user_directory_path(instance, filename): # file will be uploaded to MEDIA_ROOT/user_<id>/<filename> return 'uploads/%Y%m%d-{0}'.format(filename) class FileModel(models.Model): title = models.CharField(max_length=50) file = models.FileField(upload_to=user_directory_path) # 多个文件上传 class FileFieldForm(forms.Form): file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True})) # 多个文件上传视图 def upload_file(request): if request.method == 'POST': form = FileFieldForm(request.POST, request.FILES) files = request.FILES.getlist('file_field')#获得多个文件上传进来的文件列表。 if form.is_valid():#表单数据如果合法 for f in files: handle_uploaded_file(f)#处理上传来的文件 return HttpResponse('文件上传成功!') else: form = FileFieldForm() return render(request, 'blog/upload.html', {'form':form})
快捷函数:django.shortcuts模块
render:渲染模板并返回 response
request:请求对象
template_name : 模板名称路径
context:添加到模板的字典数据
status:状态码
using:加载模块的模板引擎
from django.shortcuts import render def my_view(request): # View code here... return render(request, 'myapp/index.html', { 'foo': 'bar', }, content_type='application/xhtml+xml')
redirect:重定向
from django.shortcuts import redirect # 通过调用函数 def my_view(request): ... obj = MyModel.objects.get(...) return redirect(obj) # 通过传递视图的名称和可选的一些位置或关键字参数; 使用以下reverse()方法将反向解析URL def my_view(request): ... return redirect('some-view-name', foo='bar') # 通过传递硬编码的URL来重定向到: def my_view(request): ... return redirect('/some/url/')
get_object_or_404(klass,* args,** kwargs):调用get()
给定的模型管理器,但它会引发Http404
而不是模型的 DoesNotExist
异常。
klass:一Model
类,一个Manager
,或QuerySet
从哪个实例来获取对象。
**kwargs:查找参数,应采用get()和接受的格式filter()
from django.shortcuts import get_object_or_404 def my_view(request): obj = get_object_or_404(MyModel, pk=1) get_object_or_404(Book, title__startswith='M', pk=1) get_object_or_404(Book.dahl_objects, title='Matilda')
会话:当SessionMiddleware
激活时,每个HttpRequest
对象将具有一个 session
属性,这是一个类似字典的对象。
__setitem__: request.session['fav_color'] = 'blue'
get:fav_color = request.session.get('fav_color', 'red')
flush:从会话中删除当前会话数据并删除会话cookie。确保用户无法从浏览器再次访问以前的会话数据
set_expiry:设置会话的到期时间。您可以传递许多不同的值:1.value
是整数,则会话将在多秒不活动后到期。2.如果value
是一个datetime
或timedelta
物体,该会议将在相应的日期/时间到期。3.value
是0
,用户的会话cookie将在用户的Web浏览器关闭时到期 4.value
是None
,则会话将恢复为使用全局会话到期策略。
def login(request): m = Member.objects.get(username=request.POST['username']) if m.password == request.POST['password']: request.session['member_id'] = m.id return HttpResponse("You're logged in.") else: return HttpResponse("Your username and password didn't match.")
表单:表单必须指定两样东西:1.负责响应用户输入数据的URL地址 2.数据请求使用的HTTP方法。
# HTML模板 <form action="/your-name/" method="post"> <label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" value="{{ current_name }}"> <input type="submit" value="OK"> </form> # form表单 from django import forms class NameForm(forms.Form): your_name = forms.CharField(label='Your name', max_length=100) # 这样整个表单在第一次渲染时,会显示如下: <label for="your_name">Your name: </label> <input id="your_name" type="text" name="your_name" maxlength="100" required> # 视图函数 from django.http import HttpResponseRedirect from django.shortcuts import render from .forms import NameForm def get_name(request): # if this is a POST request we need to process the form data if request.method == 'POST': form = NameForm(request.POST) if form.is_valid(): # 表单验证,如果不通过则返回带有数据的form,让用户重新填写(带之前的数据) return HttpResponseRedirect('/thanks/') else: form = NameForm() # 空表单 return render(request, 'name.html', {'form': form}) # name.html,所有的表单字段及其属性都将通过Django模板语言从 {{ form }} 中被解包成HTML标记。 <form action="/your-name/" method="post"> {% csrf_token %} {{ form }} # {{}}是替代变量 <input type="submit" value="Submit"> </form>
include 标签:标签允许在模板中包含其它的模板的内容 模板继承: HTML文件: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>菜鸟教程(runoob.com)</title> </head> <body> <h1>Hello World!</h1> <p>菜鸟教程 Django 测试。</p> {% block mainbody %} <p>original</p> {% endblock %} </body> </html> 继承的HTML文件:所有的 {% block %}...{% endblock %}
标签告诉模板引擎,子模板可以重载这些部分。
{%extends "base.html" %}
{% block mainbody %}
<p>继承了 base.html 文件</p>
{% endblock %}
已验证的表单数据放在 form.cleaned_data
from django.core.mail import send_mail 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 HttpResponseRedirect('/thanks/')
手动渲染模板:
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p> <p><label for="id_message">Message:</label> <textarea name="message" id="id_message" required></textarea></p> <p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p> <p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p> # 手动渲染 {{ form.non_field_errors }} <div class="fieldWrapper"> {{ form.subject.errors }} <label for="{{ form.subject.id_for_label }}">Email subject:</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> {{ form.cc_myself }} </div> # 完整的 <label> 元素还可以使用 label_tag() 来生成。 <div class="fieldWrapper"> {{ form.subject.errors }} {{ form.subject.label_tag }} {{ form.subject }} </div>
从模型创建表单:django.forms.ModelFrom
字段基本与模型一样。ForeignKey -> ModelChoiceField,ManyToMaryField -> ModelMultipleChoiceField,ModelChoiceField 和 ModelMultipleChoiceField 的选项为一个QuerySet
如果模型字段设置了blank = True,那么表单字段的required属性被设置为False,否则为True。
如果模型字段设置了 choices
,那么表单字段的 widget
会被设置为 Select
,其选项来自模型字段的 choices
。
from django.db import models from django.forms import ModelForm TITLE_CHOICES = ( ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ) class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) 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) class AuthorForm(ModelForm): class Meta: model = Author fields = ['name', 'title', 'birth_date'] class BookForm(ModelForm): class Meta: model = Book fields = ['name', 'authors'] # 等同于以下 from django import forms class AuthorForm(forms.Form): name = forms.CharField(max_length=100) title = forms.CharField( max_length=3, widget=forms.Select(choices=TITLE_CHOICES), ) birth_date = forms.DateField(required=False) class BookForm(forms.Form): name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
常用字段:
error_messages = { 'invalid':'格式错误', 'required':'用户名不能为空', 'min_length':'字符串太短', 'max_length':'字符串太长', 'max_value':'数字必须小于', 'min_value':'数字必须大于 } fields.CharField() fields.IntegerField() fields.EmailField() fields.URLField() fields.SlugField() fields.GenericIPAddressField() # IP地址 fields.DateField() fields.DatetimeField() # 是否为空 最长 最短 正则 fields.RegexField('正则表达式',error_messages={'invalid':'格式错误'}) Field required=True, 是否允许为空 widget=widgets.Select, HTML插件 label=None, 用于生成Label标签或显示内容 initial=None, 初始值 help_text='', 帮助信息(在标签旁边显示) error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直) validators=[], 自定义验证规则 localize=False, 是否支持本地化 disabled=False, 是否可以编辑 label_suffix=None Label内容后缀 ********************************************************************************* obj = fields.CharField() {{obj.as_p}} # 自动生成标签 {{obj.t1}} ********************************************************************************* CharField(Field) max_length=None, 最大长度 min_length=None, 最小长度 strip=True 是否移除用户输入空白 IntegerField(Field) max_value=None, 最大值 min_value=None, 最小值 FloatField(IntegerField) ... DecimalField(IntegerField) max_value=None, 最大值 min_value=None, 最小值 max_digits=None, 总长度 decimal_places=None, 小数位长度 BaseTemporalField(Field) input_formats=None 时间格式化 DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 DurationField(Field) 时间间隔:%d %H:%M:%S.%f ... RegexField(CharField) regex, 自定制正则表达式 max_length=None, 最大长度 min_length=None, 最小长度 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} EmailField(CharField) ... FileField(Field) allow_empty_file=False 是否允许空文件 ImageField(FileField) ... 注:需要PIL模块,pip3 install Pillow 以上两个字典使用时,需要注意两点: - form表单中 enctype="multipart/form-data" - view函数中 obj = MyForm(request.POST, request.FILES) URLField(Field) ... BooleanField(Field) ... NullBooleanField(BooleanField) ... ChoiceField(Field) ... choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) required=True, 是否必填 widget=None, 插件,默认select插件 label=None, Label内容 initial=None, 初始值 help_text='', 帮助提示 ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段 limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField) ... django.forms.models.ModelMultipleChoiceField TypedChoiceField(ChoiceField) coerce = lambda val: val 对选中的值进行一次转换 empty_value= '' 空值的默认值 MultipleChoiceField(ChoiceField) ... TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 对选中的每一个值进行一次转换 empty_value= '' 空值的默认值 ComboField(Field) fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) MultiValueField(Field) PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 SplitDateTimeField(MultiValueField) input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 required=True, widget=None, label=None, initial=None, help_text='' GenericIPAddressField protocol='both', both,ipv4,ipv6支持的IP格式 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 SlugField(CharField) 数字,字母,下划线,减号(连字符) ... UUIDField(CharField) uuid类型 ...
覆盖默认字段:要为字段指定自定义组件,请使用内部 Meta
类的 widgets
属性。widgets属性接收一个数据字典。其中每个元素的键必须是模型中的字段名之一,键值就是我们要自定义的内容了
from django.utils.translation import gettext_lazy as _ class AuthorForm(ModelForm): class Meta: model = Author fields = ('name', 'title', 'birth_date') labels = { 'name': _('Writer'), } help_texts = { 'name': _('Some useful help text.'), } error_messages = { 'name': { 'max_length': _("This writer's name is too long."), }, }
模板(存放在 templates 文件夹):用于分割文档的表示(presentation)和数据(data)的字符串文本。模板定义了占位符(placeholders)和各种定义文档应该如何显示的基本逻辑(即模板标签,template tag)。通常,模板用来生成 HTML,但是 Ddjango 模板同样能够生成任何基于文本的格式。{{ }} 里面是变量,{% %}是模板标签(需要封闭),可以是for、if等循环分支语句, | 是过滤器(linux的管道符类似)
配置:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], # 定义引擎应按搜索顺序查找模板源文件的目录列表。 'APP_DIRS': True, # 引擎是否应该在已安装的应用程序中查找模板。每个后端都定义了应该存储其模板的应用程序内的子目录的常规名称。 'OPTIONS': { # ... some options here ... }, }, ] <html> <head><title>Ordering notice</title></head> <body> <p>Dear {{ person_name }},</p> <p>Thanks for placing an order from {{ company }}. It's scheduled to ship on {{ ship_date|date:"F j, Y" }}.</p> <p>Here are the items you've ordered:</p> <ul> {% for item in item_list %} <li>{{ item }}</li> {% endfor %} </ul> {% if ordered_warranty %} <p>Your warranty information will be included in the packaging.</p> {% endif %} <p>Sincerely,<br />{{ company }}</p> </body> </html>
基于类的视图:从本质上讲,基于类的视图允许您使用不同的类实例方法响应不同的HTTP请求方法,而不是在单个视图函数中使用条件分支代码。
from django.http import HttpResponse def my_view(request): if request.method == 'GET': # <view logic> return HttpResponse('result') ## 等价于 from django.http import HttpResponse from django.views import View class MyView(View): def get(self, request): # <view logic> return HttpResponse('result')
因为Django的URL解析器期望将请求和关联的参数发送到可调用函数而不是类,所以基于类的视图具有一个 as_view()
类方法,该方法返回一个函数,当请求到达匹配关联模式的URL时,可以调用该函数。该函数创建类的实例,调用setup()
初始化其属性,然后调用其dispatch()
方法。 dispatch
查看请求以确定它是否为GET
, POST
等等,并将请求中继到匹配方法(如果已定义),或者HttpResponseNotAllowed
如果不是则引发:
# urls.py from django.urls import path from myapp.views import MyView urlpatterns = [ path('about/', MyView.as_view()), ]
配置类属性:1.在类中直接硬编码 2.在配置as.view时传参数
# 第一种 from django.http import HttpResponse from django.views import View class GreetingView(View): greeting = "Good Day" def get(self, request): return HttpResponse(self.greeting) # 第二种 urlpatterns = [ path('about/', GreetingView.as_view(greeting="G'day")), ]