Django'da - Model Devralma - Bir üst modelin niteliğini geçersiz kılmanıza izin veriyor mu?


102

Bunu yapmak istiyorum:

class Place(models.Model):
   name = models.CharField(max_length=20)
   rating = models.DecimalField()

class LongNamedRestaurant(Place):  # Subclassing `Place`.
   name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
   food_type = models.CharField(max_length=25)

Bu, kullanmak istediğim sürümdür (herhangi bir öneriye açık olsam da): http://docs.djangoproject.com/en/dev/topics/db/models/#id7

Bu Django'da destekleniyor mu? Değilse, benzer sonuçlar elde etmenin bir yolu var mı?


lütfen aşağıdaki cevabı kabul eder misin, django
1.10'dan

@holms yalnızca temel sınıf soyutsa!
Micah Walter

Yanıtlar:


66

Güncellenen cevap: İnsanların yorumlarda belirttiği gibi, orijinal cevap soruyu doğru şekilde cevaplamıyordu. Nitekim LongNamedRestaurantveri tabanında sadece model oluşturulmuştu Place, olmadı.

Bir çözüm, örneğin bir "Yeri" temsil eden soyut bir model oluşturmaktır. AbstractPlaceve ondan miras al:

class AbstractPlace(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class Place(AbstractPlace):
    pass

class LongNamedRestaurant(AbstractPlace):
    name = models.CharField(max_length=255)
    food_type = models.CharField(max_length=25)

Lütfen @Mark cevabını da okuyun , soyut olmayan bir sınıftan miras alınan nitelikleri neden değiştiremeyeceğinize dair harika bir açıklama yapıyor.

(Bunun yalnızca Django 1.10: Django 1.10'dan önce, soyut bir sınıftan miras alınan bir niteliği değiştirmek mümkün olmadığı için mümkün olduğunu unutmayın.)

Orijinal cevap

Django 1.10'dan beri mümkün ! Sadece istediğini yapmalısın:

class Place(models.Model):
    name = models.CharField(max_length=20)
    rating = models.DecimalField()

    class Meta:
        abstract = True

class LongNamedRestaurant(Place):  # Subclassing `Place`.
    name = models.CharField(max_length=255)  # Notice, I'm overriding `Place.name` to give it a longer length.
    food_type = models.CharField(max_length=25)

8
Yerin soyut olması gerekiyor, değil mi?
DylanYoung

4
Sadece soruda gönderilen kodun Django 1.10'dan beri çalıştığını söylediğim için farklı bir soruyu yanıtladığımı sanmıyorum. Kullanmak istediği şeyle ilgili yayınladığı bağlantıya göre, Place sınıfını soyut yapmayı unuttuğunu unutmayın.
qmarlats

2
Bunun neden kabul edilen cevap olduğundan emin değilim ... OP çoklu tablo kalıtımı kullanıyor. Bu cevap yalnızca soyut temel sınıflar için geçerlidir.
MrName

1
soyut sınıflar Django
1.10'dan

2
Orijinal cevap olarak @NoamG, Placedolayısıyla o oldu, soyut oldu değil veritabanında yarattı. Ama OP istedi hem Placeve LongNamedRestaurantveritabanında oluşturulacak. Bu nedenle ben eklemek cevabım güncellenen AbstractPlace“taban” dır modeli (yani. Soyut) modeli hem Placeve LongNamedRestaurantgelen devralır. Şimdi hem Placeve LongNamedRestaurantOP istedi olarak, veritabanında oluşturulur.
qmarlats

61

Hayır, değil :

Alan adının "gizlenmesine" izin verilmez

Normal Python sınıfı kalıtımda, bir alt sınıfın ana sınıftan herhangi bir özniteliği geçersiz kılmasına izin verilir. Django'da buna Fieldörnek olan öznitelikler için izin verilmiyor (en azından şu anda değil). Bir temel sınıfın adlı bir alanı authorvarsa, authoro temel sınıftan miras alan herhangi bir sınıfta çağrılan başka bir model alanı oluşturamazsınız .


11
Neden imkansız olduğu için cevabımı görün. İnsanlar bunu seviyor çünkü mantıklı, sadece hemen belli olmuyor.
Mark

4
@ leo-the-manic User._meta.get_field('email').required = Trueİşe yarayacağını düşünüyorum , emin değilim.
Jens Timmerman

@ leo-the-manic, @JensTimmerman, @utapyngo Sınıfınızın özellik değerini ayarlamak, miras alınan alanlar üzerinde bir etkiye sahip olmayacaktır. _metaEbeveyn sınıfına göre hareket etmelisiniz , örneğin MyParentClass._meta.get_field('email').blank = False( emailYönetici'de devralınan bir alanı zorunlu kılmak için )
Peterino

1
Ooops, üzgünüm, @ utapyngo'nun yukarıdaki kodu doğru, ancak daha sonra sınıf gövdesinin dışına yerleştirilmesi gerekiyor ! Ana sınıf alanını önerdiğim gibi ayarlamak istenmeyen yan etkilere neden olabilir.
Peterino

Tüm alt sınıfların tümünün belirli bir ada sahip bir alana sahip olmasını garanti etmek için, alt sınıfların her birinde bir alanın, soyut üst sınıfta aynı ada sahip bir alandan farklı bir türde olmasını istiyorum. utapyngo'nun kodu bu ihtiyacı karşılamıyor.
Daniel

28

Yani soyut olmadıkça mümkün değildir, ve burada neden: LongNamedRestaurantaynı zamanda bir olan Placebir sınıf olarak değil, aynı zamanda veritabanında sadece. Yer tablosu her saf Placeve her biri için bir giriş içerir LongNamedRestaurant. LongNamedRestaurantyalnızca food_typeyer tablosuna bir referans ve ile fazladan bir tablo oluşturur .

Bunu yaparsanız Place.objects.all(), aynı zamanda a olan her yeri alırsınız LongNamedRestaurantve bu bir Place(olmadan food_type) örneği olur . Yani Place.nameve LongNamedRestaurant.nameaynı veritabanı sütununu paylaşan ve bu nedenle aynı türden olması gerekir.

Bunun normal modeller için mantıklı olduğunu düşünüyorum: her restoran bir yerdir ve en azından o mekanın sahip olduğu her şeye sahip olmalıdır. Belki de bu tutarlılık, 1.10'dan önceki soyut modellerin orada veritabanı problemleri vermemesine rağmen neden mümkün olmamasıdır. @Lampslave'in belirttiği gibi, 1.10'da mümkün oldu. Kişisel olarak dikkatli olmanızı tavsiye ederim: Sub.x Super.x'i geçersiz kılarsa, Sub.x'in Super.x'in bir alt sınıfı olduğundan emin olun, aksi takdirde Sub, Super yerine kullanılamaz.

Çözümler : AUTH_USER_MODELYalnızca e-posta alanını değiştirmeniz gerekiyorsa, epeyce kod çoğaltması içeren özel bir kullanıcı modeli ( ) oluşturabilirsiniz. Alternatif olarak, e-postayı olduğu gibi bırakabilir ve tüm formlarda gerekli olduğundan emin olabilirsiniz. Bu, diğer uygulamaların kullanması durumunda veritabanı bütünlüğünü garanti etmez ve tam tersi şekilde çalışmaz (kullanıcı adı gerekmemesini istiyorsanız).


Sanırım 1.10'daki değişikliklerden kaynaklanıyor: "Soyut temel sınıflardan miras alınan geçersiz kılma model alanlarına izin verildi." docs.djangoproject.com/en/2.0/releases/1.10/#models
lamplave

Henüz çıkmadığı için bundan şüpheliyim, ama bunu eklemek iyi bir şey, teşekkürler!
Mark

19

Bkz. Https://stackoverflow.com/a/6379556/15690 :

class BaseMessage(models.Model):
    is_public = models.BooleanField(default=False)
    # some more fields...

    class Meta:
        abstract = True

class Message(BaseMessage):
    # some fields...
Message._meta.get_field('is_public').default = True

2
AttributeError: öznitelik ayarlanamıyor (((((ancak seçimleri ayarlamaya çalışıyorum)
Alexey

Bu Django
1.11'de

9

Kodunuzu yeni bir uygulamaya yapıştırın, INSTALLED_APPS uygulamasına ekleyin ve syncdb'yi çalıştırın:

django.core.exceptions.FieldError: Local field 'name' in class 'LongNamedRestaurant' clashes with field of similar name from base class 'Place'

Görünüşe göre Django bunu desteklemiyor.


7

Bu süper soğuk kod parçası, soyut üst sınıflardaki alanları 'geçersiz kılmanıza' izin verir.

def AbstractClassWithoutFieldsNamed(cls, *excl):
    """
    Removes unwanted fields from abstract base classes.

    Usage::
    >>> from oscar.apps.address.abstract_models import AbstractBillingAddress

    >>> from koe.meta import AbstractClassWithoutFieldsNamed as without
    >>> class BillingAddress(without(AbstractBillingAddress, 'phone_number')):
    ...     pass
    """
    if cls._meta.abstract:
        remove_fields = [f for f in cls._meta.local_fields if f.name in excl]
        for f in remove_fields:
            cls._meta.local_fields.remove(f)
        return cls
    else:
        raise Exception("Not an abstract model")

Alanlar soyut ana sınıftan çıkarıldığında, ihtiyaç duyduğunuz şekilde yeniden tanımlayabilirsiniz.

Bu benim işim değil. Buradaki orijinal kod: https://gist.github.com/specialunderwear/9d917ddacf3547b646ba


6

Belki Contrib_to_class ile başa çıkabilirsiniz:

class LongNamedRestaurant(Place):

    food_type = models.CharField(max_length=25)

    def __init__(self, *args, **kwargs):
        super(LongNamedRestaurant, self).__init__(*args, **kwargs)
        name = models.CharField(max_length=255)
        name.contribute_to_class(self, 'name')

Syncdb iyi çalışıyor. Bu örneği denemedim, benim durumumda sadece bir kısıtlama parametresini geçersiz kılıyorum, bu yüzden ... bekle ve gör!


1
Ayrıca Contrib_to_class'ın argümanları da garip görünüyor (aynı zamanda yanlış bir yol mu?) Bunu hafızadan yazmışsınız gibi görünüyor. Lütfen test ettiğiniz gerçek kodu verebilir misiniz? Eğer bunu çalıştırdıysan, tam olarak nasıl yaptığını bilmek isterim.
Michael Bylstra

Bu benim için çalışmıyor. Çalışan bir örnekle de ilgilenirdi.
garromark

lütfen blog.jupo.org/2011/11/10/django-model-field-injection'a bakın Contrib_to_class (<ModelClass>, <fieldToReplace>)
goh

3
Place._meta.get_field('name').max_length = 255sınıftaki vücut, hile yapmalı, geçersiz kılmaz __init__(). Daha da özlü olur.
Peterino

4

Bunun eski bir soru olduğunu biliyorum, ancak benzer bir sorun yaşadım ve bir çözüm buldum:

Aşağıdaki dersleri aldım:

class CommonInfo(models.Model):
    image = models.ImageField(blank=True, null=True, default="")

    class Meta:
        abstract = True

class Year(CommonInfo):
    year = models.IntegerField() 

Ancak, süper sınıfın görüntü alanını boş bırakılabilir tutarken Yılın devralınan görüntü alanının gerekli olmasını istedim. Sonunda, doğrulama aşamasında görüntüyü uygulamak için ModelForms'u kullandım:

class YearForm(ModelForm):
    class Meta:
        model = Year

    def clean(self):
        if not self.cleaned_data['image'] or len(self.cleaned_data['image'])==0:
            raise ValidationError("Please provide an image.")

        return self.cleaned_data

admin.py:

class YearAdmin(admin.ModelAdmin):
    form = YearForm

Görünüşe göre bu sadece bazı durumlar için geçerli (kesinlikle alt sınıf alanında daha katı kurallar uygulamanız gereken yerlerde).

Alternatif clean_<fieldname>()olarak clean(), bunun yerine yöntemi kullanabilirsiniz , örneğin bir alanın towndoldurulması gerekirse:

def clean_town(self):
    town = self.cleaned_data["town"]
    if not town or len(town) == 0:
        raise forms.ValidationError("Please enter a town")
    return town

1

Model alanlarını geçersiz kılamazsınız, ancak clean () yöntemini geçersiz kılarak / belirterek kolayca başarılabilir. E-posta alanıyla ilgili sorun yaşadım ve bunu Model düzeyinde benzersiz hale getirmek istedim ve şöyle yaptım:

def clean(self):
    """
    Make sure that email field is unique
    """
    if MyUser.objects.filter(email=self.email):
        raise ValidationError({'email': _('This email is already in use')})

Hata mesajı daha sonra "e-posta" adıyla Form alanına alınır.


Soru, bir karakter alanının maks_uzunluğunu uzatmakla ilgilidir. Bu veritabanı tarafından zorlanırsa, bu "çözüm" yardımcı olmaz. Temel modelde daha uzun maks_uzunluğu belirtmek ve burada daha kısa uzunluğu zorlamak için clean () yöntemini kullanmak geçici bir çözüm olabilir.
DylanYoung

0

Çözümüm bir sonraki kadar basit , modeldeki fo alan özniteliğini monkey patchingnasıl değiştirdiğime dikkat edin :max_lengthnameLongNamedRestaurant

class Place(models.Model):
   name = models.CharField(max_length=20)

class LongNamedRestaurant(Place):
    food_type = models.CharField(max_length=25)
    Place._meta.get_field('name').max_length = 255
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.