Django Exists() / ~Exists() return if there is no matching data?

EDIT:

As per schillingt's answer below I have switched to using Case/When:

        context['db_orders'] = Order.objects.filter(
            retailer_code=self.object.retailer_code).annotate(
            in_db=Case(When(Q(Subquery(self.object.suppliers.filter(
                supplier_code=(OuterRef('supplier_code')))
            ), then=Value(True), default=Value(False), output_field=NullBooleanField()))))

However I'm now struggling with an errror:

FieldError at /retailers/A001/

Cannot resolve expression type, unknown output_field

Original question:

I have the DetailView below with a query/subquery that checks whether supplier_code exists within instances of a second model set up to represent purchase orders received into a live stock database.

The intention is for this to function as a checklist/ticksheet that will return whether or not the order has been received for each supplier expected to send one.

Getting it to return that there is a match seems to be working fine, and if there is a value it does not recognize (I have purposefully created an invalid order that won't match against the list) it will return that there is no match.

However I need this to also tell me that there is simply no data, yet I don't seem to be able to achieve this.

For example the below shows the template output; G001 is the 'fake' code I have set up and G002 is a valid one that exists in the suppliers list. However if there is not an order present for G002 it will not return anything.

    Order not received: G001
    Order received: G002

I have tried writing a second query for the context that is a mirror of context['db_orders'] but using the ~Exists() and then nesting the if statements in the template but this will just tell me that the orders both exist and don't exist or vice versa.

 context['not_db_orders'] = Order.objects.filter(
            retailer_code=self.object.retailer_code).annotate(in_db=~Exists(squery))

I've also tried to do this in the template using 'is not' or 'is None' or 'is False' but cannot seem to get the output I need

Ultimately the intended output is a table that lists all the suppliers expected into a particular retailer with some manner of 'Yes' or 'No' next to them based on whether the order exists among the Order instances. (The template HTML doesn't currently reflect this but that is not the issue)

Template:

{% extends 'tick_sheet/base.html' %}

{% block content %}

<h1>{{ object.retailer_name }}</h1>
<ul>
    {% for supplier in object.get_supplier_values %}
    <li>{{ supplier }}</li>
    {% endfor %}
</ul>

<ul>
{% for item in db_orders %}
        {% if item.in_db %}
            <li>Order received: {{ item.supplier_code }} - {{ item.supplier_name }}</li>
        {% elif not item.in_db or item.in_db is None %}
            <li>Order not received: {{ item.supplier_code }} - {{item.supplier_name}}</li>
        {% endif %}
{% endfor %}
</ul>
{% endblock content %}

The DetailView:

class RetailerDetailView(DetailView):

    model = Retailer
    slug_field = 'retailer_code'
    slug_url_kwarg = 'retailer_code'

    def get_context_data(self, **kwargs):

        context = super().get_context_data(**kwargs)
        context['now'] = timezone.now()
        context['title'] = 'Order Checklist'

        squery = self.object.suppliers.filter(
            supplier_code=OuterRef('supplier_code'))

        context['db_orders'] = Order.objects.filter(
            retailer_code=self.object.retailer_code).annotate(in_db=Exists(squery))

        return context

Models.py

from django.db import models
from django.utils import timezone


class Order(models.Model):

    ''' To simulate connection to main stock db '''

    retailer_code = models.CharField(max_length=4)
    retailer_name = models.CharField(max_length=100)
    supplier_code = models.CharField(max_length=4)
    supplier_name = models.CharField(max_length=100)
    order_reference = models.CharField(max_length=20)
    despatch_date = models.DateTimeField(default=timezone.now)

    def __str__(self):

        return f"< {self.order_reference}', {self.supplier_name}, {self.retailer_name} >"


# -------------------------------------------------------------------------------------

class Retailer(models.Model):

    retailer_code = models.CharField(max_length=4)
    retailer_name = models.CharField(max_length=100)
    suppliers = models.ManyToManyField('Supplier')
    slug = models.SlugField(unique=True, null=True)

    def get_supplier_values(self):

        return [(suppliers.supplier_code + ' - ' + suppliers.supplier_name) for suppliers in self.suppliers.all()]

    def save(self, *args, **kwargs):

        self.slug = self.slug or slugify(self.retailer_code)
        super().save(*args, **kwargs)

    def __str__(self):

        return f"< {self.retailer_code} - {self.retailer_name} >"


class Supplier(models.Model):

    supplier_code = models.CharField(max_length=4)
    supplier_name = models.CharField(max_length=100)

    def __str__(self):

        return f"< {self.supplier_code}, {self.supplier_name} >"

1 answer

  • answered 2020-02-12 22:59 schillingt

    If there's a difference in the case between False and None you can't use Exists. That is a strictly boolean operation. You will need to use a Subquery that returns a NullableBooleanField whose result is calculated with When and Case