一,項目題目:Book單表的增刪改查頁面
該項目主要練習使用Django開發一個Book單表的增刪改查頁面,通過這個項目鞏固自己這段時間學習Django知識。
二,項目需求:
開發一個簡單的Book增刪改查頁面 要求: 實現一個書籍的增刪改查功能即可 盡量規范化代碼 查詢操作: 1,查找A出版社出版過的書籍價格大於100 2,查詢某月出版的所有python書籍名稱 3,查詢價格為100,或者150的所有書籍名稱及其出版社名稱 4,查詢價格在100-200之間的所有書籍名稱及其價格 5,查詢所有A出版社出版的書籍價格,(降序排列,去重)
三,編碼規范需求:
編碼規范需求:15% 1. 代碼規范遵守pep8 (https://python.org/dev/peps/pep-0008/) 2. 函數有相應的注釋 3. 程序有文檔說明文件(README.md參考:https://github.com/csrftoken/vueDrfDemo) 4. 程序的說明文檔必須包含的內容:程序的開發環境(django版本)、程序的實現的功能、程序的啟動方式、登錄用戶信息、程序的運行效果 5. 程序設計的流程圖:
四,項目思路
1,創建項目及其APP
1,創建project django-admin startproject Book_single 2,創建APP python manage.py startapp app01 3,settings配置設置模板 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'template')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] 4,將APP添加到settings.py里面 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01' ] 5,設置時區和語言 Django默認使用美國時間和英語,在項目的settings文件中,如下圖所示: LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True 我們將其改為 亞洲/上海 時間和中文 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False
2,配置URL
這里將全局配置的urls.py文件和APP下面的urls.py分開寫。(自己在APP下面創建一個urls.py)這樣做的好處:
- 1,全局配置文件中的urls.py模型主要管理本項目的全局url配置(雖然目前此項目只有一個APP)
- 2,每個APP的urls.py模塊管理自己APP下的url集。
2.1 全局配置文件(Book_single/urls.py)
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('app01/', include('app01.urls')), ]
2.2 APP下url配置文件(Book_single/app01/urls.py)
name的作用就是對自己的URL進行命名,讓自己能夠處於Django的任意處,尤其是模板內顯式的引用它,這是一個非常強大的功能,相當於給URL取了一個全局變量名,不會將url匹配地址寫死。
from django.urls import path, re_path from app01 import views app_name = 'app01' urlpatterns = [ path('books/', views.books, name='books'), path('addbooks/', views.addbooks, name='addbooks'), path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), path('query/', views.query, name='query'), path('test/', views.test, name='test') ]
在二級路由(即APP的URls文件中),在urlpatterns后,應該加上app_name='app_name',否則有可能會發生報錯。
3,設計數據庫
3.1 數據庫模式設計
作為一個單表增刪改查操作項目,很明顯,我們至少需要一個Book表,用來保存下面信息:圖書標題,價格,出版日期,出版社等等,代碼如下:
Book_single/app01/models.py
from django.db import models # Create your models here. class Book_single(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32, unique=True) price = models.DecimalField(max_digits=8, decimal_places=2) pub_date = models.DateField() publish = models.CharField(max_length=32) def __str__(self): return self.title
各字段含義:
- id必填,自增字段,並且是主鍵
- title,最長不超過32個字符,並且唯一,也就是不能有相同名字
- price,設置了精度的十進制數字,數字允許的最大位數是8位,小數的最大位數是2.
- 出版日期,設置出版日期
- 出版社,最長不超過32位
- 使用__str__幫助人性化顯示對象信息
3.2 更改settings配置,設置數據庫
在settings中修改DATABASES:
1,注銷掉下面設置 # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases # DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } # } 2, 新增下面設置 import pymysql pymysql.install_as_MySQLdb() DATABASES = { 'default':{ 'ENGINE': 'django.db.backends.mysql', 'NAME': 'django', 'USER': 'root', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '3306', } }
3.3 數據庫遷移
python manage.py makemigrations python manage.py migrate
結果展示:
D:\Django\Book_single>python manage.py makemigrations Migrations for 'app01': app01\migrations\0001_initial.py - Create model Book_single D:\Django\Book_single>python manage.py migrate Operations to perform: Apply all migrations: admin, app01, auth, contenttypes, sessions Running migrations: Applying app01.0001_initial... OK
4,編寫視圖
由於是單表操作,而且視圖函數比較簡單,所以我們不需要初步規划視圖函數,直接寫即可。
Book_single/app01/views.py
from django.shortcuts import render, HttpResponse, redirect # Create your views here. from app01 import models def books(request): book_list = models.Book_single.objects.all() return render(request, 'app01/books.html', locals()) def addbooks(request): # title = 'python1' # price = 100 # publish = '機械出版社' # pub_date = '2019-3-3' # book_obj = models.Book_single.objects.create(title=title, price=price, # publish=publish, pub_date=pub_date) # book_obj.save() # return HttpResponse("OK") if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有選項不能為空'}) models.Book_single.objects.create(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books') else: return render(request, 'app01/addbook.html') def delbook(request, id): # return HttpResponse("OK") print(models.Book_single.objects.filter(id=id)) models.Book_single.objects.filter(id=id).delete() # 兩次請求 return redirect('/app01/books/') def changebook(request, id): book_obj = models.Book_single.objects.filter(id=id).first() print(book_obj) if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有選項不能為空'}) models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books/') else: return render(request, 'app01/change.html', {'book_obj': book_obj}) def query(request): # 1, 查詢Publish1出版過價格大於100的書籍 book_list = models.Book_single.objects.filter(publish='Publish1', price__gt=100) print(book_list) # 2,查詢2019年1月出版的所有以py開頭的數據名稱 book_list = models.Book_single.objects.filter(title__startswith='py', pub_date__year=2019, pub_date__month=1).values('title') print(book_list) # 3,查詢價格為50,100 或者150 的所有書籍的名稱及其出版社名稱 book_list = models.Book_single.objects.filter(price__in=[50, 100, 150]).values('title', 'publish') print(book_list) # 4,查詢價格在100到200之間所有書籍名稱及其價格 book_list = models.Book_single.objects.filter(price__range = [100, 200]).values('title', 'price') print(book_list) # 5, 查詢所有Publish1出版的書籍的價格(由高到底排序,去重) book_list = models.Book_single.objects.filter(publish='Publish1').values('price').distinct().order_by('-price') print(book_list) return HttpResponse('OK') def test(request): return HttpResponse("OK")
5,創建HTML頁面文件
5.1,創建四個HTML文件
先不填充內容,創建即可。在項目的根路徑下創建一個template目錄,然后在template目錄下創建一個app01目錄,在里面創建HTML,如下:
5.2,引入Bootstrap
Bootstrap3.3.7的下載地址:請點擊我
JQuery 的下載地址:請點擊我
注意:{% static '相對路徑' %} 這個Django為我們提供的靜態文件加載方法,可以將頁面與靜態文件鏈接起來。
根目錄下新建一個static目錄,並將解壓后的bootstrap-3.3.7目錄,整體拷貝到static目錄中,而且由於Bootstrap依賴於JQuery,所以我們需要引入JQuery,並且在static目錄下,新建一個CSS和JS目錄,作為以后的樣式文件和JS文件的存放地,創建展示圖如下:
然后打開項目的settings文件,在最下面添加配置,用於指定靜態文件的搜索目錄:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
5.3 創建base.html模板
既然要將前端頁面做的像個樣子,那么就要各寫各的。一個網站要有自己的統一風格和公共部分,可以將這部分內容集中到一個基礎模板base.html中。現在在根目錄下的template中新建一個base.html文件作為站點的基礎模板。
在Bootstrap文檔中,為我們提供了一個非常簡單而且又實用的基礎模板,代碼如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> <title>Bootstrap 101 Template</title> <!-- Bootstrap --> <link href="css/bootstrap.min.css" rel="stylesheet"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <h1>你好,世界!</h1> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script> </body> </html>
將其整體拷貝到base.html文件中。
然后修改base內容,最后base.html文件的內容如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3個meta標簽*必須*放在最前面,任何其他內容都*必須*跟隨其后! --> {% block title %} <title>base</title> {% endblock title %} <!-- Bootstrap --> <link href="/static/bootstrap-3.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- HTML5 shim 和 Respond.js 是為了讓 IE8 支持 HTML5 元素和媒體查詢(media queries)功能 --> <!-- 警告:通過 file:// 協議(就是直接將 html 頁面拖拽到瀏覽器中)訪問頁面時 Respond.js 不起作用 --> <!--[if lt IE 9]> <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> <![endif]--> <link rel="stylesheet" href="/static/CSS/base.css"> </head> <body> <div class="container my-con"> <div class="col-md-2"> {% block operation %} <h2>operation</h2> {% endblock operation %} </div> <div class="col-md-10"> {% block con %} <h2>content</h2> {% endblock con %} </div> </div> <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依賴 jQuery,所以必須放在前邊) --> <script src="/static/JS/jquery-3.2.1.min.js"></script> <!-- 加載 Bootstrap 的所有 JavaScript 插件。你也可以根據需要只加載單個插件。 --> <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script> </body> </html>
5.4 完成自己編寫的HTML內容
addbook.html
{% extends 'base.html' %} {% block title %} <title>addbook</title> {% endblock title %} {% block operation %} <h2>添加書籍</h2> {% endblock operation %} {% block con %} <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="title">書籍名稱</label> <input type="text" class="form-control" id="title" name="title"> </div> <div class="form-group"> <label for="price">價格</label> <input type="text" class="form-control" id="price" name="price"> </div> <div class="form-group"> <label for="pub-date">出版日期</label> <input type="text" class="form-control" id="pub-date" name="pub-date"> </div> <div class="form-group"> <label for="publish">出版社</label> <input type="text" class="form-control" id="publish" name="publish"> </div> <button type="submit" class="btn btn-success pull-right">提交</button> </form> <p style="color: red;">{{ ret }}</p> {% endblock con %}
books.html
{% extends 'base.html' %} {% block title %} <title>books</title> {% endblock title %} {% block operation %} <h2>查看書籍</h2> {% endblock operation %} {% block con %} <a href="{% url 'app01:addbook' %}" class='btn btn-primary' role='button'>添加書籍</a> <div class="table-responsive"> <table class="table table-striped table-bordered table-hover"> <thead> <tr class="active"> <td><strong>書籍名單</strong></td> <td><strong>價格</strong></td> <td><strong>出版日期</strong></td> <td><strong>出版社</strong></td> <td><strong>刪除操作</strong></td> <td><strong>編輯操作</strong></td> </tr> </thead> <tbody> {% for book in book_list %} <tr> <td>{{ book.title }}</td> <td>{{ book.price }}</td> <td>{{ book.pub_date|date:'Y-m-d' }}</td> <td>{{ book.publish }}</td> <td><a href="/app01/{{ book.pk }}/delete" class="btn btn-danger" role="button" >刪除</a></td> <td><a href="/app01/{{ book.pk }}/change" class="btn btn-info" role="button" >編輯</a></td> </tr> {% endfor %} </tbody> </table> </div> {% endblock con %}
change.html
{% extends 'base.html' %} {% block title %} <title>change</title> {% endblock title %} {% block operation %} <h2>編輯書籍</h2> {% endblock operation %} {% block con %} <form action="" method="post"> {% csrf_token %} <div class="'form-group"> <label for="title">書籍名稱</label> <input type="text" class="form-control" id="title" name="title" value="{{ book_obj.title }}"> </div> <div class="'form-group"> <label for="price">價格</label> <input type="text" class="form-control" id="price" name="price" value="{{ book_obj.price }}"> </div> <div class="'form-group"> <label for="pub-date">出版日期</label> <input type="text" class="form-control" id="pub-date" name="pub-date" value="{{ book_obj.pub_date|date:'Y-m-d' }}"> </div> <div class="'form-group"> <label for="publish">出版社</label> <input type="text" class="form-control" id="publish" name="publish" value="{{ book_obj.publish }}"> </div> <button type="submit" class="btn btn-success pull-right">提交</button> </form> <p style="color: red;">{{ ret }}</p> {% endblock con %}
5.5 部分模板語法的解析
從上面的HTML代碼,我們可以發現出現了許多模板語言。最多的就是使用大括號和百分比的組合來表示。
下面學習一下標簽的使用。標簽看起來像下面的格式。但是標簽比變量更加復雜:一些在輸出中創建文本,一些通過循環或者邏輯來控制流程,一些加載其后的變量將使用到的額外信息到模板中。一些標簽需要開始和結束標簽等等。
5.5.1 {% csrf_token %}
csrf_token 標簽,用於生成csrf_token的標簽,用於防治跨站攻擊驗證。注意如果你的view的index使用的是render_to_response方法,則不會生效。
其實這只會生成一個input 標簽,和其他表單標簽一起提交給后台。
5.5.2 extend(繼承)模板標簽
在實際應用中,我們將用到Django模板系統來創建整個HTML頁面。這就帶來了一個常見的Web開發問題:在整個網站中,如何減少共有頁面區域(比如站點導航)所引起的重復和冗余代碼?
解決該問題的傳統做法是使用服務器端的includes,我們可以在HTML頁面中使用該指令將一個網頁嵌入到另一個中。事實上,DJango通過 {% include %} 支持了這種方法。
但是Django解決此類問題的首先方法是使用更加優雅的策略——模板繼承。
本質上來說,模板繼承就是先構造一個基礎框架模板,而后在其子模板中對它所包含站點共用部分和定義塊進行重載。
讓我們通過修改 current_datetime.html 文件,為 current_datetime 創建一個更加完整的模板來體會一下這種做法:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>The current time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>It is now {{ current_date }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
這看起來很好,但是如果我們要為hours_ahead視圖創建另一個模板會發生什么呢?
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>Future time</title> </head> <body> <h1>My helpful timestamp site</h1> <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> <hr> <p>Thanks for visiting my site.</p> </body> </html>
很顯然,我們剛才重復了大量的HTML代碼。詳細一下,如果有一個更典型的網站,它由導航條,樣式表,可能還會有一些JavaScript 代碼,事情必將以向每個模板填充各種冗余的HTML而告終。
解決這個問題的服務器端include方案是找出兩個模板中的共同部分,將其保存為不同的模板片段,然后在每個模板中進行include。也許你可以把模板頭部的一些代碼保存為header.html 文件:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head>
你可能也會將底部保存到文件 footer.html:
<hr> <p>Thanks for visiting my site.</p> </body> </html>
對基於include 的策略,頭部和底部的包含很簡單。麻煩的是中間部分。在此范例中,每個頁面都有一個My helpful timestamp site</h1> 標題,但是這個標題不能放在 header.html 中,因為每個頁面的 <title> 是不同的。 如果我們將 <h1> 包含在頭部,我們就不得不包含 <title> ,但這樣又不允許在每個頁面對它進行定制。 何去何從呢?
Django的模板繼承系統解決了這些問題,你可以將其視為服務器端include的逆向思維版本。你可以對那些不同的代碼進行定義,而不是共同代碼段。
第一步是定義基礎模板,該框架之后將由子模板所繼承。以下是我們目前所講述范例的基礎模板:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html lang="en"> <head> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>My helpful timestamp site</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Thanks for visiting my site.</p> {% endblock %} </body> </html>
這個叫做base.html 的模板定義了一個簡單的HTML框架文檔,我們將本站點的所有頁面中使用。子模板的作用就是重載,添加護着保留那些塊的內容。(這個文件可以保存在template目錄下,命令為base.html)
我們將使用模板標簽{% block %} 。 所有的 {% block %} 標簽告訴模板引擎,子模板可以重載這些部分。 每個{% block %}標簽所要做的是告訴模板引擎,該模板下的這一塊內容將有可能被子模板覆蓋。
現在我們已經有了一個基本模板,我們可以修改 current_datetime.html 模板來 使用它:
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %} <p>It is now {{ current_date }}.</p> {% endblock %}
再為hours_ahead 視圖創建一個模板,看起來是這樣的:
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %} <p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p> {% endblock %}
看起來很漂亮是不是?每個模板只包含對自己而言獨一無二的代碼。無需多余的部分。如果想要對站點級進行設計修改,僅僅需要修改base.html,所有其他模板會立即反映出所做的修改。
以下是其工作方式:
在加載 current_datetime.html 模板時,模板引擎發現了 {% extends %} 標簽, 注意到該模板是一個子模板。 模板引擎立即裝載其父模板,即本例中的 base.html 。此時,模板引擎注意到 base.html 中的三個 {% block %} 標簽,並用子模板的內容替換這些 block 。因此,引擎將會使用我們在 { block title %} 中定義的標題,對 {% block content %} 也是如此。 所以,網頁標題一塊將由{% block title %}替換,同樣地,網頁的內容一塊將由 {% block content %}替換。
注意由於子模板並沒有定義 footer 塊,模板系統將使用在父模板中定義的值。 父模板 {% block %} 標簽中的內容總是被當作一條退路。繼承並不會影響到模板的上下文。 換句話說,任何處在繼承樹上的模板都可以訪問到你傳到模板中的每一個模板變量。你可以根據需要使用任意多的繼承次數。 使用繼承的一種常見方式是下面的三層法:
<1> 創建 base.html 模板,在其中定義站點的主要外觀感受。 這些都是不常 修改甚至從不修改的部分。 <2> 為網站的每個區域創建 base_SECTION.html 模板(例如, base_photos.html 和 base_forum.html )。這些模板對base.html 進行拓展,並包含區域特定的風格與設計。 <3> 為每種類型的頁面創建獨立的模板,例如論壇頁面或者圖片庫。 這些模板拓展 相應的區域模板。
這個方法可最大限度的重用代碼,並使得向公共區域(如區域級的導航)添加內容稱為一件輕松的工作。
5.5.3 模板繼承的一些訣竅
<1>如果在模板中使用 {% extends %} ,必須保證其為模板中的第一個模板標記。 否則,模板繼承將不起作用。 <2>一般來說,基礎模板中的 {% block %} 標簽越多越好。 記住,子模板不必定義 父模板中所有的代碼塊,因此你可以用合理的缺省值對一些代碼塊進行填充,然后只對 子模板所需的代碼塊進行(重)定義。 俗話說,鈎子越多越好。 <3>如果發覺自己在多個模板之間拷貝代碼,你應該考慮將該代碼段放置到父模板的 某個 {% block %} 中。如果你需要訪問父模板中的塊的內容,使用 {{ block.super }} 這個標簽吧,這一個魔法變量將會表現出父模板中的內容。 如果只想在上級代碼塊基 礎上添加內容,而不是全部重載,該變量就顯得非常有用了。 <4>不允許在同一個模板中定義多個同名的 {% block %} 。 存在這樣的限制是因 為block 標簽的工作方式是雙向的。 也就是說,block 標簽不僅挖了一個要填的坑,也定義了在父模板中這個坑所填充 的內容。如果模板中出現了兩個相同名稱的 {% block %} 標簽,父模板將無從得知 要使用哪個塊的內容。
五,注意事項及其補充內容
5.1,django.db.utils.ProgrammingError: (1146, u"Table'' doesn't exist")解決辦法
5.1.1 現象
在數據庫中刪除了一張表,重新執行python manage.py migrate 時報錯,提示不存在這張表
5.1.2 原因
主要是因為Django一般在第一次遷移的時候新建表,后面都不會新建表,而是只檢查字段等等的變化,所以我們既然已經刪除了這張表,django檢查這張表的字段變化的時候自然報錯了。
5.1.3 解決方法
解決方法仍然是執行數據庫遷移命令
python manage.py makemigrations python manage.py migrate
只不過在執行上面代碼之前,把第一次執行遷移創建的那個記錄刪除掉,否則它檢查到已經執行過第一次了,那么它后面就不會創建表了。
在該APP模塊下,有一個migtations文件夾,除了前兩個文件外,其他的文件都刪除,其實每一次如果有變化的話,這邊就會生成一個文件,下面的這個001_initial.py看名字就知道是第一次遷移的時候生成的,也就是因為有它的存在,所以以后每次再執行就不再創建表了。
其次,在數據庫里面也有相應的記錄,也要刪除。我們仔細看看數據庫里面存的是什么,在Django_migrations里面,這表里面存的都是每次遷移的記錄,當然記錄的是什么模塊以及對應的文件名字,比如我們這里的模塊是dtheme,這里的文件名叫做001_initial,和我們文件夾里面是一一對應的,同樣,刪除這條記錄。
然后在執行數據庫遷移命令即可。
注意:如果這個APP模塊下面還有其他的model的話,那么其他的model創建的表也要刪除掉,相當於我們這樣的解決方案是針對整個APP模塊的,要執行就會全部重新生成,不然會提示部分表已經存在錯誤。
5.2 django.db.utils.InternalError: (1366, "Incorrect string value: '\\xE6\\x9C\\xBA\\xE6\\xA2\\xB0...' for column 'publish' at row 1")
這個問題,在MySQL中修改配置文件。
具體請參考博客:請點擊我
5.2 補充1:正則匹配
上面代碼我們用到了正則匹配,下面學習一下正則匹配的用法。
(如果想全面學習正則表達式的使用:請點擊我)
這里介紹與Django有關的知識點。我們最常用的正則匹配就是下面兩個:
^ 表示會匹配行或者字符串的起始位置,有時還會匹配整個文檔的起始位置 $ 表示匹配行或者字符串的結尾
Django在檢查URL模式之前,移除每一個申請的URL開頭的斜杠(/)。這意味着我們寫的URL模式不用包括斜杠(/)。
舉個例子:
from django.urls import path, re_path from app01 import views urlpatterns = [ path('login/<int:id>/', views.login), # 上下兩個是同樣的效果 re_path(r'login/(\d+)/', views.login), ]
5.3 補充2:re_path和path
在新版Django2.X中,URL的路由表示用path 和re_path代替,模塊的導入由Django1.X版本的url變為Django2.X中的path,re_path。
看下面兩幅圖:
而且該url() 函數傳遞了四個參數,兩個必需:regex和view,以及兩個可選:kwargs 和 name。也就是說正則表達式和視圖是兩個必填參數。
函數path() 具有四個參數,兩個必需參數 :route 和view,兩個可選參數:kwargs 和 name。即路由和視圖是必填參數。
所以兩者的主要區別就在於url() 是要寫正則表達式(regex)的路由,而path()時寫非正則路由(route),接下來主要看一下path() 函數的四個參數含義。
1,path() 參數: route route 是一個匹配URL的准則(類似於正則表達式),當Django響應一個 請求時,它會從urlpaterns的第一項開始,按順序依次匹配列表中的項,直到 找到匹配的項。 這些准則不會匹配GET 和 POST 參數或域名。例如:URLconf在處理請求 https://www.example.com.myapp/時候,它會嘗試匹配myapp/。處理請 求https://www.example.com/myapp/?page=3時,也只會嘗試匹配myapp/ 2, path()參數: view 當django找到一個匹配的准則,就會調用這個特定的視圖函數,並傳入一個 HttpRequest對象作為第一個參數,被“捕獲”的參數以關鍵字參數的形式傳入。 3, path()參數:kwargs 任意個關鍵字參數可以作為一個字典傳遞給目標視圖函數 4, path()參數: name 為你的URL取名能使你在Django的任意地方唯一的引用它,尤其是在模板中。 這個有用的特性運行你只改一個文件就能全局的修改某個URL模式。
5.4 path() 的用法
上面介紹的path中,第一個參數route使用的是非正則表達式可以表示的普通路由路徑。
注意:
- 要從URL捕獲值,請使用尖括號
- 捕獲的值可以選擇包括轉換器類型。例如:用<int:name> 捕獲整數參數。如果未包含轉換器 / ,則匹配除字符之外的任何字符串。
- 沒有必要添加前導斜杠,因為每個URL都有。例如,articles 不是 /articles。
默認情況下,以下路徑轉換器可用:
- str- 匹配除路徑分隔符之外的任何非空字符串‘/’。如果轉換器未包含在表達式中,則這是默認值。
- int- 匹配零或者任何正整數,返回一個int
- slug- 匹配由ASCII字母或者數字組成的任何slug字符串,以及連字符和下划線字符。例如 : building-your-1st-django-site。
- uuid- 匹配格式化的UUID,要防止多個URL映射到同一頁面,必須包含短划線並且字母必須為小寫。例如:0787878d3-4545-456a-d45ad6d4s5d4a65。返回一個UUID實例。
- path- 匹配任何非空字符串,包括路徑分隔符 ‘/’。這使得你可以匹配完整的URL路徑,而不僅僅是URL路徑的一部分str。
比如要匹配一個視圖中的函數路由,該函數有兩個形參:
def peopleList(request,book_id)
第一個request是默認的,那么路由自動匹配該函數的第二個形參,匹配格式:<int:book_id>,並返回一個正整數或者零值。
urlpatterns = [ path('booklist/', views.booklist, name='booklist'), path('<int:book_id>/', views.peopleList, name='peopleList'), ]
5.5 re_path() 的用法
而如果遇上路徑和轉換器語法都不足以定義的URL模式,那么就需要使用正則表達式,這時候就需要使用re_path() ,而非path()。
from django.urls import re_path
在Python正則表達式中 ,命名正則表達式組的語法是(?P<name>pattern),組name的名稱,並且pattern是要匹配的模式。
還是以上面的為例,采用正則表達式寫下來,如下:
re_path(r'^(\d+)/$',views.peopleList,name='peopleList'), # 類似於下面 path('<int:book_id>/', views.peopleList, name='peopleList'),
這樣也可以匹配到views視圖中的peopleList函數的形參。
所以這兩種使用方式在使用上根據實際情況自行使用。
5.6 re_path和path的區別
1,re_path和path的作用都是一樣的。只不過re_path是在寫url的時候可以用正則表達式,功能更加強大。
2,寫正則表達式都推薦使用原生字符串,也就是r開頭的字符串
3,在正則表達式中定義變量,需要使用圓括號括起來。這個參數是由名字的,那么需要使用(?P<參數的名字>)。然后在后面添加正則表達式的規則。
4,如果不是特別需求,直接使用path就夠了,省的將代碼搞的很麻煩(因為正則表達式是非常晦澀的,特別是一些比較復雜的正則表達式,可能今天寫的明天就忘了),除非是URL中確實是需要使用正則表達式來解決才使用re_path。
舉個例子
urlpatterns = [ re_path(r"^list/(?P<year>\d{4})/$", views.article_list), re_path(r"^list/(?P<month>\d{2})/$", views.article_list_month) ]
第一個表達式以list開頭,中間需要有4個數字,一個都不能多也不能少,再以'/'結尾。形如list/2019/這樣的字符串才能被識別,同理,第二句是需要形如 list/02/這樣的字符串才能被識別。
六,結果展示
6.1 項目樹形圖展示:
6.2 增加頁面展示
增加成功,直接跳轉到查看界面
6.3 查看頁面展示
6.4 編輯頁面展示
6.5 創建的數據庫如下:
七,代碼展示及部分代碼解析
7.1 完整代碼
傳送門:請點擊我(小編的GitHub)
7.2 部分注意寫錯的代碼
感覺上面代碼加上GitHub的代碼,就不需要展示了。。。。。。
7.2.1 注意代碼1
算了,這里還是放上APP的urls.py
from django.urls import path, re_path from app01 import views app_name = 'app01' urlpatterns = [ path('books/', views.books, name='books'), path('addbooks/', views.addbooks, name='addbooks'), path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), path('query/', views.query, name='query'), path('test/', views.test, name='test') ]
和views.py里面的刪除和修改函數:
def delbook(request, id): # return HttpResponse("OK") print(models.Book_single.objects.filter(id=id)) models.Book_single.objects.filter(id=id).delete() # 兩次請求 return redirect('/app01/books/') def changebook(request, id): book_obj = models.Book_single.objects.filter(id=id).first() print(book_obj) if request.method == 'POST': title = request.POST.get('title') price = request.POST.get('price') pub_date = request.POST.get('pub-date') publish = request.POST.get('publish') if title == '' or price == '' or pub_date == '' or publish == '': return render(request, 'app01/addbook.html', {'ret': '所有選項不能為空'}) models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish) return redirect('/app01/books/') else: return render(request, 'app01/change.html', {'book_obj': book_obj})
為什么重點放這里呢,當然主要是因為自己犯錯了,這里需要注意的。
因為這里有一個匹配視圖中函數路由的函數,里面又兩個形參:
path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'),
第一個request是默認的,所以路由會自動匹配該函數的第二個形參,匹配格式為<int:id> , 並返回一個正整數或者零值。
(這里也可以用re_path進行匹配,這里附上兩個類似的代碼)
urlpatterns1 = [ path('<int:id>/delete', views.delbook, name='delbook'), path('<int:id>/change/', views.changebook, name='changebook'), # 等同於下面 re_path(r'^(\d+)/delete', views.delbook, name='delbook'), re_path(r'^(\d+)/change', views.changebook, name='changebook'), ]
7.2.2 注意代碼2
在settings.py中,添加配置,用於指定靜態文件的搜索目錄,注意代碼如下:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
注意 將STATICFILES 不能寫錯!!!
7.3 部分代碼解析
7.3.1 獲取所有數據顯示在頁面上
book_list = models.Book_single.objects.all()
通過上面獲取數據,拿到數據后,渲染給前端;前端通過for循環的方式,取出數據。
目的就是通過Book_single(這個數據表)里面的字段拿到對應的數據。
7.3.2 刪除功能的制作
models.Book_single.objects.filter(id=id).delete()
通過上面的代碼,找到數據庫中對應的id字段,刪除這個ID字段對應的這行數據。
7.3.3 編輯功能的制作
book_obj = models.Book_single.objects.filter(id=id).first()
通過上面代碼,拿到需要編譯的ID,然后在數據庫中找到對應的ID字段,然后對內容進行修改。
修改完之后,對數據庫對應字段的內容進行更新。
models.Book_single.objects.filter(id=id).update(title=title, price=price, pub_date=pub_date, publish=publish)
7.3.4 查詢功能的代碼
查詢部分代碼比較簡單,這里回顧一下。
如果不懂:請點擊這里
def query(request): # 1, 查詢Publish1出版過價格大於100的書籍 book_list1 = models.Book_single.objects.filter(publish='Publish1', price__gt=100) # 2, 查詢2019年1月出版的所有以pyth開頭的書籍名稱 book_list2 = models.Book_single.objetcs.filter(title__startwith='pyth', pub_date__year=2019, pub_date__month=1).values('title') # 3, 查詢價格為50, 100或者 150 的所有書籍的名稱及其出版社名稱 book_list3 = models.Book_single.objects.filter(price__in[50, 100, 150]).values ('title, 'publish') #4, 查詢價格在100到200之間所有書籍名稱及其價格 book_list4 = models.Book_single.objects.filter(price__range= [100, 200]).values ('title', 'price') #5,查詢所有Publish1出版的書籍的價格(由高到低排序,去重) book_list5 = models.Book_single.objects.filter(publish="Publisher1").values ('price').distinct().order_by('-price')