I am working on Django where I have two models Gigs and Orders and I am calculating average Completion time of order of every gig.
in order model I have two fields order start time (which I'm sending whenever seller accepts the order) and order completed time (which I'm sending when seller delivered) the order.
but the problem is that if I have data in Orders table related to some gig and I retrieve the gig then it worked perfectly but if I tries to retrieve a gig with no orders yet ( in orders table there is no record/field containing that item/gig) then it gives me following error
Complex aggregates require an alias
Models.py
class Gigs(models.Model):
title = models.CharField(max_length=255)
category = models.ForeignKey(Categories , on_delete=models.CASCADE)
images = models.ImageField(blank=True, null = True, upload_to= upload_path)
price = models.DecimalField(max_digits=6, decimal_places=2)
details = models.TextField()
seller = models.ForeignKey(User,default=None, on_delete=models.CASCADE)
@property
def average_completionTime(self):
if getattr(self, '_average_completionTime', None):
return self._average_completionTime
return self.gig.aggregate(Avg(F('orderCompletedTime') - F('orderStartTime')))
class Orders(models.Model):
buyer = models.ForeignKey(User,default=None, on_delete=models.CASCADE,related_name='buyer_id')
seller = models.ForeignKey(User,default=None, on_delete=models.CASCADE,related_name='seller_id')
item = models.ForeignKey(Gigs,default=None, on_delete=models.CASCADE,related_name='gig')
payment_method= models.CharField(max_length=10)
address = models.CharField(max_length=255)
mobile = models.CharField(max_length=13,default=None)
quantity = models.SmallIntegerField(default=1)
status = models.CharField(max_length=13,default='new order')
orderStartTime = models.DateTimeField(default=timezone.now)
orderCompletedTime = models.DateTimeField(default=timezone.now)
isCompleted = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
Views.py
class RetrieveGigsAPI(GenericAPIView, RetrieveModelMixin):
def get_queryset(self):
return Gigs.objects.annotate(
_average_completionTime=Avg(
ExpressionWrapper(F('gig__orderCompletedTime') - F('gig__orderStartTime'), output_field=DurationField()),
filter=Q(gig__isCompleted=True),
)
)
serializer_class = GigsSerializerWithAvgTime
permission_classes = (AllowAny,)
def get(self, request , *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
Serializers.py
class GigsSerializerWithAvgTime(serializers.ModelSerializer):
average_completionTime = serializers.SerializerMethodField()
def get_average_completionTime(self, obj):
return obj.average_completionTime
class Meta:
model = Gigs
fields = ['id','title','category','price','details','seller','images','average_completionTime']
Example
for example if we have following data in orders table where item is foreign key from gigs table Now if I retrieve gig 22 it worked well and gives me all the fields in Api including average completion time but if I want to get gig 23 or any other it gives me error
but I want it should return all other fields same and average completion time : null
full Trackback of error
Environment:
Request Method: GET
Request URL: http://localhost:8000/seller/GigApi/23/
Django Version: 3.0.11
Python Version: 3.9.1
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'djoser',
'accounts',
'adminuser',
'seller',
'buyer',
'corsheaders']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware']
Traceback (most recent call last):
File "C:\Python39\lib\site-packages\django\db\models\query.py", line 374, in aggregate
arg.default_alias
File "C:\Python39\lib\site-packages\django\db\models\aggregates.py", line 65, in default_alias
raise TypeError("Complex expressions require an alias")
During handling of the above exception (Complex expressions require an alias), another exception occurred:
File "C:\Python39\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Python39\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Python39\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Python39\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
return view_func(*args, **kwargs)
File "C:\Python39\lib\site-packages\django\views\generic\base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Python39\lib\site-packages\rest_framework\views.py", line 509, in dispatch
response = self.handle_exception(exc)
File "C:\Python39\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
self.raise_uncaught_exception(exc)
File "C:\Python39\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
raise exc
File "C:\Python39\lib\site-packages\rest_framework\views.py", line 506, in dispatch
response = handler(request, *args, **kwargs)
File "C:\FYP\backend\seller\views.py", line 76, in get
return self.retrieve(request, *args, **kwargs)
File "C:\Python39\lib\site-packages\rest_framework\mixins.py", line 56, in retrieve
return Response(serializer.data)
File "C:\Python39\lib\site-packages\rest_framework\serializers.py", line 548, in data
ret = super().data
File "C:\Python39\lib\site-packages\rest_framework\serializers.py", line 246, in data
self._data = self.to_representation(self.instance)
File "C:\Python39\lib\site-packages\rest_framework\serializers.py", line 515, in to_representation
ret[field.field_name] = field.to_representation(attribute)
File "C:\Python39\lib\site-packages\rest_framework\fields.py", line 1870, in to_representation
return method(value)
File "C:\FYP\backend\seller\serializers.py", line 21, in get_average_completionTime
return obj.average_completionTime
File "C:\FYP\backend\seller\models.py", line 29, in average_completionTime
return self.gig.aggregate(Avg(F('orderCompletedTime') - F('orderStartTime')))
File "C:\Python39\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Python39\lib\site-packages\django\db\models\query.py", line 376, in aggregate
raise TypeError("Complex aggregates require an alias")
Exception Type: TypeError at /seller/GigApi/23/
Exception Value: Complex aggregates require an alias
CodePudding user response:
As @Iain Shelvington has mentioned, you can set a name to the aggregate like this to resolve the error:
return self.gig.aggregate(average_completion=Avg(F('orderCompletedTime') - F('orderStartTime')))['average_completion']
# ^^^ Add this
And additionally, since in the get_queryset
it can potentially already have a calculated value, you can try to change getattr
to hasattr
and just return:
@property
def average_completionTime(self):
if hasattr(self, '_average_completionTime'):
return self._average_completionTime
return self.gig.aggregate(
average_completion=Avg(F('orderCompletedTime') - F('orderStartTime'))
)['average_completion']
This is to check if _average_completionTime
is already calculated for a gig instance, to prevent calculating it again.