1. 程式人生 > >Python自動化開發學習20-Django

Python自動化開發學習20-Django

python django

ORM

一邊寫一個示例,一邊回顧一下之前的內容,引出新的知識點展開講解

回顧-創建項目

下面就從創建項目開始,一步一步先做一個頁面出來。
一、先創建一個新的Django項目
項目名是:week20,App名是:app01
技術分享圖片
因為是通過PyCharm創建的項目,創建的時候填上了 app name 。所以PyCharm幫我麽創建好了app同時也完成了註冊。否則就去settings.py裏面手動添加註冊app

INSTALLED_APPS = [
    ‘django.contrib.admin‘,
    ‘django.contrib.auth‘,
    ‘django.contrib.contenttypes‘,
    ‘django.contrib.sessions‘,
    ‘django.contrib.messages‘,
    ‘django.contrib.staticfiles‘,
    ‘app01.apps.App01Config‘,
]

二、創建表結構
一共需要2張表,一張人員信息表,一張部門表。做一個外鍵關聯。
人員信息表(UserInfo):

uid name age ip dept_id
1 Adam 22 192.168.1.101 1
2 Bob 31 192.168.1.102 1
3 Cara 26 192.168.1.103 2

部門表(Dept):

id name name_en
1 運維 Operation
2 開發 Development
3 市場 Marketing
4 銷售 Sales

表結構的文件是app01/models.py

from django.db import models

# Create your models here.

class UserInfo(models.Model):
    uid = models.AutoField(primary_key=True)  # 自己建主鍵
    name = models.CharField(max_length=32, db_index=True)  # 加索引
    age = models.IntegerField()
    ip = models.GenericIPAddressField(protocol=‘ipv4‘, db_index=True)  # 使用ipv4驗證
    dept = models.ForeignKey(‘Dept‘, models.CASCADE, to_field=‘id‘)  # 外鍵

class Dept(models.Model):
    name = models.CharField(max_length=32)
    name_en = models.CharField(max_length=32)

三、創建數據庫
切換到命令行執行如下2條命令:

python manage.py makemigrations
python manage.py migrate

然後去Dept表裏把部門先創建好,就按照上面的表格的內容。通過PyCharm就可以直接連接我們的Sqlite數據庫並操作。
技術分享圖片

四、寫對應關系
先把urls.py裏面的對應關系寫好:

from app01 import views
urlpatterns = [
    path(‘admin/‘, admin.site.urls),
    path(‘dept/‘, views.dept),
]

然後就是完成處理函數和html頁面了

五、寫處理函數


處理函數是app01/views.py

from django.shortcuts import render

# Create your views here.

from app01 import models

def dept(request):
    depts1 = models.Dept.objects.all()
    return render(request, ‘dept.html‘, {‘depts1‘: depts1})

六、寫頁面的html
在templates文件夾裏創建頁面文件dept.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>部門列表</h1>
<ul>
    {% for row in depts1 %}
        <li>{{ row.id }} - {{ row.name }} - {{ row.name_en }}</li>
    {% endfor %}
</ul>
</body>
</html>

七、檢查
上面的步驟都是一氣呵成寫下來的,寫到這裏可以檢查一下了。運行之後,打開頁面檢查是否能在頁面中顯示部門的數據。

獲取數據的3種方式

目前我們都是通過 models.Dept.objects.all() 這個方法來獲取到數據的。現在看看另外的兩種方式。
修改處理函數:

def dept(request):
    depts1 = models.Dept.objects.all()
    print(depts1)  # 每個元素都是一個對象
    # depts2 = models.Dept.objects.all().values()
    depts2 = models.Dept.objects.all().values(‘id‘, ‘name‘)
    # 也可以只取出部分字段,這裏不取name_en,到前端就獲取不到
    # depts2依然是一個QuerySet類型,但是列表裏的元素不是對象而是字典了
    # 在前端獲取值的時候,取對象的屬性和取字典的value都是點一個名稱,所以前端用起來是一樣的
    print(depts2)  # 每個元素都是一個字典
    depts3 = models.Dept.objects.all().values_list()  # 也是可以只取部分字段的
    print(depts3)  # 每個元素都是一個元組
    return render(request, ‘dept.html‘,
                  {‘depts1‘: depts1,
                   ‘depts2‘: depts2,
                   ‘depts3‘: depts3})

直接把3中方法的數據都返回給前端,前端也寫3個列表來顯示數據。修改後的templates/dept.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>部門列表(對象)</h1>
<ul>
    {% for row in depts1 %}
        <li>{{ row.id }} - {{ row.name }} - {{ row.name_en }}</li>
    {% endfor %}
</ul>
<h1>部門列表(字典)</h1>
<ul>
    {% for row in depts2 %}
        <li>{{ row.id }} - {{ row.name }} - {{ row.name_en }}</li>
    {% endfor %}
</ul>
<h1>部門列表(元祖)</h1>
<ul>
    {% for row in depts3 %}
        <li>{{ row.0 }} - {{ row.1 }} - {{ row.2 }}</li>
    {% endfor %}
</ul>
</body>
</html>

運行後print的結果:

<QuerySet [<Dept: Dept object (1)>, <Dept: Dept object (2)>, <Dept: Dept object (3)>, <Dept: Dept object (4)>]>
<QuerySet [{‘id‘: 1, ‘name‘: ‘運維‘, ‘name_en‘: ‘Operation‘}, {‘id‘: 2, ‘name‘: ‘開發‘, ‘name_en‘: ‘Development‘}, {‘id‘: 3, ‘name‘: ‘市場‘, ‘name_en‘: ‘Marketing‘}, {‘id‘: 4, ‘name‘: ‘銷售‘, ‘name_en‘: ‘Sales‘}]>
<QuerySet [(1, ‘運維‘, ‘Operation‘), (2, ‘開發‘, ‘Development‘), (3, ‘市場‘, ‘Marketing‘), (4, ‘銷售‘, ‘Sales‘)]>

