本章包含如下內(nèi)容:




* 使用CRUDL函數(shù)創(chuàng)建應(yīng)用" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷資訊 > 網(wǎng)站運(yùn)營(yíng) > Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第3章 表單和視圖PART 1

Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第3章 表單和視圖PART 1

時(shí)間:2023-05-28 01:57:01 | 來源:網(wǎng)站運(yùn)營(yíng)

時(shí)間:2023-05-28 01:57:01 來源:網(wǎng)站運(yùn)營(yíng)

Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第3章 表單和視圖PART 1:本文完整目錄請(qǐng)見 [**Django 3網(wǎng)頁(yè)開發(fā)指南 - 第4版**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第1章 Django 3.0入門)




本章包含如下內(nèi)容:




* 使用CRUDL函數(shù)創(chuàng)建應(yīng)用

* 保存模型實(shí)例的作者

* 上傳圖片

* 通過自定義模型創(chuàng)建表單布局

* 通過django-crispy-forms創(chuàng)建表單布局

* 處理formsets

* 過濾對(duì)象列表

* 管理分頁(yè)列表

* 編寫基于類的視圖

* 添加Open Graph和Twitter Card數(shù)據(jù)

* 添加http://schema.org用詞

* 生成PDF文檔

* 通過Haystack和Whoosh實(shí)現(xiàn)多語言搜索

* 通過Elasticsearch DSL實(shí)現(xiàn)多語言搜索




## 引言




在模型中定義了數(shù)據(jù)庫(kù)結(jié)構(gòu)時(shí),視圖提供了要向用戶顯示內(nèi)容或讓用戶輸入新數(shù)據(jù)及更新數(shù)據(jù)的端點(diǎn)(endpoint)。本章中, 我們集中學(xué)習(xí)管理表單的視圖、列表視圖及向HTML生成替代輸出的視圖。這最簡(jiǎn)化的示例中,URL規(guī)則及模板創(chuàng)建就交給讀者了。




## 技術(shù)要求




要使用本章中的代碼,同時(shí),讀者需要最新的穩(wěn)定版Python、MySQL或PostgreSQL數(shù)據(jù)庫(kù)以及虛擬環(huán)境中創(chuàng)建的Django項(xiàng)目中。部分小節(jié)要求有特定的Python依賴。此外,要生成PDF文件,需要有cairo、pango、gdk-pixbuf及l(fā)ibffi庫(kù)。搜索需要用到Elasticsearch服務(wù)端。更多詳情在相應(yīng)的小節(jié)中會(huì)進(jìn)行討論。




本章中的大部分模板會(huì)使用Bootstrap 4 CSS框架來保持美觀度。




本章中的代碼請(qǐng)見[GitHub倉(cāng)庫(kù)](alanhou/django3-cookbook)的Chapter03目錄。




## 使用CRUDL函數(shù)創(chuàng)建應(yīng)用




在計(jì)算機(jī)科學(xué)領(lǐng)域,CRUDL是Create(創(chuàng)建/增), Read(讀取/查), Update(更新/改), Delete(刪除/刪)和List(列舉)函數(shù)的縮寫。很多具有交互功能的Django項(xiàng)目要求我們實(shí)現(xiàn)所有這些函數(shù)來對(duì)網(wǎng)站進(jìn)行數(shù)據(jù)的管理。本小節(jié)中,我們學(xué)習(xí)如何通過這些基本函數(shù)來創(chuàng)建URL和視圖。




### 準(zhǔn)備工作




我們來創(chuàng)建一個(gè)名為ideas的應(yīng)用并將添加到設(shè)置文件的INSTALLED_APPS中。在該應(yīng)用中創(chuàng)建如下包含帶有翻譯文件的IdeaTranslations模型及Idea模型:




```

# myproject/apps/idea/models.py

import uuid




from django.db import models

from django.urls import reverse

from django.conf import settings

from django.utils.translation import gettext_lazy as _




from myproject.apps.core.model_fields import TranslatedField

from myproject.apps.core.models import (

CreationModificationDateBase, UrlBase

)




RATING_CHOICES = (

(1, "★☆☆☆☆"),

(2, "★★☆☆☆"),

(3, "★★★☆☆"),

(4, "★★★★☆"),

(5, "★★★★★"),

)




class Idea(CreationModificationDateBase, UrlBase):

uuid = models.UUIDField(

primary_key=True, default=uuid.uuid4, editable=False

)

author = models.ForeignKey(

settings.AUTH_USER_MODEL,

verbose_name=_("Author"),

on_delete=models.SET_NULL,

blank=True,

null=True,

related_name="authored_ideas",

)

title = models.CharField(_("Title"), max_length=200)

content = models.TextField(_("Content"))




categories = models.ManyToManyField(

"categories.Category",

verbose_name=_("Categories"),

related_name="category_ideas",

)

rating = models.PositiveIntegerField(

_("Rating"),

choices=RATING_CHOICES,

blank=True,

null=True

)

translated_title = TranslatedField("title")

translated_content = TranslatedField("content")




class Meta:

verbose_name = _("Idea")

verbose_name_plural = _("Ideas")




def __str__(self):

return self.title




def get_url_path(self):

return reverse("ideas:idea_detail", kwargs={"pk": self.pk})




class IdeaTranslations(models.Model):

idea = models.ForeignKey(

Idea,

verbose_name=_("Idea"),

on_delete=models.CASCADE,

related_name="translations",

)

language = models.CharField(_("Language"), max_length=7)

title = models.CharField(_("Title"), max_length=200)

content = models.TextField(_("Content"))




class Meta:

verbose_name = _("Idea Translations")

verbose_name_plural = _("Idea Translations")

ordering = ["language"]

unique_together = [["idea", "language"]]




def __str__(self):

return self.title

```







我們使用了前一章中的一些概念:繼承了模型mixin并使用了一個(gè)模型翻譯表。閱讀*使用模型mixin*及*操作模型翻譯數(shù)據(jù)表*小節(jié)了解更多內(nèi)容。我們將在本章的剩余小節(jié)中使用ideas應(yīng)用及這些模型。




此外,創(chuàng)建一個(gè)同級(jí)categories應(yīng)用并包含Category和CategoryTranslations模型:




```

# myproject/apps/categories/models.py

from django.db import models

from django.utils.translation import gettext_lazy as _




from myproject.apps.core.model_fields import TranslatedField




class Category(models.Model):

title = models.CharField(_("Title"), max_length=200)




translated_title = TranslatedField("title")




class Meta:

verbose_name = _("Category")

verbose_name_plural = _("Categories")




def __str__(self):

return self.title




class CategoryTranslations(models.Model):

category = models.ForeignKey(

Category,

verbose_name=_("Category"),

on_delete=models.CASCADE,

related_name="translations",

)

language = models.CharField(_("Language"), max_length=7)




title = models.CharField(_("Title"), max_length=200)




class Meta:

verbose_name = _("Category Translations")

verbose_name_plural = _("Category Translations")

ordering = ["language"]

unique_together = [["category", "language"]]




def __str__(self):

return self.title




```







### 如何實(shí)現(xiàn)...




Django中的CRUDL功能由表單、視圖和URL規(guī)則所組成。下面進(jìn)行創(chuàng)建:




1. 在ideas應(yīng)用中新增forms.py文件,添加用于對(duì)Idea模型實(shí)例進(jìn)行新增和修改的模型表單:




```

# myprojects/apps/ideas/forms.py

from django import forms

from .models import Idea




class IdeaForm(forms.ModelForm):

class Meta:

model = Idea

fields = "__all__"

```







2. 在ideas應(yīng)用中添加views.py用于新增操作Idea模型的視圖:




```

# myproject/apps/ideas/views.py

from django.contrib.auth.decorators import login_required

from django.shortcuts import render, redirect, get_object_or_404

from django.views.generic import ListView, DetailView




from .forms import IdeaForm

from .models import Idea




class IdeaList(ListView):

model = Idea




class IdeaDetail(DetailView):

model = Idea

context_object_name = "idea"




@login_required

def add_or_change_idea(request, pk=None):

idea = None

if pk:

idea = get_object_or_404(Idea, pk=pk)




if request.method == "POST":

form = IdeaForm(

data=request.POST,

files=request.FILES,

instance=idea

)




if form.is_valid():

idea = form.save()

return redirect("ideas:idea_detail", pk=idea.pk)

else:

form = IdeaForm(instance=idea)




context = {"idea": idea, "form": form}

return render(request, "ideas/idea_form.html", context)




@login_required

def delete_idea(request, pk):

idea = get_object_or_404(Idea, pk=pk)

if request.method == "POST":

idea.delete()

return redirect("ideas:idea_list")

context = {"idea": idea}

return render(request, "ideas/idea_deleting_confirmation.html", context)

```







3. 在ideas應(yīng)用創(chuàng)建urls.py文件并添加URL規(guī)則:




```

# myproject/apps/ideas/urls.py

from django.urls import path




from .views import (

IdeaList,

IdeaDetail,

add_or_change_idea,

delete_idea,

)




urlpatterns = [

path("", IdeaList.as_view(), name="idea_list"),

path("add/", add_or_change_idea, name="add_idea"),

path("<uuid:pk>/", IdeaDetail.as_view(), name="idea_detail"),

path("<uuid:pk>/change/", add_or_change_idea, name="change_idea"),

path("<uuid:pk>/delete/", delete_idea, name="delete_idea"),

]

```







4. 現(xiàn)在我們把這些URL規(guī)則插入到項(xiàng)目的URL配置中。我們還會(huì)包含Django社區(qū)的auth應(yīng)用中的賬戶URL規(guī)則,這樣@login_required裝飾器就可以正常運(yùn)行了:




```

# myproject/urls.py

from django.contrib import admin

from django.conf.urls.i18n import i18n_patterns

from django.urls import include, path

from django.conf import settings

from django.conf.urls.static import static

from django.shortcuts import redirect




urlpatterns = i18n_patterns(

path("", lambda request: redirect("ideas:idea_list")),

path("admin/", admin.site.urls),

path("accounts/", include("django.contrib.auth.urls")),

path("ideas/", include(("myproject.apps.ideas.urls", "ideas"), namespace="ideas")),

)

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

urlpatterns += static("/media/", document_root=settings.MEDIA_ROOT)

```







5. 現(xiàn)在可以創(chuàng)建如下模板了:

* 包含登錄表單的registration/login.html

* 包含對(duì)ideas進(jìn)行列舉的ideas/idea_list.html

* 有關(guān)單個(gè)idea詳情的ideas/idea_detail.html

* 帶有添加或修改 idea 表單的ideas/idea_form.html

* 包含確認(rèn)刪除 idea 空表單的ideas/idea_deleting_confirmation.html




在模板中,可以通過如下命名空間和path名稱來調(diào)用ideas應(yīng)用的URL:




```

{% load i18n %}

<a href="{% url 'ideas:change_idea' pk=idea.pk %}">{% trans "Change this idea" %}</a>

<a href="{% url 'ideas:add_idea' %}">{% trans "Add idea" %}</a>

```







> ??如果碰到問題或是希望節(jié)省時(shí)間,可以查看本書代碼文件中的相應(yīng)模板,地址為https://github.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition/tree/master/ch03/myproject_virtualenv/src/django-myproject/myproject/templates/ideas




### 實(shí)現(xiàn)原理...




本例中,我們使用UUID字段作為Idea模型的主鍵。借助這一ID,每個(gè)idea的唯一URL都是無法靠猜來知道的。另一種方式是對(duì)URL使用slug字段,但這時(shí)要確保會(huì)生成slug且在整個(gè)網(wǎng)站中是唯一的。




> 出于安全考慮不推薦對(duì)URL使用默認(rèn)的遞增ID:那樣用戶就能夠推算出數(shù)據(jù)庫(kù)中有多少條記錄并可以嘗試訪問他們可能沒有權(quán)限訪問的前一條或后一條記錄。




在我們示例中,我們使用通用的視圖類來列出并讀取idea以及視圖函數(shù)來增、改、刪這些idea。在數(shù)據(jù)庫(kù)中更改記錄的視圖通過@login_required裝飾器要求為認(rèn)證用戶。使用視圖類或針對(duì)所有的CRUDL函數(shù)的視圖函數(shù)都沒有問題。




在成功新增或修改idea之后,這些用戶會(huì)被重定向到詳情視圖。在刪除idea之后,用戶會(huì)被重定向到列表視圖。




### 擴(kuò)展知識(shí)...




