def chunked_qs(qs, chunk_size=10000, fields=None):
    """
    Generator to iterate over the given QuerySet, chunk_size rows at a time

    Usage:

        >>> qs = FailureLine.objects.filter(action='test_result')
        >>> for qs in chunked_qs(qs, chunk_size=10000, fields=['id', 'message']):
        ...     for line in qs:
        ...         print(line.message)

    Note: While Django 2.0 provides chunking [1] via QuerySet.iterator() we
    can't make use of this while using MySQL which doesn't support streaming
    results.

    [1]: https://docs.djangoproject.com/en/2.0/ref/models/querysets/#iterator
    """
    min_id = 0

    while True:
        chunk = qs.filter(id__gt=min_id).order_by('id')

        if fields is not None:
            chunk = chunk.only(*fields)

        # Cast to a list to execute the QuerySet here and allow us to get the
        # last ID when updating min_id.  We can't use .last() later as it
        # ignores the slicing we do.
        rows = list(chunk[:chunk_size])

        total = len(rows)

        if total < 1:
            break

        yield rows

        # update the minimum ID for next iteration
        min_id = rows[-1].id
Generator to iterate over the given QuerySet, chunk_size rows at a time

    Usage:

        >>> qs = FailureLine.objects.filter(action='test_result')
        >>> for qs in chunked_qs(qs, chunk_size=10000, fields=['id', 'message']):
        ...     for line in qs:
        ...         print(line.message)

    Note: While Django 2.0 provides chunking [1] via QuerySet.iterator() we
    can't make use of this while using MySQL which doesn't support streaming
    results.

    [1]: https://docs.djangoproject.com/en/2.0/ref/models/querysets/#iterator