How to calculate average of fields on some if conditions in django? - django

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 I want to calculate average of only those orders where isCompleted = True
Models.py
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)
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')))
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())
)
)
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']
please tell me how can I get the average of only those orders completion time where iscompleted is True

You can specify a filter to Avg to just aggregate on completed orders based on isCompleted like this:
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),
# ^^^ Add this
)
)

if isCompleted:
foo = Gigs.objects.annotate(_average_completionTime=Avg(
ExpressionWrapper(F('gig__orderCompletedTime') F('gig__orderStartTime'), output_field=DurationField())
)
)
return foo

Related

Convert function base view to class based view (DRF)

can someone help me convert this function component to class based view (rest framework concrete view)?
I tried converting but landed with errors where serializer is false.
Product image is required in the query.
class Product(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
stock = models.IntegerField()
is_available = models.BooleanField(default=True)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
class ProductImage(models.Model):
image = models.ImageField(upload_to=get_upload_path)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="product_images")
def store(request, category_slug):
cat = None
products = None
if category_slug != None:
cat = get_object_or_404(Category, slug=category_slug)
products = Product.objects.filter(category=cat)
product_count = products.count()
else:
products = Product.objects.filter(is_available=True)
products.count()
context = {
'products': products,
'product_count': product_count
}
return render(request, 'store.html', context)
what i tried with rest framework.
Serializers:
class ProductImageSerializer(serializers.ModelSerializer):
class Meta: model = ProductImage
fields = ('image',)
class ProductSerializer(serializers.ModelSerializer):
product_images = ProductImageSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = "__all__"
def create(self, validated_data):
profile_data = validated_data.pop('product_images')
product = Product.objects.create(**validated_data)
return product
View:
I tried with concrete views, product is filtered by category.
class Store(generics.ListCreateAPIView):
queryset = Product.objects.filter(is_available=True)
serializer_class = ProductSerializer
lookup_field = 'category_slug'
lookup_url_kwarg = 'category_slug'
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
cat_slug = self.kwargs.get('category_slug')
products = None
if cat_slug is not None:
category = get_object_or_404(Category, slug=cat_slug)
products = queryset.filter(category=category, is_available=True)
else:
products = queryset.filter(is_available=True)
if products.count() == 1:
serializer = ProductSerializer(data=products.first())
elif products.count() > 1:
serializer = ProductSerializer(data=products, many=True)
if not serializer.is_valid():
print(serializer.errors)
data = serializer.data
return Response(data, status=status.HTTP_200_OK)
any help is appreciated.

Moving logic from Django views to models

This is my model:
class Car(models.Model):
make = models.CharField(max_length=30)
model = models.CharField(max_length=30)
rating = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)], default=0, blank=True)
avg_rating = models.FloatField(default=0, blank=True)
rates_number = models.IntegerField(default=0, blank=True)
def __str__(self):
return self.make + ' ' + self.model
What's the best way to move the logic from the following perform_create function (in views.py) to my models?
class CarRate(generics.CreateAPIView):
serializer_class = CarRatingSerializer
queryset = Car.objects.all()
def perform_create(self, serializer):
pk = serializer.validated_data['car_id']
rating = serializer.validated_data['rating']
queryset = Car.objects.all()
car_queryset = get_object_or_404(queryset, pk=pk)
if car_queryset.rates_number == 0:
car_queryset.avg_rating = rating
else:
car_queryset.avg_rating = (car_queryset.avg_rating + rating)/2
car_queryset.avg_rating = round(car_queryset.avg_rating, 1)
car_queryset.rates_number = car_queryset.rates_number + 1
car_queryset.save()
It would be much better to create two models. Think about how you are counting average rating. This would be some better idea for now:
class Car(models.Model):
make = models.CharField(max_length=30)
model = models.CharField(max_length=30)
def rates_number(self):
return self.rates.all().count()
def avg_rating(self):
# count average_rating from relation to Rate objects and return it
return average_rating
class CarRate(models.Model):
value = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(5)], default=0, blank=True)
car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name='rates')

How to calculate the points based on the price of the total purchase in django models?

