您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Django有哪些常見的錯(cuò)誤”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
Django是用于構(gòu)建Web應(yīng)用程序的非常好的框架,但在我們還不太熟悉的情況下開發(fā)中可能由于某些的疏忽會而帶來一些細(xì)微的錯(cuò)誤,本篇目的是供我總結(jié)的一些內(nèi)容,供參考,總結(jié)下來也方便自己后續(xù)避免犯錯(cuò),在本文中,我們將開發(fā)一個(gè)示例Django應(yīng)用程序,該應(yīng)用程序可以處理各種組織的員工管理。
示例代碼:
from django.contrib.auth import get_user_modelfrom django.core.exceptions import ValidationErrorfrom django.db import modelsUser = get_user_model() class Organization(models.Model): name = models.CharField(max_length=100) datetime_created = models.DateTimeField(auto_now_add=True, editable=False) is_active = models.BooleanField(default=True) class Employee(models.Model): user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="employees") organization = models.ForeignKey( Organization, on_delete=models.CASCADE, related_name="employees") is_currently_employed = models.BooleanField(default=True) reference_id = models.CharField(null=True, blank=True, max_length=255) last_clock_in = models.DateTimeField(null=True, blank=True) datetime_created = models.DateTimeField(auto_now_add=True, editable=False) def clean(self):
不使用select_related和prefetch_related
假設(shè)我們編寫了一些遍歷每個(gè)組織員工的代碼。
for org in Organization.objects.filter(is_active=True):for emp in org.employees.all():if emp.is_currently_employed:do_something(org, emp)
此循環(huán)導(dǎo)致查詢數(shù)據(jù)庫中的每個(gè)員工。這可能會導(dǎo)致成千上萬的查詢,這會減慢我們的應(yīng)用程序的速度。但是,如果我們添加與組織查詢相關(guān)的prefetch_related,我們將使查詢量最小化。
for org in Organization.objects.filter(is_active=True).prefetch_related( "employees"):
添加這些方法無需大量工作即可大大提高性能,但是添加它們很容易忘記。對于ForeignKey或OneToOneField,請使用select_related。對于反向的ForeignKey或ManyToManyField,請使用prefetch_related。我們可以通過從employee表開始并使用數(shù)據(jù)庫過濾結(jié)果來提高效率。由于函數(shù)do_something使用員工的組織,因此我們?nèi)匀恍枰砑觭elect_related。如果我們不這樣做,則循環(huán)可能導(dǎo)致對組織表的成千上萬次查詢。
for emp in Employee.objects.filter( organization__is_active=True, is_currently_employed=True ).select_related("organization"):do_something(emp.organization, emp)
向CharField或TextField添加null
Django的文檔建議不要向CharField添加null = True。查看我們的示例代碼,該員工的參考ID包含null = True。在示例應(yīng)用程序中,我們可以選擇與客戶的員工跟蹤系統(tǒng)集成,并使用reference_id作為集成系統(tǒng)的ID。
reference_id = models.CharField(null=True, blank=True, max_length=255)
添加null = True表示該字段具有兩個(gè)“無數(shù)據(jù)”值,即null和空字符串。按照慣例,Django使用空字符串表示不包含任何數(shù)據(jù)。通過將null作為“無數(shù)據(jù)”值,我們可以引入一些細(xì)微的錯(cuò)誤。假設(shè)我們需要編寫一些代碼來從客戶系統(tǒng)中獲取數(shù)據(jù)。
if employee.reference_id is not None: fetch_employee_record(employee)
理想情況下,可以使用if employee.reference_id:編寫if語句來處理任何“無數(shù)據(jù)”值,但是我發(fā)現(xiàn)實(shí)際上并不會發(fā)生這種情況。由于reference_id可以為null或空字符串,因此我們在此處創(chuàng)建了一個(gè)錯(cuò)誤,如果reference_id為空字符串,系統(tǒng)將嘗試獲取員工記錄。顯然,這是行不通的,并且會導(dǎo)致我們的系統(tǒng)出現(xiàn)錯(cuò)誤。根據(jù)Django的文檔,將null = True添加到CharField存在一個(gè)例外。當(dāng)需要同時(shí)將blank = True和unique = True添加到CharField時(shí),則需要null = True。
使用order_by或last降序或升序
Django的order_by默認(rèn)為升序。通過在關(guān)鍵字前面添加-,可以指示Django提供降序排列。讓我們看一個(gè)例子。
oldest_organization_first = Organization.objects.order_by("datetime_created")newest_organization_first = Organization.objects.order_by("-datetime_created")
在datetime_created前面加上減號后,Django首先為我們提供了最新的組織。相反,沒有減號,我們首先獲得最早的組織。錯(cuò)誤地使用默認(rèn)的升序會導(dǎo)致非常細(xì)微的錯(cuò)誤。Django查詢集還帶有最新的,它根據(jù)傳遞的關(guān)鍵字字段為我們提供了表中的最新對象。最新的方法默認(rèn)為降序,而order_by默認(rèn)為升序。
oldest_organization_first = Organization.objects.latest("-datetime_created")newest_organization_first = Organization.objects.latest("datetime_created")
在多個(gè)項(xiàng)目中,由于last和order_by之間的默認(rèn)值不同,導(dǎo)致引入了一些錯(cuò)誤。請謹(jǐn)慎編寫order_by和last查詢。讓我們看看使用last和order_by進(jìn)行的等效查詢。
oldest_org = Organization.objects.order_by("datetime_created")[:1][0]oldest_other_org = Organization.objects.latest("-datetime_created") oldest_org == oldest_other_org True newest_org = Organization.objects.order_by("-datetime_created")[:1][0]newest_other_org = Organization.objects.latest("datetime_created") newest_org == newest_other_org True
忘記保存時(shí)調(diào)用clean方法
根據(jù)Django的文檔,模型的save方法不會自動調(diào)用模型驗(yàn)證方法,例如clean,validate_unique和clean_fields。在我們的示例代碼中,員工模型包含一個(gè)clean的方法,該方法指出last_clock_in不應(yīng)在員工進(jìn)入系統(tǒng)之前發(fā)生。
def clean(self):try:if self.last_clock_in < self.datetime_created:raise ValidationError("Last clock in must occur after the employee entered"" the system.")except TypeError:# Raises TypeError if there is no last_clock_in because# you cant compare None to datetimepass
假設(shè)我們有一個(gè)視圖可以更新員工的last_clock_in時(shí)間,作為該視圖的一部分,我們可以通過調(diào)用save來更新員工。
from django.http import HttpResponsefrom django.shortcuts import get_object_or_404from django.views.decorators.http import require_http_methodsfrom example_project.helpers import parse_requestfrom example_project.models import Employee@require_http_methods(["POST"])def update_employee_last_clock_in(request, employee_pk):clock_in_datetime = parse_request(request) employee = get_object_or_404(Employee, pk=employee_pk) employee.last_clock_in = clock_in_datetime employee.save()return HttpResponse(status=200)
在我們的示例視圖中,我們調(diào)用save而不調(diào)用clean或full_clean,這意味著傳遞到我們視圖中的clock_in_datetime可能發(fā)生在員工創(chuàng)建datetime__date之前,并且仍保存到數(shù)據(jù)庫中。這導(dǎo)致無效數(shù)據(jù)進(jìn)入我們的數(shù)據(jù)庫。讓我們修復(fù)我們的錯(cuò)誤。
employee.last_clock_in = clock_in_datetime employee.full_clean() employee.save()
現(xiàn)在,如果clock_in_datetime在員工的datetime_created之前,full_clean將引發(fā)ValidationError,以防止無效數(shù)據(jù)進(jìn)入我們的數(shù)據(jù)庫。
保存時(shí)不包括update_fields
Django Model的save方法包括一個(gè)名為update_fields的關(guān)鍵字參數(shù)。在針對Django的典型生產(chǎn)環(huán)境中,人們使用gunicorn在同一臺計(jì)算機(jī)上運(yùn)行多個(gè)Django服務(wù)器進(jìn)程,并使用celery運(yùn)行后臺進(jìn)程。當(dāng)調(diào)用不帶update_fields的保存時(shí),整個(gè)模型將使用內(nèi)存中的值進(jìn)行更新。讓我們看一下實(shí)際的SQL來說明。
>>> user = User.objects.get(id=1) >>> user.first_name = "Steven" >>> user.save()UPDATE "users_user" SET "password" = 'some_hash', "last_login" = '2021-02-25T22:43:41.033881+00:00'::timestamptz, "is_superuser" = false, "username" = 'stevenapate', "first_name" = 'Steven', "last_name" = '', "email" = 'steven@laac.dev', "is_staff" = false, "is_active" = true, "date_joined" = '2021-02-19T21:08:50.885795+00:00'::timestamptz, WHERE "users_user"."id" = 1>>> user.first_name = "NotSteven" >>> user.save(update_fields=["first_name"])UPDATE "users_user" SET "first_name" = 'NotSteven' WHERE "users_user"."id" = 1
一次調(diào)用不帶update_fields的保存將導(dǎo)致保存用戶模型上的每個(gè)字段。使用update_fields時(shí),僅first_name更新。在頻繁寫入的生產(chǎn)環(huán)境中,在沒有update_fields的情況下調(diào)用save可能導(dǎo)致爭用情況。假設(shè)我們有兩個(gè)進(jìn)程正在運(yùn)行,一個(gè)運(yùn)行我們的Django服務(wù)器的gunicorn工人和一個(gè)celery worker。按照設(shè)定的時(shí)間表,celery worker將查詢外部API,并可能更新用戶的is_active。
from celery import taskfrom django.contrib.auth import get_user_modelfrom example_project.external_api import get_user_statusUser = get_user_model() @task def update_user_status(user_pk):user = User.objects.get(pk=user_pk) user_status = get_user_status(user)if user_status == "inactive":user.is_active = Falseuser.save()
celery worker啟動任務(wù),將整個(gè)用戶對象加載到內(nèi)存中,并查詢外部API,但是外部API花費(fèi)的時(shí)間比預(yù)期的長。當(dāng)celery worker等待外部API時(shí),同一用戶連接到我們的gunicorn worker,并向他們的電子郵件提交更新,將其更新從steven@laac.dev更改為steven@stevenapate.com。電子郵件更新提交到數(shù)據(jù)庫后,外部API響應(yīng),并且celery worker將用戶的is_active更新為False。
在這種情況下,celery worker會覆蓋電子郵件更新,因?yàn)樵摴ぷ髡邥谔峤浑娮余]件更新之前將整個(gè)用戶對象加載到內(nèi)存中。當(dāng)celery worker將用戶加載到內(nèi)存中時(shí),該用戶的電子郵件為steven@laac.dev。該電子郵件將保留在內(nèi)存中,直到外部API響應(yīng)并覆蓋電子郵件更新為止。最后,代表數(shù)據(jù)庫內(nèi)部用戶的行包含舊電子郵件地址steven@laac.dev和is_active = False。讓我們更改代碼以防止出現(xiàn)這種情況。
if user_status == "inactive":user.is_active = Falseuser.save(update_fields=["is_active"])
如果以前的情況是使用更新的代碼發(fā)生的,那么在celery worker更新is_active之后,用戶的電子郵件仍為steven@stevenapate.com,因?yàn)樵摳聝H寫入is_active字段。僅在極少數(shù)情況下(例如創(chuàng)建新對象),才應(yīng)調(diào)用不帶update_fields的保存。雖然可以通過不調(diào)用簡單的save方法在代碼庫中解決此問題,但第三方Django程序包可能包含此問題。例如,Django REST Framework不在PATCH請求上使用update_fields。Django REST Framework是我喜歡使用的出色軟件包,但無法解決此問題。將第三方軟件包添加到Django項(xiàng)目時(shí),請記住這一點(diǎn)。
寫在最后
我已經(jīng)多次犯了所有這些錯(cuò)誤。我希望這篇文章能揭示日常代碼中潛在的錯(cuò)誤,并防止這些錯(cuò)誤發(fā)生。我喜歡使用Django,而且我認(rèn)為這是構(gòu)建Web應(yīng)用程序的非常好的框架。但是,在任何大型框架下,復(fù)雜性都會變得模糊不清,都可能會犯錯(cuò)誤,該趟的坑一個(gè)也不會少。
“Django有哪些常見的錯(cuò)誤”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。