Tạo một mô hình với hai tùy chọn, nhưng một khóa ngoại bắt buộc


9

Vấn đề của tôi là tôi có một mô hình có thể lấy một trong hai khóa ngoại để nói nó là mô hình gì. Tôi muốn nó lấy ít nhất một nhưng không phải cả hai. Tôi có thể vẫn là một mô hình hay tôi nên chia nó thành hai loại. Đây là mã:

class Inspection(models.Model):
    InspectionID = models.AutoField(primary_key=True, unique=True)
    GroupID = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
    SiteID = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)

    @classmethod
    def create(cls, groupid, siteid):
        inspection = cls(GroupID = groupid, SiteID = siteid)
        return inspection

    def __str__(self):
        return str(self.InspectionID)

class InspectionReport(models.Model):
    ReportID = models.AutoField(primary_key=True, unique=True)
    InspectionID = models.ForeignKey('Inspection', on_delete=models.CASCADE, null=True)
    Date = models.DateField(auto_now=False, auto_now_add=False, null=True)
    Comment = models.CharField(max_length=255, blank=True)
    Signature = models.CharField(max_length=255, blank=True)

Vấn đề là Inspectionmô hình. Điều này nên được liên kết với một nhóm hoặc một trang web, nhưng không phải cả hai. Hiện tại với thiết lập này, nó cần cả hai.

Tôi thà không phải chia nó thành hai mô hình gần giống nhau GroupInspectionSiteInspectionvì vậy, bất kỳ giải pháp nào giữ nó như một mô hình sẽ là lý tưởng.


Có lẽ sử dụng phân lớp là tốt hơn ở đây. Bạn có thể tạo một Inspectionlớp, sau đó phân lớp thành SiteInspectionGroupInspectioncho các phần không phổ biến.
Willem Van Onsem

Có thể không liên quan, nhưng unique=Truemột phần trong FK bạn các lĩnh vực phương tiện mà chỉ có một Inspectionví dụ có thể tồn tại trong một cho GroupIDhay SiteIDdụ - IOW, đó là một mối quan hệ 1-1, không phải là một một đến nhiều. Đây thực sự là những gì bạn muốn ?
bruno Desthuilliers

"Hiện tại với thiết lập này, nó cần cả hai." => về mặt kỹ thuật, nó không - ở cấp cơ sở dữ liệu, bạn có thể đặt cả hai hoặc một trong hai khóa đó (với thông báo đã đề cập ở trên). Chỉ khi sử dụng ModelForm (trực tiếp hoặc thông qua quản trị viên django) thì các trường đó sẽ được đánh dấu là bắt buộc và đó là vì bạn đã không vượt qua đối số 'blank = True'.
bruno Desthuilliers

@brunodesthuilliers Có ý tưởng là có Inspectionmột liên kết giữa Grouphoặc SiteInspectionID, sau đó tôi có thể có nhiều "kiểm tra" dưới dạng InspectionReportmột mối quan hệ. Điều này đã được thực hiện để tôi có thể dễ dàng sắp xếp hơn Datecho tất cả các hồ sơ liên quan đến một Grouphoặc Site. Hy vọng điều đó có ý nghĩa
CalMac

@ Cm5029 Nhóm), sắp xếp chúng theo ngày và bạn đã hoàn tất.
bruno Desthuilliers

Câu trả lời:


5

Tôi muốn đề nghị bạn thực hiện xác nhận theo cách Django

bằng cách ghi đè cleanphương thức của Django Model

class Inspection(models.Model):
    ...

    def clean(self):
        if <<<your condition>>>:
            raise ValidationError({
                    '<<<field_name>>>': _('Reason for validation error...etc'),
                })
        ...
    ...

Tuy nhiên, lưu ý, giống như Model.full_clean (), phương thức clean () của mô hình không được gọi khi bạn gọi phương thức save () của mô hình. nó cần được gọi thủ công để xác thực dữ liệu của mô hình hoặc bạn có thể ghi đè phương thức lưu của mô hình để làm cho nó luôn gọi phương thức Clean () trước khi kích hoạt Modelphương thức lưu lớp


Một giải pháp khác có thể giúp là sử dụng GenericRelations , để cung cấp trường đa hình liên quan đến nhiều hơn một bảng, nhưng có thể là trường hợp nếu các bảng / đối tượng này có thể được sử dụng thay thế cho nhau trong thiết kế hệ thống từ nơi đầu tiên.


