Django form needs pagination and saved data - how????

671 views
Skip to first unread message

Jim Illback

unread,
Jul 28, 2017, 8:57:31 PM7/28/17
to Django users
I use the latest versions of Django, python, bootstrap3, and crispy forms for this project.

I am trying to present a list of flashcards to a user, sorted by category (e.g., Math, Vocabulary, etc.). Then, I want the user to check either the full category or individual flashcards to be added to their practice account. 

To maintain user inputted checkboxes on multiple pages, I must use POST. But, pagination only uses GET. So, a hack on pagination is to wrap the Next/Previous buttons in a sub-form and POST using the buttons. This works for pagination, but then my normal “I’m done, submit this session and process the updates” button - my submit button - does nothing: no GET, no POST, nothing. 

How can I enable pagination, multiple screen data accumulation across pages, and final submission of the session when complete? 

Just FYI - for me, session data and hidden forms haven’t worked because the data doesn’t come back to the Django server. So, request.POST.get(‘CAT') returns NULL even though the web page has a value in the tag ‘CAT’. Session data doesn’t work because I can’t get the data to the server.

Here are sub-sections of my code. The “hidden” fields (cat and crd) are visible during my testing. They are filled with the IDs of any/all selected categories (cat) or flashcards (crd) - comma separated.

Views.py:
class CardsCreate(TemplateView):
    template_name = 'sel_cards_add.html'
    model = FlashCard

    def dispatch(self, request, *args, **kwargs):
        print('DISPATCH:')
        student_id = kwargs.get('student_id')

        try:
            flashcards = FlashCard.objects.all().order_by('category', 'front')
        except FlashCard.DoesNotExist:
            flashcards = None
        except Exception as e:
            print('type=%s args=%s (%s)' % (type(e), e.args, e))
            raise Http404

        flashcard_paginator = paginator.Paginator(flashcards, 2)
        try:
            page = int(request.GET.get('page', '1'))
        except ValueError:
            page = 1
        try:
            flashcards = flashcard_paginator.page(page)
        except (paginator.EmptyPage, paginator.InvalidPage):
            flashcards = flashcard_paginator.page(flashcard_paginator.num_pages)

        context = {'flashcards': flashcards, 'id': student_id, 'title': 'Available Flashcards By Category'}
        print('End of DISPATCH:')
        return render(request, self.template_name, context)

Template (sel_cards_add.html):
{% extends 'base.html' %}
{% load crispy_forms_tags %}

<form id="myForm" action="{% url 'add_cards' id %}" method="POST">
    {% block content %}
    {% csrf_token %}
        
    <div class = "row">
      <div class="col-sm-8 col-sm-offset-1">


        <h1>{{ title }}</h1>
        {% for flashcard in flashcards %}
            {% ifchanged flashcard.category %}
                </table>
                <h3><input type="checkbox" onchange="chkcat(this)" name="chkcat{{ flashcard.category_id }}" /> &nbsp;&nbsp;Category:  {{ flashcard.category }}</h3>
        <table>
            <tr>
              <th>Select?</th>
              <th>Flashcard</th>
              <th>Answer</th>
              <th>Easy Factor</th>
            </tr>
            {% endifchanged %}
            <tr>
                <td><input type="checkbox" onchange="chkid(this)" name="chkid{{ flashcard.id }}" /></td>
                <td>{{ flashcard.front }}</td>
                <td>{{ flashcard.answer }}</td>
                <td>{{ flashcard.easy_factor }}</td>
            </tr>
        {% empty %}
            <tr><td align="center" colspan="4">No flashcards to select yet.</td></tr>
        {% endfor %}
        </table>
        <br>
        <input type="text" id="cat" name="CATS" value="{{ CATS }}" />
        <input type="text" id="crd" name="CRDS" value="{{ CRDS }}" />
      <br>
        <div class="pagination">
            <span class="step-links">
                <table class="pagination" style="border: none;"><tr style="border: none;"><td style="border: none;">
                {% if flashcards.has_previous %}
                <span>
                    <form action="?page={{ flashcards.previous_page_number }}" method="POST">
                     {% csrf_token %}
                        <button type="submit">Previous</button>
                    </form></span>
                {% endif %}
            </td><td style="border: none;">
                    <span>Page {{ flashcards.number }} of {{ flashcards.paginator.num_pages }}</span>
            </td><td style="border: none;">
                {% if flashcards.has_next %}
                <span>
                    <form action="?page={{ flashcards.next_page_number }}" method="POST">
                     {% csrf_token %}
                        <button type="submit">Next</button>
                    </form></span>
                {% endif %}
            </td></tr></table>
            </span>
        </div>
        <br>
        <button type="submit" class="btn btn-default">Add Selections to User</button>
        &nbsp;&nbsp;
        <a href="{% url 'home' %}" class="btn btn-primary" role="button" padding="15px">Cancel</a>
      </div>
      </div>


<script type="text/javascript">
  function chkcat(element) {
      var x = $(element).attr('name');
      var num = x.substring(6,12);
      var categorys = document.getElementById("cat").value;
      var array = strToArray(categorys);
      var checked = document.getElementsByName(x)[0].checked;
      if (checked) {
          if ((array.indexOf(num)) == -1) {
              array.push(num.concat(","));
          }
      }
      else {
          array = valueRemove(num,array);
      }
      document.getElementById("cat").value = array;
  }

  function chkid(element) {
      var x = $(element).attr('name');
      var num = x.substring(5,11);
      var cards = document.getElementById("crd").value;
      var array = strToArray(cards);
      var checked = document.getElementsByName(x)[0].checked;
      if (checked) {
          if ((array.indexOf(num)) == -1) {
              array.push(num.concat(","));
          }
      }
      else {
          array = valueRemove(num,array);
      }
      document.getElementById("crd").value = array;
  }
  
function valueRemove(obj, array) {
    var hold = [];
    var objLoc = array.indexOf(obj);
    if (objLoc == -1) {
    return array;
}
    else {
      for (var i=(array.length-1); i >= 0; i--) {
        if (i == a) {
            array.pop();
            }
            else {
            hold.push(array.pop());
            }
        }
        return hold;
    }
}

function strToArray(txt) {
var array = [];
var i = 0;
var j = 0;
var len = txt.length;
for (i=0; i < len; i++) {
if (txt[i] == ",") {
array.push(txt.substring(j, i));
j = i+1;
}
}
    return array;
}
</script>

{% endblock content %}

</form>

Thanks for any help with this issue. Is there a much better approach using Django?

Jim Illback

James Schneider

unread,
Jul 30, 2017, 7:51:47 AM7/30/17
to django...@googlegroups.com


On Jul 28, 2017 5:56 PM, "Jim Illback" <suba...@hotmail.com> wrote:
I use the latest versions of Django, python, bootstrap3, and crispy forms for this project.

I am trying to present a list of flashcards to a user, sorted by category (e.g., Math, Vocabulary, etc.). Then, I want the user to check either the full category or individual flashcards to be added to their practice account. 

To maintain user inputted checkboxes on multiple pages, I must use POST. But, pagination only uses GET. So, a hack on pagination is to wrap the Next/Previous buttons in a sub-form and POST using the buttons. This works for pagination, but then my normal “I’m done, submit this session and process the updates” button - my submit button - does nothing: no GET, no POST, nothing. 

How can I enable pagination, multiple screen data accumulation across pages, and final submission of the session when complete? 

I can't speak to your submit button issue, other than either a piece of JavaScript is getting in the way, or the button is located outside of a <form> tag.

As for the rest of your troubles, it really sounds like you need a form wizard framework, which is what I think you mean by "pagination". Django used to ship one as part of the contrib package, but has since moved it out to it's own separately maintained app.


It handles keeping data state across multiple form submissions, along with dynamic form additions/omissions based on previous steps.

Another option you may consider is developing this process like a single-page-application, where all of the data and forms are managed via JS, and the final collection of data is submitted as a single blob to the server for permanent storage. That's obviously over simplified, but I hope you catch my meaning.

-James

Jim Illback

unread,
Jul 30, 2017, 7:51:26 PM7/30/17
to Django users
Thanks for your reply, James. Appreciate it. BTW, if you check my template, the button is within the form tags, but you probably are right on the JS - I don’t know JS much at all so just copied other people’s code.

I may be mistaken, but I think the wizard link below requires a “static” or semi-static forms flow path (form1, form2, …, formlast). Generally, when you do pagination, there is no preset number of pages - it depends upon the data being requested or available. So, I think that option would not work. But, if I’m wrong, I’d love to know. Thanks.

And, as for using Java just so you can use Django, doesn’t that raise the obvious question - so why use Django? 

To try to illustrate that my issue is a common business issue, here’s a scenario that is exactly the same situation and one used by almost everyone:

A new car purchase site needs the user to pick the car and model. Then, that data stays static throughout the transaction. However, the user might want different options (tires, engines, transmission types, etc.). And, those options are always too many for one page, so they must be done using pagination. When all the options are reviewed - or maybe just one page of options or two or… - then the final idea is to show the user the car’s price, have all the selections chosen listed out for the user to review, and ask for further actions like "do you want to purchase this car?”. 

Are these types of business scenarios just not in Django’s wheelhouse? 


--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CA%2Be%2BciVCh5oEr3rHLEUB4L%3Dpus_bL8H%2BxpmCev5syjes_XOBDg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

James Schneider

unread,
Jul 30, 2017, 8:55:53 PM7/30/17
to django...@googlegroups.com


On Jul 30, 2017 4:50 PM, "Jim Illback" <suba...@hotmail.com> wrote:
Thanks for your reply, James. Appreciate it. BTW, if you check my template, the button is within the form tags, but you probably are right on the JS - I don’t know JS much at all so just copied other people’s code.

I thought that's how you're supposed to program in JS? Been doing it that way for years. ;-)


