Получение ошибки при сериализации списка значений [AttributeError: объект 'list' не имеет атрибута 'user_project'].
У меня есть две модели Project и Shift с отношением Many-to-one. И я хочу собрать статистику для всех объектов Shift.
Как это должно происходить:
Пользователь отправляет запрос GET с параметром, который указывает какой Проект нужно посчитать статистику по всем его Сменам, вызывается функция, которая считает статистику, складывает все в список и возвращает его, список должен быть сериализован и отправлен пользователю. Но при отправке я получаю ошибку
[AttributeError: объект 'list' не имеет атрибута 'user_project']
Если бы мне нужно было сериализовать модель или QuerySet, у меня, вероятно, не было бы никаких проблем, но здесь у меня обычный список. Я написал отдельный сериализатор специально для значений из этого списка, но ничего не работает. Скорее всего, я написал его неправильно
Если есть лучший способ сделать это, пожалуйста, посоветуйте мне.
models.py
class Project(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
task = models.CharField(max_length=128)
technical_requirement = models.TextField()
customer = models.CharField(max_length=64, blank=True)
customer_email = models.EmailField(blank=True)
start_of_the_project = models.DateField()
salary_per_hour = models.FloatField()
project_cost = models.FloatField(blank=True, default=0)
project_duration = models.DurationField(blank=True, default=datetime.timedelta(0))
class Shift(models.Model):
user_project = models.ForeignKey('Project', on_delete=models.CASCADE)
shift_start_time = models.DateTimeField()
shift_end_time = models.DateTimeField()
shift_duration_time = models.DurationField(blank=True, null=True)
salary_per_shift = models.FloatField(blank=True, null=True)
serializers.py
class ShiftStatisticSerializer(serializers.Serializer):
user_project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all(), many=True)
number_of_shifts = serializers.IntegerField()
number_of_hours = serializers.FloatField()
duration_mean = serializers.FloatField()
salary_mean = serializers.FloatField()
project_cost = serializers.FloatField()
views.py
class ShiftStatisticView(APIView):
queryset = Shift.objects.all()
authentication_classes = [authentication.JWTAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
serializer = ShiftStatisticSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
valid = serializer.validated_data.pop('user_project')
query = self.get_queryset()
statistic = project_services.get_shift_statistics(validated_data=valid[0], queryset=query)
serializer = ShiftStatisticSerializer(statistic)
return Response(serializer.data)
def get_queryset(self):
return self.queryset.filter(user_project_id=self.request.data['user_project'])
project_services.py
def get_shift_statistics(validated_data, queryset):
df = pd.DataFrame(list(queryset.values()))
number_of_shifts = df["id"].count()
number_of_hours = df["shift_duration_time"].sum() / np.timedelta64(1, 'h')
duration_mean = df["shift_duration_time"].mean() / np.timedelta64(1, 'h')
salary_mean = df["salary_per_shift"].mean()
statistics = [validated_data,
number_of_shifts,
number_of_hours,
duration_mean,
salary_mean,
validated_data.project_cost,]
return statistics
Я думаю, что поле user_project
является полем внешнего ключа и атрибут не должен быть списочного типа. Вам нужно удалить атрибуты many=True
, queryset
в PrimaryKeyRelatedField
.
class ShiftStatisticSerializer(serializers.Serializer):
user_project = serializers.IntegerField()
...
Вся цель сериализации данных заключается в том, чтобы взять объект(ы) python и поместить их в формат, который можно разобрать в json-ответ ИЛИ взять кучу внешних данных и проверить их перед тем, как что-то с ними сделать - как правило, создать или обновить модель. Здесь, похоже, вы ожидаете от запроса только id и хотите сгенерировать данные для передачи обратно. Похоже, что в основном вы делаете это в своей функции get_shift_statistics
. Если вы просто хотите вернуть список без ключей, я бы просто вернул результат этой функции, обернутый в объект Django Rest Framework (DRF) Response
.
Ваш запрос get будет выглядеть следующим образом:
def get(self, request):
statistic = project_services.get_shift_statistics(self.get_queryset())
return Response(statistic)
и вы могли бы переписать свою функцию get_shift_statistics
, чтобы удалить бит validated_data
. Вы не проверяете передаваемые данные, а вычисляете их.
def get_shift_statistics(queryset):
df = pd.DataFrame(list(queryset.values()))
number_of_shifts = df["id"].count()
number_of_hours = df["shift_duration_time"].sum() / np.timedelta64(1, 'h')
duration_mean = df["shift_duration_time"].mean() / np.timedelta64(1, 'h')
salary_mean = df["salary_per_shift"].mean()
statistics = [number_of_shifts,
number_of_hours,
duration_mean,
salary_mean,
queryset.first().user_project.project_cost,]
return statistics
Что касается того, почему вы получаете такую ошибку - сериализатор ожидает данные в определенном формате: либо модель Django, либо словарь пар ключ-значение. Если вы передадите ему плоский список, он не знает, как его обработать. Кроме того, результатом работы сериализатора DRF всегда будет словарь, где ключи - это перечисленные атрибуты, а значения - это значения, которые сериализатор может определить из данных, которые вы ему передаете. Поэтому сериализатор, используемый подобным образом, не совсем подходит, если вы хотите вернуть плоский список.
При этом вы можете передать список в сериализатор DRF, но вы должны включить флаг many=True
, и он будет интерпретировать этот список как список объектов для сериализации, а затем сериализует каждый объект по отдельности и вернет обратно список сериализованных объектов.
Есть ли какая-то причина, по которой вам требуется список на фронт-энде, а не словарь? Я обнаружил, что в большинстве случаев возврат json-объекта с правильным ключом из бэкенда часто является лучшим способом передачи данных. Так вы сможете более естественно сериализовать данные и использовать DRF в соответствии с его назначением. Вы все еще можете выполнять итерации по значениям в словаре и имеете дополнительное преимущество - возможность получить любое отдельное значение по ключу, а не знать, в каком индексе оно находится в списке.