I am trying to create points earned by users after buying something and placed an order from the frontend. Also, I need to save the points on the database because users later use that points to buy something.
The points system looks like this.
Point System for % of the total purchase
Upto 10,000 = 1 %
10k to 50k =2.75%
50K plus = 5%
I haven't saved the price in DB, I just used it as a property so that it remains safe and cant be changed by anyone. It calculates whenever the get or post API is called.
class Order(models.Model):
ORDER_STATUS = (
('To_Ship', 'To Ship',),
('Shipped', 'Shipped',),
('Delivered', 'Delivered',),
('Cancelled', 'Cancelled',),
)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
order_status = models.CharField(max_length=50,choices=ORDER_STATUS,default='To_Ship')
ordered_date = models.DateTimeField(auto_now_add=True)
ordered = models.BooleanField(default=False)
#property
def total_price(self):
# abc = sum([_.price for _ in self.order_items.all()])
# print(abc)
return sum([_.price for _ in self.order_items.all()])
def __str__(self):
return self.user.email
class Meta:
verbose_name_plural = "Orders"
ordering = ('-id',)
class OrderItem(models.Model):
orderItem_ID = models.CharField(max_length=12, editable=False, default=id_generator)
order = models.ForeignKey(Order,on_delete=models.CASCADE, blank=True,null=True,related_name='order_items')
item = models.ForeignKey(Product, on_delete=models.CASCADE,blank=True, null=True)
order_variants = models.ForeignKey(Variants, on_delete=models.CASCADE,blank=True,null=True)
quantity = models.IntegerField(default=1)
ORDER_STATUS = (
('To_Ship', 'To Ship',),
('Shipped', 'Shipped',),
('Delivered', 'Delivered',),
('Cancelled', 'Cancelled',),
)
order_item_status = models.CharField(max_length=50,choices=ORDER_STATUS,default='To_Ship')
#property
def price(self):
total_item_price = self.quantity * self.order_variants.price
return total_item_price
Updated Code:
class Points(models.Model):
order = models.OneToOneField(Order,on_delete=models.CASCADE,blank=True,null=True)
points_gained = models.IntegerField(default=0)
def collect_points(sender,instance,created,**kwargs):
if created:
if instance.total_price <= 10000:
abc = 0.01* (instance.total_price)
else:
abc = 0.75 * (instance.total_price)
return abc
post_save.connect(collect_points,sender=Order)
def save(self,*args,**kwargs):
self.points_gained = self.collect_points()
super(Points, self).save(*args, **kwargs)
I tried using Django signals and overwrite save function to create points. But when I check db, there are no rows in points table although order is made.
OrderCreate API
class OrderSerializer(serializers.ModelSerializer):
billing_details = BillingDetailsSerializer()
order_items = OrderItemSerializer(many=True)
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
#total_price = serializers.SerializerMethodField(source='get_total_price')
class Meta:
model = Order
fields = ['id','user','ordered_date','order_status', 'ordered', 'order_items', 'total_price', 'billing_details']
# depth = 1
def create(self, validated_data):
user = self.context['request'].user
if not user.is_seller:
order_items = validated_data.pop('order_items')
billing_details = validated_data.pop('billing_details')
order = Order.objects.create(user=user,**validated_data)
BillingDetails.objects.create(user=user,order=order,**billing_details)
for order_items in order_items:
OrderItem.objects.create(order=order,**order_items)
order.save()
return order
else:
raise serializers.ValidationError("This is not a customer account.Please login as customer.")
This answer is based on the comments and updated code.
I would have a relationship between the user and the points model, as the points belong to a user and not an order. Also this enables you to update the points whenever the same user orders again.
This results in the following model and post save signal:
def update_points(sender, instance, created, **kwargs):
if created:
if instance.total_price <= 10000:
points_gained = 0.01 * instance.total_price
else:
points_gained = 0.75 * instance.total_price
try:
# Check if user already has points and update if so
points = Points.objects.get(user=instance.user)
points.points_gained = points_gained
points.save(update_fields=['points_gained'])
except Points.DoesNotExist:
# User does not have points yet, create points
Points.objects.create(user=instance.user,
points_gained=points_gained)
post_save.connect(update_points, sender=Order)
class Points(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, blank=False, null=False)
points_gained = models.IntegerField(default=0)