2

Như đã đề cập trong các nhận xét, lý do "với thiết lập này cần cả hai" chỉ là bạn quên thêm các blank=Truetrường FK của mình, do đó ModelForm(một tùy chỉnh hoặc mặc định do quản trị viên tạo) sẽ khiến trường biểu mẫu được yêu cầu . Ở cấp lược đồ db, bạn có thể điền cả hai, một hoặc không một trong số các FK đó, sẽ ổn thôi vì bạn đã làm cho các trường db đó trở nên không thể (với null=Trueđối số).

Ngoài ra, (cf các bình luận khác của tôi), bạn có thể muốn kiểm tra xem FK của bạn có thực sự là duy nhất không. Về mặt kỹ thuật, điều này biến mối quan hệ của bạn thành nhiều mối quan hệ thành một mối quan hệ - bạn chỉ được phép một bản ghi 'kiểm tra' duy nhất cho một GroupID hoặc SiteId nhất định (bạn không thể có hai hoặc nhiều 'kiểm tra' cho một GroupId hoặc SiteId) . Nếu đó thực sự là những gì bạn muốn, bạn có thể muốn sử dụng OneToOneField rõ ràng (lược đồ db sẽ giống nhau nhưng mô hình sẽ rõ ràng hơn và mô tả liên quan có thể sử dụng nhiều hơn cho trường hợp sử dụng này).

Như một lưu ý phụ: trong Mô hình Django, trường ForeignKey cụ thể hóa như một thể hiện mô hình có liên quan, không phải là id thô. IOW, đưa ra điều này:

class Foo(models.Model):
    name = models.TextField()

class Bar(models.Model):
    foo = models.ForeignKey(Foo)


foo = Foo.objects.create(name="foo")
bar = Bar.objects.create(foo=foo)

sau đó bar.foosẽ giải quyết foo, không để foo.id. Vì vậy, bạn chắc chắn muốn đổi tên của bạn InspectionIDSiteIDcác lĩnh vực cho phù hợp inspectionsite. BTW, trong Python, quy ước đặt tên là 'all_lower_with_underscores' cho bất cứ điều gì khác ngoài tên lớp và hằng giả.

Bây giờ cho câu hỏi cốt lõi của bạn: không có cách SQL tiêu chuẩn cụ thể nào để thực thi ràng buộc "cái này hay cái kia" ở cấp cơ sở dữ liệu, do đó, nó thường được thực hiện bằng cách sử dụng một ràng buộc CHECK , được thực hiện trong mô hình Django với các ràng buộc "meta" của mô hình tùy chọn .

Điều này đang được nói, cách các ràng buộc thực sự được hỗ trợ và thi hành ở cấp db tùy thuộc vào nhà cung cấp DB của bạn ( ví dụ, MySQL <8.0.16 chỉ cần bỏ qua chúng ) và loại ràng buộc bạn sẽ cần ở đây sẽ không được thi hành ở dạng hoặc xác thực cấp độ mô hình , chỉ khi cố gắng lưu mô hình, do đó bạn cũng muốn thêm xác thực ở cấp độ mô hình (tốt nhất) hoặc xác thực cấp độ biểu mẫu, trong cả hai trường hợp trong phương thức (mô hình) hoặc clean()phương thức biểu mẫu .

Vì vậy, để làm cho một câu chuyện dài ngắn:

  • trước tiên hãy kiểm tra kỹ xem bạn có thực sự muốn unique=Trueràng buộc này không và nếu có thì hãy thay thế trường FK của bạn bằng OneToOneField.

  • thêm một blank=Trueđối số cho cả hai trường FK (hoặc OneToOne) của bạn

  • thêm ràng buộc kiểm tra thích hợp trong meta mô hình của bạn - tài liệu không rõ ràng nhưng vẫn đủ rõ ràng nếu bạn biết thực hiện các truy vấn phức tạp với ORM (và nếu bạn không đến lúc bạn học ;-))
  • thêm một clean()phương thức vào mô hình của bạn để kiểm tra xem bạn có một hoặc một trường khác và gây ra lỗi xác thực khác không

và bạn sẽ ổn thôi, giả sử RDBMS của bạn tôn trọng các ràng buộc kiểm tra tất nhiên.

