您好,登錄后才能下訂單哦!
本篇內容介紹了“Django有哪些常見的錯誤”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Django是用于構建Web應用程序的非常好的框架,但在我們還不太熟悉的情況下開發中可能由于某些的疏忽會而帶來一些細微的錯誤,本篇目的是供我總結的一些內容,供參考,總結下來也方便自己后續避免犯錯,在本文中,我們將開發一個示例Django應用程序,該應用程序可以處理各種組織的員工管理。
示例代碼:
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
假設我們編寫了一些遍歷每個組織員工的代碼。
for org in Organization.objects.filter(is_active=True):for emp in org.employees.all():if emp.is_currently_employed:do_something(org, emp)
此循環導致查詢數據庫中的每個員工。這可能會導致成千上萬的查詢,這會減慢我們的應用程序的速度。但是,如果我們添加與組織查詢相關的prefetch_related,我們將使查詢量最小化。
for org in Organization.objects.filter(is_active=True).prefetch_related( "employees"):
添加這些方法無需大量工作即可大大提高性能,但是添加它們很容易忘記。對于ForeignKey或OneToOneField,請使用select_related。對于反向的ForeignKey或ManyToManyField,請使用prefetch_related。我們可以通過從employee表開始并使用數據庫過濾結果來提高效率。由于函數do_something使用員工的組織,因此我們仍然需要添加select_related。如果我們不這樣做,則循環可能導致對組織表的成千上萬次查詢。
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。在示例應用程序中,我們可以選擇與客戶的員工跟蹤系統集成,并使用reference_id作為集成系統的ID。
reference_id = models.CharField(null=True, blank=True, max_length=255)
添加null = True表示該字段具有兩個“無數據”值,即null和空字符串。按照慣例,Django使用空字符串表示不包含任何數據。通過將null作為“無數據”值,我們可以引入一些細微的錯誤。假設我們需要編寫一些代碼來從客戶系統中獲取數據。
if employee.reference_id is not None: fetch_employee_record(employee)
理想情況下,可以使用if employee.reference_id:編寫if語句來處理任何“無數據”值,但是我發現實際上并不會發生這種情況。由于reference_id可以為null或空字符串,因此我們在此處創建了一個錯誤,如果reference_id為空字符串,系統將嘗試獲取員工記錄。顯然,這是行不通的,并且會導致我們的系統出現錯誤。根據Django的文檔,將null = True添加到CharField存在一個例外。當需要同時將blank = True和unique = True添加到CharField時,則需要null = True。
使用order_by或last降序或升序
Django的order_by默認為升序。通過在關鍵字前面添加-,可以指示Django提供降序排列。讓我們看一個例子。
oldest_organization_first = Organization.objects.order_by("datetime_created")newest_organization_first = Organization.objects.order_by("-datetime_created")
在datetime_created前面加上減號后,Django首先為我們提供了最新的組織。相反,沒有減號,我們首先獲得最早的組織。錯誤地使用默認的升序會導致非常細微的錯誤。Django查詢集還帶有最新的,它根據傳遞的關鍵字字段為我們提供了表中的最新對象。最新的方法默認為降序,而order_by默認為升序。
oldest_organization_first = Organization.objects.latest("-datetime_created")newest_organization_first = Organization.objects.latest("datetime_created")
在多個項目中,由于last和order_by之間的默認值不同,導致引入了一些錯誤。請謹慎編寫order_by和last查詢。讓我們看看使用last和order_by進行的等效查詢。
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
忘記保存時調用clean方法
根據Django的文檔,模型的save方法不會自動調用模型驗證方法,例如clean,validate_unique和clean_fields。在我們的示例代碼中,員工模型包含一個clean的方法,該方法指出last_clock_in不應在員工進入系統之前發生。
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
假設我們有一個視圖可以更新員工的last_clock_in時間,作為該視圖的一部分,我們可以通過調用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)
在我們的示例視圖中,我們調用save而不調用clean或full_clean,這意味著傳遞到我們視圖中的clock_in_datetime可能發生在員工創建datetime__date之前,并且仍保存到數據庫中。這導致無效數據進入我們的數據庫。讓我們修復我們的錯誤。
employee.last_clock_in = clock_in_datetime employee.full_clean() employee.save()
現在,如果clock_in_datetime在員工的datetime_created之前,full_clean將引發ValidationError,以防止無效數據進入我們的數據庫。
保存時不包括update_fields
Django Model的save方法包括一個名為update_fields的關鍵字參數。在針對Django的典型生產環境中,人們使用gunicorn在同一臺計算機上運行多個Django服務器進程,并使用celery運行后臺進程。當調用不帶update_fields的保存時,整個模型將使用內存中的值進行更新。讓我們看一下實際的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
一次調用不帶update_fields的保存將導致保存用戶模型上的每個字段。使用update_fields時,僅first_name更新。在頻繁寫入的生產環境中,在沒有update_fields的情況下調用save可能導致爭用情況。假設我們有兩個進程正在運行,一個運行我們的Django服務器的gunicorn工人和一個celery worker。按照設定的時間表,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啟動任務,將整個用戶對象加載到內存中,并查詢外部API,但是外部API花費的時間比預期的長。當celery worker等待外部API時,同一用戶連接到我們的gunicorn worker,并向他們的電子郵件提交更新,將其更新從steven@laac.dev更改為steven@stevenapate.com。電子郵件更新提交到數據庫后,外部API響應,并且celery worker將用戶的is_active更新為False。
在這種情況下,celery worker會覆蓋電子郵件更新,因為該工作者會在提交電子郵件更新之前將整個用戶對象加載到內存中。當celery worker將用戶加載到內存中時,該用戶的電子郵件為steven@laac.dev。該電子郵件將保留在內存中,直到外部API響應并覆蓋電子郵件更新為止。最后,代表數據庫內部用戶的行包含舊電子郵件地址steven@laac.dev和is_active = False。讓我們更改代碼以防止出現這種情況。
if user_status == "inactive":user.is_active = Falseuser.save(update_fields=["is_active"])
如果以前的情況是使用更新的代碼發生的,那么在celery worker更新is_active之后,用戶的電子郵件仍為steven@stevenapate.com,因為該更新僅寫入is_active字段。僅在極少數情況下(例如創建新對象),才應調用不帶update_fields的保存。雖然可以通過不調用簡單的save方法在代碼庫中解決此問題,但第三方Django程序包可能包含此問題。例如,Django REST Framework不在PATCH請求上使用update_fields。Django REST Framework是我喜歡使用的出色軟件包,但無法解決此問題。將第三方軟件包添加到Django項目時,請記住這一點。
寫在最后
我已經多次犯了所有這些錯誤。我希望這篇文章能揭示日常代碼中潛在的錯誤,并防止這些錯誤發生。我喜歡使用Django,而且我認為這是構建Web應用程序的非常好的框架。但是,在任何大型框架下,復雜性都會變得模糊不清,都可能會犯錯誤,該趟的坑一個也不會少。
“Django有哪些常見的錯誤”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。