Trying to save data from 3 forms, 1 View, to 3 models Django

Hi, I am trying to save data from: 3 forms --> through 1 view --> to 3 models. Within the View I have 3 if statements (i.e. if form1.is_valid(), if form2.is_valid(), etc). When these act alone (i.e. the other if statements are commented out), data can be save to the models. But if 2 or 3 if statements are being used I get errors, mainly Document is empty.

How the process is working: a url is entered in the form --> a class is used within the View to pull a table from the URL as a list of tuples --> those tuples are inserted into the models.

To reiterate, theses forms work individually but not all together. Any insight to what is happening here would be great.

My thoughts are that it is to do with the if statements not written correctly but I’m fairly new to Django so it may be my misunderstanding in how these work. Thanks, please see code below:

HTML:

{% extends 'base.html' %}
<h1>Event Data</h1>

    {% block head %}
    <title>Event Data</title>
    {% endblock %}

    {% block body %}
<div class="container">
      <h1>Event Data</h1>
      <form method="post">
          {% csrf_token %}
          {{ form1.as_p }}
          <button type="submit">Submit</button>
      </form>
      <form method="post">
          {% csrf_token %}
          {{ form2.as_p }}
          <button type="submit">Submit</button>
      </form>
      <form method="post">
          {% csrf_token %}
          {{ form3.as_p }}
          <button type="submit">Submit</button>
      </form>
</div>
    {% endblock %}

FORMS:

class NextEventDetailsForm(forms.Form):
    url_event = forms.URLField(required=False, label='Next Event Details:')

class CurrentEventForm(forms.Form):
    url_card = forms.URLField(required=False, label='Current Event Details:')

class ResultsForm(forms.Form):
    url_res = forms.URLField(required=False, label='Past Results:')

VIEW:

class EventStatsView(TemplateView):
     template_name = 'accounts/page.html'

    def get(self, request):
        form1 = NextEventDetailsForm(prefix='form1')
        form2 = CurrentEventForm(prefix='form2')
        form3 = ResultsForm(prefix='form3')
            return render(request, self.template_name, {
                'form1': form1,
                'form2': form2,
                'form3': form3,
            })
    
    def post(self, request):
        dbase = 'db'
        dbuser = 'user'
        event_table = 'event_table'
        current_table = 'current_table'
        res_table = 'results_table'
        
        # For SQL insert and Upsert 
        event_headers = ' column headers here'
        current_headers =  ' column headers here'
        update_current =  ' update column headers here'
        res_headers =  ' column headers here'
        update_res =  ' column headers here'
        
        form1 = NextEventDetailsForm(request.POST, prefix='form1')
        form2 = CurrentEventForm(request.POST, prefix='form2')
        form3 = ResultsForm(request.POST, prefix='form3')
        
        if form1.is_valid():
            post = form1.cleaned_data
            url_event = post.get('url_event')
            # This class converts data from URL to a list of tuples
            df = Event(url_event)
            df = df.event_details()
            conn = psycopg2.connect("host=localhost dbname=" + dbase + " user=" + dbuser)
            cur = conn.cursor()
            for event in df:
                insert_query = "INSERT INTO " + event_table + event_headers + " VALUES {};".format(event)
                insert_query = insert_query.replace('"', "'")
                print(insert_query)
                cur.execute(insert_query)
                conn.commit()
        if form2.is_valid():
            post = form2.cleaned_data
            url_card = post.get('url_card')
            df = Event(url_card)
            df = df.fight_card()
            conn = psycopg2.connect("host=localhost dbname=" + dbase + " user=" + dbuser)
            cur = conn.cursor()
            for fight in df:
                insert_query = "INSERT INTO " + current_table + current_headers + " VALUES {} ".format(fight)
                insert_query = insert_query.replace('"', "'")
                update_query = 'ON CONFLICT(id) DO UPDATE SET ' + update_current
                insert_query = insert_query + update_query
                print(insert_query)
                cur.execute(insert_query)
                conn.commit()
        if form3.is_valid():
            post = form3.cleaned_data
            url_res = post.get('url_res')
            df = resultEvent(url_res)
            df = df.fight_results()
            conn = psycopg2.connect("host=localhost dbname=" + dbase + " user=" + dbuser)
            cur = conn.cursor()
            for result in df:
                insert_query = "INSERT INTO " + res_table + res_headers + " VALUES {} ".format(result)
                insert_query = insert_query.replace('"', "'")
                update_query = 'ON CONFLICT(fight_id) DO UPDATE SET ' + update_res
                insert_query = insert_query + update_query
                print(insert_query)
                cur.execute(insert_query)
                conn.commit()
    args = {
        'form1': form1, 'form2': form2, 'form3': form3
        }
    return render(request, self.template_name, args)

You have three separate forms on the page. (I mean three pairs of <form> </form> tags.)

The submit button in each form is only going to submit the fields in that form.

If you want to submit all three forms at once, they need to be enclosed in a single pair of <form> </form> tags.

If you want only one of the three forms to be submitted at a time, then you might want to direct them to three separate views - otherwise your single view needs to determine which form was submitted. The other two are going to be empty.

Ken

Hi @KenWhitesell,

Thanks for your reply. Ideally I would like to submit 1 form at a time. I have read you can’t have multiple views for the same HTML page.

Is it possible to have 3 forms --> 3 Views --> 3 models on the 1 HTML page?

Cheers,

Don’t confuse the View being used to generate the page with the View being called for a form being submitted.

You can create your view like you’ve currently been doing - what you want to do is add the “action” attribute with the URL for the view that you want to have process the input from that form.

So you’ll have 4 views total. One that displays this page, and one for each of the forms being submitted. Your display view only needs to handle get, the three process views only handle post.

Ken

Thanks for your help!

I will have a go at this tonight.

Cheers,

On another note, you really don’t want to insert rows into your database the way your current post method is doing.

I suggest you review the Forms and ModelForms documentation, along with Models and Model methods.

Using string concatenation for building queries is a major anti-pattern, and what you’ve created is a serious security vulnerability - you’re ripe for an SQL injection attack.

If you’ve not worked through the Django tutorial yet through at least part 4, make the time to do so.

Ken

Sorry I forgot to mention that this form would be only accessible for admin users. It is solely used for collecting data from certain websites and would not be used by any other users.

I struggled to find any built-in django methods that could handle uploading a dataframe to a model and only found solutions of using SQL queries, hence the not-so-optimal insert statement.

I will definitely have a read of those to find a better solution.