Python 如何为新的ForeignKey模型使用CreateView并在表单中包含字段?

Python 如何为新的ForeignKey模型使用CreateView并在表单中包含字段?,python,django,django-crispy-forms,Python,Django,Django Crispy Forms,默认情况下,CreateView/UpdateView只包含一个下拉列表,用于选择已存在的ForeignKey相关对象 使用django crispy表单,我如何拥有一个CreateView或UpdateView,它不仅包括我的模型的字段,还包括通过ForeignKey创建新模型的字段 使用CreateView/UpdateView和使用常规FBV会更好吗?如果是的话,我会怎么做 我在加快学习Django方面没有太多问题,但要想将注意力集中在视图/表单/模型的交互方式上并不容易 class Pr

默认情况下,CreateView/UpdateView只包含一个下拉列表,用于选择已存在的ForeignKey相关对象

使用django crispy表单,我如何拥有一个CreateView或UpdateView,它不仅包括我的模型的字段,还包括通过ForeignKey创建新模型的字段

使用CreateView/UpdateView和使用常规FBV会更好吗?如果是的话,我会怎么做

我在加快学习Django方面没有太多问题,但要想将注意力集中在视图/表单/模型的交互方式上并不容易

class Property(models.Model):
    name = models.CharField(max_length=128)
    address = models.ForeignKey(PostalAddress, blank=True, null=True)

class PostalAddress(models.Model):
    street_address = models.CharField(max_length=500)
    city = models.CharField(max_length=500)
    state = USStateField()
    zip_code = models.CharField(max_length=10)

class PropertyUpdateView(UpdateView):
    model = Property

class PropertyCreateView(CreateView):
    model = Property
我一直在尝试将
form\u class=PropertyForm
添加到CreateView/UpdateView中,并使用如下方法:

class PropertyForm(ModelForm):

    def __init__(self, *args, **kwargs):
        self.helper = FormHelper()
        self.helper.form_id = 'id-propertyForm'
        self.helper.form_method = 'post'

        self.helper.layout = Layout(
            Fieldset(
                'Edit Property',
                'name',
            ),
            ButtonHolder(
                Submit('submit', 'Submit')
            )
        )

        super(PropertyForm, self).__init__(*args, **kwargs)

    class Meta:
        model = Property

…但我不知道从这里走到哪里。

我已经围绕我在评论中链接到的主题构建了一个答案

我首先找到了一种在表单中包含“添加新”链接的合乎逻辑的方法。解决方案是为我想要的表单小部件提供一个模板。我的表单如下所示:

# core/forms.py
class IntranetForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(IntranetForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_class = 'form-horizontal'

# app/forms.py
class ComplaintForm(IntranetForm):
    def __init__(self, *args, **kwargs):
        super(ComplaintForm, self).__init__(*args, **kwargs)
        self.helper.layout = Layout(
            Field(
                'case',
                css_class='input-xlarge',
                template='complaints/related_case.html',
            ),
            Field('date_received', css_class = 'input-xlarge'),
            Field('stage', css_class = 'input-xlarge'),
            Field('tags', css_class = 'input-xlarge'),
            Field('team', css_class = 'input-xlarge'),
            Field('handler', css_class = 'input-xlarge'),
            Field('status', css_class = 'input-xlarge'),
            FormActions(
                Submit(
                    'save_changes',
                    'Save changes',
                    css_class = "btn-primary"
                ),
                Button(
                    'cancel',
                    'Cancel',
                    onclick = 'history.go(-1);'
                ),
            ),
        )

    class Meta:
        model = Complaint
        fields = (
            'case',
            'date_received',
            'stage',
            'tags',
            'team',
            'handler',
            'status',
        )
<div id="div_id_case" class="control-group">
<label for="id_case" class="control-label ">Case</label>
<div class="controls">
<select class="input-xlarge select" id="id_case" name="case"></select>
&nbsp;<a href="{% url 'add_case' %}" id="add_id_case" class="add-another btn btn-success" onclick="return showAddAnotherPopup(this);">Add new</a>
</div>
</div>
{% extends 'complaints/base_complaint.html' %}
{% load crispy_forms_tags %}

{% block extra_headers %}
{{ form.media }}
{% endblock %}

{% block title %}Register complaint{% endblock %}

{% block heading %}Register complaint{% endblock %}

{% block content %}
{% crispy form %}
<script>
$(document).ready(function() {
    $( '.add-another' ).click(function(e) {
        e.preventDefault(  );
        showAddAnotherPopup( $( this ) );
    });
});

/* Credit: django.contrib.admin (BSD) */

function showAddAnotherPopup(triggeringLink) {
    /*

    Pause here with Firebug's script debugger.

    */
    var name = triggeringLink.attr( 'id' ).replace(/^add_/, '');
    name = id_to_windowname(name);
    href = triggeringLink.attr( 'href' );

    if (href.indexOf('?') == -1) {
        href += '?popup=1';
    } else {
        href += '&popup=1';
    }

    href += '&winName=' + name;

    var win = window.open(href, name, 'height=800,width=800,resizable=yes,scrollbars=yes');
    win.focus();

    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    // newId and newRepr are expected to have previously been escaped by
    newId = html_unescape(newId);
    newRepr = html_unescape(newRepr);
    var name = windowname_to_id(win.name);
    var elem = document.getElementById(name);

    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        }
    } else {
        console.log("Could not get input id for win " + name);
    }

    win.close();
}

