Can you pass data between views?

Hi.

I have two views for two different templates. The first view takes to args request and project_id and returns the context of some logic based around the project_id

def show_project_details(request,project_id):

The second view is more generic

def dashboard_view(request):

Both views render different templates
return render(request, 'pages/project_details.html', {"project": project,
return render(request, "dashboard.html", {"project_list": project_list,

On the dashboard view, i have a table that shows a list of projects using {% for project in project_list %} within this table i also want to show specific data of each project. But to return then data i need to pass in the project_id as part of the request but can’t set that within the def dashboard_view(request): as its not used to render the page, but only needed when returning the specific project data.

This is the logic within the first view

    if Response2.objects.filter(project_name_id=project_id, questionnaire_id = 4).exists():  
        evaluation_response = Response2.objects.get(project_name_id=project_id, questionnaire_id = 4)
        evaluation_positiveplus = Answer2.objects.filter(answer = "++", response=evaluation_response).count()
        evaluation_positive  = Answer2.objects.filter(answer = "+", response=evaluation_response).count() 
        evaluation_neutral = Answer2.objects.filter(answer = "0", response=evaluation_response).count()
        evaluation_negative = Answer2.objects.filter(answer = "-", response=evaluation_response).count()
        evaluation_doublenegative = Answer2.objects.filter(answer = "--", response=evaluation_response).count()
        evaluation_null = Answer2.objects.filter(answer = "" , response=evaluation_response).count()
        evaluation_total = ((evaluation_positiveplus * 2) + (evaluation_positive)) - ((evaluation_doublenegative * 2) - (evaluation_negative))            
    else:
        evaluation_response = "null"
        evaluation_positiveplus = "null"
        evaluation_positive = "null"
        evaluation_neutral = "null"
        evaluation_negative = "null"
        evaluation_doublenegative = "null"
        evaluation_null = "all"
        evaluation_total = "null"

I need to return the evaluation_total on the first view, if i add this logic to the second view then where do i get/pass in the project_id without adding to the def dashboard_view(request): or can i pass the data between the first and second view somehow?

Hope this makes sense.

Tom.

Your description makes sense, but it’s the wrong approach for this type of situation.

You cannot return the output from two different render calls as part of the same view. The shortcut function render returns a complete HttpResponse object, not just the HTML of a rendered template.

So you’re only going to want to call render once, with the output of both “processing functions” being passed as the context.

As a very brief example:

def my_view(request, some_parameter):
    output_1 = data_function_1(request, some_parameter)
    some_other_parameter = output_1.other_parameter
    output_2 = data_function_2(request, some_other_parameter)
    context = {'output_1': output_1, 'output_2': output_2}
    return render(request, "parent_template.html", context=context)

def data_function_1(request, parm):
    return some_object_with_attributes

def data_function_2(request, parm):
    return some_other_object_with_attributes

(Don’t get hung up on the specific names being used here, they’re illustrative not definitive.)

Your “parent_template” can then use the {% include %} tag to render each of those templates, possibly using the with directive to make the object you’re passing in through this directive visible to the other templates by the names those templates expect.

Thanks, Ken.

So context of a function can be passed to any template.

So if i created a html template which included a table and the data of that table was populated via a function. I can then render that table with the data in any other template using {% include 'template_name.html %}

Does the return render need to pass in the data as context or can it still be
`return render(request, ‘pages/template_name.html’, {“something”: something, “something_else”: something_else,})’

Thanks

A function doesn’t have a context. (At least not in the sense that Django uses the word when rendering templates.)
A function (generally speaking) accepts parameters and returns a result.
That result can be placed in the context being passed to the rendering engine.

This is Python syntax.

These two examples are effectively identical:

context = some_data
some_function(context)

and

some_function(some_data)

(For this purpose, the difference doesn’t matter.)

Hi Ken,

Happy new year :slight_smile:

I cant get the include to work, i actually tried this a while ago and never got it to work back then. I think i’m misunderstanding the documents.

I’ve created a simple view/template to test.

def table_view(request):
    today = now().date()
    projects = Project.objects.all()
    context = {'projects':projects}
    
    return render(request, "pages/project_table.html", context=context)
<tbody>
    {% for project in projects %}
    
    <tr>
      <td>
        <div class="d-flex px-2 py-1">
          <div>
            <img src="/static/images/small-logos/logo-xd.svg" class="avatar avatar-sm me-3" alt="xd">
          </div>
          <div class="d-flex flex-column justify-content-center">
            <h6 class="mb-0 text-sm"><a href="{% url 'show_project_details' project.id %}"> {{ project.project_name }}</a></h6>
          </div>
        </div>
      </td>
      <td>
        <h6 class="mb-0 text-sm">{{project.project_stage}}</h6>
      </td>
      <td class="align-middle text-center text-sm">
        
        <span class="text-xs font-weight-bold">21</span>
        
      </td>
      <td class="align-middle">
        <div class="progress-wrapper w-75 mx-auto">
          <div class="progress-info">
            <div class="progress-percentage">
              <span class="text-xs font-weight-bold">60%</span>
            </div>
          </div>
          <div class="progress">
            <div class="progress-bar bg-success" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
          </div>
        </div>
      </td>
    </tr>
    {% endfor %}
  </tbody>

When i load the project_table.html page the table renders with all the data using the {% for project in projects %} with {{ project.project_name }}

But when {% include "pages/project_table.html" %} within another page, the table is rendered without the data. So just an empty table.

In the documentation it states

An included template is rendered within the context of the template that includes it. This example produces the output "Hello, John!":

Context: variable person is set to "John" and variable greeting is set to "Hello".

Template:

{% include "name_snippet.html" %}

What am i missing here?

Thanks

I’m not sure. I’d need to see the view you’re using to test the include, and the “base” template that has the include.
(And a Happy New Year to you, too!)

Hey Ken,

Im just using this in my template

 <div class="row">
    {% include "pages/project_table.html" %}
</div>

and my base template view is

def dashboard_view(request):
    today = now().date()
    project_list = Project.objects.all()
    return render(request, "dashboard.html", {"project_list": project_list})

This is just a view i had already built and was just testing.

In your version that works, you’re setting your context to:

In your template, you’re using:

But look at the name you’re supplying in your context in the version that doesn’t work…

Hold on, so the context of the dashboard_view needs to have the same keyValue pairs as the project_table view?

How does the value of the context get populated if the logic is not within that view?

I know it works when passing in the same context, but isn’t that defeating the purpose of having the logic only in the one view?

Sorry I’ve think I just realise what I need to be doing.

I should be calling the function from within the dashboard_view and passing the results into the context?

This:

as written is absolutely not correct.

Your templates reference variable in the context.

So, in your template, you reference a varible named projects. That means that whatever data you create in your view needs to be assigned to the name projects within the context for the template to be able to render it.

What you can define in your context can be anything.

context = {'template_variable' : data_produced_by_view}

The key and the value do not need to “match” each other by name - it is to allow you to take any data by any name, and pass it into the template with the name that the template is going to recognize.

Hi Ken,

Do I need to map the data_view to a URL? So that the main view and call it?

If that’s the case, how does the main view know the URL to call?

Below are the views I’m testing with - am I on the right track?

def table_view(request):
    today = now().date()
    project_data = get_data_view
    context = {'projects_data':project_data}
    
    return render(request, "pages/project_table.html", context=context)

def get_data_view(request):
    project_list = Project.objects.all()
    context = {"project_list":project_list}
    return {'context':context}

Thanks

I’ve got this working now. Always so easy in the end.

The only thing I need to understand is how I pass in the project_id param to the data function.

I don’t have a project_id to pass in until I have the project data, but I do have project_list = Project.objects.all() within my main dashboard view.

So I think somehow I need to take the project_id from project_list = Project.objects.all() and pass it to the data view?

project_data = get_project_data(project_list.project_id) but this doesn’t work as i get AttributeError: 'QuerySet' object has no attribute 'project_id'

So project_list is a queryset containing all projects - not just one. Assuming each instance of Project has a unique ID, it looks like you’re going to need to call get_project_data once for each project.

However, I’ve got a funny feeling there might be an easier way of handling this. What does your Project model look like and what does get_project_data do?

My project model is

class Project(models.Model):

    project_name = models.CharField(max_length=50, blank=False, unique=True)
    project_website = models.URLField(max_length=50, blank=True)
    project_description = models.TextField(blank=True)
    project_category = models.CharField(max_length=150, blank=True)
    project_type = models.CharField(max_length=150, blank=True)



    def __str__(self):
        return str(self.project_name)

And my get project_data returns all the scoring for a project

    # Critical Fundamental Results
    if Response.objects.filter(project_name_id=project_id, questionnaire_id = 1).exists():
        response = Response.objects.get(project_name_id=project_id, questionnaire_id = 1)
        fundamental_green = Answer.objects.filter(answer = "Green", response=response).count()
        fundamental_red = Answer.objects.filter(answer = "Red", response=response).count() 
        fundamental_amber = Answer.objects.filter(answer = "Amber", response=response).count()
        fundamental_null = Answer.objects.filter(answer = "" , response=response).count()
    else:
        response = "null"
        fundamental_green = "null"
        fundamental_red = "null"
        fundamental_amber = "null"
        fundamental_null = "all"

First, a sidebar comment - I was guessing and am currently assuming that this all is part of the Questionnaire project we’ve discussed in other topics. To that end, I’m assuming all the other model definitions are the same.

So, the issue that I believe you’re trying to address is that for each Project, you want to get the counts of the different answers.

You also want to handle the special case where no Response exists for a Project.

  • It is possible that you have a Response object but no Answer supplied. Is that a part of this special case?

  • It is possible that you have a Response and you have Answer, but the answers are all the null string (’’). Is that also part of this special case?

The largest potential problem I see with your current approach is that for every project, you’re going to issue at least 1 and up to 6 additional queries for each project.

In reality, unless you’re working with 100+ projects, this is not going to be a real problem.

But, I think it’s still better that you are aware of a more appropriate approach to this.

Right now you have a query:
project_list = Project.objects.all()

But what you want in addition to that are the counts of each answer for each Project. That’s where the annotations come in.

(Note: I’m going to build this up piece by piece. The first parts of this aren’t going to make sense until we get to the end.)

First, let’s handle the special case you’ve already defined. (No Response object for a Project)

responses = Count('response')
The variable responses will contain the count of the number of Reponse objects related to a project.

Now we’ll handle the answers:

greens = Count('response__answer', filter=Q(response__answer__answer='Green'))

(One is provided. The other three are left as an exercise for the reader.)

First, perhaps the most confusing part of this is the expression response__answer__answer in the filter. We’re looking at this in the context of a Project object. So given a Project, response is the reference to the set of Response objects relating to it. (Note that this is different from getting the set of those objects, which would use the _set reference.) Now that we are referring to Response, the first answer is the reference to the Answer objects referring to that Response. Finally, the second answer is a reference to the field named answer that is part of the Answer object.

So, the English description of these clauses are that we’re going to get a count of the number of Answer objects related to the Response object related to each Project, where the answer field has the value ‘Green’.

We will then take advantage of these clauses to create one query to do all this work for us:

responses = Count('response')
greens = Count('response__answer', filter=Q(response__answer__answer='Green'))
...

project_list = Project.objects.annotate(
    response_count = responses,
    fundamental_green = greens,
    ...
)

(The ellipses show where you would add the corresponding statements for the other answers.)

When this query executes, you will then have the additional fields response_count and fundamental_green accessible on each Project object.

You would still need to test (either in the view or in the template) whether response_count is 0 to determine whether you want to display “null” or instead of the zeros that would be reported for the individual answers.

You can take this one step further and perform that comparison within the query by using a Case clause within the query to perform the test.

responses = Count('response')
greens = Count('response__answer', filter=Q(response__answer__answer='Green'))
...

project_list = Project.objects.annotate(
    response_count = responses,
    fundamental_green = Case(
        When(response_count=0, then=Value('null'), 
             default=Cast(greens, output_field=CharField())
        )
    ),
    ...
)

However, notice that this returns fundamental_green as a character string and not an integer. If you have other work where you need both, you need to annotate the object with both:

responses = Count('response')
greens = Count('response__answer', filter=Q(response__answer__answer='Green'))
...

project_list = Project.objects.annotate(
    response_count = responses,
    green_count = greens,
    fundamental_green = Case(
        When(response_count=0, then=Value('null'), 
             default=Cast('green_count', output_field=CharField())
        )
    ),
    ...   
)

This adds yet another value to the objects being returned in the queryset.

At this point, you no longer have a need to call a separate function to generate and return those values.

Amazing. Thanks, Ken.

Yeah this is all part of the questionnaire.

I might be missing this bit (probably am) how do i know the project the count is for?

would it be project_list.project_name.fundamental_green?

Thanks

project_list is not one Project, it’s a queryset of all Project. Each Project in project_list is annotated with those values.

Right i understand.

Im getting this error Cannot resolve keyword 'default' into field. for this default=Cast('green_count', output_field=CharField())

Do i need to import default?