Hello, I want to create a list similar to olx.com
. Specifically, when a user searches for a product, the results should display two featured products at the beginning, followed by eight unfeatured products(at same time it look at rank when putting at position). This pattern should be repeated on each page. Additionally, the system should calculate and consider the ranking of products.
this is my model:
class Product(BaseModel):
class Status(models.TextChoices):
APPROVED = 'approved', 'APPROVED'
WAITING = 'waiting' , 'WAITING',
REJECTED = 'refected', 'REJECTED'
owner = models.ForeignKey(
'account.Account',
on_delete=models.CASCADE,
blank=False,
null=False,
verbose_name=_("Owner"),
)
title = models.CharField(
max_length=150,
help_text=_("User-friendly attribute name"),
verbose_name=_("Name"),
)
web_id = ShortUUIDField(unique=True, max_length=5)
slug = models.SlugField(
max_length=200,
blank=True,
db_index=True,
help_text=_("Short attribute label"),
verbose_name=_("Slug"),
)
category = models.ForeignKey(
'tag_map.Category',
on_delete=models.PROTECT,
blank=False,
null=False,
verbose_name=_("Category of product"),
)
content = RichTextUploadingField(
blank=True,
default="",
help_text=_("Short description"),
verbose_name=_("Description"),
)
location = models.ForeignKey(
'tag_map.Location',
on_delete=models.PROTECT,
blank=True,
null=True
)
rank = models.IntegerField(blank=True, null=True)
is_featured = models.BooleanField(default=False, db_index=True)
is_public = models.BooleanField(default=True)
status = models.CharField(
max_length=8,
choices=Status.choices,
default=Status.WAITING
)
class Meta:
ordering = ['rank', '-created']
verbose_name = _("Product")
verbose_name_plural = _("Products")
def __str__(self):
return f'{self.title} | {self.created.strftime("%Y-%m-%d")} | {self.rank}'
def save(self, *args, **kwargs):
if self.rank is None:
self.rank = self.generate_rank_with_date()
self.slug = self.generate_slug()
super().save(*args, **kwargs)
def generate_slug(self):
return slugify(self.category.name_uz + self.title, separator='-')
class Tariff(BaseModel):
name = models.CharField(max_length=100, verbose_name=_('Tariff Name'))
duration_in_days = models.PositiveIntegerField(default=0, verbose_name='Duration in Days')
duration_in_hours = models.PositiveIntegerField(default=0, verbose_name="Duration in Hours")
price = models.DecimalField(default=0, max_digits=10, decimal_places=2)
active = models.BooleanField(default=False)
def __str__(self):
return f"{self.name} ({self.duration_in_days} days, {self.duration_in_hours} hours) -- {self.price} uzs"
def total_duration(self):
return timedelta(days=self.duration_in_days, hours=self.duration_in_hours)
class FeaturedProduct(BaseModel):
product = models.OneToOneField(
'product.Product',
on_delete=models.CASCADE,
related_name='featureds',
verbose_name=_("Product"))
tariff = models.ForeignKey(
'product.Tariff',
on_delete=models.PROTECT,
blank=True,
null=True,
verbose_name="Tariff"
)
start_time = models.DateTimeField(default=now, verbose_name="Start Time")
end_time = models.DateTimeField(blank=True, null=True, verbose_name="End Time")
def __str__(self):
return f"{self.product.title} (Featured until {self.end_time})"
def save(self, *args, **kwargs):
if not self.end_time:
self.end_time = self.start_time + self.tariff.total_duration()
super().save(*args, **kwargs)
def is_active(self):
return now() <= self.end_time
@classmethod
def featured_exists(cls, product: Product):
return cls.objects.filter(product=product).first()
@classmethod
def activate(cls, product: Product, tariff: Tariff):
featured = cls.featured_exists(product)
if featured.exists():
featured.tariff = tariff
featured.save(update_fields=['tariff'])
else:
cls.objects.create(
product=product,
tariff=tariff,
)
product.is_featured = True
product.save(update_fields=['is_featured'])
return _(f"{product} - {tariff.name}ga muvaffaqiyatli faollashtirildi.")
@classmethod
def deactivate(cls, product):
featured = cls.featured_exists(product)
if featured.exists():
product.is_featured = False
product.delete()
return True
return False