Django Custom model method with parameters, NON DRY

After much research and trouble i came up with a non DRY solution, Hope someone can make it DRY.
All im trying to get is a calculated Price which takes a parameter and displays in the template accordingly.
i have a function get_price on model vehiclecategory which takes a parameter duration which is received from frontend forms.
MODELS.PY
class VehicleCategory(models.Model):
CATEGORY_CHOICES=(
('E-Cycle', 'E-Cycle'),
('E-Scooter', 'E-Scooter')
)
main_category = models.CharField(max_length=15, choices= CATEGORY_CHOICES)
title = models.CharField(unique=True, max_length=200)
image = models.ImageField(
null=True,
blank=True,
width_field="width_field",
height_field= "height_field",
default= 'e-bike.png',
upload_to='category')
width_field = models.IntegerField(default=250)
height_field = models.IntegerField(default=250)
slug =models.SlugField(max_length=200, db_index=True, unique=True)
def __str__(self):
return self.title
#GET PRICE
def get_price(self, duration):
for item in VehiclePrice.objects.all():
if item.vehicle_category.title == self.title and (duration >= item.slab.start and duration <= item.slab.end):
return item.total_price
class Meta():
verbose_name = "Vehicle Category"
verbose_name_plural = "Vehicle Categories"
class PriceSlab(models.Model):
start = models.IntegerField()
end = models.IntegerField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '%s - %s ' % (self.start, self.end)
class VehiclePrice(CustomerStatus):
help_text= "Ensure no more than 2 digits after decimal"
vehicle_category = models.ForeignKey(VehicleCategory, on_delete= models.SET_NULL, null=True, related_name='vehicle_category_price')
slab = models.ForeignKey(PriceSlab, on_delete=models.CASCADE)
net_price = models.DecimalField(help_text= help_text, max_digits=5, decimal_places=2)
tax_percent = models.DecimalField(help_text=help_text, max_digits=4, decimal_places=2, default=18.00)
discount_percent = models.DecimalField(help_text=help_text,max_digits=4, decimal_places=2, default=0, blank=True)
#property
def total_tax(self):
tax = (self.net_price * self.tax_percent)/100
return tax
#property
def get_price(self):
total = self.net_price + self.total_tax
return total
#property
def total_discount(self):
discount = (self.get_price * self.discount_percent)/100
return discount
#property
def total_price(self):
total = self.get_price - self.total_discount
return round(total)
class Meta():
unique_together=('customer_status','vehicle_category' ,'slab')
def __str__(self):
return '%s - %s - %s' % (self.customer_status, self.vehicle_category, self.slab)
VIEWS.PY
class HomeView(ListView):
template_name = 'app/home.html'
def get(self, request):
if request.method == "GET":
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
if start_date and end_date:
start_date = datetime.strptime(start_date, "%d/%m/%Y").date()
end_date = datetime.strptime(end_date, "%d/%m/%Y").date()
duration = (end_date - start_date).days +1
print(duration)
vehiclecategory= VehicleCategory.objects.all()
context = {
'price1': VehicleCategory.objects.get(main_category= 'E-Cycle', title="Sporty").get_price(duration),
'price2': VehicleCategory.objects.get(main_category= 'E-Cycle', title="Step-Through").get_price(duration),
'price3': VehicleCategory.objects.get(main_category= 'E-Cycle', title="Fatbike").get_price(duration),
'price4': VehicleCategory.objects.get(main_category= 'E-Scooter', title="Scooter").get_price(duration),
'vehiclecategory1': vehiclecategory.filter(main_category= 'E-Cycle', title="Sporty"),
'vehiclecategory1': vehiclecategory.filter(main_category= 'E-Cycle', title="Step-Through"),
'vehiclecategory1': vehiclecategory.filter(main_category= 'E-Cycle', title="Fatbike"),
'vehiclecategory2': vehiclecategory.filter(main_category= 'E-Scooter', title="Scooter"),
'form':CartQuantityForm(),
'dateform': DateForm(),
}
else:
context={'dateform': DateForm(),}
return render(request, self.template_name, context )
after the user inputs the date range, the vehicles are displayed, but when u go to the cart and come back the same page, the page refreshes as a new one. how can keep the date range values intact and render the same page as the user got first time he searched for a vehicle, so that he can add or modify the vehicles selected???
You may put your start & end dates into your URL.
You can create 2 urls record dispatching the same view:
path(r'/prices/', HomeView.as_view())
path(r'/prices/(?P<start>\d{4}-\d{2}-\d{2})_(?P<end>\d{4}-\d{2}-\d{2})', HomeView.as_view())
Then you need to make some changes in your view:
class HomeView(ListView):
template_name = 'app/home.html'
def get(self, request, **kwargs):
start = kwargs.get('start')
end = kwargs.get('end')
if start is None or end is None:
# Ask for dates & Redirect to its new url with dates.
else:
# Check the dates, convert them to date object & do the rest.
Maybe not the best solution but the first thing came to my mind is this one.

