【问题标题】:Add row dynamically in django formset在 django formset 中动态添加行
【发布时间】:2020-07-22 22:05:09
【问题描述】:

在我的 django 应用程序中,我有两个模型,即 PlayerTeam,它们通过多对多关系连接。要在我的表格中动态添加数据,我想使用 javascript 在我的表单中添加 Add rowRemove Row 按钮,但无法这样做。

以下是详细信息:

模型.py

class Player(models.Model):
    pname = models.CharField(max_length=50)
    hscore = models.IntegerField()
    age = models.IntegerField()

    def __str__(self):
       return self.pname

class Team(models.Model):
    tname = models.CharField(max_length=100)
    player= models.ManyToManyField(Player)

    def __str__(self):
        return self.tname

Forms.py

class PlayerForm(forms.Form):
    pname = forms.CharField()
    hscore= forms.IntegerField()
    age = forms.IntegerField()

PlayerFormset= formset_factory(PlayerForm)

class TeamForm(forms.Form):
   tname= forms.CharField()
   player= PlayerFormset()

Views.py

def post(request):

   if request.POST:
        form = TeamForm(request.POST)
        form.player_instances = PlayerFormset(request.POST)
        if form.is_valid():
            team= Team()
            team.tname= form.cleaned_data['tname']
            team.save()

        if form.player_instances.cleaned_data is not None:

            for item in form.player_instances.cleaned_data:
                player = Player()
                player.pname= item['pname']
                player.hscore= item['hscore']
                player.age= item['age']
                player.save()
                team.player.add(player)
            team.save()

   else:
        form = TeamForm()
        return render(request, 'new.html', {'form':form})

新的.html

<html>
<head>

    <title>gffdfdf</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="/static/jquery.formset.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>

</head>
<body>

<div class="container">

    <form id="myForm" action="" method="post" class="">
        {% csrf_token %}
        <h2> Team</h2>
        {% for field in form %}
            {{ field.errors }}
            {{ field.label_tag }} : {{ field }}
        {% endfor %}
        {{ form.player.management_form }}

        <h3> Product Instance(s)</h3>
        <table id="table-product" class="table">
            <thead>
            <tr>
                <th>player name</th>
                <th>highest score</th>
                <th>age</th>
            </tr>

            </thead>
            {% for player in form.player %}
                <tbody class="player-instances">

                <tr>
                    <td>{{ player.pname }}</td>
                    <td>{{ player.hscore }}</td>
                    <td>{{ player.age }}</td>
                </tr>

                </tbody>
            {% endfor %}
        </table>
        <button type="submit" class="btn btn-primary">save</button>

    </form>
</div>
<script>
    $(function () {
        $('#myForm tbody tr').formset();
    })
</script>
</body>
</html>

如何使用javascript添加或删除多对多关系连接的行?

上面的代码为我们提供了以下信息:

【问题讨论】:

    标签: javascript python django django-forms formset


    【解决方案1】:

    为了保持简单和通用,我将 OP 的示例简化为单个模型和基本表单集,没有 Team-Player 多对多关系。 JavaScript 部分的原理保持不变。如果你确实想实现多对多关系,你可以使用例如一个inline formset,正如here所解释的那样。

    所以,假设我们有一个简单的模型:

    class Player(models.Model):
        name = models.CharField(max_length=50)
        age = models.IntegerField()
    

    我们的视图可能如下所示(基于docs 中的示例):

    def my_formset_view(request):
        response = None
        formset_class = modelformset_factory(
            model=Player, fields=('name', 'age'), extra=0, can_delete=True)
        if request.method == 'POST':
            formset = formset_class(data=request.POST)
            if formset.is_valid():
                formset.save()
                response = redirect(to='my_success_view')
        else:
            formset = formset_class()
        if response is None:
            response = render(
                request, 'myapp/my_formset_template.html', dict(formset=formset))
        return response
    

    下面的 my_formset_template.html django 模板(跳过样板)使我们能够添加和删除 formset-forms:

    ...
    <template id="id_formset_empty_form">{{ formset.empty_form }}</template>
    <form method="post" id="id_html_form" autocomplete="off">
        {% csrf_token %}
        <table id="id_formset_container">
            {{ formset }}
        </table>
        <div id="id_formset_add_button" style="text-decoration: underline; cursor: pointer;">Add</div>
        <input id="id_formset_submit_button" type="submit" value="Submit">
    </form>
    ...
    

    HTML <template> 元素可以轻松地从 formset.empty_form 复制内容。

    旁注:如果我们不设置autocomplete="off",浏览器会在管理表单上缓存TOTAL_FORMS的值,即使在重新加载页面后。

    现在,下面的 JavaScript 为我完成了这项工作(没有尝试优化,我只是想让它易于阅读):

    window.addEventListener('load', (event) => {
        // get form template and total number of forms from management form
        const templateForm = document.getElementById('id_formset_empty_form');
        const inputTotalForms = document.querySelector('input[id$="-TOTAL_FORMS"]');
        const inputInitialForms = document.querySelector('input[id$="-INITIAL_FORMS"]');
    
        // get our container (e.g. <table>, <ul>, or <div>) and "Add" button
        const containerFormSet = document.getElementById('id_formset_container');
        const buttonAdd = document.getElementById('id_formset_add_button');
        const buttonSubmit = document.getElementById('id_formset_submit_button');
    
        // event handlers
        buttonAdd.onclick = addForm;
        buttonSubmit.onclick = updateNameAttributes;
    
        // form counters (note: proper form index bookkeeping is necessary
        // because django's formset will create empty forms for any missing
        // indices, and will discard forms with indices >= TOTAL_FORMS, which can
        // lead to funny behavior in some edge cases)
        const initialForms = Number(inputInitialForms.value);
        let extraFormIndices = [];
        let nextFormIndex = initialForms;
    
        function addForm () {
            // create DocumentFragment from template
            const formFragment = templateForm.content.cloneNode(true);
            // a django form is rendered as_table (default), as_ul, or as_p, so
            // the fragment will contain one or more <tr>, <li>, or <p> elements,
            // respectively.
            for (let element of formFragment.children) {
                // replace the __prefix__ placeholders from the empty form by the
                // actual form index
                element.innerHTML = element.innerHTML.replace(
                    /(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
                    nextFormIndex.toString());
                // add a custom attribute to simplify bookkeeping
                element.dataset.formIndex = nextFormIndex.toString();
                // add a delete click handler (if formset can_delete)
                setDeleteHandler(element);
            }
            // move the fragment's children onto the DOM
            // (the fragment is empty afterwards)
            containerFormSet.appendChild(formFragment);
            // keep track of form indices
            extraFormIndices.push(nextFormIndex++);
        }
    
        function removeForm (event) {
            // remove all elements with form-index matching that of the delete-input
            const formIndex = event.target.dataset.formIndex;
            for (let element of getFormElements(formIndex)) {
                element.remove();
            }
            // remove form index from array
            let indexIndex = extraFormIndices.indexOf(Number(formIndex));
            if (indexIndex > -1) {
                extraFormIndices.splice(indexIndex, 1);
            }
        }
    
        function setDeleteHandler (containerElement) {
            // modify DELETE checkbox in containerElement, if the checkbox exists
            // (these checboxes are added by formset if can_delete)
            const inputDelete = containerElement.querySelector('input[id$="-DELETE"]');
            if (inputDelete) {
                // duplicate the form index instead of relying on parentElement (more robust)
                inputDelete.dataset.formIndex = containerElement.dataset.formIndex;
                inputDelete.onclick = removeForm;
            }
        }
    
        function getFormElements(index) {
            // the data-form-index attribute is available as dataset.formIndex
            // https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes#javascript_access
            return containerFormSet.querySelectorAll('[data-form-index="' + index + '"]');
        }
    
        function updateNameAttributes (event) {
            // make sure the name indices are consecutive and smaller than
            // TOTAL_FORMS (the name attributes end up as dict keys on the server)
            // note we do not need to update the indices in the id attributes etc.
            for (let [consecutiveIndex, formIndex] of extraFormIndices.entries()) {
                for (let formElement of getFormElements(formIndex)){
                    for (let element of formElement.querySelectorAll('input, select')) {
                        if ('name' in element) {
                            element.name = element.name.replace(
                                /(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
                                (initialForms + consecutiveIndex).toString());
                        }
                    }
                }
            }
            updateTotalFormCount();
        }
    
        function updateTotalFormCount (event) {
            // note we could simply do initialForms + extraFormIndices.length
            // to get the total form count, but that does not work if we have
            // validation errors on forms that were added dynamically
            const firstElement = templateForm.content.querySelector('input, select');
            // select the first input or select element, then count how many ids
            // with the same suffix occur in the formset container
            if (firstElement) {
                let suffix = firstElement.id.split('__prefix__')[1];
                let selector = firstElement.tagName.toLowerCase() + '[id$="' + suffix + '"]';
                let allElementsForId = containerFormSet.querySelectorAll(selector);
                // update total form count
                inputTotalForms.value = allElementsForId.length;
            }
        }
    }, false);
    
    
    

    请注意,简单地添加和删除表单集表单并没有那么复杂,直到出现问题:上面大约一半的行与处理边缘情况有关,例如动态添加的表单验证失败。

    【讨论】:

      猜你喜欢
      • 2019-11-27
      • 1970-01-01
      • 1970-01-01
      • 2016-07-17
      • 2018-09-21
      • 2018-07-19
      • 2013-08-02
      • 2012-08-17
      • 2014-03-24
      相关资源
      最近更新 更多