22Model that inherits from both Polymorphic and MPTT. 
33""" 
44import  django 
5+ from  six  import  integer_types , string_types 
56from  future .utils  import  with_metaclass 
7+ from  django .contrib .contenttypes .models  import  ContentType 
68from  django .core .exceptions  import  ValidationError 
79from  django .utils .translation  import  ugettext_lazy  as  _ 
8- from  django .utils .six  import  integer_types 
910from  mptt .models  import  MPTTModel , MPTTModelBase , TreeForeignKey , raise_if_unsaved 
1011from  polymorphic .base  import  PolymorphicModelBase 
1112from  polymorphic_tree .managers  import  PolymorphicMPTTModelManager 
@@ -55,15 +56,24 @@ def _validate_parent(self, parent, model_instance):
5556 elif  isinstance (parent , integer_types ):
5657 # TODO: Improve this code, it's a bit of a hack now because the base model is not known in the NodeTypePool. 
5758 base_model  =  _get_base_polymorphic_model (model_instance .__class__ )
58- 
59-  # Get parent, TODO: needs to downcast here to read can_have_children. 
6059 parent  =  base_model .objects .get (pk = parent )
6160 elif  not  isinstance (parent , PolymorphicMPTTModel ):
6261 raise  ValueError ("Unknown parent value" )
6362
6463 if  parent .can_have_children :
6564 return 
6665
66+  can_have_children  =  parent .can_have_children 
67+  if  can_have_children :
68+  child_types  =  parent .get_child_types ()
69+  if  (len (child_types ) ==  0  or 
70+  model_instance .polymorphic_ctype_id  in  child_types ):
71+  return  # child is allowed 
72+  raise  ValidationError (
73+  self .error_messages ['child_not_allowed' ].format (parent ,
74+  parent ._meta .verbose_name ,
75+  model_instance ._meta .verbose_name ))
76+ 
6777 raise  ValidationError (self .error_messages ['no_children_allowed' ])
6878
6979
@@ -75,13 +85,61 @@ class PolymorphicMPTTModel(with_metaclass(PolymorphicMPTTModelBase, MPTTModel, P
7585
7686 #: Whether the node type allows to have children. 
7787 can_have_children  =  True 
88+  #: Allowed child types for this page. 
89+  child_types  =  []
90+  # Cache child types using a class variable to ensure that get_child_types 
91+  # is run once per page class, per django initiation. 
92+  __child_types  =  {}
7893
7994 # Django fields 
8095 if  django .VERSION  >=  (1 , 10 ):
8196 objects  =  PolymorphicMPTTModelManager ()
8297 else :
8398 _default_manager  =  PolymorphicMPTTModelManager ()
8499
100+  @property  
101+  def  page_key (self ):
102+  """ 
103+  A unique key for this page to ensure get_child_types is run once per 
104+  page. 
105+  """ 
106+  return  repr (self )
107+ 
108+  def  get_child_types (self ):
109+  """ 
110+  Get the allowed child types and convert them into content type ids. 
111+  This allows for the lookup of allowed children in the admin tree. 
112+  """ 
113+  key  =  self .page_key 
114+  child_types  =  self ._PolymorphicMPTTModel__child_types 
115+  if  self .can_have_children  and  child_types .setdefault (key , None ) is  None :
116+  new_children  =  []
117+  iterator  =  iter (self .child_types )
118+  for  child  in  iterator :
119+  if  isinstance (child , string_types ):
120+  child  =  str (child ).lower ()
121+  # write self to refer to self 
122+  if  child  ==  'self' :
123+  ct_id  =  self .polymorphic_ctype_id 
124+  else :
125+  # either the name of a model in this app 
126+  # or the full app.model dot string 
127+  # just like a foreign key 
128+  try :
129+  app_label , model  =  child .rsplit ('.' , 1 )
130+  except  ValueError :
131+  app_label  =  self ._meta .app_label 
132+  model  =  child 
133+  ct_id  =  ContentType .objects .get (app_label = app_label ,
134+  model = model ).id 
135+  else :
136+  # pass in a model class 
137+  ct_id  =  ContentType .objects .get_for_model (child ).id 
138+  new_children .append (ct_id )
139+  child_types [key ] =  new_children 
140+  return  child_types [key ]
141+ 
142+ 
85143 class  Meta :
86144 abstract  =  True 
87145 ordering  =  ('tree_id' , 'lft' ,)
0 commit comments