知識點整理:不但可以獲取對象,現在也可以獲取到字典或元祖,而且還能只獲取部分的key
models.Dept.objects.all()
QuerySet類型,內部元素是對象
models.Dept.objects.all().values()
QuerySet類型,內部元素是字典,可以只取部分字段
models.Dept.objects.all().values_list()
QuerySet類型,內部元素是元祖,可以只取部分字段

回顧-一對多跨表操作

接下來把UserInfo也在網頁中顯示出來。
對應關系,urls.py

from app01 import views
urlpatterns = [
    path(‘admin/‘, admin.site.urls),
    path(‘dept/‘, views.dept),
    path(‘user/‘, views.user),
]

處理函數,views.py

def user(request):
    u1 = models.UserInfo.objects.filter(uid__gt=0)  # __gt就是大於的意思,換個寫法取到全部
    return render(request, ‘user.html‘, {‘u1‘: u1})

顯示頁面,templates/user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用戶列表</h1>
<table border="1">
    <thead>
    <tr>
        <th>id</th>
        <th>name</th>
        <th>age</th>
        <th>IP</th>
        <th>Dept.(id)</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u1 %}
        <tr uid="{{ row.uid }}" dept_id="{{ row.dept_id }}">
        <td>{{ row.uid }}</td>
        <td>{{ row.name }}</td>
        <td>{{ row.age }}</td>
        <td>{{ row.ip }}</td>
        <td>{{ row.dept_id }}</td>
        <td>{{ row.dept.name }}</td>
        <td>{{ row.dept.name_en }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>

在實際的應用中,頁面中不需要顯示出id的信息,所以 id 和 Dept.(id) 這兩列是不需要顯示的。我們可以刪除這兩列,但是後續的操作可能還是需要用到 id 的信息的。這裏是通過自定義屬性的方式把 id 信息隱藏在頁面中了。既不用顯示出來,但是頁面中用 id 的信息,需要的時候可以獲取到對應的id。

跨表操作-雙下劃線

還有一種跨表操作,使用雙下劃線。我們已經可以用點來實現跨表了,雙下劃綫同樣可以跨表,兩者的應用場景不同。
修改處理函數,views.py

def user(request):
    u1 = models.UserInfo.objects.filter(uid__gt=0)
    # 現在用另外的兩種獲取數據的方法來進行跨表
    # u2 = models.UserInfo.objects.filter(uid__gt=0).values(‘dept.name‘)
    # 上面會報錯,這裏要跨表操作不能用點,而是要用雙下劃線,如下
    u2 = models.UserInfo.objects.filter(uid__gt=0).values(‘name‘, ‘dept__name‘, ‘dept__name_en‘)
    u3 = models.UserInfo.objects.filter(uid__gt=0).values_list(‘name‘, ‘dept__name‘, ‘dept__name_en‘)
    return render(request, ‘user.html‘, {‘u1‘: u1,
                                         ‘u2‘: u2,
                                         ‘u3‘: u3})

修改顯示頁面,templates/user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用戶列表(對象)</h1>
<table border="1">
    <thead>
    <tr>
        <th>name</th>
        <th>age</th>
        <th>IP</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u1 %}
        <tr uid="{{ row.uid }}" dept_id="{{ row.dept_id }}">
        <td>{{ row.name }}</td>
        <td>{{ row.age }}</td>
        <td>{{ row.ip }}</td>
        <td>{{ row.dept.name }}</td>
        <td>{{ row.dept.name_en }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<h1>用戶列表(字典)</h1>
<table border="1">
    <thead>
    <tr>
        <th>name</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u2 %}
        <tr>
        <td>{{ row.name }}</td>
        <td>{{ row.dept__name }}</td>
        <td>{{ row.dept__name_en }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<h1>用戶列表(元組)</h1>
<table border="1">
    <thead>
    <tr>
        <th>name</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u3 %}
        <tr>
        <td>{{ row.0 }}</td>
        <td>{{ row.1 }}</td>
        <td>{{ row.2 }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>

知識點整理:
獲取數據的3種方法中的第一種,頁面中獲取到的元素直接是對象,對對象用點就可以進行跨表
另外的兩種方法,獲取到的不再是對象了,而是字典和元組。這時候取值要傳字符串,要跨表就得在字符串中使用雙下劃線

顯示序號-for循環中的forloop

在模板語言的for循環裏還有一個forloop,通過這個可以取到到序號:

  • forloop.counter :序號,從1開始
  • forloop.counter0 :序號,從0開始
  • forloop.revcounter :序號,倒序,從1開始
  • forloop.revcounter0 :序號,倒序,從0開始
  • forloop.first :是否是第一個
  • forloop.last :是否是最後一個
  • forloop.parentloop :有嵌套循環的情況下,獲取父類的以上6個值。字典的形式,可以繼續通過點來取到具體的值

修改user.html測試效果

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>用戶列表(forloop)</h1>
<table border="1">
    <thead>
    <tr>
        <th>序號1</th>
        <th>序號2</th>
        <th>序號3</th>
        <th>序號4</th>
        <th>是否第一個</th>
        <th>是否最後一個</th>
        <th>name</th>
        <th>age</th>
        <th>IP</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u1 %}
        <tr uid="{{ row.uid }}" dept_id="{{ row.dept_id }}">
        <td>{{ forloop.counter }}</td>
        <td>{{ forloop.counter0 }}</td>
        <td>{{ forloop.revcounter }}</td>
        <td>{{ forloop.revcounter0 }}</td>
        <td>{{ forloop.first }}</td>
        <td>{{ forloop.last }}</td>
        <td>{{ row.name }}</td>
        <td>{{ row.age }}</td>
        <td>{{ row.ip }}</td>
        <td>{{ row.dept.name }}</td>
        <td>{{ row.dept.name_en }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<h1>用戶列表(forloop.parentloop)</h1>
<table border="1">
    <thead>
    <tr>
        <th>parentloop</th>
        <th>name</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for i in u2 %}
        {% for row in u2 %}
            <tr>
            <td>{{ forloop.parentloop }}</td>
            <td>{{ row.name }}</td>
            <td>{{ row.dept__name }}</td>
            <td>{{ row.dept__name_en }}</td>
            </tr>
        {% endfor %}
    {% endfor %}
    </tbody>
