Поля ManyToMany не заполняются или выдают TypeError при создании сущности - PullRequest
0 голосов
/ 10 июня 2018

Это проблема, с которой я боролся некоторое время, и теперь я сдался.Я пишу модель User / Profile в Django 2.0.2 (python 3.6 + postgres 10 в Linux), которая выглядит следующим образом:

class Config_Table(models.Model):

    entity_name = models.TextField(primary_key=True)
    category = models.TextField()
    description = models.TextField(blank=True,default='')

Над таблицей хранится некоторая статическая информация, которая доставляет мне неприятности.

class UserProfile(AbstractUser):

    "The  Profile of a user with details are stored in this model."

    username = models.TextField(primary_key=True, max_length=11)
    first_name = models.TextField(max_length=50,blank=True,default='')
    last_name = models.TextField(max_length=100,blank=True,default='')
    phone_number = models.TextField(max_length=11,blank=True,default='')
    avatar = models.ImageField(blank=True, upload_to='Pictures/')
    GENDER_CHOICES = (
        ('M','Male'),
        ('F','Female'),
    )
    gender = models.CharField(max_length=1,choices=GENDER_CHOICES, default='M')
    city = models.TextField(max_length=25, blank=True, default='NY')
    description = models.TextField(max_length=2000, blank=True, default='')
    interests = models.ManyToManyField(Config_Table, blank=True, default='')
    date_of_birth = models.DateField(blank=True)
    official_docs = models.ImageField(blank=True, upload_to='Pictures/')
    team_name = models.TextField(blank=True,default='')
    debit_card_number = models.IntegerField(blank=True, default=0)
    MUSIC_CHOICES = (
        ('Rock','Rock Music'),
        ('Trad','Traditional Music'),
        ('Elec','Electronic Music'),
        ('Clas','Classical Music')
    )
    favorite_music = ArrayField(models.TextField(blank=True,default=''),size=2,blank=True, default='{}')

    class Meta:
        permissions=(("User","User level permission"),
                     ("Tour","Tourleader level permission"),
                     ("Admin","Administrators"))

my views.py:

class UserList(APIView):
    """
    List all users, or create a new user.
    """
    def get(self, request, format=None):
        users = UserProfile.objects.all()
        target_users = []
        for user in users.iterator():
            if user.is_superuser == False:
                target_users.append(user)
        serializer = UserProfileSerializer(target_users, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = UserProfileSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            print(serializer.errors)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class UserDetail(APIView):
    """
    Retrieve, update or delete a User.
    """
    pk_url_kwarg = 'username'

    def get_object(self, pk):
        try:
            return UserProfile.objects.get(pk=pk)
        except UserProfile.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        user = self.get_object(pk=pk)
        if user.is_superuser == False:
            serializer = UserProfileSerializer(user)
            return Response(serializer.data)
        else:
            return Response(status=status.HTTP_404_NOT_FOUND)

    def put(self, request, pk, format=None):
        user = self.get_object(pk)
        serializer = UserProfileSerializer(user, data=request.data, partial=True)
        if serializer.is_valid():
            for attr, value in serializer.validated_data.items():
                if attr == 'password' and attr is None:
                    serializer.validated_data['password'] = user.password
                    break
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        user= self.get_object(pk)
        user.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

и my serializers.py:

def create(self, validated_data):

    hashed_password = make_password(validated_data['password'])  # get the hashed password

    print(validated_data)

    user = UserProfile(
        username=validated_data['username'],
        email = validated_data['email'],
        first_name= validated_data['first_name'],
        last_name= validated_data['last_name'],
        phone_number=validated_data['phone_number'],
        avatar=validated_data.pop('avatar'),
        gender=validated_data['gender'],
        city=validated_data['city'],
        description=validated_data['description'],
        date_of_birth=validated_data.pop('date_of_birth'),
        # user_type=validated_data['user_type'],
        official_docs=validated_data.pop('official_docs'),
        team_name=validated_data['team_name'],
        debit_card_number=validated_data['debit_card_number'],
        favorite_music=validated_data['favorite_music'],
    )

    user.set_password(hashed_password)
    interest = validated_data['interests']
    user.interests.add(validated_data['interests'])
    user.groups.add(validated_data['groups'])
    user.save()

    return user

Проблема, с которой я сталкиваюсь, возникает внутри сериализатора, когда я хочу отправитьнекоторый объект JSON через http, как показано ниже (пример данных для тестирования):

{
    "username": "12345678004",
    "password": "thisisatest",
    "last_login": null,
    "is_superuser": false,
    "email" : "sample@gmail.com",
    "first_name": "AAA",
    "last_name": "BBB",
    "phone_number": "12045678000",
    "gender": "M",
    "city": "NY",
    "description": "",
    "date_of_birth": "2010-03-28",
    "team_name": "",
    "avatar": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAPBhAREQ8QDQ8PDxIWEBAWFRANDxAQFRUWFhURExUYHSggGBolGxUTITEhJSkrLi4uFx8zODMsNygtLisBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIAOEA4QMBIgACEQEDEQH/xAAbAAEAAgMBAQAAAAAAAAAAAAAABAUBAgMGB//EADIQAQABAwEHAQYEBwAAAAAAAAABAgMRBAUSITFBUXFhIjKRobHBM4HR8RMjQmJyguH/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/APpgCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDIAAAAAAAAAAAAAAAAAAAAAAANrduaq8RGZkGqVY0FdXON2PXn8E/SaOKIzPGrv0jwlAhW9nURHHNU/B3p0tER7sOwDjVpaJj3YcbmzqJjhmJTAFPf0FdPGPaj5/BEejRtVo4rjPKrv38gpRtdtzTXiYxLUAAAAAAAAAAAAAAAAAACIzOO670emi3b/unnP2Q9l2M1zVPKOXlaAAAAAAAAA4azTxct+scpUlVMxVieEw9ErNq2MVRVHXn57grwAAAAAAAAAAAAAAAAbW4zciO8x9QXekt7unpj0diOQAAAAAAAAA5aq3vWJj04eXUB5yRtejF2fMtQAAAAAAAAAAAAAAG9n8an/KPq0InE57A9GMUTmiJ7wyAAAAAAAAADFdWKJntAKDUT/Oq8tGa5zXPlgAAAAAAAAAAAAAAAAFxs29vWMdafolqLR39y9E9J4T4XkTmMxxiQZAAAAAAAARNpXd2xjrV9OqVM4jM8IhSay/v3pnpHLwDgAAAAAAAAAAAAAAAAAAnaDWbvs1cuk9kEB6OJ4Cp0F65E4iJqp7f9W0cgAAAAAVevuXJ4TE00+nHPkDX6ze9mn3es90AAAAAAAAAAAAAAAAAAAddPYqrrxH5z0gHOiiaqsRGZWWm2dEca+Pp0S9Pp6aKeEces9ZdQYpiIjEcIZAAAAAAnkAIep0FNWZp9mfkq71mqirEx+kvQNLtqKqcTGQefEjV6Sbc96Z5T9kcAAAAAAAAAAAAAGaKZmqIjnIN9PZmu5iPznsu7FmKLeI/dppNPFu1jrPOXcAAAAAAAAAAAAGK6YmnExmJUut0v8OvvTPKfsu2l63FduYnlIPPje/ami5MT+8NAAAAAAAAAAAFlsvT/wBc/wCv6oFm3vXYiOsr6imKaIiOUA2AAAAAAAAAAAAAAABE2jp9+1mPep+cKd6NSa6zuX57TxgEcAAAAAAAAAFjsm1xmr8o+6ycdJb3dPTHpxdgAAAAAAAAAAAAAAAAEPadresZ60/RMa105omO8A88NrlOLkx2lqAAAAAAA6aejev0x3lzStmU51XiJkFyAAAAAAAAAAAAAAAAAAACl2jRjUz6oyw2vT7VM+ivAAAAAAATdk/jz4AFsAAAAAAAAAAAAAAAAAAACBtb8KPKrAAAAAH/2Q==",
    "official_docs": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBxAPBhAREQ8QDQ8PDxIWEBAWFRANDxAQFRUWFhURExUYHSggGBolGxUTITEhJSkrLi4uFx8zODMsNygtLisBCgoKBQUFDgUFDisZExkrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrK//AABEIAOEA4QMBIgACEQEDEQH/xAAbAAEAAgMBAQAAAAAAAAAAAAAABAUBAgMGB//EADIQAQABAwEHAQYEBwAAAAAAAAABAgMRBAUSITFBUXFhIjKRobHBM4HR8RMjQmJyguH/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/APpgCIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDIAAAAAAAAAAAAAAAAAAAAAAANrduaq8RGZkGqVY0FdXON2PXn8E/SaOKIzPGrv0jwlAhW9nURHHNU/B3p0tER7sOwDjVpaJj3YcbmzqJjhmJTAFPf0FdPGPaj5/BEejRtVo4rjPKrv38gpRtdtzTXiYxLUAAAAAAAAAAAAAAAAAACIzOO670emi3b/unnP2Q9l2M1zVPKOXlaAAAAAAAAA4azTxct+scpUlVMxVieEw9ErNq2MVRVHXn57grwAAAAAAAAAAAAAAAAbW4zciO8x9QXekt7unpj0diOQAAAAAAAAA5aq3vWJj04eXUB5yRtejF2fMtQAAAAAAAAAAAAAAG9n8an/KPq0InE57A9GMUTmiJ7wyAAAAAAAAADFdWKJntAKDUT/Oq8tGa5zXPlgAAAAAAAAAAAAAAAAFxs29vWMdafolqLR39y9E9J4T4XkTmMxxiQZAAAAAAAARNpXd2xjrV9OqVM4jM8IhSay/v3pnpHLwDgAAAAAAAAAAAAAAAAAAnaDWbvs1cuk9kEB6OJ4Cp0F65E4iJqp7f9W0cgAAAAAVevuXJ4TE00+nHPkDX6ze9mn3es90AAAAAAAAAAAAAAAAAAAddPYqrrxH5z0gHOiiaqsRGZWWm2dEca+Pp0S9Pp6aKeEces9ZdQYpiIjEcIZAAAAAAnkAIep0FNWZp9mfkq71mqirEx+kvQNLtqKqcTGQefEjV6Sbc96Z5T9kcAAAAAAAAAAAAAGaKZmqIjnIN9PZmu5iPznsu7FmKLeI/dppNPFu1jrPOXcAAAAAAAAAAAAGK6YmnExmJUut0v8OvvTPKfsu2l63FduYnlIPPje/ami5MT+8NAAAAAAAAAAAFlsvT/wBc/wCv6oFm3vXYiOsr6imKaIiOUA2AAAAAAAAAAAAAAABE2jp9+1mPep+cKd6NSa6zuX57TxgEcAAAAAAAAAFjsm1xmr8o+6ycdJb3dPTHpxdgAAAAAAAAAAAAAAAAEPadresZ60/RMa105omO8A88NrlOLkx2lqAAAAAAA6aejev0x3lzStmU51XiJkFyAAAAAAAAAAAAAAAAAAACl2jRjUz6oyw2vT7VM+ivAAAAAAATdk/jz4AFsAAAAAAAAAAAAAAAAAAACBtb8KPKrAAAAAH/2Q==",
    "debit_card_number": 0,
    "favorite_music": [],
    "groups": [1],
    "user_permissions": [],
    "interests": ["Ski"]
}

Всегда возвращает TypeError:

user.interests.add(validated_data['interests'])
    Exception Type: TypeError at /users/
    Exception Value: unhashable type: 'list'

Я пробовал разные способы реализации "интересов" и "группы »в функции создания сериализатора.Я попытался проанализировать проверенные данные, выбрав дочерний элемент из Config_table и добавив его сюда, но ни один из них не работает.У меня на самом деле та же проблема с полем "группы".Кажется, что Django не может разархивировать проверенные данные в сериализаторе, и там он выдает ошибку.

Интересным моментом является то, что если я не заполняю поля "группа" и "интересы" при вызове метода POST, он работает нормально, и позже я могу обновить эти поля, вызвав PUT безпроблема.Что я могу сделать с этим?

Ответы [ 2 ]

0 голосов
/ 12 июня 2018

ОК, я решил проблему следующим образом:

Я изменил Config_table модель и добавил поле идентификатора django по умолчанию (технически, просто удалил primary_key из строки entity_name, поэтому django добавил идентификатор AutoField по умолчанию).Заново заполнил базу данных и изменил код в serializers.py, как показано ниже .. используя ответ @neverwalkaloner:

        user = UserProfile(
    ...
    )
    user.set_password(hashed_password)
    user.save()
    interests = []
    for id in validated_data['interests']:
        interests.append(id)
    user.interests.add(*interests)
    for name in validated_data['groups']:
        user.groups.add(name)
    user.save()
    return user

Обратите внимание, что Мне пришлось сохранить объект, прежде чем я хотел работать сManyToManyField.Поскольку django ожидает увидеть что-то в базе данных до добавления некоторых сущностей для полей ManyToMany. Сохраните эту заметку в своем коде.

С этим обходным путем моя проблема была решена.Но мне все еще странно, почему я не мог использовать первичный ключ вместо стандартного.Если у кого-то есть ответ на этот вопрос, я более чем готов услышать.

0 голосов
/ 10 июня 2018

Вы должны передать методу Interest.add объекты различных интересов.Не список имен.Поэтому сначала вам нужно получить объекты по имени, а затем передать распаковывающий список объектов, чтобы добавить метод, используя * синтаксис:

intersts = [] 
for name in validated_data['interests']:
   obj, created = Config_Table.objects.get_or_create(entity_name=name)
   interests.append(obj) 
user.interests.add(*interests)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...