function html_unescape(text) {
 // Unescape a string that was escaped using django.utils.html.escape.
    text = text.replace(/</g, '');
    text = text.replace(/"/g, '"');
    text = text.replace(/'/g, "'");
    text = text.replace(/&/g, '&');
    return text;
}

// IE doesn't accept periods or dashes in the window name, but the element IDs
// we use to generate popup window names may contain them, therefore we map them
// to allowed characters in a reversible way so that we can locate the correct
// element when the popup window is dismissed.
function id_to_windowname(text) {
    text = text.replace(/\./g, '__dot__');
    text = text.replace(/\-/g, '__dash__');
    text = text.replace(/\[/g, '__braceleft__');
    text = text.replace(/\]/g, '__braceright__');
    return text;
} 

function windowname_to_id(text) {
    return text;
}
</script>
{% endblock %}
请注意,在第一个字段中添加了一个模板参数。该模板如下所示:

# core/forms.py
class IntranetForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(IntranetForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_class = 'form-horizontal'

# app/forms.py
class ComplaintForm(IntranetForm):
    def __init__(self, *args, **kwargs):
        super(ComplaintForm, self).__init__(*args, **kwargs)
        self.helper.layout = Layout(
            Field(
                'case',
                css_class='input-xlarge',
                template='complaints/related_case.html',
            ),
            Field('date_received', css_class = 'input-xlarge'),
            Field('stage', css_class = 'input-xlarge'),
            Field('tags', css_class = 'input-xlarge'),
            Field('team', css_class = 'input-xlarge'),
            Field('handler', css_class = 'input-xlarge'),
            Field('status', css_class = 'input-xlarge'),
            FormActions(
                Submit(
                    'save_changes',
                    'Save changes',
                    css_class = "btn-primary"
                ),
                Button(
                    'cancel',
                    'Cancel',
                    onclick = 'history.go(-1);'
                ),
            ),
        )

    class Meta:
        model = Complaint
        fields = (
            'case',
            'date_received',
            'stage',
            'tags',
            'team',
            'handler',
            'status',
        )
<div id="div_id_case" class="control-group">
<label for="id_case" class="control-label ">Case</label>
<div class="controls">
<select class="input-xlarge select" id="id_case" name="case"></select>
&nbsp;<a href="{% url 'add_case' %}" id="add_id_case" class="add-another btn btn-success" onclick="return showAddAnotherPopup(this);">Add new</a>
</div>
</div>
{% extends 'complaints/base_complaint.html' %}
{% load crispy_forms_tags %}

{% block extra_headers %}
{{ form.media }}
{% endblock %}

{% block title %}Register complaint{% endblock %}

{% block heading %}Register complaint{% endblock %}

{% block content %}
{% crispy form %}
<script>
$(document).ready(function() {
    $( '.add-another' ).click(function(e) {
        e.preventDefault(  );
        showAddAnotherPopup( $( this ) );
    });
});

/* Credit: django.contrib.admin (BSD) */

function showAddAnotherPopup(triggeringLink) {
    /*

    Pause here with Firebug's script debugger.

    */
    var name = triggeringLink.attr( 'id' ).replace(/^add_/, '');
    name = id_to_windowname(name);
    href = triggeringLink.attr( 'href' );

    if (href.indexOf('?') == -1) {
        href += '?popup=1';
    } else {
        href += '&popup=1';
    }

    href += '&winName=' + name;

    var win = window.open(href, name, 'height=800,width=800,resizable=yes,scrollbars=yes');
    win.focus();

    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    // newId and newRepr are expected to have previously been escaped by
    newId = html_unescape(newId);
    newRepr = html_unescape(newRepr);
    var name = windowname_to_id(win.name);
    var elem = document.getElementById(name);

    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        }
    } else {
        console.log("Could not get input id for win " + name);
    }

    win.close();
}

function html_unescape(text) {
 // Unescape a string that was escaped using django.utils.html.escape.
    text = text.replace(/</g, '');
    text = text.replace(/"/g, '"');
    text = text.replace(/'/g, "'");
    text = text.replace(/&/g, '&');
    return text;
}

// IE doesn't accept periods or dashes in the window name, but the element IDs
// we use to generate popup window names may contain them, therefore we map them
// to allowed characters in a reversible way so that we can locate the correct
// element when the popup window is dismissed.
function id_to_windowname(text) {
    text = text.replace(/\./g, '__dot__');
    text = text.replace(/\-/g, '__dash__');
    text = text.replace(/\[/g, '__braceleft__');
    text = text.replace(/\]/g, '__braceright__');
    return text;
} 

function windowname_to_id(text) {
    return text;
}
</script>
{% endblock %}
那里的javascript完全是从我链接到的博客条目复制/粘贴的,它在弹出窗口中调用我的
CaseCreateView
,其中包括我们已经看过的表单。现在您需要该表单将返回值传递到原始页面

为此,我在casedetail页面中包含了以下脚本。这意味着当我的表单在保存时重定向到详细信息页面时,它将在调用脚本之前正确保存。详细信息模板如下所示:

{% extends 'complaints/base_complaint.html' %}

{% block content %}
<script>
$(document).ready(function() {
    opener.dismissAddAnotherPopup( window, "{{ case.pk }}", "{{ case }}" );
});
</script>
{% endblock %}
{%extends'投诉/base\u投诉.html%}
{%block content%}
$(文档).ready(函数(){
dismissAddAnotherPopup(窗口,{case.pk}},{{case}});
});
{%endblock%}

这将在呈现页面后立即关闭该页面。这可能对您来说并不理想,但您可以改变这种行为,例如,在保存时覆盖表单重定向到的页面,并将其用作上述脚本的方便存储,而不是其他。注意它是如何传回case主键及其unicode表示的吗?现在,这些内容将以原始形式填充所选的
字段,您就完成了。

我已经围绕我在评论中链接到的内容构建了一个答案

我首先找到了一种在表单中包含“添加新”链接的合乎逻辑的方法。解决方案是为我想要的表单小部件提供一个模板。我的表单如下所示:

# core/forms.py
class IntranetForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(IntranetForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_class = 'form-horizontal'

# app/forms.py
class ComplaintForm(IntranetForm):
    def __init__(self, *args, **kwargs):
        super(ComplaintForm, self).__init__(*args, **kwargs)
        self.helper.layout = Layout(
            Field(
                'case',
                css_class='input-xlarge',
                template='complaints/related_case.html',
            ),
            Field('date_received', css_class = 'input-xlarge'),
            Field('stage', css_class = 'input-xlarge'),
            Field('tags', css_class = 'input-xlarge'),
            Field('team', css_class = 'input-xlarge'),
            Field('handler', css_class = 'input-xlarge'),
            Field('status', css_class = 'input-xlarge'),
            FormActions(
                Submit(
                    'save_changes',
                    'Save changes',
                    css_class = "btn-primary"
                ),
                Button(
                    'cancel',
                    'Cancel',
                    onclick = 'history.go(-1);'
                ),
            ),
        )

    class Meta:
        model = Complaint
        fields = (
            'case',
            'date_received',
            'stage',
            'tags',
            'team',
            'handler',
            'status',
        )
<div id="div_id_case" class="control-group">
<label for="id_case" class="control-label ">Case</label>
<div class="controls">
<select class="input-xlarge select" id="id_case" name="case"></select>
&nbsp;<a href="{% url 'add_case' %}" id="add_id_case" class="add-another btn btn-success" onclick="return showAddAnotherPopup(this);">Add new</a>
</div>
</div>
{% extends 'complaints/base_complaint.html' %}
{% load crispy_forms_tags %}

{% block extra_headers %}
{{ form.media }}
{% endblock %}

{% block title %}Register complaint{% endblock %}

{% block heading %}Register complaint{% endblock %}

{% block content %}
{% crispy form %}
<script>
$(document).ready(function() {
    $( '.add-another' ).click(function(e) {
        e.preventDefault(  );
        showAddAnotherPopup( $( this ) );
    });
});

/* Credit: django.contrib.admin (BSD) */

function showAddAnotherPopup(triggeringLink) {
    /*

    Pause here with Firebug's script debugger.

    */
    var name = triggeringLink.attr( 'id' ).replace(/^add_/, '');
    name = id_to_windowname(name);
    href = triggeringLink.attr( 'href' );

    if (href.indexOf('?') == -1) {
        href += '?popup=1';
    } else {
        href += '&popup=1';
    }

    href += '&winName=' + name;

    var win = window.open(href, name, 'height=800,width=800,resizable=yes,scrollbars=yes');
    win.focus();

    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    // newId and newRepr are expected to have previously been escaped by
    newId = html_unescape(newId);
    newRepr = html_unescape(newRepr);
    var name = windowname_to_id(win.name);
    var elem = document.getElementById(name);

    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        }
    } else {
        console.log("Could not get input id for win " + name);
    }

    win.close();
}

function html_unescape(text) {
 // Unescape a string that was escaped using django.utils.html.escape.
    text = text.replace(/</g, '');
    text = text.replace(/"/g, '"');
    text = text.replace(/'/g, "'");
    text = text.replace(/&/g, '&');
    return text;
}

// IE doesn't accept periods or dashes in the window name, but the element IDs
// we use to generate popup window names may contain them, therefore we map them
// to allowed characters in a reversible way so that we can locate the correct
// element when the popup window is dismissed.
function id_to_windowname(text) {
    text = text.replace(/\./g, '__dot__');
    text = text.replace(/\-/g, '__dash__');
    text = text.replace(/\[/g, '__braceleft__');
    text = text.replace(/\]/g, '__braceright__');
    return text;
} 

function windowname_to_id(text) {
    return text;
}
</script>
{% endblock %}
请注意,在第一个字段中添加了一个模板参数。该模板如下所示:

# core/forms.py
class IntranetForm(ModelForm):
    def __init__(self, *args, **kwargs):
        super(IntranetForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)
        self.helper.form_class = 'form-horizontal'

# app/forms.py
class ComplaintForm(IntranetForm):
    def __init__(self, *args, **kwargs):
        super(ComplaintForm, self).__init__(*args, **kwargs)
        self.helper.layout = Layout(
            Field(
                'case',
                css_class='input-xlarge',
                template='complaints/related_case.html',
            ),
            Field('date_received', css_class = 'input-xlarge'),
            Field('stage', css_class = 'input-xlarge'),
            Field('tags', css_class = 'input-xlarge'),
            Field('team', css_class = 'input-xlarge'),
            Field('handler', css_class = 'input-xlarge'),
            Field('status', css_class = 'input-xlarge'),
            FormActions(
                Submit(
                    'save_changes',
                    'Save changes',
                    css_class = "btn-primary"
                ),
                Button(
                    'cancel',
                    'Cancel',
                    onclick = 'history.go(-1);'
                ),
            ),
        )

    class Meta:
        model = Complaint
        fields = (
            'case',
            'date_received',
            'stage',
            'tags',
            'team',
            'handler',
            'status',
        )
<div id="div_id_case" class="control-group">
<label for="id_case" class="control-label ">Case</label>
<div class="controls">
<select class="input-xlarge select" id="id_case" name="case"></select>
&nbsp;<a href="{% url 'add_case' %}" id="add_id_case" class="add-another btn btn-success" onclick="return showAddAnotherPopup(this);">Add new</a>
</div>
</div>
{% extends 'complaints/base_complaint.html' %}
{% load crispy_forms_tags %}

{% block extra_headers %}
{{ form.media }}
{% endblock %}

{% block title %}Register complaint{% endblock %}

{% block heading %}Register complaint{% endblock %}

{% block content %}
{% crispy form %}
<script>
$(document).ready(function() {
    $( '.add-another' ).click(function(e) {
        e.preventDefault(  );
        showAddAnotherPopup( $( this ) );
    });
});

/* Credit: django.contrib.admin (BSD) */

function showAddAnotherPopup(triggeringLink) {
    /*

    Pause here with Firebug's script debugger.

    */
    var name = triggeringLink.attr( 'id' ).replace(/^add_/, '');
    name = id_to_windowname(name);
    href = triggeringLink.attr( 'href' );

    if (href.indexOf('?') == -1) {
        href += '?popup=1';
    } else {
        href += '&popup=1';
    }

    href += '&winName=' + name;

    var win = window.open(href, name, 'height=800,width=800,resizable=yes,scrollbars=yes');
    win.focus();

    return false;
}

function dismissAddAnotherPopup(win, newId, newRepr) {
    // newId and newRepr are expected to have previously been escaped by
    newId = html_unescape(newId);
    newRepr = html_unescape(newRepr);
    var name = windowname_to_id(win.name);
    var elem = document.getElementById(name);

    if (elem) {
        if (elem.nodeName == 'SELECT') {
            var o = new Option(newRepr, newId);
            elem.options[elem.options.length] = o;
            o.selected = true;
        }
    } else {
        console.log("Could not get input id for win " + name);
    }

    win.close();
}

function html_unescape(text) {
 // Unescape a string that was escaped using django.utils.html.escape.
    text = text.replace(/</g, '');
    text = text.replace(/"/g, '"');
    text = text.replace(/'/g, "'");
    text = text.replace(/&/g, '&');
    return text;
}

// IE doesn't accept periods or dashes in the window name, but the element IDs
// we use to generate popup window names may contain them, therefore we map them
// to allowed characters in a reversible way so that we can locate the correct
// element when the popup window is dismissed.
function id_to_windowname(text) {
    text = text.replace(/\./g, '__dot__');
    text = text.replace(/\-/g, '__dash__');
    text = text.replace(/\[/g, '__braceleft__');
    text = text.replace(/\]/g, '__braceright__');
    return text;
} 

function windowname_to_id(text) {
    return text;
}
</script>
{% endblock %}
那里的javascript完全是从我链接到的博客条目复制/粘贴的,它在弹出窗口中调用我的
CaseCreateView
,其中包括我们已经看过的表单。现在您需要该表单将返回值传递到原始页面

为此,我在casedetail页面中包含了以下脚本。这意味着当我的表单在保存时重定向到详细信息页面时,它将在调用脚本之前正确保存。详细信息模板如下所示:

{% extends 'complaints/base_complaint.html' %}

{% block content %}
<script>
$(document).ready(function() {
    opener.dismissAddAnotherPopup( window, "{{ case.pk }}", "{{ case }}" );
});
</script>
{% endblock %}
{%extends'投诉/base\u投诉.html%}
{%block content%}
$(文档).ready(函数(){
dismissAddAnotherPopup(窗口,{case.pk}},{{case}});
});
{%endblock%}

这将在呈现页面后立即关闭该页面。这可能对您来说并不理想,但您可以改变这种行为,例如,在保存时覆盖表单重定向到的页面,并将其用作上述脚本的方便存储,而不是其他。注意它是如何传回case主键及其unicode表示的吗?现在,这些将以原始形式填充选定的
字段,您就完成了。

不知道这对您来说是否为时已晚,但我正在处理同样的问题,几乎已经破解了它。您需要JS弹出相关表单,在保存时关闭它并更新原始表单。一旦我搞定了,我会发布并回复。更新:将反映你的兴趣。我现在正朝着一个不太复杂的方向走(但没那么强大),如果可行的话,我会把它作为一个答案发布。我不知道这对你来说是否太晚了,但我只是在处理同样的事情,几乎已经破解了它。您需要JS弹出相关表单,在保存时关闭它并更新原始表单。一旦我搞定了,我会发布并回复。更新:将反映你的兴趣。我现在正朝着一个不那么复杂的方向(但不那么强大)前进,如果它奏效,我会把它作为一个答案发布。我不记得我在这个案例中最终做了什么,但我怀疑这有点像“放弃”。:D因此,我感谢您为这个答案付出的努力,我肯定会尽快重温我提出这个问题的代码库。我不记得在这个案例中我最终做了什么,但我怀疑这有点像“放弃”。:因此,我感谢您为这个答案付出的努力,我肯定会尽快重温我提出这个问题的代码库。