一、django默認支持sqlite,mysql, oracle,postgresql數據庫。javascript
<1> sqlitephp
django默認使用sqlite的數據庫,默認自帶sqlite的數據庫驅動 , 引擎名稱:django.db.backends.sqlite3css
<2> mysqlhtml
引擎名稱:django.db.backends.mysql前端
二、mysql驅動程序java
三、在django的項目中會默認使用sqlite數據庫,在settings裏有以下設置:python
建立數據庫表的步驟:mysql
一、建立model;jquery
二、建立生成數據庫的py文件:python manage.py makemigrations ;linux
三、建立數據庫表:python manage.py migrate;
注意:記得在settings裏的INSTALLED_APPS中加入'app01',而後再同步數據庫。
打開pycharm右側的databases,把建立好的db.sqlite3數據庫拖過去,就能夠操做數據庫表了,這裏咱們不用操心數據庫名的問題;
若是咱們想要使用別的數據庫,好比Mysql須要修改以下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'books', #你的數據庫名稱 'USER': 'root', #你的數據庫用戶名 'PASSWORD': '', #你的數據庫密碼 'HOST': '', #你的數據庫主機,留空默認爲localhost 'PORT': '3306', #你的數據庫端口 } }
注意:
NAME即數據庫的名字,在mysql鏈接前該數據庫必須已經建立,而上面的sqlite數據庫下的db.sqlite3則是項目自動建立 USER和PASSWORD分別是數據庫的用戶名和密碼。 設置完後,再啓動咱們的Django項目前,咱們須要激活咱們的mysql。 而後,啓動項目,會報錯:no module named MySQLdb 這是由於django默認你導入的驅動是MySQLdb,但是MySQLdb對於py3有很大問題,因此咱們須要的驅動是PyMySQL 因此,咱們只須要找到項目名文件下的__init__,在裏面寫入: import pymysql pymysql.install_as_MySQLdb() 問題解決!
四、ORM(對象關係映射)
關係對象映射(Object Relational Mapping,簡稱ORM),用於實現面向對象編程語言裏不一樣類型系統的數據之間的轉換,換言之,就是用面向對象的方式去操做數據庫的建立表以及增刪改查等操做。
優勢: 一、ORM使得咱們的通用數據庫交互變得簡單易行,並且徹底不用考慮該死的SQL語句。快速開發,由此而來。
二、能夠避免一些新手程序猿寫sql語句帶來的性能問題。
缺點:一、性能有所犧牲,不過如今的各類ORM框架都在嘗試各類方法,好比緩存,延遲加載登來減輕這個問題。效果很顯著。
二、對於個別複雜查詢,ORM仍然力不從心,爲了解決這個問題,ORM通常也支持寫raw sql。
三、經過QuerySet的query屬性查詢對應操做的sql語句
author_obj=models.Author.objects.filter(id=2) print(author_obj.query)
from django.db import models class Userinfo(models.Model): name = models.CharField(max_length=30) email = models.EmailField() memo = models.TextField()
<1> CharField #字符串字段, 用於較短的字符串. #CharField 要求必須有一個參數 maxlength, 用於從數據庫層和Django校驗層限制該字段所容許的最大字符數. <2> IntegerField #用於保存一個整數. <3> FloatField # 一個浮點數. 必須 提供兩個參數: # # 參數 描述 # max_digits 總位數(不包括小數點和符號) # decimal_places 小數位數 # 舉例來講, 要保存最大值爲 999 (小數點後保存2位),你要這樣定義字段: # # models.FloatField(..., max_digits=5, decimal_places=2) # 要保存最大值一百萬(小數點後保存10位)的話,你要這樣定義: # # models.FloatField(..., max_digits=19, decimal_places=10) # admin 用一個文本框(<input type="text">)表示該字段保存的數據. <4> AutoField # 一個 IntegerField, 添加記錄時它會自動增加. 你一般不須要直接使用這個字段; # 自定義一個主鍵:my_id=models.AutoField(primary_key=True) # 若是你不指定主鍵的話,系統會自動添加一個主鍵字段到你的 model. <5> BooleanField # A true/false field. admin 用 checkbox 來表示此類字段. <6> TextField # 一個容量很大的文本字段. # admin 用一個 <textarea> (文本區域)表示該字段數據.(一個多行編輯框). <7> EmailField # 一個帶有檢查Email合法性的 CharField,不接受 maxlength 參數. <8> DateField # 一個日期字段. 共有下列額外的可選參數: # Argument 描述 # auto_now 當對象被保存時,自動將該字段的值設置爲當前時間.一般用於表示 "last-modified" 時間戳. # auto_now_add 當對象首次被建立時,自動將該字段的值設置爲當前時間.一般用於表示對象建立時間. #(僅僅在admin中有意義...) <9> DateTimeField # 一個日期時間字段. 相似 DateField 支持一樣的附加選項. <10> ImageField # 相似 FileField, 不過要校驗上傳對象是不是一個合法圖片.#它有兩個可選參數:height_field和width_field, # 若是提供這兩個參數,則圖片將按提供的高度和寬度規格保存. <11> FileField # 一個文件上傳字段. #要求一個必須有的參數: upload_to, 一個用於保存上載文件的本地文件系統路徑. 這個路徑必須包含 strftime #formatting, #該格式將被上載文件的 date/time #替換(so that uploaded files don't fill up the given directory). # admin 用一個<input type="file">部件表示該字段保存的數據(一個文件上傳部件) . #注意:在一個 model 中使用 FileField 或 ImageField 須要如下步驟: #(1)在你的 settings 文件中, 定義一個完整路徑給 MEDIA_ROOT 以便讓 Django在此處保存上傳文件. # (出於性能考慮,這些文件並不保存到數據庫.) 定義MEDIA_URL 做爲該目錄的公共 URL. 要確保該目錄對 # WEB服務器用戶賬號是可寫的. #(2) 在你的 model 中添加 FileField 或 ImageField, 並確保定義了 upload_to 選項,以告訴 Django # 使用 MEDIA_ROOT 的哪一個子目錄保存上傳文件.你的數據庫中要保存的只是文件的路徑(相對於 MEDIA_ROOT). # 出於習慣你必定很想使用 Django 提供的 get_<#fieldname>_url 函數.舉例來講,若是你的 ImageField # 叫做 mug_shot, 你就能夠在模板中以 {{ object.#get_mug_shot_url }} 這樣的方式獲得圖像的絕對路徑. <12> URLField # 用於保存 URL. 若 verify_exists 參數爲 True (默認), 給定的 URL 會預先檢查是否存在( 即URL是否被有效裝入且 # 沒有返回404響應). # admin 用一個 <input type="text"> 文本框表示該字段保存的數據(一個單行編輯框) <13> NullBooleanField # 相似 BooleanField, 不過容許 NULL 做爲其中一個選項. 推薦使用這個字段而不要用 BooleanField 加 null=True 選項 # admin 用一個選擇框 <select> (三個可選擇的值: "Unknown", "Yes" 和 "No" ) 來表示這種字段數據. <14> SlugField # "Slug" 是一個報紙術語. slug 是某個東西的小小標記(短籤), 只包含字母,數字,下劃線和連字符.#它們一般用於URLs # 若你使用 Django 開發版本,你能夠指定 maxlength. 若 maxlength 未指定, Django 會使用默認長度: 50. #在 # 之前的 Django 版本,沒有任何辦法改變50 這個長度. # 這暗示了 db_index=True. # 它接受一個額外的參數: prepopulate_from, which is a list of fields from which to auto-#populate # the slug, via JavaScript,in the object's admin form: models.SlugField # (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField #一個校驗值是否爲合法XML的 TextField,必須提供參數: schema_path, 它是一個用來校驗文本的 RelaxNG schema #的文件系統路徑. <14> FilePathField # 可選項目爲某個特定目錄下的文件名. 支持三個特殊的參數, 其中第一個是必須提供的. # 參數 描述 # path 必需參數. 一個目錄的絕對文件系統路徑. FilePathField 據此獲得可選項目. # Example: "/home/images". # match 可選參數. 一個正則表達式, 做爲一個字符串, FilePathField 將使用它過濾文件名. # 注意這個正則表達式只會應用到 base filename 而不是 # 路徑全名. Example: "foo.*\.txt^", 將匹配文件 foo23.txt 卻不匹配 bar.txt 或 foo23.gif. # recursive可選參數.要麼 True 要麼 False. 默認值是 False. 是否包括 path 下面的所有子目錄. # 這三個參數能夠同時使用. # match 僅應用於 base filename, 而不是路徑全名. 那麼,這個例子: # FilePathField(path="/home/images", match="foo.*", recursive=True) # ...會匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField # 一個字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16># CommaSeparatedIntegerField # 用於存放逗號分隔的整數值. 相似 CharField, 必需要有maxlength參數.
<1> null : 數據庫中字段是否能夠爲空 <2> blank: django的 Admin 中添加數據時是否可容許空值 <3> default:設定缺省值 <4> editable:若是爲假,admin模式下將不能改寫。缺省爲真 <5> primary_key:設置主鍵,若是沒有設置django建立表時會自動加上: id = meta.AutoField('ID', primary_key=True) primary_key=True implies blank=False, null=False and unique=True. Only one primary key is allowed on an object. <6> unique:數據惟一 <7> verbose_name Admin中字段的顯示名稱 <8> validator_list:有效性檢查。非有效產生 django.core.validators.ValidationError 錯誤 <9> db_column,db_index 若是爲真將爲此字段建立索引 <10>choices:一個用來選擇值的2維元組。第一個值是實際存儲的值,第二個用來方便進行選擇。 如SEX_CHOICES= (( ‘F’,'Female’),(‘M’,'Male’),) gender = models.CharField(max_length=2,choices = SEX_CHOICES)
a、基本操做
---------------------增(create , save) --------------------- from app01.models import * #create方式一: Author.objects.create(name='Alvin') #create方式二: Author.objects.create(**{"name":"alex"}) #save方式一: author=Author(name="alvin") author.save() #save方式二: author=Author() author.name="alvin" author.save() ---------------------刪(delete) ------------------------- Book.objects.filter(id=1).delete() ---------------------改(update和save)------------------ # 方法一,get只能獲得一個對象 book = Book.objects.get(author='charlie') book.price = 200 book.save() # 方法二,推薦使用 Book.objects.filter(name='python').update(price=100) 注意: <1> 第二種方式修改不能用get的緣由是:update是QuerySet對象的方法, get返回的是一個model對象,它沒有update方法,而filter返回的是一個QuerySet對象 (filter裏面的條件可能有多個條件符合,好比name='alvin',可能有兩個name='alvin'的行數據)。 <2>在「插入和更新數據」小節中,咱們有提到模型的save()方法,這個方法會 更新一行裏的全部列。 而某些狀況下,咱們只須要更新行裏的某幾列。 ---------------------查(filter,value等) ---------------------- # <1>filter(**kwargs): 它包含了與所給篩選條件相匹配的對象 # <2>all(): 查詢全部結果 # <3>get(**kwargs): '''返回與所給篩選條件相匹配的對象,返回結果有且只有一個,若是符合篩選條件的 #對象超過一個或者沒有都會拋出錯誤。''' #下面的方法都是對查詢的結果再進行處理:好比 objects.filter.values()-------- # <4>values(*field): '''返回一個ValueQuerySet——一個特殊的QuerySet, 運行後獲得的並非一系列 model的實例化對象,而是一個可迭代的字典序列''' # <5>exclude(**kwargs): 它包含了與所給篩選條件不匹配的對象 # <6>order_by(*field): 對查詢結果排序 # <7>reverse(): 對查詢結果反向排序 # <8>distinct(): 從返回結果中剔除重複紀錄,all()後面跟去重沒用,由於ID沒有重複,只有在values()後面跟去重纔有用,對某一個字段進行去重; # <9>values_list(*field): 它與values()很是類似,它返回的是一個元組序列,values返回的是一個字典序列 # <10>count(): 返回數據庫中匹配查詢(QuerySet)的對象數量。 # <11>first(): 返回第一條記錄 # <12>last(): 返回最後一條記錄 # <13>exists(): 若是QuerySet包含數據,就返回True,不然返回False。
實例:
models文件內容
from django.db import models class Book(models.Model): name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() author = models.CharField(max_length=20,null=False) def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=20) age = models.IntegerField()
view文件內容
#view.py def addbook(request): # 添加表記錄方法一 book = Book(name='python',price=99,pub_data='2018-2-8',author='charlie') book.save() # 方法二 Book.objects.create(name='php',price=88,pub_data='2018-10-8',author='oldboy') # Book.objects.create(**dic)添加字典內容 return HttpResponse('添加成功') def update(request): # 方法一,get用於只獲得一個對象,若是對象重複就報錯 # 這種方法會把全部的字段都從新賦值,效率低 book = Book.objects.get(id=1) book.price = 998 book.save() # 方法二,推薦使用,update是Queryset方法,只更新查詢到的記錄 Book.objects.filter(name='python').update(price=150) return HttpResponse('修改爲功') def select(request): book_list = Book.objects.all() book_list = Book.objects.all()[:3]#前三個 book_list = Book.objects.all()[::2]#每兩個取一個 book_list = Book.objects.all()[::-1]#倒着取 # value只取指定的字段,獲得一個查詢集,內容是一個字典 ret = Book.objects.filter(name='charlie').values('name','price') # 獲得一個查詢集,內容是一個列表,列表元素是元組 ret = Book.objects.filter(name='charlie').values_list('name','price') return render(request,'index.html',locals())
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
b、利用雙下劃線方法進行模糊匹配
# 獲取個數 models.Tb1.objects.filter(name='seven').count() # 大於,小於 # models.Tb1.objects.filter(id__gt=1) # 獲取id大於1的值 models.Tb1.objects.filter(id__gte=1) # 獲取id大於等於1的值 models.Tb1.objects.filter(id__lt=10) # 獲取id小於10的值 models.Tb1.objects.filter(id__lte=10) # 獲取id小於10的值 models.Tb1.objects.filter(id__lt=10, id__gt=1) # 獲取id大於1 且 小於10的值 # in # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 獲取id等於十一、2二、33的數據 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # isnull Entry.objects.filter(pub_date__isnull=True) #某字段是否能夠爲空 # contains # models.Tb1.objects.filter(name__contains="ven") #內容裏包含某字符串 models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感 models.Tb1.objects.exclude(name__icontains="ven") #內容裏沒有某字段(不區分大小寫) # range # models.Tb1.objects.filter(id__range=[1, 2]) # 範圍bettwen and # 其餘相似 # startswith,istartswith(不區分大小寫), endswith, iendswith, # order by # models.Tb1.objects.filter(name='seven').order_by('id') # asc models.Tb1.objects.filter(name='seven').order_by('-id') # desc,反向 # group by # from django.db.models import Count, Min, Max, Sum models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" # limit 、offset # models.Tb1.objects.all()[10:20] # regex正則匹配,iregex 不區分大小寫 # Entry.objects.get(title__regex=r'^(An?|The) +') Entry.objects.get(title__iregex=r'^(an?|the) +') # date # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) # year # Entry.objects.filter(pub_date__year=2005) Entry.objects.filter(pub_date__year__gte=2005) # month # Entry.objects.filter(pub_date__month=12) Entry.objects.filter(pub_date__month__gte=6) # day # Entry.objects.filter(pub_date__day=3) Entry.objects.filter(pub_date__day__gte=3) # week_day # Entry.objects.filter(pub_date__week_day=2) Entry.objects.filter(pub_date__week_day__gte=2) # hour # Event.objects.filter(timestamp__hour=23) Event.objects.filter(time__hour=5) Event.objects.filter(timestamp__hour__gte=12) # minute # Event.objects.filter(timestamp__minute=29) Event.objects.filter(time__minute=46) Event.objects.filter(timestamp__minute__gte=29) # second # Event.objects.filter(timestamp__second=31) Event.objects.filter(time__second=2) Event.objects.filter(timestamp__second__gte=31)
c、連表操做(了不得的雙下劃線)
#Django升級到2版本以後models.ForeignKey()須要填寫on_delect參數 on_delete=models.CASCADE, # 刪除關聯數據,與之關聯也刪除 on_delete=models.DO_NOTHING, # 刪除關聯數據,什麼也不作,最好不要; on_delete=models.PROTECT, # 刪除關聯數據,引起錯誤ProtectedError on_delete=models.SET_DEFAULT #設置默認值,前提是ForeignKey必須設置的默認值; on_delete=models.SET(...) '''設置給定值,或者若是傳入了callable,則調用它的結果。 在大多數狀況下,爲了不在導入models.py時執行查詢,必須傳遞callable''' on_delete=models.SET_NULL, # 刪除關聯數據,與之關聯的值設置爲null(前提FK字段須要設置爲可空,一對一同理) # 例如:models.ForeignKey('關聯表', on_delete=models.SET_NULL, blank=True, null=True)
#models.py from django.db import models class Book(models.Model): '''書籍''' name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() publish = models.ForeignKey('Publish',on_delete=models.CASCADE) def __str__(self): return self.name class Author(models.Model): '''做者''' name = models.CharField(max_length=20) class Publish(models.Model): '''出版社''' #一對多的外鍵要加在多的哪一張表裏,一本書只能有一個出版社 name = models.CharField(max_length=30) city = models.CharField(max_length=30)
from django.shortcuts import render,HttpResponse from app.models import * #-----------------------增----------------------- # 方式一,給publish_id賦值,django默認給你的外鍵字段添加_id Book.objects.create(name='linux運維',price=88,pub_data='2016-10-8',publish_id=1) # 方式二,給publish字段直接賦值,須要先獲得一個對象 pulish_obj = Publish.objects.filter(name='西湖出版社')[0] Book.objects.create(name='Java', price=188, pub_data='2016-1-23', publish=pulish_obj) #-----------------------刪----------------------- Book.objects.filter(publish__name='西湖出版社').delete() #-----------------------改----------------------- Book.objects.filter(publish__name='人民出版社').update(price=300) #-----------------------查----------------------- #第一種方式:經過對象(不推薦使用) # 正向 book_obj = Book.objects.get(name='python') publish_obj = book_obj.publish publish_name = publish_obj.name #反向 pub_obj = Publish.objects.filter(name='西湖出版社')[0] #下面的到一個查詢集,內容是字典,取第一個字典 pub_name = pub_obj.book_set.all().values('name','price')[0] #第二種方式:經過filter(__),推薦使用 #正向查詢,指定出版社的書籍信息;從有外鍵的表查詢爲正向; book_name = Book.objects.filter(publish__name='西湖出版社').values('name','price')[0] # 反向查詢,指定書籍的出版社信息,經過 類名__字段名 pub_name = Publish.objects.filter(book__name='python').values('name','city')[0] #經過value(__) book_name = Book.objects.filter(name='Java').values('publish__name','publish__city')[0] # 查詢全部在北京的出版社出版的書籍,獲得一個列表 book_list = Publish.objects.filter(city='北京').values('book__name') book_list2 = Book.objects.filter(publish__city='北京').values('name')
from django.db import models class Book(models.Model): '''書籍''' name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() # 一對多,一本書只能有一個出版社 publish = models.ForeignKey('Publish',on_delete=models.CASCADE) # 多對多,一本書能夠有多個做者,系統會自動建立第三張表:app_book_authors #可是不能直接對這張表進行操做,由於沒有它的類 authors = models.ManyToManyField('Author') def __str__(self): return self.name class Publish(models.Model): '''出版社''' #一對多的外鍵要加在多的哪一張表裏,一本書只能有一個出版社 name = models.CharField(max_length=30) city = models.CharField(max_length=30) def __str__(self): return self.name class Author(models.Model): '''做者''' name = models.CharField(max_length=20) age = models.IntegerField(default=20) def __str__(self): return self.name
from django.shortcuts import render,HttpResponse from app.models import * from django.db.models import Avg,Min,Max,Count,Sum #------------------增-------------------- #方式一 book_obj = Book.objects.get(id=4) book_obj.authors.add(1) book_obj.authors.add(*[2,3,]) #方式二 book_obj = Book.objects.get(id=4) author_objs = Author.objects.all() book_obj.authors.add(*author_objs) #------------------刪-------------------- # 先獲得一個書籍對象 book_obj = Book.objects.get(id=4) #刪除 book_obj.authors.remove(3) book_obj.authors.remove(*[1,2,]) #清空 book_obj.authors.clear() #------------------改-------------------- #重置,以設置的爲準,這裏列表不加*,已經有的不動,新內容裏沒有的就刪除 book_obj.authors.set([1,2,3]) #------------------查-------------------- #正向,找到id=2的書籍的全部做者 book_obj = Book.objects.get(id=2) book_obj.authors.all() #方式二 Book.objects.filter(id=2).values('authors__name') # 反向,找到做者的全部書籍 author_obj = Author.objects.get(id=1) author_obj.book_set.all() #全部書籍名稱和對應的做者名 book_list = Book.objects.all().values('name','authors__name') #列舉做者charlie的全部書籍 Book.objects.filter(authors__name="charlie") #-----------------聚合函數aggregate()------------------ #求全部書籍的平均價格 ret = Book.objects.all().aggregate(Avg('price'))#{'price__avg': 122.0} # 也能夠自定義名稱,結果:{'avg_price': 122.0} ret = Book.objects.all().aggregate(avg_price=Avg('price')) #做者Charlie出的全部書,Count參數能夠是book表裏任意個字段,只是查有幾條記錄,查誰都同樣 ret = Book.objects.filter(authors__name='charlie').aggregate(Count('name')) #-----------------分組函數annotate--------------------- #按做者名分組,求每一個做者全部書籍的價格總和 ret = Book.objects.values('authors__name').annotate(Sum('price')) ''' <QuerySet [{'authors__name': 'charlie', 'price__sum': 366}, {'authors__name': 'alex', 'price__sum': 88}, {'authors__name': 'james', 'price__sum': 188}]> ''' #求每一個出版社的最低價格的書 ret = Publish.objects.values('name').annotate(Min('book__price'))
#去掉authors = models.ManyToManyField('Author') #建立新類 class Book_Author(models.Model): #第三張表 book = models.ForeignKey('Book',on_delete=models.CASCADE) author = models.ForeignKey('Author',on_delete=models.CASCADE) def __str__(self): return self.name #添加 Book_Author.objects.create(book_id=3,author_id=1) # 利用對象查詢 book_obj = Book.objects.get(id=2) author_name = book_obj.book_author_set.all()[0].author print(author_name) #雙下劃線查詢 book_list1 = Author.objects.filter(name='charlie').values('book_author__book') print(book_list1) book_list2 = Book.objects.filter(book_author__author__name='charlie').values('name') print(book_list2)
from django.db.models import F,Q #F 使用查詢條件的值,專門取對象中某列值的操做 #給全部的書價格加10 Book.objects.all().update(price=F('price') + 10) # Q 進行條件或的查詢,| 或,知足任意條件 ret = Book.objects.filter(Q(price=110)|Q(name='GO')) #~ 非,不知足條件 ret = Book.objects.filter(~Q(name='GO')) #書籍名稱中包含某個字母 ret = Book.objects.filter(Q(name__contains='J')) #Q查詢和關鍵字查詢結合使用,Q查詢必定要放前面 ret = Book.objects.filter(Q(price=110),name='GO') print(ret) #Q(name__startswith='P') 書籍名稱以字母P開頭
三、補充整理
跨多張表查詢,能夠連續使用雙下劃線
class Book(models.Model): '''書籍''' name = models.CharField(max_length=20) price = models.IntegerField() pub_data = models.DateField() authors = models.ForeignKey('Author',on_delete=models.CASCADE) class Author(models.Model): '''做者''' name = models.CharField(max_length=20) age = models.IntegerField(default=20) country = models.ForeignKey('Country',on_delete=models.CASCADE) class Country(models.Model): '''國家''' name = models.CharField(max_length=20) #全部中國籍做者出的全部書籍 Book.objects.filter(authors__country__name='China') #正向查找:Book.objects.filter(authors__name='charlie') #反向查找: obj = Authors.objects.filter(name='charlie').first() obj.book_set.all() #沒有外鍵的表裏其實隱藏了一個字段:類名_set,也能夠修改這個字段名 class Book(models.Model): authors = models.ForeignKey('Author',on_delete=models.CASCADE,related_name='book') obj = Authors.objects.filter(name='charlie').first() book_list = obj.book.all()#做者對應的全部書籍對象查詢集[obj(name,price,..),obj(name,price,..),]
所謂惰性機制:Publisher.objects.all()或者.filter()等都只是返回了一個QuerySet(查詢結果集對象),它並不會立刻執行sql,而是當調用QuerySet的時候才執行。
QuerySet特色:<1> 可迭代的;<2> 可切片
QuerySet的高效使用:
<1>Django的queryset是惰性的 Django的queryset對應於數據庫的若干記錄(row),經過可選的查詢來過濾。例如,下面的代碼會得 到數據庫中名字爲‘Dave’的全部的人:person_set = Person.objects.filter(first_name="Dave") 上面的代碼並無運行任何的數據庫查詢。你可使用person_set,給它加上一些過濾條件,或者將它傳給某個函數, 這些操做都不會發送給數據庫。這是對的,由於數據庫查詢是顯著影響web應用性能的因素之一。 <2>要真正從數據庫得到數據,你能夠遍歷queryset或者使用if queryset,總之你用到數據時就會執行sql. 爲了驗證這些,須要在settings里加入 LOGGING(驗證方式) obj=models.Book.objects.filter(id=3) for i in obj: print(i) if obj: print("ok") <3>queryset是具備cache的 當你遍歷queryset時,全部匹配的記錄會從數據庫獲取,而後轉換成Django的model。這被稱爲執行 (evaluation).這些model會保存在queryset內置的cache中,這樣若是你再次遍歷這個queryset, 你不須要重複運行通用的查詢。可是若是你修改了數據庫,就須要再查詢一次,不然你查到的仍是上次的
緩存數據。 obj=models.Book.objects.filter(id=3) for i in obj: print(i) models.Book.objects.filter(id=3).update(title="GO") obj_new=models.Book.objects.filter(id=3) for i in obj: print(i) #LOGGING只會打印一次 <4>簡單的使用if語句進行判斷也會徹底執行整個queryset而且把數據放入cache,雖然你並不須要這些 數據!爲了不這個,能夠用exists()方法來檢查是否有數據: obj = Book.objects.filter(id=4) # exists()的檢查能夠避免數據放入queryset的cache。 if obj.exists(): print("hello world!") <5>當queryset很是巨大時,cache會成爲問題 處理成千上萬的記錄時,將它們一次裝入內存是很浪費的。更糟糕的是,巨大的queryset可能會鎖住系統 進程,讓你的程序瀕臨崩潰。要避免在遍歷數據的同時產生queryset cache,可使用iterator()方法 來獲取數據,處理完數據就將其丟棄。 objs = Book.objects.all().iterator() # iterator()能夠一次只從數據庫獲取少許數據,這樣能夠節省內存 for obj in objs: print(obj.name) #BUT,再次遍歷沒有打印,由於迭代器已經在上一次遍歷(next)到最後一次了,沒得遍歷了 for obj in objs: print(obj.name) 固然,使用iterator()方法來防止生成cache,意味着遍歷同一個queryset時會重複執行查詢。因此使 用iterator()的時候要小心,確保你的代碼在操做一個大的queryset時沒有重複執行查詢。 總結: queryset的cache是用於減小程序對數據庫的查詢,在一般的使用下會保證只有在須要的時候纔會查詢數據庫。 使用exists()和iterator()方法能夠優化程序對內存的使用。不過,因爲它們並不會生成queryset cache,可能 會形成額外的數據庫查詢。總之一句話,若是查詢數據量巨大時,使用迭代器;若是數據量很小,並且又須要重複查詢
時,使用查詢集。
admin是django強大功能之一,它能從數據庫中讀取數據,呈如今頁面中,進行管理。默認狀況下,它的功能已經很是強大,若是你不須要複雜的功能,它已經夠用,可是有時候,一些特殊的功能還須要定製,好比搜索功能,下面這一系列文章就逐步深刻介紹如何定製適合本身的admin應用。
若是你以爲英文界面很差用,能夠在setting.py 文件中修改如下選項:
LANGUAGE_CODE = 'en-us' #LANGUAGE_CODE = 'zh-hans'
使用django admin 則須要如下步驟:
一、建立後臺管理員
python manage.py createsuperuser
二、配置後臺管理url
url(r'^admin/', admin.site.urls)
三、註冊和配置django admin 後臺管理頁面
a、註冊:在admin中執行以下配置
from django.contrib import admin from app01 import models admin.site.register(models.UserType) admin.site.register(models.UserInfo) admin.site.register(models.UserGroup) admin.site.register(models.Asset)
b、設置數據表字段的顯示名稱
#方式一 class UserType(models.Model): name = models.CharField(max_length=50) #meta表示數據庫中顯示的信息 class Meta: db_table = 'UserType' #數據庫表名 verbose_name = '用戶類型' #數據庫字段名 verbose_name_plural = '用戶類型' #方式二 #修改models class Book(models.Model): '''書籍''' name = models.CharField(max_length=20,verbose_name='名稱') price = models.IntegerField('價格')
c、自定義頁面展現
from django.contrib import admin from app.models import * #自定製admin類 class BookAdmin(admin.ModelAdmin): #不能夠顯示多對多的關聯字段,注意這些都是元組,注意末尾的逗號 list_display = ('id','name','price','pub_date',) #可編輯 # list_editable = ('name','price','pub_date',) # 設置字段可垂直搜索 filter_horizontal = ('authors',) #設置每頁顯示的條目 # list_per_page = 2 #根據字段搜索,關聯字段加__,三個字段中重複的部分都會被搜索到 search_fields = ('id','name','publish__name',) #根據字段過濾 list_filter = ('pub_date','publish',) #根據字段排序,能夠多個字段,依次排序,‘-id’表示降序排 ordering = ('id',) #添加書籍時隱藏字段,列表裏必須是元組 fieldsets = [ #默認顯示name,fieldes是固定的,後面跟列表 (None,{'fields':['name',]}), #將下列字段以摺疊的方式顯示 ('other information',{'fields':['price','pub_date','publish'],'classes':['collapse',]}), ] #將模型註冊到admin admin.site.register(Book,BookAdmin)
四、註冊medel類到admin的兩種方式:
admin.site.register(Book,MyAdmin)
@admin.register(Book)
五、裝飾器使用方法
from django.contrib import admin from app01.models import * # Register your models here. # @admin.register(Book)#----->單給某個表加一個定製 class MyAdmin(admin.ModelAdmin): list_display = ("title","price","publisher") search_fields = ("title","publisher") list_filter = ("publisher",) ordering = ("price",) fieldsets =[ (None, {'fields': ['title']}), ('price information', {'fields': ['price',"publisher"], 'classes': ['collapse']}), ] admin.site.register(Book,MyAdmin) admin.site.register(Publish) admin.site.register(Author)
一、django中的Form通常有一下幾種功能:
生成HTML標籤
驗證用戶輸入:ajax和form表單提交
HTML Form提交保留上次提交數據
初始化頁面顯示內容
二、用form表單提交的基本流程
a、在views.py中建立一個類:F(forms.Form),若是正規操做,應該是在APP中新建一個forms.py文件,再導入;
b、類中建立字段(包含正則表達式和HTML插件)
c、用戶以GET方式請求:
把obj=F()發送到前端,這裏不用傳入任何參數
{{ obj.user }}...前端自動生成input標籤
d、用戶以POST方式請求:
obj = Form1(request.POST),對象中包含用戶輸入信息和錯誤信息
先驗證用戶輸入是否正確,若是正確就跳轉頁面,若是錯誤就發送obj
{{ obj.errors.user.0 }} 前端顯示錯誤信息
補充:
#mark_safe,不用加safe就能夠直接渲染HTML標籤 from django.utils.safestring import mark_safe txt = mark_safe("<input type='text'/") # novalidate忽略瀏覽器的驗證 <form action="/add_user/" method="POST" novalidate> # 上傳文件記得加上enctype <form action="/test/" method="POST" enctype="multipart/form-data" novalidate> # form表單自動生成input標籤,編輯時將數據庫查詢內容做爲input標籤默認值 data = UserInfo.objects.filter(id=nid).first() obj = F1({'username':data.username,'email':data.email})
實例:
from django.shortcuts import render,redirect,HttpResponse from django import forms from django.forms import fields class Form1(forms.Form): user = fields.CharField( min_length=6, max_length=18, required=True, error_messages={ 'required':'用戶名不能爲空', 'min_length':'用戶名過短', 'max_length':'用戶名太長', } ) pwd = fields.CharField( min_length=10, required=True, error_messages = { 'required': '密碼不能爲空', 'min_length': '密碼過短', } ) age = fields.IntegerField( required=True, error_messages={ 'required': '年齡不能爲空', 'invalid': '必須爲數字', } ) email = fields.EmailField( min_length=8, required=True, error_messages={ 'required': '郵箱不能爲空', 'invalid': '格式錯誤' } ) def f1(request): if request.method == 'GET': #自動生成input標籤 obj = Form1() return render(request,'f1.html',{'obj': obj}) if request.method == 'POST': obj = Form1(request.POST) #驗證是否成功 if obj.is_valid(): #若是成功,打印用戶提交的數據,跳轉 print("驗證成功",obj.cleaned_data) return redirect('http://www.baidu.com') else: print("驗證失敗", obj.errors) return render(request, 'f1.html', {'obj': obj})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> {% load staticfiles %} </head> <body> <form action="/f1.html" method="POST" id="fm"> <p>用戶名{{ obj.user }}{{ obj.errors.user.0 }}</p> <p>密碼{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p> <p>年齡{{ obj.age }}{{ obj.errors.age.0 }}</p> <p>郵箱{{ obj.email }}{{ obj.errors.email.0 }}</p> <input type="submit" value="提交"/> <input type="button" value="ajax提交" id="ajaxSubmit"/> </form> </body> </html>
三、建立Form類時,主要涉及到 【字段】 和 【插件】,字段用於對用戶請求數據的驗證,插件用於自動生成HTML;
Field required=True, 是否容許爲空 widget=None, 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內容後綴 #字符串 CharField(Field) max_length=None, 最大長度 min_length=None, 最小長度 strip=True 是否移除用戶輸入空白 #整型 IntegerField(Field) max_value=None, 最大值 min_value=None, 最小值 #浮點型,十進制小數 DecimalField(IntegerField) max_value=None, 最大長度 min_value=None, 最小長度 max_digits=None, 總長度 decimal_places=None, 小數位長度 #下拉框 ChoiceField(Field) choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),) required=True, 是否必填 widget=None, 插件,默認select插件 label=None, Label內容 initial=None, 初始值 help_text='', 幫助提示 TypedChoiceField(ChoiceField) coerce = lambda val: val 對選中的值進行一次轉換 empty_value= '' 空值的默認值 #多選框 MultipleChoiceField(ChoiceField) TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 對選中的每個值進行一次轉換 empty_value= '' 空值的默認值 #時間 DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 #郵箱 EmailField(CharField) #自定製正則表達式 RegexField(CharField) regex, 自定製正則表達式 max_length=None, 最大長度 min_length=None, 最小長度 error_message=None, 忽略,錯誤信息使用 error_messages={'invalid': '...'} #上傳文件 FileField(Field) allow_empty_file=False 是否容許空文件 ImageField(FileField) ... 注:須要PIL模塊,pip3 install Pillow 以上兩個字典使用時,須要注意兩點: - form表單中 enctype="multipart/form-data" - view函數中 obj = MyForm(request.POST, request.FILES) #IP GenericIPAddressField protocol='both', both,ipv4,ipv6支持的IP格式 unpack_ipv4=False 解析ipv4地址,若是是::ffff:192.0.2.1時候,可解析爲192.0.2.1, PS:protocol必須爲both才能啓用 #數據庫操做相關字段 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
#views.py from django.shortcuts import render from django import forms from django.forms import fields,widgets class TestForm(forms.Form): user = fields.CharField( required=True, min_length=3, max_length=12, error_messages={'required':'不能爲空',}, #widgets定製HTML插件 # widget=widgets.Select, label = '用戶名', label_suffix='->', ) age = fields.IntegerField(max_value=100,min_value=18) email = fields.EmailField() #FileField和ImageField功能同樣 img = fields.FileField() #生成下拉框,initial默認選中,或者obj = TestForm({'city':2})也能夠默認選擇 city = fields.ChoiceField( choices=[(1,'北京'),(2,'上海'),], initial=2, ) #多選框 hobby = fields.MultipleChoiceField( choices=[(1,'足球'),(2,'籃球'),], initial=[1,2], ) country = fields.TypedChoiceField( choices=[(1, '中國'), (2, '美國'), ], initial=2, #將傳入的參數作一個數據類型轉換 'country': 2 coerce=lambda x:int(x), empty_value='null',#空值的默認值 ) def test(request): if request.method == 'GET': obj = TestForm() return render(request, 'test.html',locals()) else: #上傳的文件在request.FILES中 obj = TestForm(request.POST,request.FILES) obj.is_valid() print(obj.cleaned_data) return render(request,'test.html',locals()) #test.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {#上傳文件記得加上enctype#} <form action="/test/" method="POST" enctype="multipart/form-data" novalidate> {% csrf_token %} <p>{{ obj.user.label }}{{ obj.user }}</p> <p>{{ obj.age.label }}{{ obj.age }}</p> <p>{{ obj.email.label }}{{ obj.email }}</p> <p>{{ obj.img.label }}{{ obj.img }}</p> <p>{{ obj.city.label }}{{ obj.city }}</p> <p>{{ obj.hobby.label }}{{ obj.hobby }}</p> <p>{{ obj.country.label }}{{ obj.country }}</p> <input type="submit" value="提交"/> </form> {# 能夠所有自動生成一個標籤,可是頁面排版沒法控制,不建議使用#} {# {{ obj.as_p }}#} </body> </html>
四、Django內置插件
#widgets定製HTML插件,每個字段都有本身的默認插件,還能夠定製屬性 widget=widgets.TextInput(attrs={'class':'c1'}),
TextInput(Input) NumberInput(TextInput) EmailInput(TextInput) URLInput(TextInput) PasswordInput(TextInput) HiddenInput(TextInput) Textarea(Widget) DateInput(DateTimeBaseInput) DateTimeInput(DateTimeBaseInput) TimeInput(DateTimeBaseInput) CheckboxInput Select NullBooleanSelect SelectMultiple RadioSelect CheckboxSelectMultiple FileInput ClearableFileInput MultipleHiddenInput SplitDateTimeWidget SplitHiddenDateTimeWidget SelectDateWidget
五、經常使用的選擇插件
# 單radio,值爲字符串 user = fields.CharField( initial=2, widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) ) # 單radio,值爲字符串 user = fields.ChoiceField( choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.RadioSelect ) # 單select,值爲字符串 user = fields.CharField( initial=2, widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) ) # 單select,值爲字符串 user = fields.ChoiceField( choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) # 多選select,值爲列表 user = fields.MultipleChoiceField( choices=((1,'上海'),(2,'北京'),), initial=[1,], widget=widgets.SelectMultiple ) # 單checkbox user = fields.CharField( widget=widgets.CheckboxInput() ) # 多選checkbox,值爲列表 user = fields.MultipleChoiceField( initial=[2, ], choices=((1, '上海'), (2, '北京'),), widget=widgets.CheckboxSelectMultiple )
六、在使用選擇標籤時,須要注意choices的選項是須要從數據庫中獲取,可是因爲是靜態字段 ***獲取的值沒法實時更新***,那麼須要自定義__init__構造方法,方法給widget.choices從新從數據庫中取值,這樣每次頁面一刷新就實例化一次form對象,就會執行一次init函數,也就是會從數據庫中取一次值。
方式一:
from django.shortcuts import render from django import forms from django.forms import fields,widgets from app.models import *#models.py中的類 class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.EmailField(max_length=32) def __str__(self): return self.username #建立自定義form class LoveForm(forms.Form): price = fields.IntegerField() user_id = fields.IntegerField(widget=widgets.Select) #靜態字段 獲取的值沒法實時更新,須要自定義構造方法 def __init__(self,*args,**kwargs): # super必須在上面,它拷貝了全部的靜態字段,下面才能去內部取字段 super(LoveForm,self).__init__(*args,**kwargs) self.fields['user_id'].widget.choices = UserInfo.objects.values_list('id','username') def love(request): obj = LoveForm() return render(request,'love.html',locals())
方式二:
#使用django提供的ModelChoiceField和ModelMultipleChoiceField字段來實現 from django.forms.models import ModelChoiceField class LoveForm(forms.Form): user_id2 = ModelChoiceField( #這裏沒法顯示數據庫表中的ID之外的字段,須要models中的類加上個__str__方法,返回某個字段 #雖然同樣能夠實時更新,可是與數據庫的格式關係太大,不能靈活顯示每一個字段,不建議使用 queryset=UserInfo.objects.all() )
七、使用ajax提交
沒法自動跳轉頁面,就是在views函數中設置redirect,ajax也不會遵從,須要在前端ajax的回調函數本身使用js代碼跳轉,window.location.href = 'http://www.baidu.com'
錯誤信息須要本身顯示到頁面,obj.errors 類型<class 'django.forms.utils.ErrorDict'>繼承dict,因此用json.dumps()不會報錯。
class AjaxForm(forms.Form): price = fields.IntegerField() user_id = fields.IntegerField( widget=widgets.Select(choices=[(1, '中國'), (2, '美國'), ],) ) def ajax(request): if request.method == 'GET': #自動生成input標籤,並無開始作驗證 obj = AjaxForm() return render(request,'ajax.html',{'obj': obj}) if request.method == 'POST': import json response = {'status':True,'msg':None} #並無開始作驗證 obj = AjaxForm(request.POST) #is_valid作的驗證 if obj.is_valid(): print("驗證成功",obj.cleaned_data) #ajax沒法自動跳轉,須要在前端手動設置 return HttpResponse(json.dumps(response)) else: #<class 'django.forms.utils.ErrorDict'>繼承dict print("驗證失敗", obj.errors,type(obj.errors)) response['status'] = False response['msg'] = obj.errors return HttpResponse(json.dumps(response))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> {% load staticfiles %} </head> <body> <form action="/ajax/" method="POST" novalidate id="fm"> {% csrf_token %} {{ obj.as_p }} <input type="button" value="ajax提交" id="ajaxSubmit"/> </form> <script src="{% static 'js/jquery-1.12.4.js' %}"></script> <script> $(function () { $('#ajaxSubmit').click(function () { $.ajax({ url:'/ajax/', type:'POST', data:$('#fm').serialize(), dataType:'JSON', success:function (arg) { if(arg.status){ window.location.href = 'http://www.baidu.com'; } console.log(arg); } }) }) }) </script> </body> </html>
八、在根據源碼流程自定義方法
from django.core.exceptions import ValidationError class AjaxForm(forms.Form): username = fields.CharField() user_id = fields.IntegerField( widget=widgets.Select(choices=[(1, '中國'), (2, '美國'), ],) ) #自定義方法clean_字段名, #必須返回值self.cleaned_data['username'] #若是出錯:raise ValidationError('用戶名已存在') def clean_username(self): v = self.cleaned_data['username'] if UserInfo.objects.filter(username=v).count(): #若是用戶信息中有一個字段錯了,總體就錯誤,顯示具體錯誤的詳細信息 raise ValidationError('用戶名已存在') return v def clean_user_id(self): return self.cleaned_data['user_id']
def clean(self): ''' obj.errors錯誤信息 { __all__:[], username:[], user_id:[], } ''' # Django的總體錯誤信息放在__all__中 v1 = self.cleaned_data.get('username') v2 = self.cleaned_data.get('user_id') if v1=='charlie' and v2==1: raise ValidationError('總體錯誤信息') return self.cleaned_data
from django.core.exceptions import NON_FIELD_ERRORS #這裏添加錯誤的時候k就是None self.add_error(None, e) #可是None又被替換成__all__,因此前端須要經過.all來取 NON_FIELD_ERRORS = '__all__' # js代碼 console.log(arg.msg.__all__); #HTML模板 #模板語言裏不支持雙下劃線格式,因此用obj.non_field_errors.0
九、is_valid()驗證表單時,一共給咱們留了三個鉤子
# 自定義方法名必須是clean_字段名 def _clean_fields(self): for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.get_initial_for_field(field, name) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.get_initial_for_field(field, name) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, 'clean_%s' % name): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e) # 同時驗證多個字段或者是聯合字段,返回總體錯誤信息,放在__all__中 def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data # 沒有捕捉異常的代碼,因此這個方法不能出錯誤 def _post_clean(self): """ An internal hook for performing additional cleaning after form cleaning is complete. Used for model validation in model forms. """ pass
十、擴展:ModelForm
在使用Model和Form時,都須要對字段進行定義並指定類型,經過ModelForm則能夠省去From中字段的定義
from django import forms from crm import models class CustomerForm(forms.ModelForm): class Meta: model = models.CustomerInfo # fields = ['name','consultant','status'] # 全部字段 fields = '__all__' widgets = { 'email' : forms.PasswordInput(attrs={'class':"alex"}), }
實例:
# forms.py class EnrollmentForm(forms.ModelForm): """審覈學員報名信息""" def __new__(cls, *args, **kwargs): # cls.base_fields獲取數據庫表裏的全部字段名 for field_name in cls.base_fields: field_obj = cls.base_fields.get(field_name) # 定製生成的標籤的class屬性 field_obj.widget.attrs.update({'class': 'form-control'}) # 只讀標籤不可編輯 if field_name in cls.Meta.readonly_fields: field_obj.widget.attrs.update({'disabled': 'true'}) return forms.ModelForm.__new__(cls) class Meta: model = models.StudentEnrollment fields = '__all__' exclude = ['contract_approved_date'] readonly_fields = ['contract_agreed',] def clean(self): """負責驗證disabled字段的值是否被改動了""" if self.errors: # 全局的錯誤信息,經過customer_form.errors調用 raise forms.ValidationError(("Please fix errors before re-submit.")) if self.instance.id is not None: # 說明這是一個被修改過的表單,須要驗證disabled字段 for field in self.Meta.readonly_fields: old_field_val = getattr(self.instance,field) # 數據庫裏的數據 form_val = self.cleaned_data.get(field) # 表單裏提交的數據 if old_field_val != form_val: # 給單個字段添加錯誤信息 self.add_error(field,"Readonly Field:field should be '{value}',not'{new_value}'". format(**{'value':old_field_val,'new_value':form_val}))
from django.shortcuts import render,HttpResponse,redirect from django.contrib.auth.decorators import login_required # 必須登錄才能看到頁面 from crm import forms @login_required def contract_audit(request,enrollment_id): '''學員報名審覈頁''' enrollment_obj = models.StudentEnrollment.objects.filter(id=enrollment_id).first() if request.method == 'POST': enrollment_form = forms.EnrollmentForm(instance=enrollment_obj,data=request.POST) if enrollment_form.is_valid(): enrollment_form.save() # 報名成功後,把學員加入數據庫,stu_obj = (<Student: 鐵錘>, True) stu_obj = models.Student.objects.get_or_create(customer=enrollment_obj.customer)[0] # 加入學生表 stu_obj.class_grades.add(enrollment_obj.class_grade_id) # 加入班級表 stu_obj.customer.status = 1 # 修改狀態 stu_obj.save() # 修改報名表,是否審覈經過,和審覈經過時間 enrollment_obj.contract_approved = True enrollment_obj.contract_approved_date = datetime.now() enrollment_obj.save() # 給用戶發郵件 return redirect('/kingadmin/crm/customerinfo/%s/change/'%enrollment_obj.customer.id) else: # 建立form表單,發到前端 customer_form = forms.CustomerForm(instance=enrollment_obj.customer) enrollment_form = forms.EnrollmentForm(instance=enrollment_obj) return render(request,'crm/contract_audit.html',locals())
cookie的工做原理是:由服務器產生內容,瀏覽器收到請求後保存在本地;當瀏覽器再次訪問時,瀏覽器會自動帶上cookie,這樣服務器就能經過cookie的內容來判斷這個是「誰」了。
一、獲取Cookie:
request.COOKIES['key'] request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 參數: default: 默認值 salt: 加密鹽 max_age: 後臺控制過時時間
二、設置Cookie:
rep = HttpResponse(...) 或 rep = render(request, ...) rep.set_cookie(key,value,...) rep.set_signed_cookie(key,value,salt='加密鹽',...) 參數: key, 鍵 value='', 值 max_age=None, 超時時間 expires=None, 超時時間(IE requires expires, so set it if hasn't been already.) path='/', Cookie生效的路徑,/ 表示根路徑,特殊的:跟路徑的cookie能夠被任何url的頁面訪問 domain=None, Cookie生效的域名 secure=False, https傳輸 httponly=False 只能http協議傳輸,沒法被JavaScript獲取(不是絕對,底層抓包能夠獲取到也能夠被覆蓋)
<script src='/static/js/jquery.cookie.js'></script> $.cookie("list_pager_num", 30,{ path: '/' });
三、應用
from django.shortcuts import render,redirect import datetime def login(request): if request.method == 'POST': name = request.POST.get('user') pwd = request.POST.get('pwd') if name == 'charlie' and pwd == '123': # redirect render 都有返回值,是一個字典,裏面有鍵值對'sessionid':'5225rffg5hh5' ret = redirect('/index/') #給cookie添加一個鍵值對,下次再來發送請求的時候會帶着它來, #若是和這個同樣,就不用從新登錄了,前提是在同一個客戶端登錄 #設置cookie有效時間5秒 ret.set_cookie('username',name,max_age=5) #設置有效期三天 ret.set_cookie('username',name,expires=datetime.datetime.utcnow() +datetime.timedelta(days=3)) return ret return render(request,'login.html') def index(request): if request.COOKIES.get('username',None): name = request.COOKIES.get('username',None) return render(request,'index.html',locals()) else: return redirect('/login/')
cookie雖然在必定程度上解決了「保持狀態」的需求,可是因爲cookie自己最大支持4096字節,以及cookie自己保存在客戶端,可能被攔截或竊取,所以就須要有一種新的東西,它能支持更多的字節,而且他保存在服務器,有較高的安全性。這就是session(session默認在服務器端保存15天)。
Django中默認支持Session,其內部提供了5種類型的Session供開發者使用:
一、數據庫Session
Django默認支持Session,而且默認是將Session數據存儲在數據庫中,即:django_session 表中。 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默認) SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認) SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑(默認) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默認) SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie(默認) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸(默認) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默認) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過時(默認) SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存(默認) b. 使用 def index(request): # 獲取、設置、刪除Session中數據 request.session['k1'] request.session.get('k1',None) request.session['k1'] = 123 request.session.setdefault('k1',123) # 存在則不設置 del request.session['k1'] # 全部 鍵、值、鍵值對 request.session.keys() request.session.values() request.session.items() request.session.iterkeys() request.session.itervalues() request.session.iteritems() # 用戶session的隨機字符串 request.session.session_key # 將全部Session失效日期小於當前日期的數據刪除 request.session.clear_expired() # 檢查 用戶session的隨機字符串 在數據庫中是否 request.session.exists("session_key") # 刪除當前用戶的全部Session數據 request.session.delete("session_key") request.session.set_expiry(value) * 若是value是個整數,session會在些秒數後失效。 * 若是value是個datatime或timedelta,session就會在這個時間後失效。 * 若是value是0,用戶關閉瀏覽器session就會失效。 * 若是value是None,session會依賴全局session失效策略。
二、緩存Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過時 SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存 b. 使用同上
三、文件Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過時 SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改以後才保存 b. 使用同上
四、緩存+數據庫Session
數據庫用於作持久化,緩存用於提升效率 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 b. 使用同上
五、加密cookie Session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 b. 使用同上
六、cookie 和Session的結合使用的兩種方式
存儲在服務端:經過cookie存儲一個session_id,而後具體的數據則是保存在session中。若是用戶已經登陸,則服務器會在cookie中保存一個session_id,下次再次請求的時候,會把該session_id攜帶上來,服務器根據session_id在session庫中獲取用戶的session數據。就能知道該用戶究竟是誰,以及以前保存的一些狀態信息。這種專業術語叫作server side session。
將session數據加密,而後存儲在cookie中。這種專業術語叫作client side session。flask採用的就是這種方式,可是也能夠替換成其餘形式。
七、應用
數據庫session,須要執行python manage.py makemigrations命令,須要Django幫咱們建立session表,用來保存session內容,不然會報錯;
cookie是一個字典,裏面默認有兩個鍵值對:‘sessionid’,‘csrftoken’,session是一個字典對象,兩個均可以用request.session['username']=name方法來添加鍵值對;
刪除session:del request.session[key]
from django.shortcuts import render,redirect import datetime def login(request): if request.method == 'POST': name = request.POST.get('user') pwd = request.POST.get('pwd') if name == 'charlie' and pwd == '123': #cookie + session request.session['is_login']=True request.session['user']=name return redirect('/index/') return render(request,'login.html') def index(request): #cookie + session,加上None防止找不到報錯 if request.session.get('is_login',None): name = request.session.get('user',None) return render(request,'index.html',locals()) else: return redirect('/login/')
一、代碼實現簡單的上一頁、下一頁
#index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for row in user_list %} <li>{{ row.name }} - {{ row.age }}</li> {% endfor %} </ul> <a href="/index.html?p={{ prev_page }}">上一頁</a> <a href="/index.html?p={{ next_page }}">下一頁</a> </body> </html> #views.py from django.shortcuts import render USER_LIST = [] for i in range(1,1000): temp = {'name':'root' + str(i),'age':i} USER_LIST.append(temp) def index(request): #簡單的分頁 per_page_count = 10 current_page = int(request.GET.get('p')) #p=1 索引0-10 #p=2 索引10-20 #http://127.0.0.1:8005/index.html?p=1 start = (current_page-1)*per_page_count end = current_page*per_page_count user_list = USER_LIST[start:end] if current_page <= 1: prev_page = 1 next_page = current_page + 1 else: prev_page = current_page - 1 next_page = current_page + 1 return render(request,'index.html',locals())
二、Django內置分頁+擴展
-- Django自帶的內置分頁只能顯示上一頁和下一頁按鈕,不能顯示中間的頁碼,因此這裏須要擴展一下
from django.shortcuts import render from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger USER_LIST = [] for i in range(1,230): temp = {'name':'root' + str(i),'age':i} USER_LIST.append(temp) #擴展自帶分頁 class CustomPaginator(Paginator): def __init__(self,current_page,per_pager_num,*args,**kwargs): #當前頁 self.current_page = int(current_page) #每頁最多顯示多少頁碼 self.per_pager_num = int(per_pager_num) super(CustomPaginator,self).__init__(*args,**kwargs) def page_num_range(self): #若是總頁數小於每頁最多顯示頁碼數量,就顯示1-總頁碼 if self.num_pages < self.per_pager_num: return range(1,self.num_pages+1) #若是總頁數有不少 part = int(self.per_pager_num/2) if self.current_page <= part: return range(1,self.per_pager_num+1) #最後一頁只顯示最後10個頁碼便可 if (self.current_page+part) > self.num_pages: return range(self.num_pages-self.per_pager_num+1,self.num_pages+1) return range(self.current_page-part,self.current_page+part+1) def index1(request): #django自帶分頁 current_page = request.GET.get('p') #Paginator對象,每頁顯示10個頁碼,10條數據 paginator = CustomPaginator(current_page,11,USER_LIST,10) try: #Page對象 posts = paginator.page(current_page) except PageNotAnInteger: #若是輸入不是數字,就顯示第一頁 posts = paginator.page(1) except EmptyPage: #若是輸入爲空或數字過大,就顯示最後一頁 posts = paginator.page(paginator.num_pages) return render(request,'index1.html',locals())
#注意:這裏使用include引用了文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> {% for row in posts %} <li>{{ row.name }} - {{ row.age }}</li> {% endfor %} </ul> {% include 'include/pager.html' %} </body> </html>
<a href="/index1.html?p=1">首頁</a> {% if posts.has_previous %} <a href="/index1.html?p={{ posts.previous_page_number }}">上一頁</a> {% endif %} {% for i in paginator.page_num_range %} {% if i == posts.number %} <a style="font-size: 20px;color: blue;" href="/index1.html?p={{ i }}">{{ i }}</a> {% else %} <a href="/index1.html?p={{ i }}">{{ i }}</a> {% endif %} {% endfor %} {% if posts.has_next %} <a href="/index1.html?p={{ posts.next_page_number }}">下一頁</a> {% endif %} <span> [{{ posts.number }}/{{ paginator.num_pages }}] </span> <a href="/index1.html?p={{ paginator.num_pages }}">尾頁</a>
二、自定義分頁
— 分頁功能在每一個網站都是必要的,對於分頁來講,其實就是根據用戶的輸入計算出應該在數據庫表中的起始位置,而且須要在頁面上顯示分頁的頁面。如:[上一頁][1][2][3][4][5][下一頁]
一、設定每頁顯示數據條數
二、用戶輸入頁碼(第一頁、第二頁...)
三、設定顯示多少頁號
四、獲取當前數據總條數
五、根據設定顯示多少頁號和數據總條數計算出,總頁數
六、根據設定的每頁顯示條數和當前頁碼,計算出須要取數據表的起始位置
七、在數據表中根據起始位置取值,頁面上輸出數據
八、輸出分頁html,如:[上一頁][1][2][3][4][5][下一頁]
實例:在APP中建立pager.py
#在APP中建立pager.py class Pagination(object): def __init__(self,total_count,current_page,per_page_item_num=20,max_page_num=7): #數據總條目 self.total_count = total_count #當前頁 try: v = int(current_page) if v <= 0: v = 1 self.current_page = v except Exception as e: self.current_page = 1 #每頁顯示條目 self.per_page_item_num = per_page_item_num #每頁最多顯示頁碼 self.max_page_num = max_page_num def start(self): return (self.current_page-1)*self.per_page_item_num def end(self): return self.current_page*self.per_page_item_num @property def num_pages(self): # 總頁數 a,b = divmod(self.total_count,self.per_page_item_num) if b == 0: return a return a+1 def page_num_range(self): #若是總頁數小於每頁最多顯示頁碼數量,就顯示1-總頁碼 if self.num_pages < self.max_page_num: return range(1,self.num_pages+1) #若是總頁數有不少 part = int(self.max_page_num/2) if self.current_page <= part: return range(1,self.max_page_num+1) #最後一頁只顯示最後10個頁碼便可 if (self.current_page+part) > self.num_pages: return range(self.num_pages-self.max_page_num+1,self.num_pages+1) return range(self.current_page-part,self.current_page+part+1) def page_str(self): page_list = [] first_page = '<li><a href="/index2.html?p=1">首頁</a></li>' page_list.append(first_page) if self.current_page == 1: prev_page = '<li><a href="#">上一頁</a>' else: prev_page = '<li><a href="/index2.html?p=%s">上一頁</a></li>'%(self.current_page-1) page_list.append(prev_page) for i in self.page_num_range(): if i == self.current_page: temp = '<li class="active"><a href="/index2.html?p=%s">%s</a></li>' % (i, i) else: temp = '<li><a href="/index2.html?p=%s">%s</a></li>'%(i,i) page_list.append(temp) if self.current_page == self.num_pages: next_page = '<li><a href="#">下一頁</a></li>' else: next_page = '<li><a href="/index2.html?p=%s">下一頁</a></li>' % (self.current_page + 1) page_list.append(next_page) last_page = '<li><a href="/index2.html?p=%s">尾頁</a></li>'%self.num_pages page_list.append(last_page) return ''.join(page_list)
from django.shortcuts import render from django.core.paginator import Paginator,EmptyPage,PageNotAnInteger USER_LIST = [] for i in range(1,600): temp = {'name':'root' + str(i),'age':i} USER_LIST.append(temp) def index2(request): from app.pager import Pagination current_page = request.GET.get('p') page_obj = Pagination(600,current_page) data = USER_LIST[page_obj.start():page_obj.end()] return render(request,'index2.html',locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"/> </head> <body> <ul> {% for row in data %} <li>{{ row.name }} - {{ row.age }}</li> {% endfor %} </ul> <ul class="pagination"> {{ page_obj.page_str|safe }} </ul> </body> </html>
總結,分頁時須要作三件事:
建立處理分頁數據的類
根據分頁數據獲取數據
輸出分頁HTML,即:[上一頁][1][2][3][4][5][下一頁]
序列化就是把對象轉換成能夠保存在本地文件中的數據類型,關於Django中的序列化主要應用在將數據庫中檢索的數據返回給客戶端用戶,特別的Ajax請求通常返回的爲Json格式。
一、Python中dumps就是序列化,loads就是反序列化;
二、JavaScript中
對象轉換成字符串
-
-
str
=
JSON.stringify({
'k'
:
'v'
})
字符串轉換成對象
-
-
dict
=
JSON.parse(
str
)
三、三種狀況
QuerySet內部是:
對象(用all,filter獲取數據):serializers.serializer('json',QuerySet)
字典(用values獲取):list(QuerySet)
元組(用values_list獲取):list(QuerySet)
from django.core import serializers
import json def get_data(request): response = {'status': True, 'data': None} try: user_list = UserInfo.objects.all() # serializers只能序列化queryset對象,轉換爲字符串 response['data'] = serializers.serialize('json', user_list) #若是queryset內部是字典或元組,values和values_list獲取,道理同樣 user_list = UserInfo.objects.values('id','username') #只須要把外部的queryset變成列表形式,就能夠直接json序列化了 response['data'] = list(user_list) except Exception as e: response['status'] = False ret = json.dumps(response) return HttpResponse(ret)
四、因爲json.dumps時沒法處理datetime日期,因此能夠經過自定義處理器來作擴展,如:
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return field.strftime('%Y-%m-%d') elif isinstance(field, Response): return field.__dict__ # 對象以字典的方式輸出 else: return json.JSONEncoder.default(self, field) class Response: def __init__(self): self.status = True self.data = 'charlie' data = { 'k1': datetime.now(), 'k2': Response(), } ds = json.dumps(data, cls=JsonCustomEncoder) print(ds) # {"k1": "2019-03-08 16:18:28", "k2": {"status": true, "data": "charlie"}}