Search code examples
djangodjango-tables2

Django, Materialize, Django-tables2: Expected table or queryset, not str


I have a form and one of the fields is a MultipleChoiceField. The form is rendered in a modal using Materialize. I had issues to render the choices in the form but found this answer

I changed the field to:

comp = forms.MultipleChoiceField(choices=COMP, widget=forms.Select(choices=COMP, attrs={'class': 'browser-default'}))

which solved the rendering issue.

Once I submit the form I expect to view the data in the django-tables2 table, which worked fine, but now I receive the following error:

Exception Value: Expected table or queryset, not str

If I remove the widget the form doesn’t render as I would like but I’m able to submit the form and see the data in the table.

COMP is the field choices sequence.

COMP = (
    ('Daily', 'Daily'),
    ('Weekly', 'Weekly'),
    ('Monthly', 'Monthly'),
    ('Quarterly', 'Quarterly'),
    ('Yearly', 'Yearly'),
)

Full traceback

Traceback (most recent call last):
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
    response = get_response(request)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\views\generic\base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\views\generic\base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\Gary\Desktop\mysite\loan\views.py", line 119, in post
    return render(request, self.template_name, {'form': form})
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\shortcuts.py", line 19, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\backends\django.py", line 61, in render
    return self.template.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 171, in render
    return self._render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 163, in _render
    return self.nodelist.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 936, in render
    bit = node.render_annotated(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 903, in render_annotated
    return self.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\loader_tags.py", line 150, in render
    return compiled_parent._render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 163, in _render
    return self.nodelist.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 936, in render
    bit = node.render_annotated(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 903, in render_annotated
    return self.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\loader_tags.py", line 62, in render
    result = block.nodelist.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 936, in render
    bit = node.render_annotated(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django\template\base.py", line 903, in render_annotated
    return self.render(context)
  File "C:\Users\Gary\AppData\Local\Programs\Python\Python38-32\lib\site-packages\django_tables2\templatetags\django_tables2.py", line 145, in render
    raise ValueError("Expected table or queryset, not {}".format(klass))

Exception Type: ValueError at /debtors/
Exception Value: Expected table or queryset, not str

forms.py

from django import forms
from loan.models import Debtor, DebtorTransaction

COMP = (
    ("Monthly", "Monthly"),
    ("Quarterly", "Quarterly"),
    ("Half Yearly", "Half Yearly"),
    ("Yearly", "Yearly"),
)


class DebtorForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(DebtorForm, self).__init__(*args, **kwargs)
        self.fields['comp'].label = "Compounding Period"

    comp = forms.MultipleChoiceField(choices=COMP, widget=forms.SelectMultiple(choices=COMP, attrs={'class': 'browser-default'}))

    class Meta:
        model = Debtor
        widgets = {
            'courtdate': forms.DateInput(attrs={'class': 'datepicker'}),
        }

        fields = '__all__'
        exclude = [
            'status',
            'account',
            'loan',
            'number',
            'unique']


class DebitTransactionForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.id = kwargs.pop('debtor', [])
        super(DebitTransactionForm, self).__init__(*args, **kwargs)

    class Meta:
        model = DebtorTransaction
        widgets = {
        }
        fields = '__all__'
        exclude = [
            'debtor',
            'date',
            'type',
            'initiationfee',
            'transaction_balance',
            'interest',
            'rate']


class CreditTransactionForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.id = kwargs.pop('debtor', [])
        super(CreditTransactionForm, self).__init__(*args, **kwargs)

    class Meta:
        model = DebtorTransaction
        widgets = {
        }
        fields = '__all__'
        exclude = [
            'debtor',
            'date',
            'type',
            'initiationfee',
            'transaction_balance',
            'interest',
            'rate']


class InitiationForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.id = kwargs.pop('debtor', [])
        super(InitiationForm, self).__init__(*args, **kwargs)

    class Meta:
        model = DebtorTransaction
        widgets = {
        }
        fields = '__all__'
        exclude = [
            'amount',
            'description',
            'debtor',
            'date',
            'type',
            'transaction_balance',
            'interest',
            'rate']

tables.py

import django_tables2 as tables
from .models import Debtor, DebtorTransaction
from django_tables2.utils import A


def balance_footer(table):
    try:
        s = sum(x.debtoraccount.balance for x in table.data)
    except Exception as e:
        raise

    return '{:,}'.format(s).replace(',', ' ')


class DebtorsTable(tables.Table):
    balance = tables.Column(accessor='debtoraccount.balance', footer=balance_footer,
                            attrs={"td": {'style': 'text-align: right;'},
                                   "th": {'style': 'text-align: right;'}})
    courtdate = tables.DateColumn(verbose_name='Court Date', format='d F Y',
                                  attrs={"td": {'style': 'text-align: center;'},
                                         "th": {'style': 'text-align: center;'}})
    LMS = tables.Column(attrs={"td": {'style': 'text-align: center;'}, "th": {'style': 'text-align: center;'}})
    link = tables.LinkColumn('loan:debtors_with_pk', text='Open', args=[A('pk')], orderable=False, empty_values=(),
                             verbose_name='')

    def render_balance(self, value):
        return '{:,}'.format(value).replace(',', ' ')

    class Meta:
        model = Debtor
        template_name = "django_tables2/bootstrap.html"
        fields = ("name", "LMS", "courtdate")
        sequence = ("link", "name", "LMS", "courtdate", "balance")


class TransactionTable(tables.Table):
    transaction_balance = tables.Column(verbose_name='Balance', attrs={"td": {'style': 'text-align: right;'},
                                                                       "th": {'style': 'text-align: right;'}})
    amount = tables.Column(verbose_name='Amount', attrs={"td": {'style': 'text-align: right;'},
                                                         "th": {'style': 'text-align: right;'}})
    type = tables.Column(verbose_name='', attrs={"td": {'style': 'text-align: center;'},
                                                 "th": {'style': 'text-align: center;'}})
    date = tables.DateColumn(verbose_name='Date', format='d F Y', attrs={"td": {'style': 'text-align: center;'},
                                                                         "th": {'style': 'text-align: center;'}})

    def render_amount(self, value, record):
        if record.type == DebtorTransaction.CREDIT:
            return '- {:,}'.format(value).replace(',', ' ')
        else:
            return '{:,}'.format(value).replace(',', ' ')

    def render_transaction_balance(self, value):
        return '{:,}'.format(value).replace(',', ' ')

    class Meta:
        model = DebtorTransaction
        template_name = "django_tables2/bootstrap.html"
        fields = ("date", "description", "amount", "type", "transaction_balance")

views.py

from django.shortcuts import render, redirect
from django.views.generic import TemplateView
from loan.forms import DebtorForm, DebitTransactionForm, CreditTransactionForm, InitiationForm
from loan.models import Debtor, DebtorAccount, DebtorTransaction
from loan.tables import DebtorsTable, TransactionTable
from django_tables2 import RequestConfig
from decimal import Decimal


class DebtorView(TemplateView):
    template_name = 'loan/skuld.html'

    def get(self, request, pk=None):
        model = Debtor, DebtorTransaction
        form1 = DebitTransactionForm()
        form2 = CreditTransactionForm()
        form3 = InitiationForm()
        debtor = Debtor.objects.get(pk=pk)
        debtor_id = debtor.id

        table2 = TransactionTable(DebtorTransaction.objects.all().filter(debtor_id=debtor_id))
        RequestConfig(request).configure(table2)

        args = {'form1': form1, 'form2': form2, 'form3': form3, 'debtor': debtor, 'table2': table2}

        return render(request, self.template_name, args)

    def post(self, request, pk=None):
        args = {}
        if request.method == 'POST':
            debtor = DebtorAccount.objects.get(pk=pk)
            form1 = DebitTransactionForm(request.POST, debtor)
            form2 = CreditTransactionForm(request.POST, debtor)
            form3 = InitiationForm(request.POST, debtor)
            success = False

            if 'debit_button' in request.POST and form1.is_valid() and form3.is_valid():
                publish1 = form1.save(commit=False)
                publish1.debtor = DebtorAccount.objects.get(pk=pk)
                publish1.type = 'DEBIT'
                publish1.save()
                publish3 = form3.save(commit=False)
                form1 = DebitTransactionForm()
                publish3.debtor = DebtorAccount.objects.get(pk=pk)
                if publish3.initiationfee is True:
                    skuldenaar = publish1.amount

                    def get_initiationfee(skuldenaar):
                        if publish3.initiationfee is True:
                            if skuldenaar >= Decimal('8850'):
                                initiation = Decimal('1050')
                            else:
                                initiation = (skuldenaar * Decimal('0.1')) + Decimal('165')
                        else:
                            initiation = Decimal('0')

                        return(initiation)

                    publish3.amount = get_initiationfee(skuldenaar)
                    publish3.description = 'Initiation Fee'
                    publish3.type = 'INITIATION'
                    publish3.save()
                    form3 = InitiationForm()
                success = True

            if 'credit_button' in request.POST and form2.is_valid():
                publish2 = form2.save(commit=False)
                publish2.debtor = DebtorAccount.objects.get(pk=pk)
                publish2.type = 'CREDIT'
                publish2.save()
                form2 = DebitTransactionForm()
                success = True

            if success:
                return redirect('loan:debtors_with_pk', pk=pk)

        else:
            debtor = DebtorAccount.objects.get(pk=pk)
            form1 = DebitTransactionForm(request.POST, debtor)
            form2 = CreditTransactionForm(request.POST, debtor)
            form3 = InitiationForm(request.POST, debtor)

        args['form1'] = form1
        args['form2'] = form2
        args['form3'] = form3

        return render(request, self.template_name, args)

    def success(request):
        return render(request, 'loan/skuld.html', {})


class AllDebtorsView(TemplateView):
    template_name = 'loan/debtors.html'

    def get(self, request, pk=None):
        model = Debtor
        form = DebtorForm()
        debtors = Debtor.objects.all()
        table1 = DebtorsTable(Debtor.objects.all().filter())
        RequestConfig(request).configure(table1)

        field_names = [f.name for f in model._meta.get_fields()]

        data = [[getattr(ins, name) for name in field_names]
                for ins in Debtor.objects.prefetch_related().all()]

        args = {'form': form, 'field_names': field_names, 'data': data, 'debtors': debtors, 'table1': table1}

        return render(request, self.template_name, args)

    def post(self, request):
        form = DebtorForm(request.POST)
        if form.is_valid():
            form.save()
            form = DebtorForm
            return redirect('/debtors')
        return render(request, self.template_name, {'form': form})

template

{% extends 'loan/base.html' %}
{% load render_table from django_tables2 %}

<html lang="en">
        {% block title %}
            All Debtors
        {% endblock %}

        {% block body %}
            <div id="modal1" class="modal">
                <div class="modal-content">
                    <h4>New Debtor</h4><br>
                    <form method="post">
                        {% csrf_token %}
                        {{ form.as_p }}
                        <div class="modal-footer">
                            <button type="submit">Save</button>
                        </div>
                    </form>
                </div>
            </div>
            <div class="container">
                <div class="fixed-action-btn">
                    <a class="btn-floating btn-large waves-effect waves-light blue darken-1 btn modal-trigger" href="#modal1"">
                        <i class="large material-icons">person_add</i>
                    </a>
                </div>
            </div><br>
            <div class="container">
                <h5>Debtors</h5>
                {% render_table table1 %}
            </div>

            <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>

            <script>
                jQuery(document).ready(function($) {
                    $(".clickable-row").click(function() {
                  window.location = $(this).data('url');
                    });
                });
            </script>

            <script>
                $(document).ready(function(){
                    $('.fixed-action-btn').floatingActionButton();
                });
            </script>

            <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>

            <script>
                $(document).ready(function(){
                    $('.modal').modal();
                });
            </script>

            <script>
                $(document).ready(function(){
                    $('.datepicker').datepicker({format: 'yyyy-mm-dd', firstDay: 1});
                });
            </script>
        {% endblock %}
</html>

What am I missing? How can I resolve this?

Please let me know if I should post additional code, thank you


Solution

  • I installed django-debug-toolbar as @schillingt suggested. Every time I removed the widget the table will render, but when I run it with the following line nothing would get passed to render_table.

    comp = forms.MultipleChoiceField(choices=COMP, widget=forms.Select(choices=COMP, attrs={'class': 'browser-default'}))
    

    I then came across the following answer. And had a read of the documents.

    According to the documents a MultipleChoiceField normalizes to a list of strings, whereas as a ChoiceField normalizes to a single string. I changed the MultipleChoiceField to a ChoiceField and it worked, the form gets submitted and the table renders.

    I don’t fully understand the technicality of the change but I assume that every time I removed the widget from the above line the form input to select a choice disappeared and the default value of the model field was used and the tables was rendered.

    I wrongly assumed it was the widget causing the problem.

    My understanding is that the MultipleChoiceField from the form created a list of strings and passed to the model which expected a single string. The ChoiceField which normalizes to a single string fixed the issue.

    The following worked for me:

    comp = forms.ChoiceField(choices=COMP, widget=forms.Select(attrs={'class': 'browser-default'}))