</table>
<h1>用戶列表(forloop.parentloop.counter)</h1>
<table border="1">
    <thead>
    <tr>
        <th>父循環的序號</th>
        <th>當前循環的序號</th>
        <th>name</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for i in u3 %}
        {% for row in u3 %}
            <tr>
            <td>{{ forloop.parentloop.counter }}</td>
            <td>{{ forloop.counter }}</td>
            <td>{{ row.0 }}</td>
            <td>{{ row.1 }}</td>
            <td>{{ row.2 }}</td>
            </tr>
        {% endfor %}
    {% endfor %}
    </tbody>
</table>
</body>
</html>

回顧-引入靜態文件

這裏先準備好jQuery,後面要用到。在項目目錄下創建一個static文件夾用來存放我們的靜態文件。然後在settings.py的最後加上路徑:

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, ‘static‘),
)

回顧-添加數據的示例

先來寫html。添加數據要有一個添加按鈕,按鈕需要綁定事件,這裏要用到js。事件是彈出一個模態對話框。對話框裏填入數值,但是部門要用下拉列表來做。下拉列表的選項需要處理函數先去獲取 depts1 = models.Dept.objects.all() ,然後返回給頁面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .hide{
            display: none;
        }
        .shade{
            position: fixed;
            top: 0;
            right: 0;
            left: 0;
            bottom: 0;
            background: black;
            opacity: 0.5;
            z-index: 10;
        }
        .add-modal{
            position: fixed;
            height: 300px;
            width: 400px;
            margin-left: -200px;
            top: 100px;
            left: 50%;
            z-index: 20;
            background: white;
            padding: 20px 50px;
        }
    </style>
</head>
<body>
<h1>用戶列表</h1>
<div>
    <input id="add-user" type="button" value="添加" />
</div>
<table border="1">
    <thead>
    <tr>
        <th>序號1</th>
        <th>name</th>
        <th>age</th>
        <th>IP</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u1 %}
        <tr uid="{{ row.uid }}" dept_id="{{ row.dept_id }}">
        <td>{{ forloop.counter }}</td>
        <td>{{ row.name }}</td>
        <td>{{ row.age }}</td>
        <td>{{ row.ip }}</td>
        <td>{{ row.dept.name }}</td>
        <td>{{ row.dept.name_en }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>
<div class="shade hide"></div>
<div class="add-modal hide">
    <form action="/user/" method="POST">
        <p><input type="text" placeholder="name" name="name"></p>
        <p><input type="text" placeholder="age" name="age"></p>
        <p><input type="text" placeholder="IP" name="ip"></p>
        <p>
            <select name="dept-id">
                {% for op in depts1 %}
                    <option value="{{ op.id }}">{{ op.name }}({{ op.name_en }})</option>
                {% endfor %}
            </select>
        </p>
        <p>
            <input type="submit" value="提交">
            <input id="cancel" type="button" value="取消">
        </p>
    </form>
</div>
<script src="/static/js/jquery-1.12.4.js"></script>
<script>
    $(function () {
        $(‘#add-user‘).click(function () {
            $(‘.shade, .add-modal‘).removeClass(‘hide‘)
        });
        $(‘#cancel‘).click(function () {
            $(‘.shade, .add-modal‘).addClass(‘hide‘)
        })
    })
</script>
</body>
</html>

修改處理函數,這次要寫一個GET方法,還要寫一個POST方法。

def user(request):
    if request.method == ‘GET‘:
        u1 = models.UserInfo.objects.filter(uid__gt=0)
        depts1 = models.Dept.objects.all()
        return render(request, ‘user.html‘, {‘u1‘: u1, ‘depts1‘: depts1})
    elif request.method == ‘POST‘:
        dic1 = {
            ‘name‘: request.POST.get(‘name‘),
            ‘age‘: request.POST.get(‘age‘),
            ‘ip‘: request.POST.get(‘ip‘),
            ‘dept_id‘: request.POST.get(‘dept-id‘),
        }
        models.UserInfo.objects.create(**dic1)
        return redirect(‘/user/‘)

完成之後,現在就可以愉快的添加數據了。不過目前數據驗證我們還沒法做。

AJAX

數據驗證

接著上面的示例,現在就來實現簡單的驗證。這裏要實現的是服務器端的驗證。模態對話框裏提交表單的頁面增加一個按鈕,然後在jQuery裏綁定事件。下面只貼上修改的部分代碼

