M2M 字段小部件将数据添加或更新为内联表单集
M2M field widget to add or update data as inline formset
我花了很多时间寻找一个我认为应该是 Django 中非常基本的功能的特性。但我无法让它工作,
我找不到与 django 的 m2m 小部件功能相同的小部件,但如果它不存在,也会创建新的模型实例。
注意:这里的模型实例已经存在意味着输入inline widget的数据已经存在于数据库中。
例如
如果我有以下模型:
class Outcome(models.Model):
outcome = models.CharField(max_length=255)
outcome_short_name = models.CharField(max_length=10, blank=True, null=True)
class Course(models.Model):
course_title = models.CharField(
verbose_name=COURSE_SINGULAR + " title", max_length=200, unique=True
)
course_outcome = models.ManyToManyField(
Outcome, verbose_name=COURSE_SINGULAR + " outcome", blank=True
)
然后我希望 "Outcomes" 在创建课程时显示为此图像:
Image of adding new course with inline outcomes
现在,如果用户添加的结果数据已经存在,那么它应该只将它们映射到课程。否则它应该首先将结果存储到数据库中,然后将它们映射到课程。
任何正确方向的指导将不胜感激。
谢谢,
编辑:
正如@dirkgroten 建议使用 modelformset,我将 FormView 更改为:
class CourseFormView(FormView):
template_name = "course/course_form.html"
form_class = CourseForm
success_url = "/admin/"
def get_context_data(self, **kwargs):
context = super(CourseFormView, self).get_context_data(**kwargs)
if self.request.POST:
context["outcomes"] = OutcomeFormSet(self.request.POST)
else:
context["outcomes"] = OutcomeFormSet(queryset=Outcome.objects.none())
return context
def form_valid(self, form, **kwargs):
super(CourseFormView, self).get_context_data(**kwargs)
context = self.get_context_data()
outcomes_formset = context["outcomes"]
if not outcomes_formset.is_valid():
return super().form_invalid(form)
cleaned_data = form.cleaned_data
cleaned_data.pop("course_outcome")
course = Course.objects.create(**cleaned_data)
course.save()
outcomes_formset.instance = course
outcomes_formset.save()
course.course_outcome.set(Outcome.objects.filter(course_outcome=course))
return super().form_valid(form)
一切看起来都很好,除了我的 model_formset 如果表单集中的表单数据已经存在于数据库中,则未验证。
例如。如果我在表单集中输入 (outcome="test_outcome", outcome_short_name="test_short") 并且结果 table 中已经存在相同的数据,那么我的表单集会给出错误:
具有此 Outcome 和 Outcome 简称的结果已经存在。
有什么办法可以解决这种情况,或者我做错了什么。
您可以在上面进行测试:http://code.gdy.club:8001/course/add/
outcomes_list: http://code.gdy.club:8001/outcome/
谢谢,
--
苏拉吉
https://hacksj4u.wordpress.com
https://github.com/SurajDadral
您需要自己处理 Outcome
已经存在的情况。验证表单时的默认设置是假定将创建一个新对象,因此如果您的字段设置为 unique_together
,则单个表单将不会验证。
您可以在 OutcomeFormset
的 clean()
方法上这样做:
from django.core.exceptions import NON_FIELD_ERRORS
def clean(self):
super().clean()
for i in range(0, self.total_form_count()):
form = self.forms[i]
if form.non_field_errors() and len(form.errors) == 1:
# the only error is probably due to unique_together constraint
try:
form.instance = Outcome.objects.get(
outcome=form.data.get(form.add_prefix('outcome')),
outcome_short_name=form.data.get(form.add_prefix('outcome_short_name')))
except Outcome.DoesNotExist:
pass # some other error so we should keep it
else:
del form._errors[NON_FIELD_ERRORS]
然后在您看来,在保存表单集时,您应该遍历所有表单,而不是保存实例具有 pk
:
的表单
for form in outcomes_formset:
outcome = form.instance
if not outcome.pk:
outcome = form.save()
course.course_outcome.add(outcome)
我花了很多时间寻找一个我认为应该是 Django 中非常基本的功能的特性。但我无法让它工作,
我找不到与 django 的 m2m 小部件功能相同的小部件,但如果它不存在,也会创建新的模型实例。
注意:这里的模型实例已经存在意味着输入inline widget的数据已经存在于数据库中。
例如 如果我有以下模型:
class Outcome(models.Model):
outcome = models.CharField(max_length=255)
outcome_short_name = models.CharField(max_length=10, blank=True, null=True)
class Course(models.Model):
course_title = models.CharField(
verbose_name=COURSE_SINGULAR + " title", max_length=200, unique=True
)
course_outcome = models.ManyToManyField(
Outcome, verbose_name=COURSE_SINGULAR + " outcome", blank=True
)
然后我希望 "Outcomes" 在创建课程时显示为此图像: Image of adding new course with inline outcomes
现在,如果用户添加的结果数据已经存在,那么它应该只将它们映射到课程。否则它应该首先将结果存储到数据库中,然后将它们映射到课程。
任何正确方向的指导将不胜感激。
谢谢,
编辑: 正如@dirkgroten 建议使用 modelformset,我将 FormView 更改为:
class CourseFormView(FormView):
template_name = "course/course_form.html"
form_class = CourseForm
success_url = "/admin/"
def get_context_data(self, **kwargs):
context = super(CourseFormView, self).get_context_data(**kwargs)
if self.request.POST:
context["outcomes"] = OutcomeFormSet(self.request.POST)
else:
context["outcomes"] = OutcomeFormSet(queryset=Outcome.objects.none())
return context
def form_valid(self, form, **kwargs):
super(CourseFormView, self).get_context_data(**kwargs)
context = self.get_context_data()
outcomes_formset = context["outcomes"]
if not outcomes_formset.is_valid():
return super().form_invalid(form)
cleaned_data = form.cleaned_data
cleaned_data.pop("course_outcome")
course = Course.objects.create(**cleaned_data)
course.save()
outcomes_formset.instance = course
outcomes_formset.save()
course.course_outcome.set(Outcome.objects.filter(course_outcome=course))
return super().form_valid(form)
一切看起来都很好,除了我的 model_formset 如果表单集中的表单数据已经存在于数据库中,则未验证。
例如。如果我在表单集中输入 (outcome="test_outcome", outcome_short_name="test_short") 并且结果 table 中已经存在相同的数据,那么我的表单集会给出错误:
具有此 Outcome 和 Outcome 简称的结果已经存在。
有什么办法可以解决这种情况,或者我做错了什么。
您可以在上面进行测试:http://code.gdy.club:8001/course/add/
outcomes_list: http://code.gdy.club:8001/outcome/
谢谢,
--
苏拉吉
https://hacksj4u.wordpress.com
https://github.com/SurajDadral
您需要自己处理 Outcome
已经存在的情况。验证表单时的默认设置是假定将创建一个新对象,因此如果您的字段设置为 unique_together
,则单个表单将不会验证。
您可以在 OutcomeFormset
的 clean()
方法上这样做:
from django.core.exceptions import NON_FIELD_ERRORS
def clean(self):
super().clean()
for i in range(0, self.total_form_count()):
form = self.forms[i]
if form.non_field_errors() and len(form.errors) == 1:
# the only error is probably due to unique_together constraint
try:
form.instance = Outcome.objects.get(
outcome=form.data.get(form.add_prefix('outcome')),
outcome_short_name=form.data.get(form.add_prefix('outcome_short_name')))
except Outcome.DoesNotExist:
pass # some other error so we should keep it
else:
del form._errors[NON_FIELD_ERRORS]
然后在您看来,在保存表单集时,您应该遍历所有表单,而不是保存实例具有 pk
:
for form in outcomes_formset:
outcome = form.instance
if not outcome.pk:
outcome = form.save()
course.course_outcome.add(outcome)