Robert
Robert

Reputation: 375

Django complex query comparing 2 models

This may be a design question.

Question is "What is the best way to find offers that needs to have feedback sent by logged in user". In Feedbacks site there are 3 tabs: "Sent", "Received", "Send feedback". "Send feedback" tab there's a table with "Offer id","username(buyer/sender" and "Send feedback" link pointing to feedback form.

Here's the code which should help understand what I mean.

Offers are displayed until some user buys it. Offer is being closed, and new Order (storing order details) instance is created for this offer.

I'm trying to implement a Feedback app, where both sides of offer transaction can send feedback about transaction.

Let's skip the "ended" or "running" offer problem.

class Offer(models.Model):
    """Offer is displayed for 5 days, then it's being ended by run everyday cron script.
       If someone buys the offer end_time is being set, and offer is treated as ended.
       Both sides of transaction may send feedback.
    """    
    whose = models.ForeignKey(User, verbose_name="User who has created the offer")
    end_time = models.DateTimeField(blank=True, null=True, help_text="")
    field = ()
    fields2 = ()
    order = models.ForeignKey(Order, balnk=True, null=True, help_text="Order details")

class Order(models.Model):
    """stores order details like user, date, ip etc."""
    order_1_field_details = ()
    who = models.ForeignKey(User, verbose_name="User who bought the offer")
    offer_id = models.PositiveIntegerField("know it's unnecessary")
    offer_data = models.TextField('offer data dict here')

class Feedback(models.Model):
    offer_id = models.PositiveIntegerField()
    sent_by = models.ForeignKey(User, verbose_name="Offer sender")
    received_by = models.ForeignKey(User, verbose_name="Offer receiver")

    def get_offer(self):
        try: 
            Offer.objects.get(id=self.offer_id)
        except Offer.DoesNotExist:
            return None  # offer moved to archive

In first draft there was a offer = models.ForeignKey(Offer) instead of offer_id field, but I am going to move some old offers from Offer table to another one for archiving. I would like the feedback stay even if I 'archive' the offer. In feedback list there will be an 'Offer id" link and for offers older than 60 days user will see "moved to archive" when clicking "details".

All I can think of at the moment is getting offers which hasn't expired, but there was a buyer.

ended() is a manager returning self.filter(end_date__isnull=False)

offers_with_buyer = models.Q(Offer.objects.ended().filter(whose__exact=request.user, order__isnull=False) | models.Q(Offer.objects.ended().filter(order__who__exact=request.user)

How do I check if there's a feedback for these offers ? I know I should return user and offer id from queryset above and check if they exist in Feedback.offer_id and Feedback.sent_by.. or maybe I should change model design completely ...

Upvotes: 0

Views: 157

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239250

First, how you're handling the end date is very contrived. If the offer ends 5 days after it's created, then just set that automatically:

from datetime import datetime, timedelta

class Offer(models.Model):
     ...
     def save(self, *args, **kwargs):
         self.end_date = datetime.now() + timedelta(days=5)

         super(Offer, self).save(*args, **kwargs)

Then, just modify your ended manager to return instead: self.filter(end_date__lte=datetime.now())

However, I generally prefer to add additional methods to my default manager to deal with the data in various ways:

class OfferQuerySet(models.query.QuerySet):
    def live(self):
        return self.filter(end_date__gt=datetime.now())

    def ended(self):
        return self.filter(end_date__lte=datetime.now())

class OfferManager(models.Manager):
    use_for_related_fields = True

    def get_query_set(self):
        return OffersQuerySet(self.model)

    def live(self, *args, **kwargs):
        return self.get_query_set().live(*args, **kwargs)

    def ended(self, *args, **kwargs):
        return self.get_query_set().ended(*args, **kwargs)

(Defining a custom QuerySet and then using methods on the Manager that just proxy to the QuerySet is the way the Django core team does it, and allows you to use the methods anywhere in the chain, instead of just the front.)

As far as "archiving" your Offers go, it's extremely bad practice to divvy out similar data into two different models/tables. It's exponentially increases the order of complexity in your app. If you want to "archive" an offer. Add a field such as:

 is_archived = models.BooleanField(default=False)

You can then create another method in your Manager and QuerySet subclasses to filter out just archived or live offers:

 def live(self):
     return self.filter(is_archived=False, end_date__gt=datetime.now())

 def archived(self):
     return self.filter(is_archived=True)

If you really need another model (such as for a separate view in the admin for just archived offers) you can create a proxy model:

 class ArchivedOfferManager(models.Manager):
     def get_query_set(self):
         return super(ArchivedOfferManager, self).get_query_set().filter(is_archived=True)

     def create(self, **kwargs):
         kwargs['is_archived'] = True
         return super(ArchivedOfferManager, self).create(**kwargs)

 class ArchivedOffer(models.Model)
     class Meta:
         proxy = True

     objects = ArchivedOfferManager()

     def save(self, *args, **kwargs):
         self.is_archived = True
         super(ArchivedOffer, self).save(*args, **kwargs)

Upvotes: 1

Related Questions