djongo icon indicating copy to clipboard operation
djongo copied to clipboard

Array and Embedded Fields vs Error **Abstract models cannot be instantiated** at Django 3.2.4

Open artu-hnrq opened this issue 3 years ago • 14 comments

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//person/add/) I receive the error Abstract models cannot be instantiated from .../django/db/models/base.py

I've asked about it in StackOverflow

artu-hnrq avatar Jun 09 '21 16:06 artu-hnrq

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.

roomm avatar Jun 17 '21 14:06 roomm

We did it, yeeh, thank you @roomm. It worked. :D :+1:

cscervantes avatar Jun 24 '21 07:06 cscervantes

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...

florealcab avatar Aug 10 '21 13:08 florealcab

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:

NandoVasconcellos avatar Aug 18 '21 11:08 NandoVasconcellos

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.

eqprog avatar Aug 19 '21 03:08 eqprog

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 avatar Dec 16 '21 19:12 fdelavega

@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" :)

florealcab avatar Dec 16 '21 19:12 florealcab

yep @florealcab you are right, thank you

fdelavega avatar Dec 16 '21 19:12 fdelavega

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?

prasetyaputraa avatar Feb 16 '22 14:02 prasetyaputraa

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()

nolandpham avatar Aug 22 '22 17:08 nolandpham

Still having this problem. Djongo is very good but it is now very out of date with last update 8 months ago.

thethiny avatar Feb 21 '23 20:02 thethiny

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

caot avatar Mar 14 '24 17:03 caot