スキップしてメイン コンテンツに移動

Djangoでサブフォームがある場合について

サブフォーム側に複数レコードがあり、なおかつ複数サブフォームを使う必要があり、はじめはインラインフォームをつかってできないかとおもいやっていたのですが、登録はともかく、更新などがうまくいきませんでした。そこでしらべたところ、ほぼそのままなんですが、こちらの記事を参考にしたところ、(ありがとうございます m(__)m) django-extra-viewsを使うことで凡そのことができたので、備忘録として挙げてみました。
まず、django-extra-viewsをインストールします。debianではpython3-django-extra-viewsパッケージがあるので、これをインストールしておきます。
つづいてviews.pyとforms.pyですが以下の様にしました。
#views.py
class artCreateView(CreateWithInlinesView):
    model = art
    fields = ('art_name', 'cat1', 'cat2', 'cat3', 'cat4', 'url1', 'url2',)
    context_object_name = 'art'
    inlines = [art_propForm]
    template_name = 'myapp/edit.html'
    success_url = reverse_lazy('pc:top')
    factory_kwargs = { 'can_order': True, 'can_delete': True}

class  artUpdateView(UpdateWithInlinesView):
    model = art
    form_class = artForm
    inlines = [art_propForm]
    template_name = 'myapp/edit.html'
    success_url = reverse_lazy('pc:top')
    factory_kwargs = { 'can_order': True, 'can_delete': True}
#forms.py
from django import forms
from extra_views.advanced import InlineFormSetFactory
from .models import art, art_prop

class artForm(forms.ModelForm):
    class Meta:
        model = art
        fields = ('art_name', 'cat1', 'cat2', 'cat3', 'cat4', 'url1', 'url2',)
    
    def save(self, commit=True):
        instance = super(artForm, self).save(commit=commit)

        if commit:
            instance.save()

        return instance

class art_propForm(InlineFormSetFactory):
    model = art_prop
    fields = '__all__'
viewsとformsで基本的に必要な記述は一サブフォームの場合これだけです。非常にシンプルですね。models.pyは割愛しますが、つづいてurls.pyとテンプレートは以下のようにしました。
from django.urls import path, include
from . import views

app_name = 'myapp'

urlpatterns = [
    path('create/', views.artCreateView.as_view(), name='create'),
    path('update/<int:pk>/', views.artUpdateView.as_view(), name='update'),
]
{% extends 'base/base.html' %}
{% load static%}

{% block content %}

<form action="." method="post">
{% csrf_token %}

<p>article</p>
<table>
    {{ form.as_table }}
</table>

{% for formset in inlines %}
    {{ formset.prefix }}
    <p>子テーブル(child{{forloop.counter}})</p>
    <table id="formset{{forloop.counter}}" class="table table-sm ">
        {{ formset.management_form }}
        {% for form in formset %}
            <!-- ヘッダ出力 -->
            {% ifequal forloop.counter0 0 %}
            {% for field in form %}
                <td class="bg-light">
                    {% if field.field.widget.is_hidden %}
                    {% else %}
                    {{ field.label_tag }}
                    {% endif %}
                </td>
            {% endfor %}
            {% endifequal %}
            <tr>
                {% for field in form %}
                <td>
                    {{ field }}
                </td>
                {% endfor %}
            </tr>
        {% endfor %}
    </table>
    <button id="add{{forloop.counter}}" type="button" class="ui standard button">add1</button>
    <button id="del{{forloop.counter}}" type="button" class="ui standard button">del1</button>
    
{% endfor %}
<br/>
<br/>
<input class="ui standard button" type="submit" value="登録" />

{% endblock content %}

