Search code examples
djangodjango-modelsdjango-migrations

Django UniqueConstraint doesn't works with ManyToManyField, got exception FieldDoesNotExist


The migration stopped working when switching from ForeignKey between Shop and user to a ManyToManyField. I wanted shops to be able to be owned by different users at the same time:

class Shop(models.Model):
    name = models.CharField('name', max_length=120)
    #user = models.ForeignKey(User, on_delete=models.CASCADE)   ##before
    shopuser= models.ManyToManyField(User, related_name="shopusers", blank=True)    ##after

    class Meta:
        constraints = [models.UniqueConstraint(fields=['shopuser', 'name'], name='user cant have the same shop twice!')] 
    
    ## after:
    @property
    def get_shopuser(self):
        return ", ".join([u.username for u in self.shopuser.all()])

class Warehouse(models.Model):
    address = models.CharField('address', max_length=120)
    user = models.ManyToManyField(User, related_name="User", blank=True)

django.core.exceptions.FieldDoesNotExist: NewShop has no field named 'shopusers'

I thought by choosing a related name I can use multiple relations to the User model? I already tried completely deleting my database (and migrations folder) and migrate from start, but tht did not help :(

Example where I would call the code:

admin.py:

@admin.register(Shop)
class ShopAdmin(admin.ModelAdmin):
    list_display = ("name", "related_shopuser")
    list_filter = ("name", "shopuser")
    fieldsets = [("Requrired Information", {"description": "These fields are required", 
                                            "fields": (("name", "shopuser"))}),]

    def related_shopuser(self, obj):
        return obj.get_shopuser

Where does Djanog migrations get the NewShop from FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))? Is it automatically generated from the ModelName Shop?


Solution

  • Issue

    The UniqueConstraint on ManyToManyField will not work as expected. If you want to enforce the constraint, you should define a intermediate model and connect them using the through--(Django doc) parameter.

    For the sake of simplicity, I assume you have a model as below,

    class Shop(models.Model):
        name = models.CharField('name', max_length=120)
        user = models.ForeignKey(User, on_delete=models.CASCADE)
    
        class Meta:
            constraints = [
                models.UniqueConstraint(
                    fields=[
                        'user',
                        'name'
                    ],
                    name='user cant have the same shop twice!'
                )
            ]

    and now you want make the user field to a ManyToManyField from ForeignKey

    Note: I have changed the field name to users from user, which is more approriate.

    Method-1 : Remove unique constraint

    class Shop(models.Model):
        name = models.CharField('name', max_length=120)
        users = models.ManyToManyField(User)

    Now, run makemigrations and migrate commands

    Method-2: use an intermediate model

    class ShopUserIntermediateModel(models.Model):
        shop = models.ForeignKey('Shop', models.CASCADE)
        user = models.ForeignKey(User, models.CASCADE)
    
        class Meta:
            constraints = [
                models.UniqueConstraint(
                    fields=[
                        'shop',
                        'user'
                    ],
                    name='user cant have the same shop twice!'
                )
            ]
    
    
    class Shop(models.Model):
        name = models.CharField('name', max_length=120)
        users = models.ManyToManyField(User, through=ShopUserIntermediateModel)