pythondjangopython-social-authmanytomanyfield

Django with social auth: Direct assignment to the forward side of a many-to-many set is prohibited


When trying to authenticate (create) a user in Django, I get the following error:

Direct assignment to the forward side of a many-to-many set is prohibited. Use posts.set() instead.

I'm aware that there are similar questions to this on Stack overflow, and that according to the documentation:

[The model] needs to have a value for field "id" before this many-to-many relationship can be used. ... You can’t associate it until it’s been saved.

However, since I'm using Python Social Auth, it creates the user in the backend, so I'm not able to save the user and modify the field later. (Although for me personally, I don't need to add anything to the many-to-many field when first creating the user)

Here's my code:

models.py

...
from waters.models import Water

class User(AbstractUser):
    id = models.AutoField(primary_key=True)
    posts = models.ManyToManyField(Water) # MANY TO MANY FIELD CAUSING ERROR
...

oauth.py

from social_core.backends.oauth import BaseOAuth2

class <name>Oauth2(BaseOAuth2):
    name = "<name>"
    AUTHORIZATION_URL = "<url>"
    ACCESS_TOKEN_URL = "<url>"
    ACCESS_TOKEN_METHOD = "POST"
    EXTRA_DATA = [("refresh_token", "refresh_token", True), ("expires_in", "expires")]

    def get_scope(self):
        return ["read"]

    def get_user_details(self, response):
        profile = self.get_json("<url>", params={"access_token": response["access_token"]})
        return {
            "id": profile["id"],
            "username": profile["username"],
            ...
        }

    def get_user_id(self, details, response):
        return details["id"]

How can I resolve this error without modifying any of the social auth source files?


Solution

  • I was able to bypass the error by extending the social authentication pipeline (documentation). First, I made a method to create the user appropriately to bypass the error (as stated in the answer linked in the question):

    authentication/pipeline.py

    from authentication.models import User
    
    def create_user(backend, user, response, *args, **kwargs):
        u = User()
        for k, v in kwargs["details"].items():
            setattr(u, k, v)
        u.save()
        # Optionally update many to many field here
    

    and plopped that before social auth's create_user method:

    settings.py

    ...
    SOCIAL_AUTH_PIPELINE = (
        "social_core.pipeline.social_auth.social_details",
        "social_core.pipeline.social_auth.social_uid",
        "social_core.pipeline.social_auth.auth_allowed",
        "social_core.pipeline.social_auth.social_user",
        "social_core.pipeline.social_auth.associate_by_email",
        "authentication.pipeline.create_user", #ADDED
        "social_core.pipeline.user.create_user",
        "social_core.pipeline.social_auth.associate_user",
        "social_core.pipeline.social_auth.load_extra_data",
    )
    ...