{% block extrajs %}
<script>
    $('#add1').click(function() {
        var prop = $("#formset1 tr:last-child")
        var formCount = $('#id_art_prop_set-TOTAL_FORMS').val();
        clone = prop.clone().insertAfter(prop);
//        clone.children().not(':last').children().each(function(idx,elm){
        clone.children().children().each(function(idx,elm){
            console.log("add1: before updating...");
            updateElementIndex(this,'art_prop_set', formCount);
            $(this).val('');
            $(this).prop('checked', false)
        });
        $('#id_art_prop_set-TOTAL_FORMS').val(++formCount);
    });

    $('#del1').on('click', function(){
        var prop = $('#formset1 tr');
        var line = $('#id_art_prop_set-TOTAL_FORMS').val();
        var i = 0;
        var j = line;
        prop.each(function(IDX, ELM){
            if ($(ELM).find('input[type="checkbox"]').prop('checked')){
                i++;
                //console.log("i is : ", i);
                //console.log("DELETE is checkd: ", IDX);
                if (line == 1 || line == i ){
                    $(ELM).find("input").val('');
                    $(ELM).find("input:checkbox").prop('checked', false);
                    j=1;
                }
                else {
                    $(ELM).hide();
                    j = j -1;
                }                
            }
            else {
                //console.log("DELET is not checkd. ", IDX);
            }
//            $('#id_art_prop_set-TOTAL_FORMS').val(j);          
        });

        prop = $("#formset1 tr")
        console.log('prop.length', prop.length);
        for (var i=0, formCount = prop.length; i < formCount; ++i) {
//            $(prop.get(i)).children().not(':last').children().each(function() {
            $(prop.get(i)).find('input').not('label').each(function() {
                console.log('i - 1: ', i - 1);
                if (i == 0){
                    console.log('do nothing ...')
                }
                else
                {
//                    updateElementIndex(this, 'art_prop_set', i - 1);
                }              
            });
        }
    });

    function updateElementIndex(el, prefix, ndx) {
        var id_regex = new RegExp('(' + prefix + '-\\d+)');
        var replacement = prefix + '-' + ndx;
        if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
        if (el.id) el.id = el.id.replace(id_regex, replacement);
        if (el.name) el.name = el.name.replace(id_regex, replacement);
    };

    $('#add2').click(function() {
    });
    $('#del2').click(function() {
    })
</script>
{% endblock %}
テンプレートは、javascriptの部分がごちゃごちゃしていて汚いのですが、とりあえず以上の様にしました。サブフォームを展開するときに、id=formset{{forloop.counter}}としてあるので、追加・削除(実際には非表示化で更新時に削除)ボタンをクリックしたときに、目的のサブフォーム列を操作できるようにjavascript側で識別して動作できるようにできました。このあと画像の登録も追加する予定なのですが、コードの記述量が非常にすくなくて済みそうです。
今回は以上です。それでは。

コメント

このブログの人気の投稿

wsdd を使ってSamba サーバをネットワークに表示

Windows 10のアップデートで、セキュリティー対応のため、smbv1がデフォルトではインストールされなくなり、Samba serverがエクスプローラーのネットワークに表示されなくなってしまいました。そこで、いくつか方法を調べたのですが、linuxでwsdの実装がないか探したところ、 https://github.com/christgau/wsdd が、見つかりましたので、さっそくインストールしてみました。まだパッケージにはないようですが、インストール自身は簡単です。wsdd自体は以下のように取得し、linkを張っておきます。 cd /usr/local/bin/ sudo wget https://raw.githubusercontent.com/christgau/wsdd/master/src/wsdd.py sudo chmod 755 wsdd.py sudo ln -sf wsdd.py wsdd こちらのsambaサーバはDebianなので、/etc/systemd/system/wsdd.serviceは以下のようにしました。 [Unit] Description=Web Services Dynamic Discovery host daemon Requires=network-online.target After=network.target network-online.target multi-user.target [Service] Type=simple ExecStart=/usr/local/bin/wsdd -d MYDOMAIN [Install] WantedBy=multi-user.target wsdd -d MYDOMAINのところを、環境にあわせて書き換えてください。 次に、systemdに登録・起動テストを行います。 systemctl enable wsdd systemctl start wsdd 起動に成功すると、エクスプローラーのネットワークに表示されます。  なおこのwsddはpython3が必要です。一度試してみてください。SMBv1/CIFSを停止していても、大丈夫です。 cで書かれたほかのwsddの実装もあるようなので、いずれパッケージになるかもしれませ