<!-- 提交表單的部分 -->
<div class="add-modal hide">
    <form action="/user/" method="POST">
        <p><input type="text" placeholder="name" name="name" id="name"></p>
        <p><input type="text" placeholder="age" name="age" id="age"></p>
        <p><input type="text" placeholder="IP" name="ip" id="ip"></p>
        <p>
            <select name="dept-id" id="dept-id">
                {% for op in depts1 %}
                    <option value="{{ op.id }}">{{ op.name }}({{ op.name_en }})</option>
                {% endfor %}
            </select>
        </p>
        <p>
            <input type="submit" value="提交">
            <input type="button" id="ajax-submit" value="Ajax提交" />
            <input id="cancel" type="button" value="取消">
        </p>
    </form>
</div>

上面只是添加了一個按鈕,另外是把所有需要提交的input和select標簽加上了id屬性,方便用比較簡單的邏輯來獲取到屬性。
下面就要用jQuery來發一個Ajax請求,$.ajax 這個就是jQuery提供的Ajax的功能。

<!-- jQuery部分 -->
<script>
    $(function () {
        $(‘#add-user‘).click(function () {
            $(‘.shade, .add-modal‘).removeClass(‘hide‘)
        });
        $(‘#cancel‘).click(function () {
            $(‘.shade, .add-modal‘).addClass(‘hide‘)
        });
        $(‘#ajax-submit‘).click(function () {
            $.ajax({
                url: ‘/ajax_add_user/‘,
                type: ‘POST‘,
                data: {
                    ‘name‘: $(‘#name‘).val(),
                    ‘age‘: $(‘#age‘).val(),
                    ‘ip‘: $(‘#ip‘).val(),
                    ‘dept-id‘: $(‘#dept-id‘).val()},
                success: function (data) {
                    if(data == ‘OK‘){
                        location.reload()
                    }else{
                        alert(data)
                    }
                }
            })
        })
    })
</script>

例子中我們的Ajax請求是發送到 url: ‘/ajax/‘, 這裏的,先去配置一下urls.py

from app01 import views
urlpatterns = [
    path(‘admin/‘, admin.site.urls),
    path(‘dept/‘, views.dept),
    path(‘user/‘, views.user),
    path(‘ajax_add_user/‘, views.ajax_add_user),
]

然後是處理函數

def ajax_add_user(request):
    dic1 = {
        ‘name‘: request.POST.get(‘name‘),
        ‘age‘: request.POST.get(‘age‘),
        ‘ip‘: request.POST.get(‘ip‘),
        ‘dept_id‘: request.POST.get(‘dept-id‘),
    }
    for k, v in dic1.items():
        if not v:
            return HttpResponse(‘字段 %s 不能為空‘ % k)
    else:
        models.UserInfo.objects.create(**dic1)
        return HttpResponse(‘OK‘)

完成,現在就有了簡單的驗證。服務器端會對提交過來的數據進行驗證,所有數據都不能為空,如果為空就返回錯誤信息。否則驗證通過,在數據庫添加數據。頁面收到服務端返回的字符串後,會觸發 success 回調函數。返回驗證通過就刷新頁面,否則彈出框顯示返回的錯誤信息。

優化驗證

上面的驗證比較簡陋,個各種情況驗證不是本節要講的。這裏要講的是即使你的驗證再完善也可能會有遺漏。漏過驗證的數據就會提交到去更新數據庫。如果數據不符合數據庫的數據格式,比如age提交一個字符串,那麽程序會報錯。報錯系統並不會崩潰,我們調試的時候可以看到錯誤信息,但是客戶端是不知道發生了什麽的。
下面就通過try來捕獲異常,之後可以返回自定義的消息內容,或者也可以把異常信息返回

import json
def ajax_add_user(request):
    ret = {‘status‘: True, ‘error‘: None, ‘data‘: None}
    try:
        dic1 = {
            ‘name‘: request.POST.get(‘name‘),
            ‘age‘: request.POST.get(‘age‘),
            ‘ip‘: request.POST.get(‘ip‘),
            ‘dept_id‘: request.POST.get(‘dept-id‘),
        }
        for k, v in dic1.items():
            if not v:
                ret[‘status‘] = False
                ret[‘error‘] = ‘字段 %s 不能為空‘ % k
                break
        else:
            models.UserInfo.objects.create(**dic1)
    except Exception as e:
        print(e)
        ret[‘status‘] = False
        ret[‘error‘] = ‘請求錯誤:%s‘ % str(e)
    return HttpResponse(str(ret[‘error‘]))
    # return HttpResponse(json.dumps(ret))

序列化返回的消息(JSON)

到這裏為止,我們Ajax請求,都是用HttpResponse返回結果的。目前返回也只需要使用HttpResponse,不要其他的方法。
HttpResponse返回的內容是字符串,使用JSON序列化字符串,就可以返回更多的信息了,並且客戶端處理起來也很方便。上面的例子已經這麽做了。把例子中最後的return修改成返回JSON字符串。
然後修改html來處理返回的JSON字符串。另外再優化一個錯誤消息的顯示方式,不要彈出框,寫個span標簽顯示了頁面中:

<!-- 上面的代碼不變,就不貼了 -->
<!-- 模態對話框,添加了一個span標簽顯示錯誤信息 -->
<div class="add-modal hide">
    <form action="/user/" method="POST">
        <p><input type="text" placeholder="name" name="name" id="name"></p>
        <p><input type="text" placeholder="age" name="age" id="age"></p>
        <p><input type="text" placeholder="IP" name="ip" id="ip"></p>
        <p>
            <select name="dept-id" id="dept-id">
                {% for op in depts1 %}
                    <option value="{{ op.id }}">{{ op.name }}({{ op.name_en }})</option>
                {% endfor %}
            </select>
        </p>
        <p><span id="error-msg">?</span></p>
        <p>
            <input type="submit" value="提交">
            <input type="button" id="ajax-submit" value="Ajax提交" />
            <input id="cancel" type="button" value="取消">
        </p>
    </form>
</div>
<script src="/static/js/jquery-1.12.4.js"></script>
<!-- 修改了success的匿名函數的內容,現在data是返回的JSON字符串 -->
<script>
    $(function () {
        $(‘#add-user‘).click(function () {
            $(‘.shade, .add-modal‘).removeClass(‘hide‘)
        });
        $(‘#cancel‘).click(function () {
            $(‘.shade, .add-modal‘).addClass(‘hide‘)
        });
        $(‘#ajax-submit‘).click(function () {
            $.ajax({
                url: ‘/ajax_add_user/‘,
                type: ‘POST‘,
                data: {
                    ‘name‘: $(‘#name‘).val(),
                    ‘age‘: $(‘#age‘).val(),
                    ‘ip‘: $(‘#ip‘).val(),
                    ‘dept-id‘: $(‘#dept-id‘).val()},
                success: function (data) {
                    var obj = JSON.parse(data);
                    if(obj.status){
                        location.reload()
                    }else{
;                        $(‘#error-msg‘).text(obj.error)
                    }
                }
            })
        })
    })
</script>

小結-Ajax知識點

Ajax請求的語法:

$.ajax({
    url: ‘/ajax/‘,  // 提交到哪裏
    type: ‘POST‘,  // 以什麽方式提交
    data: {‘k1‘: ‘v1‘, ‘k2‘: ‘v2‘},  // 提交的數據,字典的形式
    // 服務的返回數據之後觸發的函數,回調函數
    // 下面的匿名函數中的參數data是服務端返回的字符串
    success: function (data) {
        alert(data)
    }
})

其他Ajax請求:

  • $.ajax(url,[settings])
  • $.get(url,[data],[fn],[type])
  • $.getJSON(url,[data],[fn])
  • $.getScript(url,[callback])
  • $.post(url,[data],[fn],[type])

推薦還是用基本的第一個,並且其他方法本質上還是調用了第一個方法來實現的。其他的請求方法知道一下,看見能認識就好,自己用的話,用第一個就好了,這個是本質。
Ajax返回:
建議,永遠讓服務端返回序列化的JSON字符串。這個雖然不是必須的,但是大家都是這麽玩的。

import json
return HttpResponse(json.dumps(dic))

返回使用HttpResponse,不要用別的。redirect肯定是不能用的,頁面不會跳轉。render返回的也是字符,所以可以這麽用,但是render返回還要渲染否則頁面沒數據,並且頁面也不好處理。

示例-刪除功能

要做刪除功能,需要在表格的每一行增加一列,放置按鈕。順便把編輯按鈕也一起加上,稍後再綁定事件。
修改表格增加一列:

<table border="1">
    <thead>
    <tr>
        <th>序號1</th>
        <th>name</th>
        <th>age</th>
        <th>IP</th>
        <th>Dept.(name)</th>
        <th>Dept.(name_en)</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    {% for row in u1 %}
        <tr uid="{{ row.uid }}" dept_id="{{ row.dept_id }}">
        <td>{{ forloop.counter }}</td>
        <td>{{ row.name }}</td>
        <td>{{ row.age }}</td>
        <td>{{ row.ip }}</td>
        <td>{{ row.dept.name }}</td>
        <td>{{ row.dept.name_en }}</td>
        <td>
            <a class="edit">編輯</a>|<a class="delete">刪除</a>
        </td>
        </tr>
    {% endfor %}
    </tbody>
</table>

然後是jQuery裏綁定事件。要刪除某條數據,需要獲取到該條數據uid。uid我們之前已經存放到頁面的tr標簽裏了,獲取到uid提交之後就交給處理函數了。另外頁面也要變化,這裏不需要刷新頁面(刷新也是可以的)。只要收到刪除成功的請求後把這個re給remove掉就可以了。

    $(function () {
        $(‘.delete‘).click(function () {
            var tr_obj = $(this).parent().parent();  // 保留這個tr的標簽對象,確認數據刪除後remove掉
            var uid = tr_obj.attr(‘uid‘);  // 取到uid
            $.ajax({
                url: ‘/ajax_del_user/‘,
                type: ‘POST‘,
                data: {‘uid‘: uid},
                success: function (data) {
                    var obj = JSON.parse(data);
                    if(obj.status){
                        tr_obj.remove()
                    }else{
                        $(‘#error-msg‘).text(obj.error)
                    }
                }
            })
        });
    })

處理函數比較簡單,根據uid查找到數據後delete掉

def ajax_del_user(request):
    ret = {‘status‘: True, ‘error‘: None, ‘data‘: None}
    try:
        uid = request.POST.get(‘uid‘)
        models.UserInfo.objects.filter(uid=uid).delete()
    except Exception as e:
        ret[‘status‘] = False
        ret[‘error‘] = ‘請求錯誤:%s‘ % e
    return HttpResponse(json.dumps(ret))

示例-編輯功能

編輯還是用模態對話框來實現,為了避免混淆,就單獨再搞一個。直接把之前的刪除的div復制一份。把class改掉。
頁面裏的所有元素的id也都要改,id不能重復,這裏不用id了都刪掉。
提交按鈕也不要了,Ajax提交要的並且起一個新的id名。取消按鈕把id換成class,刪除頁面的取消按鈕和jQuery的綁定操作哪裏也相應的修改一下

<div class="edit-modal hide">
    <h1>編輯用戶</h1>
    <form action="/user/" method="POST">
        <p><input type="text" placeholder="name" name="name"></p>
        <p><input type="text" placeholder="age" name="age"></p>
        <p><input type="text" placeholder="IP" name="ip"></p>
        <p>
            <select name="dept-id">
                {% for op in depts1 %}
                    <option value="{{ op.id }}">{{ op.name }}({{ op.name_en }})</option>
                {% endfor %}
            </select>
        </p>
        <p><span id="error-msg">?</span></p>
        <p>
            <input type="button" id="ajax-edit-submit" value="Ajax提交" />
            <input class="cancel" type="button" value="取消">
        </p>
    </form>
</div>

編輯頁面和新增頁面的差別主要是編輯頁面的輸入框裏是需要填入默認值的,包括select框也要選中對應的選項。這裏Ajax請求的data部分用了一個新的更簡單的方法,之後再展開。

    $(function () {
        $(‘.edit‘).click(function () {
            $(‘.shade, .edit-modal‘).removeClass(‘hide‘);
            var uid = $(this).parent().parent().attr(‘uid‘);
            // 把uid保存在頁面裏,提交修改的時候需要用到
            $(‘.edit-modal form‘).attr(‘uid‘, uid);
            var dept_id = $(this).parent().parent().attr(‘dept_id‘);
            // 給select下拉列表填上值
            $(‘.edit-modal form select‘).val(dept_id);
            // 下面是為其他input填值,課上跳過了。
            var obj = $(this).parent().siblings(‘td‘).first().next();
            $(‘.edit-modal form :text‘).each(function () {
                $(this).val(obj.text());
                obj = obj.next();
            })
        });
        $(‘#ajax-edit-submit‘).click(function () {
            $.ajax({
                url: ‘/ajax_edit_user/‘,
                type: ‘POST‘,
                data: $(this).parents(‘form‘).serialize()+‘&‘+‘uid=‘+$(‘.edit-modal form‘).attr(‘uid‘),
                success: function (data) {
                    var obj = JSON.parse(data);
                    if(obj.status){
                        location.reload()
                    }else{
                        $(‘#error-msg‘).text(obj.error)
                    }
                }
            })
        });
    })

處理函數都是一樣的,這裏要用update更新數據。

def ajax_edit_user(request):
    ret = {‘status‘: True, ‘error‘: None, ‘data‘: None}
    try:
        uid = request.POST.get(‘uid‘)
        dic1 = {
            ‘name‘: request.POST.get(‘name‘),
            ‘age‘: request.POST.get(‘age‘),
            ‘ip‘: request.POST.get(‘ip‘),
            ‘dept_id‘: request.POST.get(‘dept-id‘),
        }
        for k, v in dic1.items():
            if not v:
                ret[‘status‘] = False
                ret[‘error‘] = ‘字段 %s 不能為空‘ % k
                break
        else:
            models.UserInfo.objects.filter(uid=uid).update(**dic1)
    except Exception as e:
        ret[‘status‘] = False
        ret[‘error‘] = ‘請求錯誤:%s‘ % e
    return HttpResponse(json.dumps(ret))

Ajax使用serialize() 提交form表單

上面的例子已經使用了serialize() 來獲取提交請求的data數據。這裏不需要去一個一個獲取了。使用serialize() 方法可以直接把form表單裏的所有的name和對應的值一次獲取到。
例子中還有個問題,就是還要提交一個uid,這個uid不在表單裏。這裏有兩個方法。
一、為uid寫一個input標簽,然後把標簽隱藏了。這樣表單裏就有uidle並且頁面上也不會顯示出來
二、對serialize() 方法獲取到的值進行再加工。serialize()方法把表單裏的內容序列化成了字符串,如例子中那樣可以再追加上我們的字符串

外鍵操作-多對多

首先更新我們的表結構,我們已經有人員信息表(UserInfo)和部門表(Dept)。部門表這裏不需要了。再創建一張客戶信息表(CustomerInfo)。一家客戶可以有多個人員負責,一個人員也可以同時負責多家客戶,這就是一個多對多的關系。

自定義關系表

一個多對多的關系在數據庫中除了有兩張被關聯的表之外,還要有一張結合表。人員信息表(UserInfo)原本就有了,現在要加上客戶信息表(CustomerInfo)和結合表(UserToCustomer)。表結構如下:

from django.db import models

# Create your models here.

class CustomerInfo(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField(max_length=32)

class UserInfo(models.Model):
    uid = models.AutoField(primary_key=True)  # 自己建主鍵
    name = models.CharField(max_length=32, db_index=True)  # 加索引
    age = models.IntegerField()
    ip = models.GenericIPAddressField(protocol=‘ipv4‘, db_index=True)  # 使用ipv4驗證
    dept = models.ForeignKey(‘Dept‘, models.CASCADE, to_field=‘id‘)  # 外鍵

class UserToCustomer(models.Model):
    user_obj = models.ForeignKey(‘UserInfo‘, models.CASCADE, to_field=‘nid‘)
    customer_obj = models.ForeignKey(‘CustomerInfo‘, models.CASCADE, to_field=‘id‘)

class Dept(models.Model):
    name = models.CharField(max_length=32)
    name_en = models.CharField(max_length=32)

上面是手動創建的所有的表,並且在結合表裏手動寫了兩個一對多關聯。這樣也創建完成了一個多對多關系。這也是一種創建多對多關系的方法,自定義關系表。這種方法可以自定義這個表,我們可以根據需要再添加上別的字段。

自動創建關系表

結合表也是可以不用手動創建的,而是由Django自動幫我麽創建。把上面的結合表去掉,在客戶信息表(CustomerInfo)多對一個 models.ManyToManyField ,之後Django會自動幫我麽創建好結合表:

from django.db import models

# Create your models here.

class CustomerInfo(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField(max_length=32)
    userInfo = models.ManyToManyField(‘UserInfo‘)

class UserInfo(models.Model):
    uid = models.AutoField(primary_key=True)  # 自己建主鍵
    name = models.CharField(max_length=32, db_index=True)  # 加索引
    age = models.IntegerField()
    ip = models.GenericIPAddressField(protocol=‘ipv4‘, db_index=True)  # 使用ipv4驗證
    dept = models.ForeignKey(‘Dept‘, models.CASCADE, to_field=‘id‘)  # 外鍵

# class UserToCustomer(models.Model):
#     user_obj = models.ForeignKey(‘UserInfo‘, models.CASCADE, to_field=‘nid‘)
#     customer_obj = models.ForeignKey(‘CustomerInfo‘, models.CASCADE, to_field=‘id‘)

class Dept(models.Model):
    name = models.CharField(max_length=32)
    name_en = models.CharField(max_length=32)

自動創建只能幫我們創建一張3個字段的表:自增id,關聯表的主鍵,被關聯表的主鍵。如果想加額外的數據就只能用自定義關系表來創建額外的字段了。

設置關聯關系

ORM都是通過類來進行數據庫操作的。自定義關系表,直接可以獲得結合表的類,直接操作結合表就可以進行數據庫操作了。這部分都是舊知識點了,就不舉例了。創建一個關聯關系的方法:

UserToCustomer.objects.create(user_obj_uid=1, customer_obj_id=1)

對於自動創建關聯關系表,由於並沒有結合表的類,無法直接對結合表進行操作。這裏可以獲取到對象,比如客戶表id=1的那條數據對象,使用提供的方法對這個對象的關聯系進行操作,添加、刪除、清除、設置。

obj = CustomerInfo.objects.get(id=1)  # 先獲取到一個對象,下面都是對id=1的關聯關系進行操作
obj.userInfo.add(1)  # 添加一個關系
obj.userInfo.add(2, 3, 4)  # 多個參數添加多個關系
obj.userInfo.add(*[2, 3, 4])  # 通過列表添加多個關系
obj.userInfo.remove(1)  # 刪除一個關系,同樣支持多個參數或列表
obj.userInfo.clear()  # 清除這個id的所有的關系
obj.set([3, 5, 7])  # 設置關系。這個id的其他關系都會清除,最後只有這個列表中的關系。相當於先清除在添加。這裏沒星號

上面沒有獲取的方法,獲取的方法和之前獲取數據的方法一樣。models.UserInfo.objects 後面能使用什麽方法,這裏的obj就可以使用什麽方法。比如:.all() 所有被關聯的表的對象。all() 方法獲取到的一定是一個QuerySet對象,在這裏裏面的每個元素是一個被關聯的表 UserInfo 的對象。

顯示客戶列表(查)

如果上面還沒有把表結構更新到數據庫,現在就去更新一下

python manage.py makemigrations
python manage.py migrate

現在只有空表,這裏先看怎麽顯示數據,所以還得自己手動去數據庫裏加上點數據。除了添加客戶表還要去結合表裏也加點數據。
先準備好urls.py的對應關系和views.py的處理函數。下面是處理函數:

def customer(request):
    if request.method == ‘GET‘:
        customers = models.CustomerInfo.objects.all()
        return render(request, ‘customer.html‘, {‘customers‘: customers})
    elif request.method == ‘POST‘:
        # 一會還要做添加
        pass

這裏直接把CustomerInfo表裏的所有的對象都傳到頁面了,在頁面裏遍歷這個customers就能獲取到裏面所有的數據,包括被關聯的UserInfo。html頁面文件customer.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>客戶列表</h1>
<table border="1">
    <thead>
    <tr>
        <th>客戶名稱</th>
        <th>e-mail</th>
        <th>負責人</th>
    </tr>
    </thead>
    <tbody>
    {% for customer in customers %}
        <tr>
        <td>{{ customer.name }}</td>
        <td>{{ customer.email }}</td>
        <td>
{#            可以去掉下面的註釋,查看customer.userInfo.all的內容#}
{#            {{ customer.userInfo.all }}#}
            {% for user in  customer.userInfo.all %}
                {{ user.name }}
            {% endfor %}
        </td>
        </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>

因為是多對多的關系,customer.userInfo.all 裏是所有的被關聯的對象,可能是多個。這裏就需要再一個for循環遍歷每一個被關聯的對象,然後獲取到被關聯對象裏的屬性。

添加客戶(增)

頁面簡單點直接放在客戶列表的下面好了。關聯客戶需要使用下拉列表,現在可以關聯多個客戶,所以要用復選的下拉列表(multiple),通過form提交到後臺要獲取值就需要用getlist來獲取多個值。客戶列表的後面接著寫:

<h1>添加客戶</h1>
<form action="/customer/" method="POST">
    <p><input type="text" placeholder="name" name="name"></p>
    <p><input type="text" placeholder="email" name="email"></p>
    <p>
        <select name="users" multiple>
            {% for user in users %}
                <option value="{{ user.uid }}">{{ user.name }}</option>
            {% endfor %}
        </select>
    </p>
        <p>
            <input type="submit" value="提交">
            <input type="button" id="ajax-submit" value="Ajax提交" />
        </p>
</form>

處理函數,不但要寫POST方法,GET方法現在還需要多提交一個UserInfo給前臺的下拉列表:

def customer(request):
    if request.method == ‘GET‘:
        customers = models.CustomerInfo.objects.all()
        # user表也要傳到前端,添加的時候下拉列表要用到
        users = models.UserInfo.objects.all()
        return render(request, ‘customer.html‘, {‘customers‘: customers, ‘users‘: users})
    elif request.method == ‘POST‘:
        name = request.POST.get(‘name‘)
        email = request.POST.get(‘email‘)
        users = request.POST.getlist(‘users‘)  # 這裏有多個值要用getlist來獲取一個列表
        obj = models.CustomerInfo.objects.create(name=name, email=email)  # 創建記錄並獲取對象
        obj.userInfo.add(*users)  # 添加關聯的記錄
        return redirect(‘/customer/‘)

上面例子中,添加客戶數據的同時獲取到返回值。這裏不用再去數據庫裏查找了,直接對這個返回的對象進行關聯關系的操作。

用Ajax提交-Ajax知識補充

這裏直接上例子,並且對Ajax進行一些擴展。在頁面上增加事件綁定,添加客戶的後面接著寫:

<script src="/static/js/jquery-1.12.4.js"></script>
<script>
    $(function () {
        $(‘#ajax-submit‘).click(function () {
            $.ajax({
                url: ‘/ajax_add_customer/‘,
                type: ‘POST‘,
                // data: {‘name‘: ‘Test‘, ‘email‘: ‘[email protected]‘, ‘users‘: [1, 2, 3]},  // 提交測試數據
                data: $(this).parents(‘form‘).serialize(),
                dataType: ‘JSON‘,  // 下面匿名函數中的data現在直接就是JSON對象了
                traditional: true,  // 默認無法提交字典的,要提交字典得加上這個,否則交上去的是None
                success: function (data) {
                    // var obj = JSON.parse(data);  // 上面轉了,這裏就不用再手動轉了
                    if(data.status){
                        location.reload()
                    }
                },
                error: function () {
                    alert(‘後臺發生未知錯誤‘)
                }
            })
        })
    })
</script>

還有處理函數:

def ajax_add_customer(request):
    ret = {‘status‘: True, ‘errpr‘: None, ‘data‘: None}
    name = request.POST.get(‘name‘)
    email = request.POST.get(‘email‘)
    users = request.POST.getlist(‘users‘)
    obj = models.CustomerInfo.objects.create(name=name, email=email)
    obj.userInfo.add(*users)
    return HttpResponse(json.dumps(ret))

Ajax補充知識點

使用serialize() 方法可以直接把form表單裏的所有的name和對應的值一次獲取到。之前用過了
dataType: ‘JSON‘, 原本返回的是字符串,現在會直接把字符串轉成JSON對象
traditional: true,默認無法提交列表,提交後數據會變成None提交出去。要想提交列表得加上這句
error匿名函數,當後臺拋出異常後會執行這個函數。後臺用try捕獲到的異常不會執行這裏。

編輯功能(改)-打開新url頁面操作

這裏用打開新url的方式來做編輯功能。打開新url雖然要新建一個頁面,但是也有它的應用場景。如果一個頁面的內容比較多,那麽可能是放到模態對話框中種,甚至一個頁面都不夠放。對於如果頁面內容會很多的場景,使用打開新url的方式會更好
原來的頁面裏只要在表格每行的最後加上一個編輯的按鈕實現跳轉即可

        <td>
            <a href="/customer-edit-{{ customer.id }}/">編輯</a>
        </td>

這裏用的是a標簽,直接發送一個帶id的GET請求,urls.py中的對應關系應該這麽寫:

    path(‘customer-edit-<int:customer_id>/‘, views.customer_edit),

接下來是處理函數:

def customer_edit(request, customer_id):
    if request.method == ‘GET‘:
        customer = models.CustomerInfo.objects.filter(id=customer_id).first()
        users = models.UserInfo.objects.all()
        return render(request, ‘customer-edit.html‘, {‘customer‘: customer, ‘users‘: users})
    elif request.method == ‘POST‘:
        dic = {
            ‘name‘: request.POST.get(‘name‘),
            ‘email‘: request.POST.get(‘email‘),
        }
        obj = models.CustomerInfo.objects.filter(id=customer_id)
        obj.update(**dic)
        users = request.POST.getlist(‘users‘)
        obj.first().userInfo.set(users)
        return redirect(‘/customer/‘)

GET請求部分,傳了3個值給前端。customer就是當前被編輯的客戶的屬性,前端自動填充到input框裏。users傳遞的是員工的屬性,前端要提取其中的uid和name,放到selec的選項中。
POST方法需要分別提交因為其實數據庫是是兩張表,這裏的關聯修改用的是set方法。選擇的是哪些就設置關聯哪些。
編輯頁面,customer-edit.html。自動填充數據之前也都會,就是select多選的默認選中有點點變化。input框直接用模板語言在頁面裏就填上了,select框通過jQuery賦值語句val選上:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>編輯客戶列表</h1>
<form action="/customer-edit-{{ customer.id }}/" method="POST">
    <p><input type="text" placeholder="name" name="name" value="{{ customer.name }}"></p>
    <p><input type="text" placeholder="email" name="email" value="{{ customer.email }}"></p>
    <p>
        <select name="users" multiple>
            {% for user in users %}
                <option value="{{ user.uid }}">{{ user.name }}</option>
            {% endfor %}
        </select>
    </p>
    <p>
        <input type="submit" value="提交">
    </p>
</form>
<script src="/static/js/jquery-1.12.4.js"></script>
<script>
    $(function () {
        var user_list = [];
        {% for user in customer.userInfo.all %}
            user_list.push({{ user.uid }});
        {% endfor %}
        $(‘select‘).val(user_list)
    })
</script>
</body>
</html>

最後的刪除就不寫了。
另外講了一對多和多對多,都是單向的操作,一直沒講反查。應該是下節的內容

Python自動化開發學習20-Django