Use Django view to group every 3 objects into seperate context variables?

Viewed 179

I want to break up my project query projects = Project.objects.all() from my views.py file into groups of 3. I apologize if my question was worded poorly. I'm not entirely sure how to ask this in a short, concise way. Any suggestions on how to better word this and I'll update.

My view function currently:

def project_index(request):
    projects = Project.objects.all()
    project_len = len(projects)
    project_list = zip(*[iter(projects)]*3)
    context = {
        'projects': projects
    }
    return render(request, 'project_index.html', context)

I was thinking I could use a dictionary somehow, but after starting to work through this I find myself a little lost as to how to implement it. Ideally, I need to get every 3 objects and create a new context variable for those objects. For example lets say there are 9 objects in total:

def project_index(request):
    projects = Project.objects.all()
    project_len = len(projects)              <---gets the total number of objects
    project_list = zip(*[iter(projects)]*3)  <---converts objects into list, groups of 3(not
                                                 sure if this will work for what I am trying
                                                 to do, but it was a thought.)
    context = {
        'projects': projects,
        'group1': group1, <---each group contains 3 objects(object1, object2, object3)
        'group2': group2, <---(object3, object4, object5)
        'group3': group3, <---(object6, object7, object8)
    }
    return render(request, 'project_index.html', context)

These groups will be used later on to create groups of cards on my website.

2 Answers

You can "unpack" the elements in separate variables:

def project_index(request):
    projects = Project.objects.all()
    project_len = len(projects)
    # for nine elements
    group1, group2, group3 = zip(*[iter(projects)]*3)
    context = {
        'projects': projects,
        'group1': group1,
        'group2': group2,
        'group3': group3
    }
    return render(request, 'project_index.html', context)

or with an arbitrary number of elements:

def project_index(request):
    projects = Project.objects.all()
    project_len = len(projects)
    groups = zip(*[iter(projects)]*3)
    context = {
        'projects': projects,
        **{f'group{i}': val for i, val in enumerate(groups, 1)}
    }
    return render(request, 'project_index.html', context)

That being said, passing these as individual elements, might not be a good idea. Since at the template end, you then will somehow have to access it. Therefore it might be better to pass it as an iterable of iterables, and thus use two nested loops in the template.

Another approach, which you might find useful elsewhere, so I'll mention it, would be to split the list/queryset (iterable) into chunks using a method like;

def chunkify(lst, chunk_size):
    """
    Split a given list of things into smaller chunks.
    """
    lst = list(lst)
    for i in xrange(0, len(lst), chunk_size):
        yield lst[i:i + chunk_size]

Using this you can then split something up into smaller parts. I use it for when performing calculation type operations on querysets.

    for chunk in chunkify(my_iterable, chunk_size=3):
        logger.info("Splitting my objects into chunks of 3")
        context['thing'] = chunk
Related