0

I am a newbie at Django and I have come across this problem with my code. I have a Custom User Model and an Account model which are related by many-to-many field.

During SignUp a user is asked to either create an Account or not ( join other account through a link ).

  • If the User creates an Account then he is the owner of the account and other Users can join the account.(Did not finish the code for ownership)
  • One User can be a part of several accounts at the same time.
  • Creation of Account(or not) and the User takes place in the Signup view.

I read up about the nested serializer in the documentation and i think this should create the two models instances. How to create relationships in one view using nested serializers? Other Approaches to solve the issue?

Models

class Account(models.Model):
    AccountName = models.TextField(max_length=100, blank=False, null=False)
class User(AbstractBaseUser):
    AccountName = models.ManyToManyField(Account)
    CreateAccount = models.BooleanField(blank=False, null=False)
    EmailId = models.EmailField(max_length=128, blank=False, null=False, unique=True)

    is_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    USERNAME_FIELD = 'EmailId'
    REQUIRED_FIELDS = ['AccountName', 'CreateAccount',]

    # Implemented the other req. functions

    objects = MyAccountManager()

Serializers

class AccountCreationSerializer(ModelSerializer):
    class Meta:
        model = Account
        fields = ['AccountName']
class SignUpSerializer(ModelSerializer):

    AccountName = AccountCreationSerializer()

    class Meta:
        model = User
        fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
        extra_kwargs = {'password': {'write_only': True, 'required': True}}

    def create(self, validated_data):
        AccountName = validated_data.pop('AccountName')

        if validated_data['CreateAccount']: #Create only when this is True
            Account.objects.create(AccountName=AccountName, **AccountName)

        userAcc = User.objects.create_user(**validated_data)

        return userAcc

View

class SignUpView(APIView):

    def post(request):
        # to edit
        signup_serializer = SignUpSerializer(data=request.data)
        
        # rest of the view

The request // Ignoring the quotes

EmailID: xyz@gmail.com
AccountName: TestAcc
CreateAccount: False
Password: ****

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

Create_user in Custom model

    def create_user(self, EmailId, AccountName, CreateAccount, password):
        if not EmailId:
            raise ValueError("Users must have an email")

        user = self.model(
            EmailId=self.normalize_email(EmailId),
            AccountName=AccountName,
            CreateAccount=CreateAccount,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

I am pretty sure I am making some mistake regarding the manytomany field but haven't been able to figure out the solution. Any help would be of benefit to me. TIA!
otaku_weeb
  • 107
  • 10
  • https://stackoverflow.com/questions/50015204/direct-assignment-to-the-forward-side-of-a-many-to-many-set-is-prohibited-use-e – iklinac Aug 29 '20 at 20:55

1 Answers1

2

You can not save value directly to many-to-many field. Database does not allow you to do so. It only allows you to add them for associating the relationship between the two tables ( i.e User, Account ). Replace your code segment for Serializer file with the following one.

class AccountCreationSerializer(ModelSerializer):
    class Meta:
        model = Account
        fields = ['AccountName']


class SignUpSerializer(ModelSerializer):
    AccountName = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = ['EmailId', 'AccountName', 'CreateAccount', 'password']
        extra_kwargs = {'password': {'write_only': True, 'required': True}}

    def validate(self, attrs):
        attrs = super(SignUpSerializer, self).validate(attrs=attrs)
        attrs.update({"AccountName": self.initial_data.get("AccountName")})
        return attrs

    def create(self, validated_data):
        AccountName = validated_data.pop('AccountName')
        acc = Account.objects.create(AccountName=AccountName) if "CreateAccount" in validated_data and validated_data['CreateAccount'] else None

        userAcc = User.objects.create_user(**validated_data)
        if acc:
            userAcc.AccountName.add(acc)

        return userAcc

Finally, replace your SignUpView class in the following way:

class SignUpView(APIView):
    serializer_class = SignUpSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        is_valid_serializer = serializer.is_valid(raise_exception=True)
        if is_valid_serializer:
            with transaction.atomic():
                serializer.save()
        # Rest of your code segments to finalize the response

UPDATE

There is a problem with your create_user method. You are here passing the many-to-many field reference (AccountName), which you shouldn't. As I mentioned earlier, you can not save directly many-to-many field. You just need to associate the relation between them. Omit that and it will work!!!

Follow this new definition for this method (create_user).

    def create_user(self, EmailId, CreateAccount, password):
        if not EmailId:
            raise ValueError("Users must have an email")

        user = self.model(EmailId=self.normalize_email(EmailId), CreateAccount=CreateAccount)

        user.set_password(password)
        user.save(using=self._db)
        return user
sayem
  • 171
  • 1
  • 11
  • I added the code but it stills behave the same. I need to dynamically add an instance of Account and User and associate their relation in the same runtime and this doesn't bind them together i think. I am creating a superuser and upon providing details for Account and CreateAccount = True, It still gives me the same error. – otaku_weeb Aug 29 '20 at 21:13
  • @otaku_weeb, I have edited the answer. Check it. Hope it will help you this time. – sayem Aug 29 '20 at 22:37
  • It still throws the same error `Direct assignment to the forward side of a many-to-many set is prohibited. Use AccountName.set() instead.` – otaku_weeb Aug 29 '20 at 23:40
  • @otaku_weeb, I don't think so. Perhaps you are not following the source code as exactly as I stated here. In my source code, since I have already handled the `many-to-many` field related case properly, the previous error shouldn't have raised again. That solution I gave here was already tested properly using my machine. The API is working properly. If you feel that it is still not as expected, then you need to update your question for these methods (`post`, `create_user`) and the model manager (`MyAccountManager`) as well. – sayem Aug 30 '20 at 08:28
  • I have added included the create_user function. This is how i have alrways created my custom users. Would you please tell me what the problem is with it? And thanks a lot for helping so far. – otaku_weeb Aug 30 '20 at 11:28
  • 1
    @otaku_weeb, Please check the **UPDATE** section of my answer. – sayem Aug 30 '20 at 11:45
  • @ SayemBari thank you this works perfectly. Although creating a superuser with the account is still not possible but it works well otherwise! Thanks a lot man! – otaku_weeb Aug 30 '20 at 21:04