Chỉ cần lưu ý rằng, với thiết kế này, Inspectionmô hình của bạn hoàn toàn vô dụng (nhưng rất tốn kém!) - bạn sẽ có được các tính năng chính xác tương tự với chi phí thấp hơn bằng cách chuyển trực tiếp FK (và các ràng buộc, xác nhận, v.v.) InspectionReport.

Bây giờ có thể có một giải pháp khác - giữ mô hình Kiểm tra, nhưng đặt FK làm OneToOneField ở đầu kia của mối quan hệ (trong Trang web và Nhóm):

class Inspection(models.Model):
    id = models.AutoField(primary_key=True) # a pk is always unique !

class InspectionReport(models.Model):
    # you actually don't need to manually specify a PK field,
    # Django will provide one for you if you don't
    # id = models.AutoField(primary_key=True)

    inspection = ForeignKey(Inspection, ...)
    date = models.DateField(null=True) # you should have a default then
    comment = models.CharField(max_length=255, blank=True default="")
    signature = models.CharField(max_length=255, blank=True, default="")


class Group(models.Model):
    inspection = models.OneToOneField(Inspection, null=True, blank=True)

class Site(models.Model):
    inspection = models.OneToOneField(Inspection, null=True, blank=True)

Và sau đó bạn có thể nhận được tất cả các báo cáo cho một Trang web hoặc Nhóm cụ thể với yoursite.inspection.inspectionreport_set.all().

Điều này tránh việc phải thêm bất kỳ ràng buộc hoặc xác nhận cụ thể nào, nhưng với chi phí của một mức độ bổ sung bổ sung ( joinmệnh đề SQL, v.v.).

Giải pháp nào trong số đó là "tốt nhất" thực sự phụ thuộc vào ngữ cảnh, vì vậy bạn phải hiểu ý nghĩa của cả hai và kiểm tra xem bạn thường sử dụng mô hình của mình như thế nào để tìm ra phương pháp phù hợp hơn với nhu cầu của riêng bạn. Theo như tôi quan tâm và không có nhiều bối cảnh (hoặc nghi ngờ), tôi muốn sử dụng giải pháp với các mức độ ít hơn, nhưng YMMV.

NB liên quan đến các mối quan hệ chung: chúng có thể hữu ích khi bạn thực sự có nhiều mô hình liên quan có thể và / hoặc không biết trước mô hình nào mà người ta sẽ muốn liên quan đến chính bạn. Điều này đặc biệt hữu ích cho các ứng dụng có thể tái sử dụng (nghĩ các tính năng "bình luận" hoặc "thẻ", v.v.) hoặc các ứng dụng có thể mở rộng (khung quản lý nội dung, v.v.). Nhược điểm là nó làm cho việc truy vấn nặng hơn nhiều (và khá không thực tế khi bạn muốn thực hiện các truy vấn thủ công trên db của mình). Từ kinh nghiệm, họ có thể nhanh chóng trở thành một bot / mã writ và hoàn hảo, vì vậy tốt hơn là giữ chúng khi không có giải pháp nào tốt hơn (và / hoặc khi việc bảo trì và thời gian chạy không phải là vấn đề).

2 xu của tôi.


2

Django có giao diện mới (kể từ 2.2) để tạo các ràng buộc DB: https://docs.djangoproject.com/en/3.0/ref/models/constraint/

Bạn có thể sử dụng một CheckConstraintđể thực thi một và chỉ một là không có giá trị. Tôi sử dụng hai cho rõ ràng:

class Inspection(models.Model):
    InspectionID = models.AutoField(primary_key=True, unique=True)
    GroupID = models.OneToOneField('PartGroup', on_delete=models.CASCADE, blank=True, null=True)
    SiteID = models.OneToOneField('Site', on_delete=models.CASCADE, blank=True, null=True)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=~Q(SiteID=None) | ~Q(GroupId=None),
                name='at_least_1_non_null'),
            ),
            models.CheckConstraint(
                check=Q(SiteID=None) | Q(GroupId=None),
                name='at_least_1_null'),
            ),
        ]

Điều này sẽ chỉ thực thi các ràng buộc ở cấp độ DB. Bạn sẽ cần xác thực các đầu vào trong biểu mẫu hoặc tuần tự hóa của mình theo cách thủ công.

