<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          五個常見的Django錯誤

          共 7678字,需瀏覽 16分鐘

           ·

          2021-05-02 10:10

          Django是用于構(gòu)建Web應(yīng)用程序的非常好的框架,但在我們還不太熟悉的情況下開發(fā)中可能由于某些的疏忽會而帶來一些細微的錯誤,本篇目的是供我總結(jié)的一些內(nèi)容,供參考,總結(jié)下來也方便自己后續(xù)避免犯錯,在本文中,我們將開發(fā)一個示例Django應(yīng)用程序,該應(yīng)用程序可以處理各種組織的員工管理。

          示例代碼:

          from django.contrib.auth import get_user_modelfrom django.core.exceptions import ValidationErrorfrom django.db import models
          User = 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):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


          不使用select_related和prefetch_related
          假設(shè)我們編寫了一些遍歷每個組織員工的代碼。

          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ù)庫中的每個員工。這可能會導(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表示該字段具有兩個“無數(shù)據(jù)”值,即null和空字符串。按照慣例,Django使用空字符串表示不包含任何數(shù)據(jù)。通過將null作為“無數(shù)據(jù)”值,我們可以引入一些細微的錯誤。假設(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)實際上并不會發(fā)生這種情況。由于reference_id可以為null或空字符串,因此我們在此處創(chuàng)建了一個錯誤,如果reference_id為空字符串,系統(tǒng)將嘗試獲取員工記錄。顯然,這是行不通的,并且會導(dǎo)致我們的系統(tǒng)出現(xiàn)錯誤。根據(jù)Django的文檔,將null = True添加到CharField存在一個例外。當需要同時將blank = True和unique = True添加到CharField時,則需要null = True。

          使用order_by或last降序或升序
          Django的order_by默認為升序。通過在關(guān)鍵字前面添加-,可以指示Django提供降序排列。讓我們看一個例子。

          oldest_organization_first = Organization.objects.order_by("datetime_created")
          newest_organization_first = Organization.objects.order_by("-datetime_created")

          在datetime_created前面加上減號后,Django首先為我們提供了最新的組織。相反,沒有減號,我們首先獲得最早的組織。錯誤地使用默認的升序會導(dǎo)致非常細微的錯誤。Django查詢集還帶有最新的,它根據(jù)傳遞的關(guān)鍵字字段為我們提供了表中的最新對象。最新的方法默認為降序,而order_by默認為升序。

          oldest_organization_first = Organization.objects.latest("-datetime_created")
          newest_organization_first = Organization.objects.latest("datetime_created")

          在多個項目中,由于last和order_by之間的默認值不同,導(dǎo)致引入了一些錯誤。請謹慎編寫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_orgTrue
          >>> newest_org = Organization.objects.order_by("-datetime_created")[:1][0]>>> newest_other_org = Organization.objects.latest("datetime_created")>>> newest_org == newest_other_orgTrue


          忘記保存時調(diào)用clean方法
          根據(jù)Django的文檔,模型的save方法不會自動調(diào)用模型驗證方法,例如clean,validate_unique和clean_fields。在我們的示例代碼中,員工模型包含一個clean的方法,該方法指出last_clock_in不應(yīng)在員工進入系統(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è)我們有一個視圖可以更新員工的last_clock_in時間,作為該視圖的一部分,我們可以通過調(diào)用save來更新員工。

          from django.http import HttpResponsefrom django.shortcuts import get_object_or_404from django.views.decorators.http import require_http_methods
          from 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ù)進入我們的數(shù)據(jù)庫。讓我們修復(fù)我們的錯誤。

          employee.last_clock_in = clock_in_datetimeemployee.full_clean()employee.save()

          現(xiàn)在,如果clock_in_datetime在員工的datetime_created之前,full_clean將引發(fā)ValidationError,以防止無效數(shù)據(jù)進入我們的數(shù)據(jù)庫。

          保存時不包括update_fields
          Django Model的save方法包括一個名為update_fields的關(guān)鍵字參數(shù)。在針對Django的典型生產(chǎn)環(huán)境中,人們使用gunicorn在同一臺計算機上運行多個Django服務(wù)器進程,并使用celery運行后臺進程。當調(diào)用不帶update_fields的保存時,整個模型將使用內(nèi)存中的值進行更新。讓我們看一下實際的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" = '[email protected]',"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)致保存用戶模型上的每個字段。使用update_fields時,僅first_name更新。在頻繁寫入的生產(chǎn)環(huán)境中,在沒有update_fields的情況下調(diào)用save可能導(dǎo)致爭用情況。假設(shè)我們有兩個進程正在運行,一個運行我們的Django服務(wù)器的gunicorn工人和一個celery worker。按照設(shè)定的時間表,celery worker將查詢外部API,并可能更新用戶的is_active。

          from celery import taskfrom django.contrib.auth import get_user_model
          from example_project.external_api import get_user_status
          User = get_user_model()

          @taskdef 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 = False user.save()

          celery worker啟動任務(wù),將整個用戶對象加載到內(nèi)存中,并查詢外部API,但是外部API花費的時間比預(yù)期的長。當celery worker等待外部API時,同一用戶連接到我們的gunicorn worker,并向他們的電子郵件提交更新,將其更新從[email protected]更改為[email protected]。電子郵件更新提交到數(shù)據(jù)庫后,外部API響應(yīng),并且celery worker將用戶的is_active更新為False。

          在這種情況下,celery worker會覆蓋電子郵件更新,因為該工作者會在提交電子郵件更新之前將整個用戶對象加載到內(nèi)存中。當celery worker將用戶加載到內(nèi)存中時,該用戶的電子郵件為[email protected]。該電子郵件將保留在內(nèi)存中,直到外部API響應(yīng)并覆蓋電子郵件更新為止。最后,代表數(shù)據(jù)庫內(nèi)部用戶的行包含舊電子郵件地址[email protected]和is_active = False。讓我們更改代碼以防止出現(xiàn)這種情況。

          if user_status == "inactive": user.is_active = False user.save(update_fields=["is_active"])

          如果以前的情況是使用更新的代碼發(fā)生的,那么在celery worker更新is_active之后,用戶的電子郵件仍為[email protected],因為該更新僅寫入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項目時,請記住這一點。

          寫在最后
          我已經(jīng)多次犯了所有這些錯誤。我希望這篇文章能揭示日常代碼中潛在的錯誤,并防止這些錯誤發(fā)生。我喜歡使用Django,而且我認為這是構(gòu)建Web應(yīng)用程序的非常好的框架。但是,在任何大型框架下,復(fù)雜性都會變得模糊不清,都可能會犯錯誤,該趟的坑一個也不會少。

          文章轉(zhuǎn)載:Python運維技術(shù)

          (版權(quán)歸原作者所有,侵刪)


          點擊下方“閱讀原文”查看更多

          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  开心五月天.色网 | 欧美淫色视频免费观看 | 蜜臀久久99精品久久宅男 | 麻豆午夜成人无码电影 | 肏屄在线观看 |