0

I'm encountering a ValueError in my Django application when trying to save a User object with a related UserRoleAssociation in the admin interface. The error occurs in a multi-database setup where both the UserRoleAssociation and Role models are intended to use the members database. The error message is:

ValueError: Cannot assign "<Role: Role object (67c43a2e-c4e7-4846-bd31-700bf5d35e82)>": the current database router prevents this relation.

I have a Django project with two databases: default and members. The User, UserRoleAssociation, and Role models are all configured to use the members database via admin classes. The error occurs when saving a User object with an inline UserRoleAssociation in the Django admin.

Models

# models.py
class Role(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    role_name = models.CharField(max_length=255, unique=True)

    class Meta:
        db_table = "roles"

class UserRoleAssociation(models.Model):
    user = models.ForeignKey("User", on_delete=models.CASCADE)
    role = models.ForeignKey("Role", on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.role.role_name}"
    
    class Meta:
        unique_together = ["user", "role"]
        db_table = "user_role_association"

class User(models.Model):
    phone_number = models.CharField(max_length=15)
    is_premium = models.BooleanField(default=False)
    roles = models.ManyToManyField("Role", through=UserRoleAssociation, related_name="users")

    class Meta:
        db_table = "users"
        app_label = "members"
        base_manager_name = 'objects'
        default_manager_name = 'objects'

Admin Configuration I use a custom MultiDBModelAdmin and MultiDBTabularInline to enforce the members database:

# admin.py
class MultiDBModelAdmin(admin.ModelAdmin):
    using = "members"

    def save_model(self, request, obj, form, change):
        obj.save(using=self.using)

    def delete_model(self, request, obj):
        obj._state.db = self.using
        obj.delete(using=self.using)
    
    def delete_queryset(self, request, queryset):
        queryset.using(self.using).delete()

    def get_queryset(self, request):
        return super().get_queryset(request).using(self.using)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        return super().formfield_for_foreignkey(db_field, request, using=self.using, **kwargs)

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
    
    def _save_related(self, request, form, formsets, change):
        """Ensure related objects are saved in the correct database."""
        for formset in formsets:
            formset.instance._state.db = self.using  # Force the database
        super()._save_related(request, form, formsets, change)

class MultiDBTabularInline(admin.StackedInline):
    using = "members"

    def get_queryset(self, request):
        return super().get_queryset(request).using(self.using)

    def formfield_for_manytomany(self, db_field, request, **kwargs):
=        # Tell Django to populate ManyToMany widgets using a query
        # on the 'members' database.
        return super().formfield_for_manytomany(
            db_field, request, using=self.using, **kwargs
        )


class UserRoleAssociationInline(MultiDBTabularInline):
    model = UserRoleAssociation
    extra = 1

@admin.register(User)
class MemberAdmin(MultiDBModelAdmin):
    list_display = ["id"]
    inlines = [UserRoleAssociationInline]

The error occurs when saving a User object with a UserRoleAssociation inline in the admin interface.

1 Answer 1

0

Make sure your "DatabaseRouter" allows relation
#routers.py

class MembersRouter:
    def db_for_read(self, model, **hints):
        if model._meta.app_label == "members":
            return "members"
        return None

    def db_for_write(self, model, **hints):
        if model._meta.app_label == "members":
            return "members"
        return None

    def allow_relation(self, obj1, obj2, **hints):
        if obj1._meta.app_label == "members" and obj2._meta.app_label == "members":
            return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        if app_label == "members":
            return db == "members"
        return None

**
Also check you have to add this into your settings.py**
DATABASE_ROUTERS = ['path.to.routers.MembersRouter']

Now, Force correct DB to when saving the inline model "UserRoleSelection"

def _save_related(self, request, form, formsets, change):
    for formset in formsets:
        instances = formset.save(commit=False)
        for obj in instances:
            obj._state.db = self.using
            obj.save(using=self.using)
        formset.save_m2m()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.