此外可以使用Django消息框架來在成功添加、修改或刪除時(shí)在頁(yè)面頂部顯示成功消息。




可以在[官方文檔](The messages framework)中閱讀相關(guān)內(nèi)容。




### 相關(guān)內(nèi)容




* [**第2章 模型和數(shù)據(jù)庫(kù)結(jié)構(gòu)**](https://alanhou.org/models-database-structure/)中*使用模型mixin*一節(jié)

* [**第2章 模型和數(shù)據(jù)庫(kù)結(jié)構(gòu)**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第2章 模型和數(shù)據(jù)庫(kù)結(jié)構(gòu))中*操作模型翻譯數(shù)據(jù)表*一節(jié)

* *保存模型實(shí)例的作者*一節(jié)

* [**第4章 模板和JavaScript**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第4章 模板和JavaScript)中*安排base.html模板*一節(jié)




## 保存模型實(shí)例的作者




Django的每個(gè)視圖的第一個(gè)參數(shù)是HttpRequest對(duì)象,按照慣例名稱為request。它包含有關(guān)由瀏覽器或其它客戶端發(fā)請(qǐng)求的元數(shù)據(jù),包含當(dāng)前語言碼、用戶數(shù)據(jù)、cookie和session數(shù)據(jù)。默認(rèn),視圖使用表單來接受GET或POST數(shù)據(jù)、文件、初始數(shù)據(jù)及其它參數(shù);但是它們不是默認(rèn)就可以訪問HttpRequest對(duì)象。在某些情況下,額外將HttpRequest傳遞給表單會(huì)很用,尤其是在想要根據(jù)其它請(qǐng)求數(shù)據(jù)或在表單中處理當(dāng)前用戶或IP的保存時(shí)過濾掉表單字段的選項(xiàng)時(shí)。




在本節(jié)中,我們將學(xué)習(xí)表單的示例,其中可以添加或修改idea,并將當(dāng)前用戶保存為作者。




### 準(zhǔn)備工作




我們將在前一小節(jié)示例的基礎(chǔ)上進(jìn)行演示。




### 如何實(shí)現(xiàn)...




要完成本小節(jié),執(zhí)行如下兩步:




1. 修改IdeaForm模型如下:




```

# myprojects/apps/ideas/forms.py

from django import forms

from .models import Idea




class IdeaForm(forms.ModelForm):

class Meta:

model = Idea

exclude = ["author"]




def __init__(self, request, *args, **kwargs):

self.request = request

super().__init__(*args, **kwargs)




def save(self, commit=True):

instance = super().save(commit=False)

instance.author = self.request.user

if commit:

instance.save()

self.save_m2m()

return instance

```







2. 修改視圖來添加或修改idea:




```

# myproject/apps/ideas/views.py

from django.contrib.auth.decorators import login_required

from django.shortcuts import render, redirect, get_object_or_404




from .forms import IdeaForm

from .models import Idea




@login_required

def add_or_change_idea(request, pk=None):

idea = None

if pk:

idea = get_object_or_404(Idea, pk=pk)

if request.method == "POST":

form = IdeaForm(request, data=request.POST, files=request.FILES, instance=idea)

if form.is_valid():

idea = form.save()

return redirect("ideas:idea_detail", pk=idea.pk)

else:

form = IdeaForm(request, instance=idea)




context = {"idea": idea, "form": form}

return render(request, "ideas/idea_form.html", context)

```







### 實(shí)現(xiàn)原理...




我們來看下這個(gè)表單。首先,我們從表單中排除了author字段,因?yàn)橄M褂贸绦騺磉M(jìn)行處理。我們重寫了__init__()方法來接收HttpRequest作為第一個(gè)參數(shù)并在表單中進(jìn)行存儲(chǔ)。模型表單的save()方法處理模型的存儲(chǔ)。commit參數(shù)告訴模型表單立即存儲(chǔ)實(shí)例或者是創(chuàng)建并調(diào)用實(shí)例,但暫不保存。在本例中,我們獲取了實(shí)例但不進(jìn)行保存,然后通過當(dāng)前用戶為author賦值。最后在commit為True時(shí)我們保存了該實(shí)例。我們會(huì)動(dòng)態(tài)調(diào)用表單所添加的save_m2m() 方法來保存多對(duì)多關(guān)聯(lián),如categories。




在視圖中,我們只對(duì)表單傳遞request變量作為每一個(gè)參數(shù)。




### 相關(guān)內(nèi)容




* *使用CRUDL函數(shù)創(chuàng)建應(yīng)用*一節(jié)

* *上傳圖片*一節(jié)




## 上傳圖片




在本節(jié)中,我們將了解處理圖片上傳的最簡(jiǎn)單方式。我們會(huì)對(duì)Idea模型添加一個(gè)picture字段,并為不同用途創(chuàng)建不同大小版本的圖片。




### 準(zhǔn)備工作




對(duì)于具有版本的圖片,我們需要用到Pillow和django-imagekit庫(kù)。下面通過pip來在虛擬環(huán)境中進(jìn)行安裝(并在requirements/_base.txt中進(jìn)行添加):

```

(env)$ pip install Pillow

(env)$ pip install django-imagekit==4.0.2

```







然后在設(shè)置的INSTALLED_APPS添加imagekit。




### 如何實(shí)現(xiàn)...




執(zhí)行如下步驟完成本小節(jié)中的開發(fā):




1. 修改Idea模型,添加一個(gè)picture字段以及各圖片版本規(guī)格:




```

# myproject/apps/ideas/models.py import contextlib

import os




from imagekit.models import ImageSpecField

from pilkit.processors import ResizeToFill




from django.db import models

from django.utils.translation import gettext_lazy as _

from django.utils.timezone import now as timezone_now




from myproject.apps.core.models import (

CreationModificationDateBase,

UrlBase

)




def upload_to(instance, filename):

now = timezone_now()

base, extension = os.path.splitext(filename)

extension = extension.lower()

return f"ideas/{now:%Y/%m}/{instance.pk}{extension}"




class Idea(CreationModificationDateBase, UrlBase): # attributes and fields...

picture = models.ImageField(

_("Picture"), upload_to=upload_to

)

picture_social = ImageSpecField(

source="picture",

processors=[ResizeToFill(1024, 512)],

format="JPEG",

options={"quality": 100},

)

picture_large = ImageSpecField(

source="picture",

processors=[ResizeToFill(800, 400)],

format="PNG"

)

picture_thumbnail = ImageSpecField(

source="picture",

processors=[ResizeToFill(728, 250)],

format="PNG"

)

# other fields, properties, and methods...




def delete(self, *args, **kwargs):

from django.core.files.storage import default_storage

if self.picture:

with contextlib.suppress(FileNotFoundError):

default_storage.delete(

self.picture_social.path

)

default_storage.delete(

self.picture_large.path

)

default_storage.delete(

self.picture_thumbnail.path

)

self.picture.delete()

super().delete(*args, **kwargs)

```