Hyper-V Server2019にワークグループ環境下でWindows10(1809)から接続

Hyper-V server 2019に、ワークグループ環境にてWindows10(1809)から接続してみました。Windows10にHyper-V管理ツールがインストールされていることと、Hyper-V Serverをインストール済であることが前提です。以下、Hyper-V serverは名前がHyperVSV、アドレスは192.168.1.110としています。 まず、Hyper-V server上で、powershellを起動し、以下のコマンドを入力します。 Enable-WSManCredSSP -Role Server -Force 続いて、クライアントのWindows10のpowershell で以下のコマンドを入力します。 winrm quickconfig -Force Enable-WSManCredSSP -Role Client -DelegateComputer * -Force さらに、クライアントマシンで、gpedit(グループポリシーエディタ)を起動し、以下の要領でポリシーを設定します。 a. [コンピューターの構成]->[管理テンプレート]->[システム]->[資格情報の委任]->[NTLMのみのサーバー認証で新しい資格情報の委任を許可する] を有効にし、サーバを一覧に追加[表示...]ボタンをクリックして、「WSMAN/*」を追加 b. [コンピューターの構成]->[管理テンプレート]->[システム]->[資格情報の委任]->[NTLM のみのサーバー認証で保存された資格情報の委任を許可する] を有効にし、サーバを一覧に追加[表示...]ボタンをクリックして、「*」を追加 また、名前解決できるように、(notepadを管理者権限で実行し)C:\Windows\System32\Drivers\etc\hostsにサーバ名とIPアドレスの対を追加。 192.168.1.110 HyperVSV 最後に、Hyper-Vマネージャーを起動し、Windows10からHyper-V サーバに接続します。手順は以下の通りです。 「サーバーに接続」->コンピュータの選択->別のコンピューターに[HyperVSV]と入力し、[別のユーザーとして接続する

フレッツ光クロス:MAP-E ROUTER by Debian Box (iptables)

フレッツ光クロスがようやく開通したので、Debianにてrouterを構成し接続してみました。なお、プロバイダーを選ぶにあたっては、IPoE方式がそれぞれ異なるため検討したところ、IPoEでは、MAP-Eでもv6plusとocnバーチャルコネクトがあり、前者がポート数240なのに対し、後者は約4倍のポート数が使えるようなネットの情報をみて、OCNバーチャルコネクトを選択しました。(プロバイダーとしてはぷららです。なおDS-LiteはCE側でのNATではないので今回は見送りました。)そこで、OCN バーチャルコネクトをDebian(iptables)で実現するとどうなるかと思い、ネットの情報を頼りにしつつ、設定した次第です。 実際に試した結果、とりあえず通信できていますが、MAP-Eは本来マッピングルールをマップサーバから取得するはずなので、今回のやり方が正解とはいえませんし、仕様変更されると通信できなくなる可能性があります。あくまでも参考程度ですが、本稿をUPしてみました。 2023/03/16追記: こちら にゲームコンソールNAT越え(Nintendo Switch ナットタイプ A判定)対応版を投稿しました。 2023/03/28追記:※1の記述および3行無効化によりNAT越え(Nintendo Switch ナットタイプ B判定)できるようになりました。 構成は以下の通りです。 ルーターがDebianで回線がOCNバーチャルコネクトであること以外はなにも特別なところはない構成です。 さて、いきなり設定ですが、まず、割り当てられたプレフィックスを確認します。 確認は、 dhclient -6 -d -P enp2s0 とします。出力の中に 前略 RCV: | | X-- IAPREFIX 2400:4050:5c71:af00::/56 後略 このようにプレフィックスが表示されるので、その確認したプレフィックスを書き留めておきます。これを こちらで 入力します。すると、 CE: 2400:4050:5c71:af00:99:f171:c600:2f00 IPv4 アドレス: 153.241.113.198 ポート番号:(1776-1791 2800-2815 3824-3839) 4848-4863 5872-5887 6896-