Home > Enterprise >  Getting an error when serializing a list of values [AttributeError: 'list' object has no a
Getting an error when serializing a list of values [AttributeError: 'list' object has no a

Time:06-15

I have two models Project and Shift with a Many-to-one relationship. And I want to collect statistics for all Shift objects.

How this should happen:

The User sends a GET request with a parameter that specifies which Project to calculate statistics for all its Shift, the function is called which calculates statistics and puts it all into the list and returns it, the list should be serialized and sent to the User. But I get an error when I send it.

[AttributeError: 'list' object has no attribute 'user_project']

If I needed to serialize a model or QuerySet I probably wouldn't have any problems, but here I have a regular list. I wrote a separate serializer specifically for values from this list, but nothing works. Most likely I wrote it wrong.

If there is a better way to do it, please advise me.

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

postman

Traceback:

Traceback (most recent call last):
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/views/generic/base.py", line 84, in view
    return self.dispatch(request, *args, **kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
    response = self.handle_exception(exc)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
    raise exc
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
    response = handler(request, *args, **kwargs)
  File "/Users/Tokio/Developer/ShiftBot/shift/views.py", line 83, in get
    return Response(serializer.data)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 555, in data
    ret = super().data
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 253, in data
    self._data = self.to_representation(self.instance)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/serializers.py", line 509, in to_representation
    attribute = field.get_attribute(instance)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/relations.py", line 538, in get_attribute
    relationship = get_attribute(instance, self.source_attrs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/fields.py", line 97, in get_attribute
    instance = getattr(instance, attr)
AttributeError: 'list' object has no attribute 'user_project'

When I remove many=True, queryset like in David Lu's answer.

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/threading.py", line 946, in run
    self._target(*self._args, **self._kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/utils/autoreload.py", line 64, in wrapper
    fn(*args, **kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/management/commands/runserver.py", line 134, in inner_run
    self.check(display_num_errors=True)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/management/base.py", line 487, in check
    all_issues = checks.run_checks(
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/checks/registry.py", line 88, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/checks/urls.py", line 14, in check_url_config
    return check_resolver(resolver)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/core/checks/urls.py", line 24, in check_resolver
    return check_method()
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 480, in check
    for pattern in self.url_patterns:
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/utils/functional.py", line 49, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 696, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/utils/functional.py", line 49, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/urls/resolvers.py", line 689, in urlconf_module
    return import_module(self.urlconf_name)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/Tokio/Developer/ShiftBot/ShiftBot/urls.py", line 7, in <module>
    path('shift/', include('shift.urls')),
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/django/urls/conf.py", line 38, in include
    urlconf_module = import_module(urlconf_module)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/Tokio/Developer/ShiftBot/shift/urls.py", line 2, in <module>
    from shift import views
  File "/Users/Tokio/Developer/ShiftBot/shift/views.py", line 10, in <module>
    from shift.serializers import ProjectSerializer, ShiftSerializer, ShiftStatisticSerializer
  File "/Users/Tokio/Developer/ShiftBot/shift/serializers.py", line 31, in <module>
    class ShiftStatisticSerializer(serializers.Serializer):
  File "/Users/Tokio/Developer/ShiftBot/shift/serializers.py", line 32, in ShiftStatisticSerializer
    user_project = serializers.PrimaryKeyRelatedField()
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/relations.py", line 252, in __init__
    super().__init__(**kwargs)
  File "/Users/Tokio/Developer/ShiftBot/venv/lib/python3.10/site-packages/rest_framework/relations.py", line 107, in __init__
    assert self.queryset is not None or kwargs.get('read_only'), (
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.

CodePudding user response:

I think user_project field is the foreign key field and the attribute should not be the list type. You need to remove the many=True, queryset attribute in the PrimaryKeyRelatedField.

class ShiftStatisticSerializer(serializers.Serializer):
    user_project = serializers.IntegerField()
    ...

CodePudding user response:

The entire purpose of serializing data is to take python object(s) and put them in a format that can be parsed into a json response OR to take a bunch of external data and validate it before doing something with - generally creating or updating a model. Here, you seem to be expecting only an id from your request and want to generate data to pass back. You seem to be basically doing that in your get_shift_statistics function. If you just want to return a list with no keys, I would just return the result of that function wrapped in a Django Rest Framework (DRF) Response object.

Your get request would then look something like this:

def get(self, request):
    statistic = project_services.get_shift_statistics(self.get_queryset())
    return Response(statistic)

and you could rewrite your get_shift_statistics function to remove the validated_data bit. You are not validating data that's passed in, you're calculating it.

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

As to why you are getting the error you are - the serializer expects data in a particular format: either a Django model or a key-value pair dictionary. If you pass it a flat list, it doesn't know how to process it. Also the output of a DRF serializer is always going to be a dictionary, where the keys are the listed attributes and the values are the values the serializer is able to determine from the data you pass it. So a serializer used like this is not really appropriate if you want to return a flat list.

That being said, you CAN pass a list into a DRF serializer, but you have to include the many=True flag, and it will interpret said list as a list of objects to serialize and will then serialize each individually and return back a list of serialized objects.

Is there any reason you require a list on the front end as opposed to a dictionary? I've found that in most cases returning a properly-keyed json object from the backend is often the superior way of passing data. This way you can serialize the data more naturally and use the DRF more in the way it is intended. You can still iterate over the values in the dictionary and have the added benefit of being able to grab any single value by key rather than having to know which index it is located at in a list.

  • Related