2. 和前面小節(jié)中一樣,在forms.py中為Idea模型創(chuàng)建模型表單IdeaForm。

3. 在添加或修改idea的視圖中,確保在表單中在request.POST之后post請(qǐng)求還提交request.FILES:




```

# myproject/apps/ideas/views.py

from django.contrib.auth.decorators import login_required

from django.shortcuts import (render, redirect, get_object_or_404)

from django.conf import settings




from .forms import IdeaForm

from .models import Idea




@login_required

def add_or_change_idea(request, pk=None):

idea = None

if pk:

idea = get_object_or_404(Idea, pk=pk)

if request.method == "POST":

form = IdeaForm( request,

data=request.POST,

files=request.FILES,

instance=idea,

)

if form.is_valid():

idea = form.save()

return redirect("ideas:idea_detail", pk=idea.pk)

else:

form = IdeaForm(request, instance=idea)




context = {"idea": idea, "form": form}

return render(request, "ideas/idea_form.html", context)

```







4. 在模板中,記得為multipart/form- data設(shè)置編碼類型,如下:




```

<form action="{{ request.path }}" method="post"

enctype="multipart/form-data">

{% csrf_token %}

{{ form.as_p }}

<button type="submit">{% trans "Save" %}</button>

</form>

```







> ??如果像*通過django-crispy-forms創(chuàng)建表單布局*一節(jié)中所描述那樣使用django-crispy-form,則會(huì)在表單中自動(dòng)添加enctype屬性。




### 實(shí)現(xiàn)原理...




Django模型表單是動(dòng)態(tài)地由模型進(jìn)行創(chuàng)建的。它們提供來自模型的指定字段,因此無需在表單中手動(dòng)重新定義這些字段。在前例中,我們?yōu)镮dea模型創(chuàng)建了一個(gè)模型表單。在保存表單時(shí),表單會(huì)知道在數(shù)據(jù)庫(kù)中如何保存每個(gè)字段、如何上傳文件并在media目錄中進(jìn)行保存。




本例中的upload_to() 函數(shù)用于將圖片保存到指定目錄并重新定義其名稱,這樣不會(huì)與其它模型實(shí)例中的文件名相沖突。每個(gè)文件保存的路徑類似ideas/2020/01/0422c6fe- b725-4576-8703-e2a9d9270986.jpg,其中包含上傳的年、月以及Idea實(shí)例的主鍵。




> 一些文件系統(tǒng)(如FAT32和NTFS)對(duì)每個(gè)目錄中的文件存在上限;因此按照上傳日期、字母或其它條件分割到不同目錄是一種好實(shí)踐。




我們使用django-imagekit中的ImageSpecField來創(chuàng)建3種圖片規(guī)格:




* picture_social用于社交分享

* picture_large用于詳情視圖

* picture_thumbnail用于列表視圖




圖片規(guī)格在數(shù)據(jù)庫(kù)中不進(jìn)行關(guān)聯(lián),而只是在CACHE/images/ideas/2020/01/0422c6fe-b725-4576-8703- e2a9d9270986/這樣的文件路徑中按默認(rèn)文件存儲(chǔ)進(jìn)行保存。




在模板中,可以按如下使用原始或指定圖片版本:

```

<img src="{{ idea.picture.url }}" alt="" />

<img src="{{ idea.picture_large.url }}" alt="" />

```







在Idea模型定義最后,我們重寫了delete()方法來在刪除Idea實(shí)例本身之前從磁盤上刪除各版本圖片及圖片本身。




### 相關(guān)內(nèi)容




* *通過django-crispy-forms創(chuàng)建表單布局*一節(jié)

* [**第4章 模板和JavaScript**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第4章 模板和JavaScript)中的*編排base.html模板*一節(jié)

* [**第4章 模板和JavaScript**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第4章 模板和JavaScript)中的*使用響應(yīng)式圖片*一節(jié)




## 通過自定義模型創(chuàng)建表單布局




在Django的早前版本中,所有表單的渲染都獨(dú)立放在Python代碼中處理,但從Django 1.11開始,就引入了基于模板的表單組件渲染。在本小節(jié)中,我們將學(xué)習(xí)如何對(duì)表單組件使用自定義模板。我們會(huì)使用Django后臺(tái)表單來講解自定義組件模板如何提升字段的易用性。




### 準(zhǔn)備工作




我們先創(chuàng)建Idea模型的默認(rèn)后臺(tái)管理并添加翻譯:




```

# myproject/apps/ideas/admin.py

from django import forms

from django.contrib import admin

from django.utils.translation import gettext_lazy as _




from myproject.apps.core.admin import LanguageChoicesForm




from .models import Idea, IdeaTranslations




class IdeaTranslationsForm(LanguageChoicesForm):

class Meta:

model = IdeaTranslations

fields = "__all__"




class IdeaTranslationsInline(admin.StackedInline):

form = IdeaTranslationsForm

model = IdeaTranslations

extra = 0




@admin.register(Idea)

class IdeaAdmin(admin.ModelAdmin):

inlines = [IdeaTranslationsInline]

fieldsets = [

(_("Author and Category"),

{"fields": ["author", "categories"]}),

(_("Title and Content"),

{"fields": ["title", "content", "picture"]}),

(_("Ratings"),

{"fields": ["rating"]}),

]

```







此時(shí)如訪問ideas的后臺(tái)表單,界面類似下面這樣:




