ค่าเริ่มต้นของฟิลด์โมเดล Django อิงจากฟิลด์อื่นในโมเดลเดียวกัน


92

ฉันมีแบบจำลองที่ฉันต้องการมีชื่อวิชาและชื่อย่อของพวกเขา (ข้อมูลเขาค่อนข้างไม่ระบุตัวตนและติดตามด้วยชื่อย่อ)

ตอนนี้ฉันเขียน

class Subject(models.Model):

    name = models.CharField("Name", max_length=30)
    def subject_initials(self):
        return ''.join(map(lambda x: '' if len(x)==0 else x[0],
                           self.name.split(' ')))
    # Next line is what I want to do (or something equivalent), but doesn't work with
    # NameError: name 'self' is not defined
    subject_init = models.CharField("Subject Initials", max_length=5, default=self.subject_initials)

ตามที่ระบุไว้ในบรรทัดสุดท้ายฉันต้องการให้ชื่อย่อถูกเก็บไว้ในฐานข้อมูลเป็นฟิลด์ (ไม่ขึ้นกับชื่อ) แต่เริ่มต้นด้วยค่าเริ่มต้นตามฟิลด์ชื่อ อย่างไรก็ตามฉันมีปัญหาเนื่องจากนางแบบ django ดูเหมือนจะไม่มี 'ตัวเอง'

ถ้าฉันเปลี่ยนสายเป็นsubject_init = models.CharField("Subject initials", max_length=2, default=subject_initials)ฉันจะทำ syncdb ได้ แต่สร้างวิชาใหม่ไม่ได้

เป็นไปได้ไหมใน Django การมีฟังก์ชันที่เรียกได้จะตั้งค่าเริ่มต้นให้กับบางฟิลด์ตามค่าของฟิลด์อื่นหรือไม่

(สำหรับคนที่อยากรู้อยากเห็นเหตุผลที่ฉันต้องการแยกชื่อย่อร้านค้าของฉันแยกต่างหากในบางกรณีที่ไม่ค่อยพบบ่อยนักที่นามสกุลแปลก ๆ อาจแตกต่างจากชื่อที่ฉันกำลังติดตามอยู่เช่นมีคนอื่นตัดสินใจว่า Subject 1 ชื่อชื่อย่อ "John O'Mallory" คือ "JM" มากกว่า "JO" และต้องการแก้ไขแก้ไขในฐานะผู้ดูแลระบบ)

คำตอบ:


89

นางแบบต้องมี "ตัวเอง" อย่างแน่นอน! เพียงแค่คุณพยายามกำหนดแอตทริบิวต์ของคลาสโมเดลว่าขึ้นอยู่กับอินสแตนซ์โมเดล เป็นไปไม่ได้เนื่องจากอินสแตนซ์ไม่มี (และไม่สามารถ) อยู่ก่อนที่คุณจะกำหนดคลาสและแอตทริบิวต์ของมัน

เพื่อให้ได้เอฟเฟกต์ที่คุณต้องการให้แทนที่เมธอด save () ของคลาสโมเดล ทำการเปลี่ยนแปลงตามที่คุณต้องการกับอินสแตนซ์ที่จำเป็นจากนั้นเรียกใช้เมธอดของ superclass เพื่อทำการบันทึกจริง นี่คือตัวอย่างสั้น ๆ

def save(self, *args, **kwargs):
    if not self.subject_init:
        self.subject_init = self.subject_initials()
    super(Subject, self).save(*args, **kwargs)

ซึ่งครอบคลุมอยู่ในOverriding Model Methodsในเอกสารประกอบ


5
โปรดทราบว่าใน Python 3 คุณสามารถเรียกsuper().save(*args, **kwargs)(โดยไม่มีSubject, selfอาร์กิวเมนต์) ดังตัวอย่างในเอกสารอ้างอิง
Kurt Peek

