How to filter Django model for AND and OR simultaneously

50 Views Asked by At

Let's say I have a User model and a Dataset model. I want to get all datasets that (have a certain type AND (belong to a certain user OR don't belong to anyone)). What is the best approach to achieve this?

Code sample:

class User(AbstractUser):
    username = models.CharField(max_length=150,unique=True,)
    first_name = models.CharField(blank=True, max_length=150)
    last_name = models.CharField(blank=True, max_length=150)


class Dataset(models.Model):
    name = models.CharField(null=False, unique=True, max_length=50)
    type = (models.PositiveIntegerField())
    UserID = models.ForeignKey(
        User,
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name="ds_owner",
    )

I am trying to figure out how to select something like this:

def view_name(request):
    usr = User.objects.get(id=request.user.id) 
    allowed_datasets = Dataset.objects.filter(type=1 AND (UserID=usr OR UserID__isnull=True))
1

There are 1 best solutions below

0
willeM_ Van Onsem On BEST ANSWER

You can use Q objects [Django-doc] to present filtering options as object, and then use & and | for conjunctions and disjunctions.

You thus can filter with:

from django.db.models import Q


def view_name(request):
    usr = User.objects.get(id=request.user.id)
    allowed_datasets = Dataset.objects.filter(
        Q(type=1) & (Q(UserID=usr) | Q(UserID=None))
    )

or shorter:

from django.db.models import Q


def view_name(request):
    usr = User.objects.get(id=request.user.id)
    allowed_datasets = Dataset.objects.filter(
        Q(UserID=usr) | Q(UserID=None), type=1
    )

Note: It is often better to use get_object_or_404(…) [Django-doc], then to use .get(…) [Django-doc] directly. In case the object does not exists, for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using .get(…) will result in a HTTP 500 Server Error.