![](https://upload-images.jianshu.io/upload_images/14565748-8e13f6e56c6784a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




### 如何實(shí)現(xiàn)...




學(xué)習(xí)本小節(jié),需執(zhí)行如下步驟:




1. 通過將django.forms添加到INSTALLED_APPS中、在模板配置中將APP_DIRS標(biāo)記設(shè)置為True并使用TemplatesSetting表單渲染器來確保模板系統(tǒng)能夠發(fā)現(xiàn)自定義模板:




```

# myproject/settings/_base.py

INSTALLED_APPS = [

"django.contrib.admin",

"django.contrib.auth",

"django.contrib.contenttypes",

"django.contrib.sessions",

"django.contrib.messages",

"django.contrib.staticfiles",

"django.forms",

# other apps...

]




TEMPLATES = [

{

"BACKEND": "django.template.backends.django.DjangoTemplates",

"DIRS": [os.path.join(BASE_DIR, "myproject", "templates")],

"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",

"django.template.context_processors.media",

"django.template.context_processors.static",

"myproject.apps.core.context_processors .website_url",

]

},

}

]




FORM_RENDERER = "django.forms.renderers.TemplatesSetting"

```







2. 編輯admin.py文件如下:




```

# myproject/apps/ideas/admin.py

from django import forms

from django.contrib import admin

from django.utils.translation import gettext_lazy as _




from myproject.apps.core.admin import LanguageChoicesForm




from myproject.apps.categories.models import Category

from .models import Idea, IdeaTranslations




class IdeaTranslationsForm(LanguageChoicesForm):

class Meta:

model = IdeaTranslations

fields = "__all__"




class IdeaTranslationsInline(admin.StackedInline):

form = IdeaTranslationsForm

model = IdeaTranslations

extra = 0




class IdeaForm(forms.ModelForm):

categories = forms.ModelMultipleChoiceField(

label=_("Categories"),

queryset=Category.objects.all(),

widget=forms.CheckboxSelectMultiple(),

required=True,

)




class Meta:

model = Idea

fields = "__all__"




def __init__(self, *args, **kwargs):

super().__init__(*args, **kwargs)




self.fields[

"picture"

].widget.template_name = "core/widgets/image.html"




@admin.register(Idea)

class IdeaAdmin(admin.ModelAdmin):

form = IdeaForm

inlines = [IdeaTranslationsInline]




fieldsets = [

(_("Author and Category"),

{"fields": ["author", "categories"]}),

(_("Title and Content"), "picture"]}),

(_("Ratings"), {"fields": ["rating"]}),

]

```







3. 最后,為picture字段創(chuàng)建一個(gè)模板:




```

{# core/widgets/image.html #}

{% load i18n %}




<div style="margin-left: 160px; padding-left: 10px;">

{% if widget.is_initial %}

<a href="{{ widget.value.url }}">

<img src="{{ widget.value.url }}" width="624" height="auto" alt="" />

</a>

{% if not widget.required %}<br />

{{ widget.clear_checkbox_label }}:

<input type="checkbox" name="{{ widget.checkbox_name }}" id="{{ widget.checkbox_id }}">

{% endif %}<br />

{{ widget.input_text }}:

{% endif %}

<input type="{{ widget.type }}" name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}>

</div>

<div class="help">

{% trans "Available formats are JPG, GIF, and PNG." %}

{% trans "Minimal size is 800 x 800 px." %}

</div>

```







### 實(shí)現(xiàn)原理...




此時(shí)再訪問ideas的后臺(tái)界面,效果如下:




![](https://upload-images.jianshu.io/upload_images/14565748-80485ae051c32fad.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




這里有兩處變動(dòng):




* 分類選項(xiàng)此時(shí)使用帶有多個(gè)復(fù)選框的組件

* 圖片字段通過指定的模板進(jìn)行了渲染,使用所指定文件類型和尺寸顯示著圖片預(yù)覽和幫助文件。




我們?cè)谶@里所做的是,重寫idea模型表單、修改分類組件及圖片字段的模板。




Django中默認(rèn)的表單渲染器是django.forms.renderers.DjangoTemplates,它僅在應(yīng)用目錄中搜索模板。我們將其修改為django.forms.renderers.TemplatesSetting讓它同時(shí)還在DIRS路徑中進(jìn)行查找。




### 相關(guān)內(nèi)容




* [**第2章 模型和數(shù)據(jù)庫(kù)結(jié)構(gòu)**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第2章 模型和數(shù)據(jù)庫(kù)結(jié)構(gòu))中*操作模型翻譯數(shù)據(jù)表*一節(jié)

* *上傳圖片*一節(jié)

* *通過django-crispy-forms創(chuàng)建表單布局*一節(jié)




## 通過django-crispy-forms創(chuàng)建表單布局




Django應(yīng)用django-crispy-forms讓我們可以使用如下一種CSS框架構(gòu)建、自定義及復(fù)用表單:Uni-Form、Bootstrap 3、Bootstrap 4或Foundation。django-crispy-forms的使用與Django自帶的fieldsets有些類似;但它更為高級(jí)、定制化更強(qiáng)??梢栽赑ython代碼中定義表單布局而無需擔(dān)心每個(gè)字段在HTML中如何展示。此外,如果需要添加指定的HTML屬性或標(biāo)簽,也可以輕松實(shí)現(xiàn)。django-crispy-forms所使用的所有標(biāo)記位于templates內(nèi),可在需要時(shí)進(jìn)行重寫。




本小節(jié)中,我們將使用用于開發(fā)響應(yīng)式、mobile-first網(wǎng)頁(yè)項(xiàng)目的流行前臺(tái)框架Bootstrap 4,來為前臺(tái)表單創(chuàng)建漂亮的布局,用于添加或編輯ideas。




### 準(zhǔn)備工作




首先我們使用在本章中創(chuàng)建的ideas應(yīng)用。接著逐一執(zhí)行如下步驟:




1. 記得為網(wǎng)站創(chuàng)建一個(gè)base.html 模板。更多詳情參見[**第4章 模板和JavaScript**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第4章 模板和JavaScript)中安排base.html模板一節(jié)。

2. 根據(jù)https://getbootstrap.com/docs/4.3/getting-started/introduction/將Bootstrap 4前端框架中的CSS和JS文件集成到base.html模板中。

3. 通過pip在虛擬環(huán)境中安裝django-crispy-forms(并將其添加到requirements/_base.txt中):




```

(env)$ pip install django-crispy-forms

```







4. 在設(shè)置的INSTALLED_APPS中添加crispy_forms,然后設(shè)置bootstrap4為項(xiàng)目中所使用的模板包:




```

# myproject/settings/_base.py

INSTALLED_APPS = (

# ...

"crispy_forms",

"ideas",

)

# ...

CRISPY_TEMPLATE_PACK = "bootstrap4"

```







### 如何實(shí)現(xiàn)...




按照如下步驟操作:




1. 修改ideas的模型表單:




```

# myproject/apps/ideas/forms.py

from django import forms

from django.utils.translation import ugettext_lazy as _

from django.conf import settings

from django.db import models




from crispy_forms import bootstrap, helper, layout




from .models import Idea




class IdeaForm(forms.ModelForm):

class Meta:

model = Idea

exclude = ["author"]




def __init__(self, request, *args, **kwargs):

self.request = request

super().__init__(*args, **kwargs)




self.fields["categories"].widget = forms.CheckboxSelectMultiple()




title_field = layout.Field(

"title", css_class="input-block-level"

)

content_field = layout.Field(

"content", css_class="input-block-level", rows="3"

)

main_fieldset = layout.Fieldset(

_("Main data"), title_field, content_field

)




picture_field = layout.Field(

"picture", css_class="input-block-level"

)

format_html = layout.HTML(

"""{% include "ideas/includes/picture_guidelines.html" %}"""

)

picture_fieldset = layout.Fieldset(

_("Picture"),

picture_field,

format_html,

title=_("Image upload"),

css_id="picture_fieldset",

)




categories_field = layout.Field(

"categories", css_class="input-block-level"

)

categories_fieldset = layout.Fieldset(

_("Categories"), categories_field,

css_id="categories_fieldset"

)




submit_button = layout.Submit("save", _("Save"))

actions = bootstrap.FormActions(submit_button)

self.helper = helper.FormHelper()

self.helper.form_action = self.request.path

self.helper.form_method = "POST"

self.helper.layout = layout.Layout(

main_fieldset,

picture_fieldset,

categories_fieldset,

actions,

)




def save(self, commit=True):

instance = super().save(commit=False)

instance.author = self.request.user

if commit:

instance.save()

self.save_m2m()

return instance

```







2. 然后通過如下內(nèi)容創(chuàng)建picture_guidelines.html模板:




```

{# ideas/includes/picture_guidelines.html #}

{% load i18n %}

<p class="form-text text-muted">

{% trans "Available formats are JPG, GIF, and PNG." %}

{% trans "Minimal size is 800 × 800 px." %}

</p>

```







3. 最后更新ideas表單的模板:




```

{# ideas/idea_form.html #}

{% extends "base.html" %}

{% load i18n crispy_forms_tags static %}




{% block content %}

<a href="{% url "ideas:idea_list" %}">{% trans "List of ideas" %}</a>

<h1>

{% if idea %}

{% blocktrans trimmed with title=idea.translated_title %}

Change Idea "{{ title }}

{% endblocktrans %}

{% else %}

{% trans "Add Idea" %}

{% endif %}

</h1>

{% crispy form %}

{% endblock %}

```







### 實(shí)現(xiàn)原理...




在ideas的模型表單中,我們創(chuàng)建了一個(gè)表單幫助類,布局由主字段集、圖片字段集、分類字段集和提交按鈕所組成。每個(gè)字段集由不同字段組成。每個(gè)字段集、字段或按鈕可以帶有其它參數(shù),成為字段的屬性,如rows="3"或placeholder=_("Please enter a title")。對(duì)于HTML類和id屬性,有特定的參數(shù)css_class和css_id。




idea表單的頁(yè)面類似下面這樣:




![](https://upload-images.jianshu.io/upload_images/14565748-8848edd212a2c6c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




和前面小節(jié)類似,我們修改了目錄字段的組件并為圖片字段添加了額外的幫助文本。




### 擴(kuò)展知識(shí)...




前例對(duì)于基礎(chǔ)使用已經(jīng)足夠了。但如果需要在表單中設(shè)置指定標(biāo)記,則仍需重寫并修改django-crispy-forms應(yīng)用的模板,因?yàn)樵赑ython文件中沒有硬編碼的標(biāo)記,而是所有生成的標(biāo)記均通過模板進(jìn)行渲染。只需將django-crispy-forms中的模板拷貝到項(xiàng)目模板目錄中并按需修改。




### 相關(guān)內(nèi)容




* *使用CRUDL函數(shù)創(chuàng)建應(yīng)用*一節(jié)

* *通過自定義模型創(chuàng)建表單布局*一節(jié)

* 通過django-crispy-forms創(chuàng)建表單布局

* *過濾對(duì)象列表*一節(jié)

* *管理分頁(yè)列表*一節(jié)

* *編寫基于類的視圖*一節(jié)

* [**第4章 模板和JavaScript**](Django 3網(wǎng)頁(yè)開發(fā)指南第4版 第4章 模板和JavaScript)中*安排base.html模板*一節(jié)




## 處理formsets




除了常規(guī)表單或模型表單外,Django還有一個(gè)表單集的概念。這些是允許一次性創(chuàng)建或修改多個(gè)實(shí)例的同一類型表單的集合。Django表單集可通過JavaScript豐富功能,它讓我們可以對(duì)頁(yè)面動(dòng)態(tài)添加表單集。這也本小節(jié)要討論的。我們會(huì)擴(kuò)展ideas表單來允許對(duì)同一頁(yè)面添加不同語言的翻譯。




### 準(zhǔn)備工作




我們將繼續(xù)使用前一小節(jié)*通過django-crispy-forms創(chuàng)建表單布局*中的IdeaForm。




### 如何實(shí)現(xiàn)...




按照如下步驟:




1. 修改IdeaForm的表單布局:




```

# myproject/apps/ideas/forms.py

from django import forms

from django.utils.translation import ugettext_lazy as _

from django.conf import settings

from django.db import models




from crispy_forms import bootstrap, helper, layout




from .models import Idea, IdeaTranslations




class IdeaForm(forms.ModelForm):

class Meta:

model = Idea

exclude = ["author"]

def __init__(self, request, *args, **kwargs):

self.request = request

super().__init__(*args, **kwargs)




self.fields["categories"].widget = forms.CheckboxSelectMultiple()




title_field = layout.Field(

"title", css_class="input-block-level"

)

content_field = layout.Field(

"content", css_class="input-block-level", rows="3"

)

main_fieldset = layout.Fieldset(

_("Main data"), title_field, content_field

)

picture_field = layout.Field(

"picture", css_class="input-block-level"

)

format_html = layout.HTML(

"""{% include "ideas/includes/picture_guidelines.html" %}"""

)




picture_fieldset = layout.Fieldset(

_("Picture"),

picture_field,

format_html,

title=_("Image upload"),

css_id="picture_fieldset",

)




categories_field = layout.Field(

"categories", css_class="input-block-level"

)

categories_fieldset = layout.Fieldset(

_("Categories"), categories_field,

css_id="categories_fieldset" )




inline_translations = layout.HTML(

"""{% include "ideas/forms/translations.html" %}"""

)




submit_button = layout.Submit("save", _("Save"))

actions = bootstrap.FormActions(submit_button)




self.helper = helper.FormHelper()

self.helper.form_action = self.request.path

self.helper.form_method = "POST"

self.helper.layout = layout.Layout(

main_fieldset,

inline_translations,

picture_fieldset,

categories_fieldset,

actions,

)




def save(self, commit=True):

instance = super().save(commit=False)

instance.author = self.request.user

if commit:

instance.save()

self.save_m2m()

return instance

```







2. 然后,同一文件的最后添加IdeaTranslationsForm:




```

class IdeaTranslationsForm(forms.ModelForm):

language = forms.ChoiceField(

label=_("Language"),

choices=settings.LANGUAGES_EXCEPT_THE_DEFAULT,

required=True,

)




class Meta:

model = IdeaTranslations

exclude = ["idea"]




def __init__(self, request, *args, **kwargs):

self.request = request

super().__init__(*args, **kwargs)




id_field = layout.Field("id")

language_field = layout.Field(

"language", css_class="input-block-level"

)

title_field = layout.Field(

"title", css_class="input-block-level"

)

content_field = layout.Field(

"content", css_class="input-block-level", rows="3"

)

delete_field = layout.Field("DELETE")

main_fieldset = layout.Fieldset(

_("Main data"),

id_field,

language_field,

title_field,

content_field,

delete_field,

)




self.helper = helper.FormHelper()

self.helper.form_tag = False

self.helper.disable_csrf = True

self.helper.layout = layout.Layout(main_fieldset)

```







3. 修改視圖來添加或修改ideas,如下:




```

# myproject/apps/ideas/views.py

from django.contrib.auth.decorators import login_required

from django.shortcuts import render, redirect, get_object_or_404

from django.forms import modelformset_factory

from django.conf import settings




from .forms import IdeaForm, IdeaTranslationsForm

from .models import Idea, IdeaTranslations




@login_required

def add_or_change_idea(request, pk=None):

idea = None

if pk:

idea = get_object_or_404(Idea, pk=pk)

IdeaTranslationsFormSet = modelformset_factory(

IdeaTranslations, form=IdeaTranslationsForm,

extra=0, can_delete=True

)

if request.method == "POST":

form = IdeaForm(request, data=request.POST,

files=request.FILES, instance=idea)

translations_formset = IdeaTranslationsFormSet(

queryset=IdeaTranslations.objects.filter(idea=idea),

data=request.POST,

files=request.FILES,

prefix="translations",

form_kwargs={"request": request},

)

if form.is_valid() and translations_formset.is_valid():

idea = form.save()

translations = translations_formset.save(

commit=False

)

for translation in translations:

translation.idea = idea

translation.save()

translations_formset.save_m2m()

for translation in translations_formset.deleted_objects:

translation.delete()

return redirect("ideas:idea_detail", pk=idea.pk)

else:

form = IdeaForm(request, instance=idea)

translations_formset = IdeaTranslationsFormSet(

queryset=IdeaTranslations.objects.filter(idea=idea),

prefix="translations",

form_kwargs={"request": request},

)

context = {

"idea": idea,

"form": form,

"translations_formset": translations_formset

}

return render(request, "ideas/idea_form.html", context)

```







4. 然后,編輯idea_form.html模板并在最后添加對(duì)inlines.js腳本的引用:




```

{# ideas/idea_form.html #}

{% extends "base.html" %}

{% load i18n crispy_forms_tags static %}




{% block content %}

<a href="{% url "ideas:idea_list" %}">{% trans "List of ideas" %}</a>

<h1>

{% if idea %}

{% blocktrans trimmed with title=idea.translated_title %}

Change Idea "{{ title }}"

{% endblocktrans %}

{% else %}

{% trans "Add Idea" %}

{% endif %}

</h1>

{% crispy form %}

{% endblock %}




{% block js %}

<script src="{% static 'site/js/inlines.js' %}"></script>

{% endblock %}

```







5. 為翻譯表單集創(chuàng)建模板:




```

{# ideas/forms/translations.html #}

{% load i18n crispy_forms_tags %}

<section id="translations_section" class="formset my-3">

{{ translations_formset.management_form }}

<h3>{% trans "Translations" %}</h3>

<div class="formset-forms">

{% for formset_form in translations_formset %}

<div class="formset-form">

{% crispy formset_form %}

</div>

{% endfor %}

</div>

<button type="button" class="btn btn-primary btn-sm add-inline-form">

{% trans "Add translations to another language" %}

</button>

<div class="empty-form d-none">

{% crispy translations_formset.empty_form %}

</div>

</section>

```







6. 最后,添加JavaScript來操作表單集:




```

/* site/js/inlines.js */

window.WIDGET_INIT_REGISTER = window.WIDGET_INIT_REGISTER || [];




$(function () {

function reinit_widgets($formset_form) {

$(window.WIDGET_INIT_REGISTER).each(function (index, func)

{

func($formset_form);

});

}




function set_index_for_fields($formset_form, index) {

$formset_form.find(':input').each(function () {

var $field = $(this);

if ($field.attr("id")) {

$field.attr(

"id",

$field.attr("id").replace(/-__prefix__-/, "-" + index + "-")

);

}

if ($field.attr("name")) {

$field.attr(

"name",

$field.attr("name").replace(

/-__prefix__-/, "-" + index + "-"

)

);

}

});

$formset_form.find('label').each(function () {

var $field = $(this);

if ($field.attr("for")) {

$field.attr(

"for",

$field.attr("for").replace(

/-__prefix__-/, "-" + index + "-"

)

);

}

});

$formset_form.find('div').each(function () {

var $field = $(this);

if ($field.attr("id")) {

$field.attr(

"id",

$field.attr("id").replace(

/-__prefix__-/, "-" + index + "-"

)

);

}

});

}




function add_delete_button($formset_form) {

$formset_form.find('input:checkbox[id$=DELETE]')

.each(function () {

var $checkbox = $(this);

var $deleteLink = $(

'<button class="delete btn btn-sm btn-danger mb-3">Remove</button>'

);

$formset_form.append($deleteLink);

$checkbox.closest('.form-group').hide();

});

}




$('.add-inline-form').click(function (e) {

e.preventDefault();

var $formset = $(this).closest('.formset');

var $total_forms = $formset.find('[id$="TOTAL_FORMS"]');

var $new_form = $formset.find('.empty-form').clone(true).attr("id", null);

$new_form.removeClass('empty-form d-none').addClass('formset-form');

set_index_for_fields($new_form, parseInt($total_forms.val(), 10));

$formset.find('.formset-forms').append($new_form);

add_delete_button($new_form);

$total_forms.val(parseInt($total_forms.val(), 10) + 1);

reinit_widgets($new_form);

});

$('.formset-form').each(function () {

$formset_form = $(this);

add_delete_button($formset_form);

reinit_widgets($formset_form);

});

$(document).on('click', '.delete', function (e) {

e.preventDefault();

var $formset = $(this).closest('.formset-form');

var $checkbox = $formset.find('input:checkbox[id$=DELETE]');

$checkbox.attr("checked", "checked");

$formset.hide();

});

});

```







### 實(shí)現(xiàn)原理...




讀者可能通過Django模型管理后臺(tái)已經(jīng)了解到表單集了。表單集在那里用于對(duì)于父模型擁有外鍵的子模型的行內(nèi)機(jī)制。




本節(jié)中,我們使用django-crispy-forms對(duì)idea表單添加了表單集。結(jié)果如下:




![](https://upload-images.jianshu.io/upload_images/14565748-1cecf5c626343262.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




可以看出,我們不一定要在表單的結(jié)尾處插入表單集,而是在中間任意有作用之處皆可。本例中,把翻譯放到可翻譯字段之后有實(shí)際意義。




翻譯表單的表單布局像IdeaForm的布局一樣有fieldset,但除此之外,還有識(shí)別每個(gè)模型實(shí)例所需的id及進(jìn)行刪除時(shí)使用的DELETE字段。DELETE字段實(shí)際上是一個(gè)復(fù)選框,在選中時(shí)從數(shù)據(jù)庫(kù)中刪除相應(yīng)內(nèi)容。同時(shí),翻譯的表單helper中有form_tag=False,不生成<form>標(biāo)簽,disable_csrf=True會(huì)不包含CSRF令牌,因?yàn)槲覀円呀?jīng)在其父表單IdeaForm中進(jìn)行過定義。