1
น่าเสียดายที่ไม่อนุญาตให้กำหนดค่าแอตทริบิวต์รูปแบบเริ่มต้นโดยอิงจากค่าอื่นซึ่งมีประโยชน์อย่างยิ่งในด้านผู้ดูแลระบบ (เช่นสำหรับช่องที่เพิ่มอัตโนมัติ) เนื่องจากจะจัดการเมื่อทำการบันทึก หรือว่าฉันเข้าใจผิด?
Vadorequest

18

ผมไม่ทราบว่ามีวิธีที่ดีกว่าการทำเช่นนี้ แต่คุณสามารถใช้จัดการสัญญาณสำหรับสัญญาณ :pre_save

from django.db.models.signals import pre_save

def default_subject(sender, instance, using):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

pre_save.connect(default_subject, sender=Subject)

1
คุณนำเข้าแทนpost_save pre_save
Ali Rasim Kocal

1
@arkocal: ขอบคุณสำหรับการแจ้งเตือนเพิ่งแก้ไข คุณสามารถแนะนำการแก้ไขด้วยตัวเองในกรณีเช่นนี้มันช่วยแก้ไขสิ่งต่างๆเช่นนี้ :)
Gabi Purcaru

1
ดูเหมือนว่าการนำไปใช้งานนี้ไม่มี**kwargsอาร์กิวเมนต์ซึ่งฟังก์ชันตัวรับทั้งหมดควรมีตามdocs.djangoproject.com/en/2.0/topics/signals/… ?
Kurt Peek

เอกสารแนะนำกับสัญญาณสำหรับเส้นทางรหัสคุณสามารถควบคุม คำตอบที่ยอมรับได้ของการลบล้างsave()อาจเปลี่ยนเป็นการลบล้าง__init__จะดีกว่าเนื่องจากมีความชัดเจนมากกว่า
Adam Johnson

7

การใช้สัญญาณ Djangoนี้สามารถทำได้ค่อนข้างต้นโดยได้รับสัญญาณจากแบบจำลองpost_init

from django.db import models
import django.dispatch

class LoremIpsum(models.Model):
    name = models.CharField(
        "Name",
        max_length=30,
    )
    subject_initials = models.CharField(
        "Subject Initials",
        max_length=5,
    )

@django.dispatch.receiver(models.signals.post_init, sender=LoremIpsum)
def set_default_loremipsum_initials(sender, instance, *args, **kwargs):
    """
    Set the default value for `subject_initials` on the `instance`.

    :param sender: The `LoremIpsum` class that sent the signal.
    :param instance: The `LoremIpsum` instance that is being
        initialised.
    :return: None.
    """
    if not instance.subject_initials:
        instance.subject_initials = "".join(map(
                (lambda x: x[0] if x else ""),
                instance.name.split(" ")))

post_initสัญญาณจะถูกส่งโดยชั้นหลังจากที่ได้ทำ initialisation บนอินสแตนซ์ ด้วยวิธีนี้อินสแตนซ์จะได้รับค่าnameก่อนที่จะทดสอบว่ามีการตั้งค่าฟิลด์ที่ไม่เป็นค่าว่างหรือไม่


เอกสารแนะนำกับสัญญาณสำหรับเส้นทางรหัสคุณสามารถควบคุม คำตอบที่ยอมรับได้ของการลบล้างsave()อาจเปลี่ยนเป็นการลบล้าง__init__จะดีกว่าเนื่องจากมีความชัดเจนมากกว่า
Adam Johnson

2

ในฐานะที่เป็นอีกทางเลือกหนึ่งของการใช้คำตอบของGabi Purcaruคุณยังสามารถเชื่อมต่อกับpre_saveสัญญาณโดยใช้receiverมัณฑนากร :

from django.db.models.signals import pre_save
from django.dispatch import receiver


@receiver(pre_save, sender=Subject)
def default_subject(sender, instance, **kwargs):
    if not instance.subject_init:
        instance.subject_init = instance.subject_initials()

ฟังก์ชั่นรับสัญญาณนี้จะใช้เวลา**kwargsข้อโต้แย้งคำหลักสัญลักษณ์แทนซึ่งจัดการสัญญาณจะต้องใช้เวลาตามhttps://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.