I may be mistaken, but I think the wizard link below requires a “static” or semi-static forms flow path (form1, form2, …, formlast). Generally, when you do pagination, there is no preset number of pages - it depends upon the data being requested or available. So, I think that option would not work. But, if I’m wrong, I’d love to know. Thanks.

By default, yes, it is expecting a known number of steps for more simple processes. However, it has all of the hooks necessary to create a fully dynamic flow. I did a dynamic flow back when this package was still in Django core, but I doubt the process has changed much.


And, as for using Java just so you can use Django, doesn’t that raise the obvious question - so why use Django? 

I'm not sure what you're referring to here. I don't think that I mentioned Java specifically, nor would I have reason to.

Perhaps a mistaken interpretation of my reference to an SPA? This is what I meant:



To try to illustrate that my issue is a common business issue, here’s a scenario that is exactly the same situation and one used by almost everyone:

A new car purchase site needs the user to pick the car and model. Then, that data stays static throughout the transaction. However, the user might want different options (tires, engines, transmission types, etc.). And, those options are always too many for one page, so they must be done using pagination. When all the options are reviewed - or maybe just one page of options or two or… - then the final idea is to show the user the car’s price, have all the selections chosen listed out for the user to review, and ask for further actions like "do you want to purchase this car?”. 

Are these types of business scenarios just not in Django’s wheelhouse? 