在表單中,如果請(qǐng)求由POST方法發(fā)送,且表單和表單集有效,那么會(huì)保存表單并創(chuàng)建相應(yīng)的翻譯實(shí)例,不事先進(jìn)行保存。這通過commit=False屬性實(shí)現(xiàn)。我們?yōu)槊總€(gè)翻譯實(shí)例分配idea,然后將翻譯內(nèi)容保存到數(shù)據(jù)庫(kù)中。最后,查看表單集中的表單是有否標(biāo)記為刪除的并從數(shù)據(jù)庫(kù)中進(jìn)行相應(yīng)的刪除。




在translations.html模板中,我們?cè)诒韱渭袖秩久總€(gè)表單,然后添加一個(gè)額外的隱藏空表單,它由JavaScript用來在表單集中生成動(dòng)態(tài)添加的新表單。




表單集中的每個(gè)表單對(duì)每個(gè)字段有前綴。如,表單集的第一個(gè)表單的title字段會(huì)有一個(gè)HTML字段名translations-0-title,同一表單集中的DELETE字段擁有HTML字段名translations-0- DELETE。空表單使用的是__prefix__來代替索引號(hào),如translations-__prefix__-title。這在Django層進(jìn)行的抽象,但在通過JavaScript操作表單集中表單時(shí)需要知曉。