Là một lưu ý phụ, có lẽ bạn nên sử dụng OneToOneFieldthay vì ForeignKey(unique=True). Bạn cũng sẽ muốn blank=True.


0

Tôi nghĩ rằng bạn đang nói về quan hệ chung , tài liệu . Câu trả lời của bạn trông tương tự như thế này .

Cách đây không lâu, tôi cần sử dụng các mối quan hệ Chung nhưng tôi đã đọc trong một cuốn sách và ở một nơi khác mà việc sử dụng nên tránh, tôi nghĩ đó là Two Scoops of Django.

Tôi đã kết thúc việc tạo ra một mô hình như thế này:

class GroupInspection(models.Model):
    InspectionID = models.ForeignKey..
    GroupID = models.ForeignKey..

class SiteInspection(models.Model):
    InspectionID = models.ForeignKey..
    SiteID = models.ForeignKey..

Tôi không chắc liệu đó có phải là một giải pháp tốt hay không và như bạn đã đề cập, bạn không nên sử dụng nó, nhưng điều này có hiệu quả trong trường hợp của tôi.


"Tôi đọc trong một cuốn sách và một nơi nào khác" là về lý do tồi tệ hơn có thể làm (hoặc tránh làm) một cái gì đó.
bruno Desthuilliers

@brunodesthuilliers Tôi nghĩ Two Scoops of Django là một cuốn sách hay.
Luis Silva

Không thể nói, tôi đã không đọc nó. Nhưng điều đó không liên quan: quan điểm của tôi là nếu bạn không hiểu tại sao cuốn sách lại nói như vậy, thì đó không phải là kiến ​​thức cũng như kinh nghiệm, đó là niềm tin tôn giáo. Tôi không bận tâm đến niềm tin tôn giáo khi nói đến tôn giáo, nhưng họ không có chỗ trong CS. Hoặc bạn hiểu những ưu và nhược điểm của một số tính năng và sau đó bạn có thể đánh giá xem nó có phù hợp trong một bối cảnh cụ thể không , hoặc bạn không nên nhại lại những gì bạn đã đọc. Có trường hợp sử dụng rất hợp lệ cho các mối quan hệ chung chung, vấn đề không phải là tránh chúng chút nào mà là biết khi nào nên tránh chúng.
bruno Desthuilliers

NB Tôi hoàn toàn hiểu rằng người ta không thể biết mọi thứ về CS - có những lĩnh vực mà tôi không có lựa chọn nào khác ngoài việc tin tưởng một số cuốn sách. Nhưng sau đó tôi có thể sẽ không trả lời các câu hỏi về chủ đề đó ;-)
bruno Desthuilliers

0

Có thể đã muộn để trả lời câu hỏi của bạn, nhưng tôi nghĩ giải pháp của tôi có thể phù hợp với trường hợp của người khác.

Tôi sẽ tạo một mô hình mới, hãy gọi nó Dependencyvà áp dụng logic trong mô hình đó.

class Dependency(models.Model):
    Group = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
    Site = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)

Sau đó, tôi sẽ viết logic để được áp dụng rất rõ ràng.

class Dependency(models.Model):
    group = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
    site = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)

    _is_from_custom_logic = False

    @classmethod
    def create_dependency_object(cls, group=None, site=None):
        # you can apply any conditions here and prioritize the provided args
        cls._is_from_custom_logic = True
        if group:
            _new = cls.objects.create(group=group)
        elif site:
            _new = cls.objects.create(site=site)
        else:
            raise ValueError('')
        return _new

    def save(self, *args, **kwargs):
        if not self._is_from_custom_logic:
            raise Exception('')
        return super().save(*args, **kwargs)

Bây giờ bạn chỉ cần tạo một mô hình duy nhất ForeignKeycho Inspectionmô hình của bạn .

Trong các viewchức năng của bạn , bạn cần tạo một Dependencyđối tượng và sau đó gán nó vào Inspectionbản ghi của bạn . Hãy chắc chắn rằng bạn sử dụng create_dependency_objecttrong các viewchức năng của bạn .

Điều này khá nhiều làm cho mã của bạn rõ ràng và bằng chứng lỗi. Việc thực thi có thể được bỏ qua quá dễ dàng. Nhưng vấn đề là nó cần kiến ​​thức trước để giới hạn chính xác này được bỏ qua.

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.