2014-10-28 25 views
3

這裏是models.pyDjango - 爲什麼我無法從admin.py訪問models.py中的類中動態生成的屬性?

class Parent(models.Model): 
    id  = models.CharField(max_length=14, primary_key=True) 
    json_dump = models.TextField(null=False) 

    def __init__(self, *args, **kwargs): 
     super(Base, self).__init__(*args, **kwargs) 
     setattr(self, 'name', json.loads(self.json_dump)['name']) 

    class Meta: 
     abstract = True 


class Child(Parent): 
    magnitude = models.IntegerField() 

在我admin.py我想配置管理兒童有name屬性顯示的,所以我有以下幾點:

class ChildAdmin(admin.ModelAdmin): 
    model = Child 

    def get_list_display(self, request): 
     return ('id', 'name', 'magnitude') 


admin.site.register(Child, ChildAdmin) 

我必須有list_displayget_list_display方法中動態生成,因爲否則Django在啓動時發出錯誤,抱怨name未在Child模型中定義。但是,在運行時,每當從數據庫實例化對象時,name都應該可用,因爲它在__init__方法中設置。

但是,當我嘗試加載管理頁面出現錯誤:

Unable to lookup 'name' on Child or ChildAdmin 

是怎麼回事?

回答

6
<class 'app.admin.ChildAdmin'>: (admin.E108) The value of 'list_display[1]' 
refers to 'name', which is not a callable, an attribute of 'ChildAdmin', 
or an attribute or method on 'app.Child'. 

以上錯誤信息可能是您收到的錯誤信息。抽象類不允許你像這樣繼承抽象類的實例屬性。它正在尋找不存在的Child類的self.name

錯誤的部分,我們想看看的是:

...which is not a callable

都能跟得上。它不是一個可調用的,它是一個屬性。

...an attribute of 'ChildAdmin',

沒有。它不是ChildAdmin類的屬性。

...or an attribute or method on 'app.Child'.

這是絆倒你的部分。 「給出的是」它不是Child類的屬性或方法,而是Parent類。

你想要做的是:

class Parent(models.Model): 
    id = models.CharField(max_length=14, primary_key=True) 
    json_dump = models.TextField(null=False) 

    class Meta: 
     abstract = True 

    @property 
    def name(self): 
     return json.loads(self.json_dump)['name'] 

class Child(Parent): 
    magnitude = models.IntegerField() 

這樣做將使得該屬性可從父Child類。或者,您可以創建一個名爲get_name的函數定義,而不是使用裝飾器@property。我發現第一個更簡單。

對此方法的警告是它不會在運行時保存名稱。如果你想這樣做,你可能想要考慮Django的信號來執行post_save掛鉤來檢索名稱值並在模型中添加name = models.CharField(...)

爲了澄清,Django不支持這一點。在啓動時,下面的代碼運行在list_display屬性檢查:

def _check_list_display_item(self, cls, model, item, label): 
    """ 
    cls=<class 'app.admin.ChildAdmin'> 
    model=<class 'app.models.Child'> 
    item='name' 
    """ 
    # 'name' is not callable 
    if callable(item): 
     return [] 
    # <class 'app.admin.ChildAdmin'> does not have the class attribute 'name' 
    elif hasattr(cls, item): 
     return [] 
    # <class 'app.models.Child'> does not have the class attribute 'name' 
    elif hasattr(model, item): 
     ... 
    else: 
     try: 
      # <class 'app.models.Child'>.Meta does not have a field called 'name' 
      model._meta.get_field(item) 
     except models.FieldDoesNotExist: 
      # This is where you end up. 
      return [ 
       # This is a deliberate repeat of E108; there's more than one path 
       # required to test this condition. 
       checks.Error(
        "The value of '%s' refers to '%s', which is not a callable, an attribute of '%s', or an attribute or method on '%s.%s'." % (
         label, item, cls.__name__, model._meta.app_label, model._meta.object_name 
        ), 
        hint=None, 
        obj=cls, 
        id='admin.E108', 
       ) 
      ] 

正如你所看到的,我已經添加了註釋,以運行以幫助您瞭解正在發生什麼的代碼。你是對的,Child的實例的確有名稱,但這不是Django正在尋找的。它正在尋找一個類屬性,而不是實例屬性。

因此,另一種方法可以解決這個(你會不會像現在這樣,要麼)是這樣做的:

class Parent(models.Model): 
    id   = models.CharField(max_length=14, primary_key=True) 
    json_dump = models.TextField(null=False) 
    name  = '' 
    other_item = '' 
    this_too = '' 
    and_this = '' 

    class Meta: 
     abstract = True 

    def __init__(self, *args, **kwargs): 
     super(Parent, self).__init__(*args, **kwargs) 
     setattr(self, 'name', json.loads(self.json_dump)['name']) 
     setattr(self, 'other_item', json.loads(self.json_dump)['other_item']) 
     setattr(self, 'this_too', json.loads(self.json_dump)['this_too']) 
     setattr(self, 'and_this', json.loads(self.json_dump)['and_this']) 

這工作。我只是測試它。 Django會在類中找到屬性。

+0

非常感謝。我不想使用@property樣式,因爲我有幾十個這樣的屬性,並且想要動態地填充對象。 此外,在Parent的'__init__'方法中,'self'的類型爲'Child',可以通過在'__init__'方法中添加'print type(self)'行來驗證。所以'setattr(self,'name',json.loads(self.json_dump)['name'])'應該設置Child對象的'name'屬性。 – jononomo 2014-10-28 22:06:07

+0

@JonCrowell我已經更新了我的答案,爲什麼Django(包含Django代碼)不喜歡你正在做的和一個替代(hacky)解決方案。查看更新答案的底部。 – 2014-10-28 22:48:13

+0

你拯救我的一天!謝謝,它在Django 1.11 [從經理註釋的字段]工作(https://stackoverflow.com/questions/17682567/how-to-add-a-calculated-field-to-a-django-model/42491803# 42491803)課也是如此。 – Mrdev 2017-08-04 13:54:00

0

正如doc提到,改變Parent模式是這樣的:

class Parent(models.Model): 
    id  = models.CharField(max_length=14, primary_key=True) 
    json_dump = models.TextField(null=False) 

    def name(self): 
     return json.loads(self.json_dump)['name'] 

    class Meta: 
     abstract = True 

所以name屬性將被顯示。

+1

我的例子被簡化了 - 實際上有幾十個屬性和一個更復雜的繼承層次結構。 – jononomo 2014-10-28 21:45:14

+0

爲了模擬數據屬性'name',該方法應該被稱爲'get_name',而不是'name'。 – 2014-11-19 09:24:40

相關問題