Subtract models.DateField to get number of days

I have a simple model that tracks work leave requests:
class LeaveRequest(models.Model):
employee = models.ForeignKey(UserProfile)
supervisor = models.ForeignKey(UserProfile, related_name='+', blank=False, null=False)
submit_date = models.DateField(("Date"), default=datetime.date.today)
leave_type = models.CharField(max_length=64, choices=TYPE_CHOICES)
start_date = models.DateField(("Date"))
return_date = models.DateField(("Date"))
total_days = models.IntegerField()
notes = models.TextField(max_length=1000)
def __unicode__ (self):
return u'%s %s' % (self.employee, self.submit_date)
class Admin:
pass
class Meta:
ordering = ['-submit_date']
In the view I need a function to calculate the number of days requested. Secondarily, I'll need a method to count only weekdays, but for now I've got the following:
def leave_screen(request, id):
records = LeaveRequest.objects.filter(employee=id)
total_days = LeaveRequest.return_date - LeaveRequest.start_date
tpl = 'vacation/leave_request.html'
return render_to_response(tpl, {'records': records })
which produces a attribute error
type object 'LeaveRequest' has no attribute 'return_date
any suggestions?
In total_days, you are calling the model and not the instance of that model - records - that you created.
If you want to view just a single Leave record, you would need to pass the id of the LeaveRequest
def leave_screen(request, id):
records = LeaveRequest.objects.get(id=id)
total_days = records.return_date - records.start_date
tpl = 'vacation/leave_request.html'
return render_to_response(tpl, {'records': records })
The answer that suggests using it as a property will work but I think I'll prefer keeping it as a field and just computing it at the time of insert.
class LeaveRequest(models.Model):
employee = models.ForeignKey(UserProfile)
supervisor = models.ForeignKey(UserProfile, related_name='+', blank=False, null=False)
submit_date = models.DateField(("Date"), default=datetime.date.today)
leave_type = models.CharField(max_length=64, choices=TYPE_CHOICES)
start_date = models.DateField(("Date"))
return_date = models.DateField(("Date"))
total_days = models.IntegerField()
notes = models.TextField(max_length=1000)
def __unicode__ (self):
return u'%s %s' % (self.employee, self.submit_date)
def save(self, *args, **kwargs):
self.total_days = (self.return_date - self.start_date).days
super(LeaveRequest, self).save(*args, **kwargs)
class Admin:
pass
class Meta:
ordering = ['-submit_date']
This way when you put in the logic for excluding weekends you are saving computation to calculate the days everytime at the time of listing all leave requests.
I wouldn't have 'total_days' as a field in the LeaveRequest class, but rather as a property.
class LeaveRequest(models.Model):
(other fields)
#property
def total_days(self):
oneday = datetime.timedelta(days=1)
dt = self.start_date
total_days = 0
while(dt <= self.return_date):
if not dt.isoweekday() in (6, 7):
total_days += 1
dt += oneday
return totaldays
# view function
def leave_screen(request, id):
# get leave request by id
leavereq = LeaveRequest.objects.get(id=id)
return render_to_response("vacation/leave_request.html", {"leavereq": leavereq})
# template code
...
<body>
{{ leavereq.total_days }}
</body>