Có cách nào để tạo một id duy nhất trên 2 trường không?


14

Đây là mô hình của tôi:

class GroupedModels(models.Model):
    other_model_one = models.ForeignKey('app.other_model')
    other_model_two = models.ForeignKey('app.other_model')

Về cơ bản, những gì tôi muốn là other_modelduy nhất trong bảng này. Điều đó có nghĩa là nếu có một bản ghi trong đó other_model_oneid 123, tôi không nên cho phép một bản ghi khác được tạo với other_model_twoid là 123. Tôi có thể ghi đè cleantôi đoán nhưng tôi đã tự hỏi nếu django có một cái gì đó được xây dựng trong.

Tôi đang sử dụng phiên bản 2.2.5 với PSQL.

Chỉnh sửa: Đây không phải là một tình huống kỳ lạ cùng nhau. Nếu tôi thêm một bản ghi với other_model_one_id=1và khác other_model_two_id=2, tôi không thể thêm một bản ghi other_model_one_id=2khác vàother_model_two_id=1


Phiên bản Django nào bạn đang sử dụng?
Willem Van Onsem

Tôi đang sử dụng phiên bản 2.2.5
Pittfall


1
Đây không phải là một tình huống duy nhất với nhau, đây là duy nhất nhưng trên 2 trường nếu điều đó có ý nghĩa.
Pittfall

Câu trả lời:


10

Tôi giải thích một số tùy chọn ở đây, có thể một trong số chúng hoặc kết hợp có thể hữu ích cho bạn.

Ghi đè save

Ràng buộc của bạn là một quy tắc kinh doanh, bạn có thể ghi đè savephương thức để giữ dữ liệu nhất quán:


class GroupedModels(models.Model): 
    # ...
    def clean(self):
        if (self.other_model_one.pk == self.other_model_two.pk):
            raise ValidationError({'other_model_one':'Some message'}) 
        if (self.other_model_one.pk < self.other_model_two.pk):
            #switching models
            self.other_model_one, self.other_model_two = self.other_model_two, self.other_model_one
    # ...
    def save(self, *args, **kwargs):
        self.clean()
        super(GroupedModels, self).save(*args, **kwargs)

Thay đổi thiết kế

Tôi đặt một mẫu dễ hiểu. Hãy giả sử kịch bản này:

class BasketballMatch(models.Model):
    local = models.ForeignKey('app.team')
    visitor = models.ForeignKey('app.team')

Bây giờ, bạn muốn tránh một đội chơi một trận đấu với chính nó, đội A chỉ có thể chơi với đội B một lần (gần như quy tắc của bạn). Bạn có thể thiết kế lại các mô hình của bạn như:

class BasketballMatch(models.Model):
    HOME = 'H'
    GUEST = 'G'
    ROLES = [
        (HOME, 'Home'),
        (GUEST, 'Guest'),
    ]
    match_id = models.IntegerField()
    role = models.CharField(max_length=1, choices=ROLES)
    player = models.ForeignKey('app.other_model')

    class Meta:
      unique_together = [ ( 'match_id', 'role', ) ,
                          ( 'match_id', 'player',) , ]

ManyToManyField.symmetrical

Điều này trông giống như một vấn đề đối xứng , django có thể xử lý nó cho bạn. Thay vì tạo GroupedModelsmô hình, chỉ cần tạo trường ManyToManyField với OtherModel:

from django.db import models
class OtherModel(models.Model):
    ...
    grouped_models = models.ManyToManyField("self")

Đây là những gì django đã được xây dựng cho các kịch bản này.


Cách tiếp cận là cách tôi đang sử dụng (nhưng hy vọng ràng buộc cơ sở dữ liệu). Cách tiếp cận 2 khác một chút ở chỗ trong kịch bản của tôi, nếu một đội đã chơi một trò chơi, họ không bao giờ có thể chơi một trò chơi nữa. Tôi đã không sử dụng cách tiếp cận 3 vì có nhiều dữ liệu tôi muốn lưu trữ trong nhóm. Cảm ơn câu trả lời.
Pittfall

