djongo
djongo copied to clipboard
Array and Embedded Fields vs Error **Abstract models cannot be instantiated** at Django 3.2.4
Array and Embedded Fields vs Error Abstract models cannot be instantiated
Python script
# models.py
class Contact(models.Model):
key = models.CharField(max_length=100)
class Meta:
abstract = True
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ('key',)
class Person(models.Model):
_id = models.ObjectIdField()
...
contact = models.ArrayField(
model_container=Contact,
model_form_class=ContactForm,
)
objects = models.DjongoManager()
# admin.py
admin.site.register(Person)
$ pip freeze
Django==3.2.4
djongo==1.3.6
pymongo==3.11.4
...
When I try to add a Person through Django Admin (in /admin/
I've asked about it in StackOverflow
I face the same issue. Looking into the django code, it seems that in version 3.2.0 have been added a check in class Model where abstract models cannot be instantiated: https://github.com/django/django/blob/225d96533a8e05debd402a2bfe566487cc27d95f/django/db/models/base.py#L413
At the moment, downgrading to django 3.1.12 works.
We did it, yeeh, thank you @roomm. It worked. :D :+1:
I wanted to keep django 3.2 to have the last official LTS version of django.
So I found a workaround. It works but it's not beautiful but it works:
Replace in the Meta class "abstract=True" by "managed=False", so the model will be not inserted in db like for abstract. On makemigrations, it will create a migration file, but don't worry, on migrate nothing happen and the database is just ignored.
Then, you will have an error saying that the class cannot have an id with "autofield" type. To fix it, just set the first field (or another) with primary_key=True. It will be ignored by djongo, so this field don't need to be unic (it will not in my case), but django is happy...
This is good until a real implementation in djongo...
I wanted to keep django 3.2 to have the last official LTS version of django.
So I found a workaround. It works but it's not beautiful but it works:
Replace in the Meta class "abstract=True" by "managed=False", so the model will be not inserted in db like for abstract. On makemigrations, it will create a migration file, but don't worry, on migrate nothing happen and the database is just ignored.
Then, you will have an error saying that the class cannot have an id with "autofield" type. To fix it, just set the first field (or another) with primary_key=True. It will be ignored by djongo, so this field don't need to be unic (it will not in my case), but django is happy...
This is good until a real implementation in djongo...
It worked But now I'm facing a different issue with this, I can't run any validation in those fields because of it. if I try to directly send a dictionary (as it will be entering in a normal way) it says that a dict object has no '_meta' (and that behavior is logic)... is there a fix for this?
What I'm doing is building a way of validate data not using Django ORM, but I find this is not going to be efficient at all at the end :/
Btw, thank you for the previous fix :+1:
I was able to get around the AutoField error by modifying the Djongo source, commenting out the check for the AutoField types and successfully migrated the changes. There are other things to work out after that but It’s a start… (yes it’s really hacky but honestly I don’t understand why the restriction is even there in this case). The next problem is that using embedded fields makes you submit “_id” fields on the embedded field data itself when you POST and validate data. Maybe this and the above solution will work?
def _validate_container(self):
for field in self.model_container._meta._get_fields(reverse=False):
“””if isinstance(field, (AutoField,
BigAutoField,
RelatedField)):
raise ValidationError(
f'Field "{field}" of model container:"{self.model_container}" '
f'cannot be of type "{type(field)}"')”””
This way I can at least get data as well as post data with null ids in the embedded fields or faked ObjectIds (from bson import ObjectId) <- yes I realize this is not ideal.
Oddly enough, the main model will generate its own ObjectId in Mongo without requiring an _id field in the model. I haven’t looked at the source enough to know why this is.
I am facing an additional issue, with the workaround proposed by @florealcab we overcome the instantiation issue, but when saving or creating the model that includes the ArrayField we get a TypeError: '' object is not subscriptable.
My models are now defined as:
class Contract(models.Model):
item_id = models.CharField(max_length=50, primary_key=True) # Workaround to prevent issues, not really a primary Key
product_id = models.CharField(max_length=50, blank=True, null=True)
offering = models.CharField(max_length=50) # Offering.pk as Foreign Key is not working for EmbeddedFields
...
class Meta:
managed = False
class Order(models.Model):
_id = models.ObjectIdField()
...
# List of contracts attached to the current order
contracts = models.ArrayField(model_container=Contract)
...
objects = models.DjongoManager()
With those models, when creating an Order with a list of contracts we get the TypeError: 'Contract' object is not subscriptable error
We are using
Django==3.2.8
djongo==1.3.6
@fdelavega I had the same problem. I fixed it by adding this function, in your case add it to the Contract model:
def __getitem__(self, name):
return getattr(self, name)
Now, the class is "subscriptable" :)
yep @florealcab you are right, thank you
I was able to get around the AutoField error by modifying the Djongo source, commenting out the check for the AutoField types and successfully migrated the changes. There are other things to work out after that but It’s a start… (yes it’s really hacky but honestly I don’t understand why the restriction is even there in this case). The next problem is that using embedded fields makes you submit “_id” fields on the embedded field data itself when you POST and validate data. Maybe this and the above solution will work?
def _validate_container(self): for field in self.model_container._meta._get_fields(reverse=False): “””if isinstance(field, (AutoField, BigAutoField, RelatedField)): raise ValidationError( f'Field "{field}" of model container:"{self.model_container}" ' f'cannot be of type "{type(field)}"')”””
This way I can at least get data as well as post data with null ids in the embedded fields or faked ObjectIds (from bson import ObjectId) <- yes I realize this is not ideal.
Oddly enough, the main model will generate its own ObjectId in Mongo without requiring an _id field in the model. I haven’t looked at the source enough to know why this is.
Do you find a way? I'm thinking about to change all the fields to CharField and doing validation outside of the ORM. I just want to make sure, is there any other way that probably not as tedious as this?
I think I pass the "Abstract models cannot be instantiated" error. Just create a instance class
# models.py
class ContactAbstract(models.Model):
key = models.CharField(max_length=100)
class Meta:
abstract = True
class Contact(ContactAbstract):
pass
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ('key',)
class Person(models.Model):
_id = models.ObjectIdField()
...
contact = models.ArrayField(
model_container=Contact,
model_form_class=ContactForm,
)
objects = models.DjongoManager()
Still having this problem. Djongo is very good but it is now very out of date with last update 8 months ago.
Here is a way to work around it:
def __init__(self, *args, **kwargs):
opts = self._meta
opts.abstract = False
super().__init__() # Django 4.2.11
opts.abstract = True