Assume, we have model
class BaseModel(models.Model):
is_a = models.BooleanField()
and two models related to this one:
class A(models.Model):
value_1 = models.IntegerField()
base = models.ForeignKey(BaseModel, related_name='a')
class B(models.Model):
value_1 = models.IntegerField()
value_2 = models.IntegerField()
base = models.ForeignKey(BaseModel, related_name='b')
What I need is to refer to A or B depending on is_a property.
For example,
base = BaseModel.objects.get(id=1)
if base.is_a:
obj = A.objects.create(value_1=1, base=base)
else:
obj = B.objects.create(value_1=1, value_2=2, base=base)
return obj
or
if base.is_a:
queryset = base.a.all()
else:
queryset = base.b.all()
return queryset
i.e., every time I have to check the is_a property.
Is there more graceful way?
There are two only related models, A and B, no other ones will appear in the nearest future.
Part of the problem can be solved with django-polymorphic, e.g.:
class A(PolymorphicModel):
...
class B(A):
...
This allows to retrieve all A's and B's with one request like base.b.all(), but the problem here is that every B creates instance of A, which is unwanted.
I've considered GenericForeignKey as well. As far as I understood it has a number of limitations like "1) You can't use GenericForeignKey in query filters ; 2) a GenericForeignKey won't appear in a ModelForm" (from GenericForeignKey or ForeignKey).
One idea is to add choices to the BaseModel to have a string representation of your boolean value. If you set the strings equal to the A and B model names, you can use the model.get_foo_display() method to return the name of the model. Then use the Python getattr() method to access attributes as variables.
class BaseModel(models.Model):
base_model_choices = (
(True, 'A'),
(False, 'B'),
)
is_a = models.BooleanField(choices=base_model_choices)
For example,
base = BaseModel.objects.get(id=1)
queryset = base.getattr(models, get_is_a_display()).all()
obj = getattr(models, get_is_a_display()).objects.create(base=base)
Related
Say I have the models
class A(models.Model):
class B(models.Model):
class C(models.model):
b = models.ForeignKey(B)
class D(models.Model):
c = models.ForeignKey(C)
a = models.ForeignKey(A)
What would a ORM query look like to select all Bs that are related to C's that are related to a specific A through table D?
As mentioned by the comment on your post you can use the autogenerated related name. But it never hurts to set it yourself.
class C(models.model):
b = models.ForeignKey(B, related_name="c")
class D(models.Model):
c = models.ForeignKey(C, related_name="d")
a = models.ForeignKey(A, related_name="d")
Then:
B.objects.filter(c__d__a=specific_a_obj).distinct()
class C (models.Model):
b = models.ForeignKey(on_delete=models.CASCADE, related_name='c_related')
final query:
c_ids = D.objects.values_list(
'c__id', flat=True).filter(a__id="specific A object id palced here")
query = B.objects.filter(c_related__id__in=c_ids).distinct()
I'm trying to add a model class with abstract=True in meta to another abstract class.For example,
class MainRecord(models.Model):
date = models.DateTimeField(auto_now_add=True,null=True)
X_data = models.EmbeddedField(
model_container=X,
model_form_class= X_Form,
)
class X(models.Model):
HRCT = models.BooleanField()
Y = models.EmbeddedField(
model_container=Y,
model_form_class=Y_Form,
)
class Meta:
abstract = True
class Y(models.Model):
Y_present = models.BooleanField()
Location = models.EmbeddedField(
model_container=Location,
model_form_class=Location_Form,
)
class Meta:
abstract = True
In Django admin, I'm able to see the embedded fields and it's corresponding checkboxes. I'm able to add/update values for the boolean field HRCT (Embedded from class X) but I'm not able to save the values/updates in the fields embedded from class Y to class X.
I have these four models all related by ManyToManyFields:
A <--> B <--> C <--> D
Given an A object I need to set a value in its related field in D.
class A(models.Model):
name = models.CharField()
# Get all foo, from differents B, C and D
#property
def get_all_foo(self):
for b in B.objects.filter(...):
for c in C.objects.filter(...):
foo = ';'.join([d.foo for d in D.objects.filter(...)])
return foo
def set_foo(self, value):
# the code I need
class B(models.Model):
a = ManyToManyField(A)
class C(models.Model):
b = ManyToManyField(B)
class D(models.Model):
c = ManyToManyField(C)
foo = models.TextField()
What I need is to set the value of foo in D from a function in A (the same way I get all the values from it). How could I do it?
Firstly, your get_all_foo method is incredibly inefficient, potentially causing thousands of separate database calls. Replace it with:
return ';'.join(D.objects.filter(c__b__a__=self).values_list('foo'))
You can use the same principle to update the D objects from A:
D.objects.filter(c__b__a__=self).update(foo=value)
I am trying to use google's ndb model, adding some auto fields and definitions prior to model definition. The below code works well. My question is, though, any specific ndb model implementation is not used ( given I will be destroyed, if google changes anything) do you see any issue with portability of below
class MetaModel(type):
def __new__(cls,name,bases,attrs):
super_new = super(MetaModel,cls).__new__
if name == "Model":
return super_new(cls,name,bases,attrs)
if attrs.get('auto_date_time',True):
attrs['date_add'] = ndb.DateTimeProperty(auto_now_add= True)
attrs['date_upd'] = ndb.DateTimeProperty(auto_now= True)
attrs['_get_kind'] = classmethod(get_kind)
attrs['__name__'] = name
attr_meta = attrs.get('Meta',None)
if attr_meta is None:
meta = type('meta',(object,),dict())
else:
meta = attr_meta
kwargs= {}
model_module = sys.modules[attrs['__module__']]
kwargs['app_label'] = model_module.__name__.split('.')[-2]
_meta = Options(meta,name,**kwargs)
attrs['_meta'] = _meta
return type(name,(ndb.Model,),attrs)
class Model(object):
__metaclass__ = MetaModel
class TesTModel(Model):
name = ndb.StringProperty(indexed=False)
tm = TestModel(name='This is the test model')
tm.put()
This seems pretty fragile. Sounds like an expando model might work for you?
Edit (based on clarification below): metaclasses and type really should be a last resort. They're confusing and hard to get right. For example, in your code snippet, subclasses of Model get ndb's metaclass instead of the one above:
class T1(Model):
pass
>>> T1().to_dict()
T1 {'date_upd': None, 'date_add': None}
class T2(Model):
auto_date_time = False
class T3(T2):
auto_date_time = True
>>> T2.__metaclass__
<class 'ndb.model.MetaModel'>
>>> T3().to_dict()
{}
You can avoid some craziness by deriving your metaclass from ndb.MetaModel:
class MyMetaModel(ndb.MetaModel):
def __new__(cls, name, bases, attrs):
return super(MyMetaModel,cls).__new__(cls, name, bases, attrs)
class MyModel(ndb.Model):
__metaclass__ = MyMetaModel
class AutoTimeModel(MyModel):
date_add = ndb.DateTimeProperty(auto_now_add=True)
date_upd = ndb.DateTimeProperty(auto_now=True)
Is it possible to instantiate a subclassed model from its parent?
class Object1(models.Model):
field1a = models.CharField()
field1b = models.CharField()
feild1c = models.ForeignKey(Object4)
class Object2(Object1):
field3 = models.CharField()
class Object3(Object1):
field3 = models.CharField()
class Object4(models.Model):
field4 = models.CharField()
What I want to do is create the base class first and then based on some rule instantiate one of the subclasses but using the already created base class.
Something like:
obj4 = Object4(field4='d')
obj1 = Object1(field1a='a', field1b='b', field1c=obj4)
if somerule:
obj2 = Object2(object1_ptr=obj1, field2='2')
else:
obj3 = Object3(object1_ptr=obj1, field3='3')
I don't want to repeat the Object1 fields in the if/else clauses. Is it possible to accomplish this? When I try this I get a Foreign key error;
Cannot add or update a child row: A foreign key constraint fails
I recommend doing something like this:
attr = dict(field1a='a', field1b='b', field1c=obj4)
obj1 = Object1(**attr)
if somerule:
attr["field2"] = 2
obj2 = Object2(**attr)
else:
attr["field3"]='3'
obj3 = Object3(**attr)
Be aware that the dictionary attr changes in place.
What you're doing is almost correct, but if you want to copy it you will have to remove the primary key.
So... this should fix it: del obj2.id
Do note however that if some other model references your model with a foreign key that it references obj1, not obj2. And obj1 will, ofcourse, still exist.