Search code examples
pythondjangodjango-rest-frameworkdjango-querysetdjango-rest-viewsets

Django filter on attribute with two values, preferring the result of one


This is kind of a weird question, so sorry in advance. I have a model in Django Rest that looks like this:

class BaseModel(models.Model):
    created_date = models.DateTimeField(auto_now_add=True)
    modified_date = models.DateTimeField(auto_now=True)

   class Meta:
        abstract = True

class Foo(BaseModel):
    barId = models.ForeignKey(Bar, unique=False)
    fizzId = models.ForeignKey(Fizz, unique=False)
    buzzId = models.IntegerField(unique=False)
    value = models.TextField()

And I have a ViewSet that needs to return a list of all the Foos that have a given {request_barId, request_buzzId, lastUpdateDate}. This is fairly straightforward,

foobar = Foo.objects.filter(
buzzId=request_buzzId,
modified_date__gt=request_lastUpdateDate,
barId=request_barId)

Here's the rub. There's a default value for buzzId that is the base list that the specified buzzId needs to overlay, replacing the instances on the base list. That's where I'm getting a little lost. I have a solution, but I feel like it's not particularly elegant and that there's go to be a way to do this cleaner. Here's my code to do the overlay:

base_foobar = Foo.objects.filter(
buzzId=base_buzzId,
modified_date__gt=request_lastUpdateDate,
barId=request_barId).exclude(
        fizzId__in=[o.fizzId for o in foobar])
result = foobar | base_foobar

And this just seems really janky. Is there a way to clean this up?

EDIT: To clarify, let's say that the list for the tuple { 1, 0, '01-01-1970' } represents the base set (buzzId: 0), and returns a list of objects containing fizzIds { 1, 2, 3, 10 }. Let's say that the tuple { 1, 1, '01-01-1970' } represents some buzzId's request for a complete set of strings. If we say that our buzzId of 1 (call it augment) has matching Foos with fizzIds { 2, 10, 15, 20 }, then our result set should look like

{ (base) 1, (augment) 2, (base) 3, (augment) 10, (augment) 15, (augment) 20 }

Does this clear it up?


Solution

  • You could use Q to combine into one statement:

    result = (Foo.objects
        .filter(
            Q(
                modified_date__gt=request_lastUpdateDate,
                barId=request_barId
            ) 
            &
            (
                Q(buzzId=request_buzzId) 
                |
                (
                    Q(buzzId=base_buzzId)
                    & 
                    ~Q(fizzId__in=[o.fizzId for o in foobar])
                )
            )
        )
        .order_by( '-buzzId' if request_buzzId < base_buzzId else 'buzzId')
        )[0] # fetch first result only
    

    Also, please format all your code in the future. It doesn't have to be like me, but it would definitely help with reading your question.