Creating Model Form with python code in form

I have a template that renders a series of text boxes for user input, which will be saved in a database to render a final document:

It is created by a nested loop, the outer defines the number of rows and the inner creates the container with the text boxes and associated graphics.

Would someone point me to documentation that has examples of including code in Model Forms as the tutorials didn’t help me learn how to do that; at least not how I could understand it.

Alternatively, is there a way to clean the data created with tag and supplied via Post?

Template:

{% load static %}

<!DOCTYPE html>
<html lang = "en">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <title>Strategy Map</title>
</head>
<form action = "{% url "survey" %}" method = "POST">
  {% csrf_token %}
<body>
    <h1>Strategy Map</h1>
  {% for n in numrows %}
<!--Create flexbox to render header graphic--> 
    <div class="container" style="top: 5%;">
<!--Create LOO shape-->    
        <div class="item_LoO_Name">
          <h1>Test</h1>
        </div>
<!--Create row of topic shapes and spacer between shapes-->
<!--Create pointed arrow and space to Outcome-->
    {% for x in numobj %}
    
      <div class="space"></div>
      <textarea id='ObjectiveTextArea' name={{ x|add:n }}></textarea>
    {% endfor %}
      <div class="space"></div>
      <div class="triangle-right"></div>
      <div class="spaceblank"></div>        
<!--Create Outcome Shape-->
      <div class="item_Outcome_Name">
        <h1>Test</h1>
      </div>
    </div> 
    {% endfor %}
  <div>
    <button onclick="location.href='{% url 'survey' %}'"><lable>Next Survey</lable></button>
  </div>
</body>
</html>
</form>

the model is:

class Objective_Text(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    objective_text = models.CharField(max_length=1000, blank = True)
    objective_num = models.CharField(max_length=10, blank = True)
    timestamp= models.TimeField(auto_now = True)

def __str__(self):
        return f"User: {self.user} Objective: {self.objective_num} Text: {self.objective_text} at {self.timestamp}"

There are a couple questions that will affect your possible / likely options.

Are these forms always going to be for new data entry? Or are there going to be cases where you’re using this to edit previously submitted data?

Is every case where you’re rendering this going to be 8 columns in 4 rows? Or is there other data that will affect the number of rows or columns being rendered?

Also, I’m not understanding how these boxes that you’re showing are going to map to instances of the Objective_Text model. Is each box on the page going to create a single instance of that model? How are they related to each other to maintain their relationship in the grid?

Note: You should adopt the perspective that Django is “process-oriented”, not “page-oriented”.
It’s generally better to work from the models, to the forms / formsets, to the views, then to the template last.
It tends to be a lot easier to create a template to match the forms than it is to try and create a form from a previously-built template.

1 Like

Ken, Thanks for the reply.

In response to your questions:

Are these forms always going to be for new data entry? Or are there going to be cases where you’re using this to edit previously submitted data?

Thy will be used to enter initial data and then for updating existing data. The resulting data will be used by a separate view for display.

Is every case where you’re rendering this going to be 8 columns in 4 rows? Or is there other data that will affect the number of rows or columns being rendered?

There will always be the 8 columns plus the two labled text. The number of rows may be 6 or 8 but a fixed number.

Also, I’m not understanding how these boxes that you’re showing are going to map to instances of the Objective_Text model. Is each box on the page going to create a single instance of that model? How are they related to each other to maintain their relationship in the grid?

Each box is a single instance of the model, so the DB would have 32 entries used to fill in each text box. Each box is labeled 1 to 32 to create unique grid locations. I guess I could create 1 row of data with 32 individual columns per row. Or 8 models of 8 columns to create 8 rows.

Note: You should adopt the perspective that Django is “process-oriented”, not “page-oriented”.
It’s generally better to work from the models, to the forms / formsets, to the views, then to the template last.
It tends to be a lot easier to create a template to match the forms than it is to try and create a form from a previously-built template.

I was trying to create a proof of concept using what I know how to do as I have not figured out how to recreate the flexbox I use to create each row using forms. I’ve tried to find some tutorials beyond The Djangoproject an djangogirls one but have had no luck. Crispy forms has potental.

Is that fixed per person?

I see where this model is related to a person. Does an individual only have one fixed set of data? In other words, if the fixed number is 6, there are only going to be 48 instances related to any individual?

My gut reaction, in the absence of any usage information beyond what you’ve posted here so far, would be to suggest another level of table organization for the data, where each row is represented as an intermediary model. In other words, if I were designing this, I’d be starting with something like this:

class Objectives(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    timestamp= models.TimeField(auto_now = True)

class ObjectiveRow(models.Model):
    objectives = models.ForeignKey(Objectives, on_delete=models.CASCADE)
    row_num = models.IntegerField()
    
class ObjectiveText(models.Model):
    objective_row = models.ForeignKey(ObjectiveRow, on_delete=models.CASCADE)
    objective_num = models.IntegerField()
    objective_text = models.CharField(max_length=1000, blank = True)

With this, you can create n-number of rows, each with x-number of text fields.

You can then create a list of formsets for the collection of the data.

As far as rendering it, you don’t need to render the complete form. You could replace

with {{ form.objective_text }} to just render the instances of that field. Everything else around that field can stay the same. (You will need to account for the formset management form and the other hidden fields for the formsets, but those don’t affect the appearance of the rendered html.)

Thanks, I’m getting closer. I added a dashboard as user is just to id who entered the data, the dashboard is the dataset; various dashboards will have different objectives.

The basic idea is this is a set of objectives for an organization, displayed as a dashboard.
The project has two main components;

  1. An input dashboard, used to input the objectives for each row, which is what I am working on.; and
  2. A results dashboard that displays the objectives, which will be colored based on performance. It can have less than 6 rows and 8 objectives per row

I setup forms and a formset, and while I do not get errors it doesn’t render the forms properly. It is filling in the flexbox but with text not text input boxes.

Here are my codesets:

Models:

class Objectives(models.Model):
    dashboard = models.ForeignKey(Dashboard, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    timestamp= models.TimeField(auto_now = True)

class ObjectiveRow(models.Model):
    objectives = models.ForeignKey(Objectives, on_delete=models.CASCADE)
    row_num = models.IntegerField()
    
class ObjectiveText(models.Model):
    objective_row = models.ForeignKey(ObjectiveRow, on_delete=models.CASCADE)
    objective_num = models.IntegerField()
    objective_text = models.CharField(max_length=1000, blank = True)


def __str__(self):
        return f"User: {self.user} Objective: {self.objective_num} Text: {self.objective_text} at {self.timestamp}"

Forms:

from django import forms
from django.forms import ModelForm
from ISO22301.models import ObjectiveText
    
class Meta:
        model = ObjectiveText
        fields = "__all__"


class LoginForm(forms.Form):
    username = forms.CharField()
    password = forms.CharField(widget=forms.PasswordInput)

class LogoutForm(forms.Form):
    username = forms.CharField()

Views

def home(request): 
    context ={} 
  
    # creating a formset 
    ObjectiveTextFormSet = formset_factory(ObjectiveText) 
    objectivetext_formset = ObjectiveTextFormSet() 
      
    # Add the formset to context dictionary 
    context = {
        'numobj': [1,2,3,4,5,6,7,8],
        'numrows': [0,8,16,24,32,40],
        'textid' : '8',
        '#objectivetext_formset': objectivetext_formset
    }

    context['formset']= objectivetext_formset
    return render(request, "ISO22301/home.html", context) 

Template

{% load static %}

<!DOCTYPE html>
<html lang = "en">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
    <title>Strategy Map</title>
</head>
<form method="POST" enctype="multipart/form-data">
  {% csrf_token %}
  {{ formset.management_form }}
<body>
    <h1>Strategy Map</h1>
  {% for n in numrows %}
<!--Create flexbox to render header graphic--> 
    <div class="container" style="top: 5%;">
<!--Create LOO shape-->    
        <div class="item_LoO_Name">
          <h1>Test</h1>
        </div>
<!--Create row of topic shapes and spacer between shapes-->
<!--Create pointed arrow and space to Outcome-->
    {% for x in numobj %}
    
      <div class="space"></div>
      <div class="Objective">
        {{ formset.form }}
      </div>
    {% endfor %}
      <div class="space"></div>
      <div class="triangle-right"></div>
      <div class="spaceblank"></div>        
<!--Create Outcome Shape-->
      <div class="item_Outcome_Name">
        <h1>Test</h1>
      </div>
    </div> 
    {% endfor %}
  <div>
    <input type="submit" value="Update Objectives">
  </div>
</body>
</html>
</form> 

Ok, you’re on the right track, but you need to create all the formsets for the list, with all the forms you’re going to need.

This means (assuming you want 6 lines) you’re going to create six instances of the formset, each with 8 forms.

This translates to something like this:

def home(request):
    # creating a formset
    ObjectiveTextFormset = modelformset_factory(
        ObjectiveText, fields=['objective_text'],
        widgets={'objective_text': Textarea(attrs={'cols': 10, 'rows': 3})},
        extra=8)

    # Create a formset for each row
    formset_list = [ 
        ObjectiveTextFormset(prefix=f'row-{i}') 
            for i in range(6) 
    ]

    # Add the formset to context dictionary
    context = {
        'formset_list' = formset_list
    }

Now, what this means in terms of your template is that you will iterate over formset_list for each row, and then over the forms in the formset for each block on that row.

e.g.:

<form method="POST" enctype="multipart/form-data">
  {% csrf_token %}
  <table>
    {% for formset in formset_list %}
      <tr>
        {{ formset.management_form }}
        <td>
        {% for form in formset %}
          {{ form.objective_text }}
        {% endfor %}
        </td>
      </tr>
    {% endfor %}
  </table>
</form>

Side note: The <form> element must be within the <body> element.

Now, when you’re loading existing elements into your formset, you won’t have any extra forms to create, assuming all boxes are populated. If you have a case where some boxes are populated and some aren’t, you’ll have other design decisions to make.

And, this just addresses the GET half of the processing. Once these forms are submitted, you’ll need to recreate the formsets bound with the post data before it can be saved to the database. (This is also going to take two different “shapes”, depending upon whether this is new data being entered or existing data being edited.)

Thanks.

While I sort through that, a few general questions:

  1. What is the advantage of forms beyond, as I understand them, the ability to validate and clean the resulting data.
  2. Can formsets contain different models, such a a radio button widget that might be repeated for several rows and a text input widget that is used once, after the rows of radio buttons, in a template?
  3. What is teh difference between your modelformset_factory and my use of formset_factory?
  4. If I put the form set within the other context, I get an error but separately it works properly.:

Error:

    context = {
        'numobj': [1,2,3,4,5,6,7,8],
        'numrows': [0,8,16,24,32,40],
        'textid' : '8',
        'objectivetext_formset': objectivetext_formset
    }
    return render(request, "ISO22301/home.html", context) 

No error:

context['formset']= objectivetext_formset
    return render(request, "ISO22301/home.html", context) 

Although the numrows, numobj is not needed with the updated formset

While I consider that sufficient reason itself to require the use of forms, especially in the case of data being submitted through an API, there are ancillary benefits as well.

  • Binds the submitted data to the underlying Python variables.
  • Provides for the transformation of data from Python variables to HTML
  • Provides the ability to track which fields were changed in a submit
  • In the case of model forms / formsets / inline formsets, provides an API for saving and retrieving data to the models, and managing foreign key relationships between related models.

A formset consists of a list of identical forms (and other stuff). A model formset contains a list of identical model forms.

So, a regular formset doesn’t contain models at all. It contains fields. Whether those fields represent fields in 0 models, 1 model, or n models is up to your code to manage.

A model formset contains fields from one model.

Beyond that, I suggest you read the entire page at Formsets | Django documentation | Django to see what all can be done to customize them.

(Now, while I could hypothesize the possibility of creating a formset consisting of different forms, perhaps using the get_form_kwargs parameter to create a formset in an ABABABAB pattern, I’ve never seen it done and I have no idea what problems would be encountered along the way.)

See Model formsets

Without seeing the error there’s no way to identify a cause.

However, you’re adding it to the context with two different keys. In your first assignment, the key is objectivetext_formset, and in the second, it’s formset. This would affect what your template needs to use.

Thanks. I’ve gotten the formset to work, with one issue, the forms are rendered in one long row rather than separate rows.

As a side question, is there a way to use a flexbox with forms? That would render the graphics a well.

Html:

{% load static %}

<!DOCTYPE html>
<html lang = "en">
<meta name="viewport" content="width=device-width, initial-scale=1">
<head>
    <link rel="stylesheet" href="{% static 'css/styles.css' %}">
<body>
    <h1>Strategy Map</h1>
    <form method="POST" enctype="multipart/form-data">
      {% csrf_token %}
    <div class="container" style="top: 5%;">  

        {% for formset in formset_list %}
          <tr>
            {{ formset.management_form }}
            <td>
            {% for form in formset %}

              {{ form.objective_text }}

            {% endfor %}
          </td>
          </tr>
        {% endfor %}

    </form>
  </div>
  <div>
    <input type="submit" value="Update Objectives">
  </div>
</form>
</body>
</html>

Views

def home(request):
    # creating a formset
    ObjectiveTextFormset = modelformset_factory(
        ObjectiveText, fields=['objective_text', 'objective_num'],
        widgets={'objective_text': Textarea(attrs={'cols': 70, 'rows': 60})},
        extra=10)

    # Create a formset for each row
    
    formset_list = [ 
            ObjectiveTextFormset(prefix=f'row-{i}') 
                    for i in range(6)         
    ]

    # Add the formset to context dictionary
    context = {
        'formset_list': formset_list
    }

    return render(request, "ISO22301/home.html", context)

forms

ObjectiveTextFormSet = modelformset_factory(ObjectiveText, fields = ["objective_text", "objective_num"],
                                            widgets={"objective_text": forms.Textarea(attrs={"cols": 80, "rows": 20})},
                                            extra=10
                                            )

Model

class ObjectiveText(models.Model):
    objective_row = models.ForeignKey(ObjectiveRow, on_delete=models.CASCADE)
    objective_num = models.IntegerField()
    objective_text = models.CharField(max_length=1000, blank = True)

The snippet of the template I provided was an example to illustrate how you could iterate over that list of formsets. It was not provided as a drop-in replacement for your template.

You need to integrate the concepts of that example into your original template.

Thanks for the help. I really appreciate it.

I solved the problem by rethinking my models and using a formset to render all 10 items as the same form.

Your model->form->view->template approach helped.

Update: Fixed text area display well. CSS was overriding the text area with its setting.

1 Like