inlines.js JavaScript腳本執(zhí)行了如下操作:




* 對(duì)表單集中的已有表單,它初始化JavaScript驅(qū)動(dòng)的組件(可以使用提示消息、日期或顏色拾取器、地圖等)并創(chuàng)建一個(gè)刪除按鈕,用于代替DELETE復(fù)選框進(jìn)行顯示。

* 在點(diǎn)擊刪除按鈕時(shí),它會(huì)勾選DELETE復(fù)選框并對(duì)用戶隱藏表單。

* 在點(diǎn)擊添加按鈕時(shí),它會(huì)復(fù)制一個(gè)空表單并將__prefix__替換為下一個(gè)可用索引、向列表添加新表單并初始化JavaScript驅(qū)動(dòng)的組件。




### 擴(kuò)展知識(shí)...




JavaScript使用數(shù)組window.WIDGET_INIT_REGISTER,它包含應(yīng)由給定表單初始化組件時(shí)調(diào)用的函數(shù)。要在另一個(gè)JavaScript文件中注冊(cè)新函數(shù)時(shí),可以使用如下代碼:

```

/* site/js/main.js */

function apply_tooltips($formset_form) {

$formset_form.find('[data-toggle="tooltip"]').tooltip();

}




/* register widget initialization for a formset form */

window.WIDGET_INIT_REGISTER = window.WIDGET_INIT_REGISTER || [];

window.WIDGET_INIT_REGISTER.push(apply_tooltips);

```







這會(huì)對(duì)所有包含data-toggle="tooltip"和title屬性的表單應(yīng)用提示消息功能,如下例所示:




```

{% trans "Remove" %}</button>

```







### 相關(guān)內(nèi)容




* *通過django-crispy-forms創(chuàng)建表單布局*一節(jié)

* [**第4章 模板和JavaScript**](https://alanhou.org/django3-templates-javascript/)中的*編排base.html模板*一節(jié)




本文首發(fā)地址: [Alan Hou 的人個(gè)博客](https://alanhou.org/forms-views/)

關(guān)鍵詞:視圖,發(fā)指

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