djangodjango-rest-frameworkbackendhttprequest

Why do I get the email but not the ID of the user in Django when using users_data = validated_data.pop(‘users’, [])?


I am working on a Django project and encountering an issue related to extracting user data in a serializer's create method.

In the code, users_data contains email addresses of users, but I need to associate users with the task using their IDs instead. However, when I replace User.objects.get(email=user_data) with User.objects.get(id=user_data), I encounter an error. How can I modify my serializer's create method to correctly associate users with their IDs instead of emails?

Here is a snippet of my code.

Payload:

{
    "id": null,
    "title": "Task Title",
    "description": "Task Description",
    "due_to": "2024-06-28T22:00:00.000Z",
    "created": null,
    "updated": null,
    "priority": "LOW",
    "category": "TECHNICAL_TASK",
    "status": "TO_DO",
    "subtasks": [
        {
            "task_id": null,
            "description": "Subtask 1",
            "is_done": false
        }
    ],
    "users": [
        1
    ]
}
class BaseTaskSerializer(serializers.ModelSerializer):
    """Serializes a task object"""
    subtasks = SubtaskSerializer(many=True, required=False)

    class Meta:
        model = Task
        fields = ['id', 'title', 'description', 'due_to', 'created', 'updated', 'priority', 'category', 'status',
                  'subtasks', 'users']
        read_only_fields = ['created', 'updated']

    def create(self, validated_data):
        #print('self: ', self)
        users_data = validated_data.pop('users', [])
        subtasks_data = validated_data.pop('subtasks', [])
        task = Task.objects.create(**validated_data)
        print(users_data)
        #print('validated_data', validated_data)
        #print('users_data', users_data)

        for user_data in users_data:
            #print(user_data)
            #print('users_dataaa: ', users_data)
            #user = User.objects.get(id=user_data)
            user = User.objects.get(email=user_data)
            task.users.add(user)

        for subtask_data in subtasks_data:
            subtask_data['task_id'] = task.id
            SubtaskSerializer().create(validated_data=subtask_data)

        return task


class WriteTaskSerializer(BaseTaskSerializer):
    users = serializers.PrimaryKeyRelatedField(queryset=User.objects.all().order_by('id'), many=True)


class ReadTaskSerializer(BaseTaskSerializer):
    users = UserSerializer(many=True, required=False)
class Task(models.Model):
    class Priority(models.TextChoices):
        LOW = "LOW"
        MEDIUM = "MEDIUM"
        URGENT = "URGENT"

    class Category(models.TextChoices):
        TECHNICAL_TASK = "TECHNICAL_TASK"
        USER_STORY = "USER_STORY"

    class TaskStatus(models.TextChoices):
        TO_DO = "TO_DO"
        AWAIT_FEEDBACK = "AWAIT_FEEDBACK"
        IN_PROGRESS = "IN_PROGRESS"
        DONE = "DONE"

    id = models.AutoField(primary_key=True)
    title = models.TextField()
    description = models.TextField(blank=True, null=True)
    due_to = models.DateTimeField()
    created = models.DateTimeField()
    updated = models.DateTimeField(auto_now_add=True)
    priority = models.TextField(choices=Priority.choices)
    category = models.TextField(choices=Category.choices)
    status = models.TextField(choices=TaskStatus.choices)
    users = models.ManyToManyField(User, related_name='tasks')

    def save(self, *args, **kwargs):
        if not self.id:
            self.created = timezone.now()
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.title}"
class UserManager(BaseUserManager):
    """Manager for user"""

    def create_user(self, email, name, password=None, **extra_fields):
        """Create, save and return a new user."""
        if not email:
            raise ValueError('User must have an email address.')
        user = self.model(email=self.normalize_email(email), name=name, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, name, password):
        """Create and save a new superuser with given details"""
        user = self.create_user(email, name, password)
        user.is_superuser = True
        user.is_staff = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    """Database model for users in the system"""
    id = models.AutoField(primary_key=True)
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=50)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    avatar_color = models.CharField(max_length=7)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

    def save(self, *args, **kwargs):
        if not self.pk:
            self.avatar_color = random.choice([
                '#FF5733', '#C70039', '#900C3F', '#581845',
                '#8E44AD', '#1F618D', '#008000', '#A52A2A', '#000080'
            ])
        super().save(*args, **kwargs)

    def get_full_name(self):
        """Retrieve full name for user"""
        return self.name

    def __str__(self):
        """Return string representation of user"""
        return self.email
class UserSerializer(serializers.ModelSerializer):
    """Serializer for the user object."""

    class Meta:
        model = get_user_model()
        fields = ['id', 'email', 'password', 'name', 'phone_number', 'avatar_color']
        extra_kwargs = {
            'email': {'required': False},
            'password': {'required': False, 'write_only': True, 'style': {'input_type': 'password'}, 'min_length': 6},
            'name': {'required': False},
            'phone_number': {'required': False},
            'avatar_color': {'read_only': True}
        }

    def create(self, validated_data):
        """Create and return a user with encrypted password."""
        return get_user_model().objects.create_user(**validated_data)

    def update(self, instance, validated_data):
        """Update and return user."""
        password = validated_data.pop('password', None)
        user = super().update(instance, validated_data)

        if password:
            user.set_password(password)
            user.save()

        return user


class AuthTokenSerializer(serializers.Serializer):
    """Serializer for the user auth token."""
    email = serializers.EmailField()
    password = serializers.CharField(
        style={'input_type': 'password'},
        trim_whitespace=False,
    )

    def validate(self, attrs):
        """Validate and authenticate the user."""
        email = attrs.get('email')
        password = attrs.get('password')
        user = authenticate(
            request=self.context.get('request'),
            username=email,
            password=password,
        )
        if not user:
            msg = _('Unable to authenticate with provided credentials.')
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user
        return attrs

Solution

  • DRF is deserializing your user_data into actual user objects. That means you can change to this:

            for user_data in users_data:
                user = User.objects.get(id=user_data.id)