Trường mô hình Django Mặc định dựa trên trường khác trong cùng một mô hình


91

Tôi có một mô hình mà tôi muốn chứa tên chủ thể và tên viết tắt của chúng (dữ liệu của anh ấy hơi ẩn danh và được theo dõi bằng tên viết tắt).

Ngay bây giờ, tôi đã viết

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)

Như được chỉ ra ở dòng cuối cùng, tôi muốn có thể có các chữ cái đầu thực sự được lưu trữ trong cơ sở dữ liệu dưới dạng một trường (độc lập với tên), nhưng nó được khởi tạo với giá trị mặc định dựa trên trường tên. Tuy nhiên, tôi đang gặp vấn đề là các mô hình django dường như không có 'bản thân'.

Nếu tôi thay đổi dòng thành subject_init = models.CharField("Subject initials", max_length=2, default=subject_initials), tôi có thể thực hiện syncdb, nhưng không thể tạo chủ đề mới.

Điều này có khả thi trong Django không, việc có một hàm có thể gọi cung cấp giá trị mặc định cho một số trường dựa trên giá trị của một trường khác?

(Đối với những người tò mò, lý do tôi muốn tách các chữ cái đầu trong cửa hàng của mình một cách riêng biệt là trong một số trường hợp hiếm hoi, những cái tên kỳ lạ có thể khác với những cái tôi đang theo dõi. Ví dụ: ai đó đã quyết định rằng Đối tượng 1 được đặt tên là tên viết tắt "John O'Mallory" là "JM" chứ không phải "JO" và muốn sửa, hãy chỉnh sửa nó với tư cách quản trị viên.)

Câu trả lời:


88

Người mẫu chắc chắn có một "cái tôi"! Chỉ là bạn đang cố gắng xác định một thuộc tính của lớp mô hình là phụ thuộc vào một cá thể mô hình; điều đó là không thể, vì cá thể không (và không thể) tồn tại trước khi bạn xác định lớp và các thuộc tính của nó.

Để có được hiệu ứng bạn muốn, hãy ghi đè phương thức save () của lớp mô hình. Thực hiện bất kỳ thay đổi nào bạn muốn đối với cá thể cần thiết, sau đó gọi phương thức của lớp cha để thực hiện lưu thực tế. Đây là một ví dụ nhanh.

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

Điều này được đề cập trong các Phương pháp Mô hình Ghi đè trong tài liệu.


4
Lưu ý rằng trong Python 3, bạn có thể chỉ cần gọi super().save(*args, **kwargs)(không có Subject, selfđối số) như trong ví dụ trong tài liệu được tham chiếu.
Kurt Peek

1
Thật tệ là điều này không cho phép xác định giá trị thuộc tính mô hình mặc định dựa trên một giá trị khác, điều này đặc biệt hữu ích trong phía Quản trị viên (ví dụ: đối với trường tự động tăng cường), vì nó xử lý nó khi lưu nó. Hay tôi đang hiểu lầm nó?
Vadorequest

18

Tôi không biết nếu có một cách tốt hơn để làm điều này, nhưng bạn có thể sử dụng một bộ xử lý tín hiệu cho các pre_savetín hiệu :

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
Bạn đã nhập post_savethay vì pre_save.
Ali Rasim Kocal

1
@arkocal: cảm ơn bạn đã quan tâm, chỉ cần sửa nó. Bạn có thể đề xuất chỉnh sửa cho mình trong trường hợp này, nó giúp sửa chữa những thứ như thế :) này
Gabi Purcaru

1
Có vẻ như việc triển khai này thiếu **kwargsđối số mà tất cả các chức năng của bộ thu phải có theo docs.djangoproject.com/en/2.0/topics/signals/… ?
Kurt Peek

Tài liệu khuyến nghị chống lại các tín hiệu cho các đường dẫn mã mà bạn kiểm soát . Câu trả lời được chấp nhận của ghi đè save(), có lẽ được thay đổi thành ghi đè __init__, tốt hơn vì nó rõ ràng hơn.
Adam Johnson

7

Sử dụng tín hiệu Django , điều này có thể được thực hiện khá sớm, bằng cách nhận các post_inittín hiệu từ mô hình.

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(" ")))

Các post_inittín hiệu được gửi bởi lớp một khi nó đã thực hiện khởi động trên ví dụ. Bằng cách này, cá thể nhận một giá trị nametrước khi kiểm tra xem các trường không thể null của nó có được đặt hay không.


Tài liệu khuyến nghị chống lại các tín hiệu cho các đường dẫn mã mà bạn kiểm soát . Câu trả lời được chấp nhận của ghi đè save(), có lẽ được thay đổi thành ghi đè __init__, tốt hơn vì nó rõ ràng hơn.
Adam Johnson

2

Là một cách triển khai thay thế cho câu trả lời của Gabi Purcaru , bạn cũng có thể kết nối với pre_savetín hiệu bằng trình receivertrang trí :

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()

Hàm bộ thu này cũng nhận các **kwargsđối số từ khóa ký tự đại diện mà tất cả bộ xử lý tín hiệu phải nhận theo https://docs.djangoproject.com/en/2.0/topics/signals/#receiver-functions .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.