nếu một đội đã chơi một trò chơi, họ không bao giờ có thể chơi một trò chơi nữa. bởi vì điều này tôi đưa match_idvào ràng buộc không giống nhau, để cho phép các đội chơi các trận đấu không giới hạn. Chỉ cần loại bỏ lĩnh vực này để hạn chế chơi lại.
dani herrera

à vâng cảm ơn bạn tôi đã bỏ lỡ điều đó và mô hình khác của tôi có thể là một lĩnh vực.
Pittfall

1
Tôi nghĩ rằng tôi thích tùy chọn số 2 tốt nhất. Vấn đề duy nhất tôi gặp phải là nó có thể cần một hình thức tùy chỉnh cho người dùng "trung bình", trong một thế giới nơi quản trị viên được sử dụng làm FE. Thật không may, tôi sống trong thế giới đó. Nhưng tôi nghĩ rằng đây nên là câu trả lời được chấp nhận. Cảm ơn!
Pittfall

Lựa chọn thứ hai là con đường để đi. Đây là một câu trả lời tuyệt vời. @Pitfall liên quan đến quản trị viên Tôi đã thêm một câu trả lời. Các hình thức quản trị không nên là một vấn đề lớn để giải quyết.
cezar

1

Đó không phải là một câu trả lời rất thỏa mãn, nhưng thật không may, sự thật là không có cách nào để làm những gì bạn mô tả với một tính năng tích hợp đơn giản.

Những gì bạn mô tả cleansẽ hoạt động, nhưng bạn phải cẩn thận để gọi nó theo cách thủ công vì tôi nghĩ nó chỉ được gọi tự động khi sử dụng ModelForm. Bạn có thể tạo một ràng buộc cơ sở dữ liệu phức tạp nhưng sẽ tồn tại bên ngoài Django và bạn phải xử lý các ngoại lệ cơ sở dữ liệu (điều này có thể khó khăn trong Django khi ở giữa một giao dịch).

Có lẽ có một cách tốt hơn để cấu trúc dữ liệu?


Vâng, bạn đúng là nó phải được gọi thủ công, đó là lý do tại sao tôi không thích cách tiếp cận này. Nó chỉ hoạt động như tôi muốn trong quản trị viên, như bạn đã đề cập.
Pittfall

0

Đã có một câu trả lời tuyệt vời từ dani herrera , tuy nhiên tôi muốn giải thích thêm về nó.

Như đã giải thích trong tùy chọn thứ hai, giải pháp theo yêu cầu của OP là thay đổi thiết kế và thực hiện hai ràng buộc duy nhất theo cặp. Sự tương đồng với các trận đấu bóng rổ minh họa vấn đề theo một cách rất thực tế.

Thay vì một trận đấu bóng rổ, tôi sử dụng ví dụ với các trò chơi bóng đá (hoặc bóng đá). Một trò chơi bóng đá (mà tôi gọi nó Event) được chơi bởi hai đội (trong mô hình của tôi là một đội Competitor). Đây là một mối quan hệ nhiều-nhiều ( m:n), với ngiới hạn là hai trong trường hợp cụ thể này, nguyên tắc này phù hợp với số lượng không giới hạn.

Đây là cách các mô hình của chúng tôi trông:

class Competitor(models.Model):
    name = models.CharField(max_length=100)
    city = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    participants = models.ManyToManyField(Competitor)

    def __str__(self):
        return self.title

Một sự kiện có thể là:

  • danh hiệu: Carabao Cup, vòng 4,
  • địa điểm: Anfield
  • thời gian: 30. Tháng 10 năm 2019, 19:30 GMT
  • những người tham gia:
    • tên: Liverpool, thành phố: Liverpool
    • Tên: Arsenal, thành phố: Luân Đôn

Bây giờ chúng ta phải giải quyết vấn đề từ câu hỏi. Django tự động tạo một bảng trung gian giữa các mô hình có mối quan hệ nhiều-nhiều, nhưng chúng ta có thể sử dụng một mô hình tùy chỉnh và thêm các trường khác. Tôi gọi mô hình đó Participant:

người tham gia lớp học (model.Model):
    VAI TRÒ = (
        ('H', 'Nhà'),
        ('V', 'Khách truy cập'),
    )
    event = model.ForeignKey (Event, on_delete = model.CASCADE)
    đối thủ cạnh tranh = mô hình.ForeignKey (Đối thủ cạnh tranh, on_delete = mô hình.CASCADE)
    vai trò = mô hình.CharField (max_length = 1, tests = ROLES)

    lớp Meta:
        unique_together = (
            ('sự kiện', 'vai trò'),
            ('sự kiện', 'đối thủ cạnh tranh'),
        )

    def __str __ (tự):
        trả về định dạng '{} - {}'. (self.event, self.get_role_display ())

ManyToManyFieldmột tùy chọn throughcho phép chúng ta chỉ định mô hình trung gian. Hãy thay đổi điều đó trong mô hình Event:

class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    participants = models.ManyToManyField(
        Competitor,
        related_name='events', # if we want to retrieve events for a competitor
        through='Participant'
    )

    def __str__(self):
        return self.title

Các ràng buộc duy nhất bây giờ sẽ tự động giới hạn số lượng đối thủ cạnh tranh trong hai sự kiện (vì chỉ có hai vai trò: NhàKhách truy cập ).

Trong một sự kiện cụ thể (trò chơi bóng đá) chỉ có thể có một đội chủ nhà và chỉ có một đội khách. Một câu lạc bộ ( Competitor) có thể xuất hiện với tư cách là đội chủ nhà hoặc là đội khách.

Làm thế nào để chúng ta quản lý tất cả những điều này trong quản trị viên? Như thế này:

from django.contrib import admin

from .models import Competitor, Event, Participant


class ParticipantInline(admin.StackedInline): # or admin.TabularInline
    model = Participant
    max_num = 2


class CompetitorAdmin(admin.ModelAdmin):
    fields = ('name', 'city',)


class EventAdmin(admin.ModelAdmin):
    fields = ('title', 'venue', 'time',)
    inlines = [ParticipantInline]


admin.site.register(Competitor, CompetitorAdmin)
admin.site.register(Event, EventAdmin)

Chúng tôi đã thêm các Participantnội tuyến trong EventAdmin. Khi chúng tôi tạo mới, Eventchúng tôi có thể chọn đội chủ nhà và đội khách. Tùy chọn max_numgiới hạn số lượng mục nhập thành 2, do đó không thể thêm 2 đội cho mỗi sự kiện.

Điều này có thể được tái cấu trúc cho một trường hợp sử dụng khác nhau. Giả sử các sự kiện của chúng tôi là các cuộc thi bơi lội và thay vì nhà và khách, chúng tôi có các làn từ 1 đến 8. Chúng tôi chỉ tái cấu trúc Participant:

class Participant(models.Model):
    ROLES = (
        ('L1', 'lane 1'),
        ('L2', 'lane 2'),
        # ... L3 to L8
    )
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
    role = models.CharField(max_length=1, choices=ROLES)

    class Meta:
        unique_together = (
            ('event', 'role'),
            ('event', 'competitor'),
        )

    def __str__(self):
        return '{} - {}'.format(self.event, self.get_role_display())

Với sửa đổi này, chúng tôi có thể có sự kiện này:

  • tiêu đề: FINA 2019, trận chung kết 50m nam,

    • Địa điểm: Trung tâm thể thao dưới nước thành phố Đại học Nambu
    • thời gian: 28. Tháng 7 năm 2019, 20:02 UTC + 9
    • những người tham gia:

      • tên: Michael Andrew, thành phố: Edina, Hoa Kỳ, vai trò: ngõ 1
      • tên: Zane Waddell, thành phố: Bloemfontein, Nam Phi, vai trò: ngõ 2
      • tên: Evgeny Rylov, thành phố: Novotroitsk, Nga, vai trò: ngõ 3
      • tên: Kliment Kolesnikov, thành phố: Moscow, Nga, vai trò: ngõ 4

      // và cứ thế trên làn 5 đến làn 8 (nguồn: Wikipedia

Một người bơi chỉ có thể xuất hiện một lần khi trời nóng và làn đường chỉ có thể bị chiếm giữ một lần khi trời nóng.

Tôi đặt mã cho GitHub: https://github.com/cezar77/competition .

Một lần nữa, tất cả các khoản tín dụng đi đến dani herrera. Tôi hy vọng câu trả lời này cung cấp một số giá trị gia tăng cho độc giả.

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.