The level of dynamic content generation required for all of those steps will likely be complex, possibly even complicated, for any generic framework to handle. The Django formtools can handle the scenario you've listed, but your code organization and naming standard will need to be strongly enforced to keep the various steps separate.

The workflow you've mentioned isn't necessarily complicated at a high level, the step decisions are rather binary. Either a person wants to select different tires, or doesn't and accepts the default selection. The code in formtools for step selection is usually that simple "if they wanted different tires in a previous step, now show them this form for tires", almost a one-liner (for each step).

Honestly, the biggest issue will be displaying the multiple pages of options, which is somewhat a separate issue. 

There are several strategies. You can display multiple form pages across multiple requests, keeping the results of each along the way, which complicates your back end code. Before the influx of JS and HTML5, this was rather common. 

The newer approach is to use JS to pull all of the possible options, and handle the UI and pagination within JS, perhaps in a pop up modal window. The data is retrieved via an API call, and once a selection is made by the user, either a form is executed containing all of the possible options that the user selected. 

Datatables.js might be a good option for this, as it can also handle the dynamic pagination of options surprisingly well, and plays excellent with Bootstrap. Heck, you can probably use DT with either strategy. It would definitely ease the logic for the server based form wizard, and likely save you a ton of JS scripting for an SPA-like display.

If it were me, I'd investigate Datatables.js with the Bootstrap integration and pagination. It's relatively easy to implement (I, like yourself, am averse to JS), and provides killer functionality with minimal custom JS (mostly single minimal function calls to control ordering of table columns). Once you have that working, try integrating the table with a form combined with pagination, I think that will be the silver bullet you're looking for.


-James

Jim Illback

unread,
Jul 31, 2017, 12:10:55 PM7/31/17
to Django users
Super advice - thanks very much, James! Appreciate your help.

Jim

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
Reply all
Reply to author
Forward
0 new messages