Search code examples
pythondjangodjango-modelsdjango-querysetdjango-filter

How to filter Django model for AND and OR simultaneously


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))

Solution

  • 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.