Django:使用modelformset_factory填充多对多字段



我尝试填充一个多对多字段。菜单和课程模型之间存在关系。我在M2M Table中添加了一些自定义字段,这样我就可以存储课程的顺序及其类型(即Starter、Appetizer等)。

为了获得一个动态的webform,我使用modelformset_factory和python-formset-js改进的pip包(https://pypi.org/project/django-formset-js-improved/)。

遵循我的views.py中的逻辑,在这里解释填充一个ManyToManyField,我遇到了以下错误。此错误适用于所有关键字参数。

menu.menu_item.add(course_position=position,
course_type=menu_item_form['course_type'],
course=course)
TypeError: add() got an unexpected keyword argument 'course_position'

我做错了什么?下面是models.py、forms.py、views.py和HTML文件

的摘录编辑我遵循文档并尝试相应地填充m2m表。https://docs.djangoproject.com/en/4.0/topics/db/models/intermediary-manytomany

我试图将值添加到模型使用,但我不知道如何访问menu_item_form的course_type字段,因为我无法验证表单:

menu_item = MenuItem(menu=menu, 
course=course, 
course_position=position, 
course_type=menu_item_form.fields['course_type'])

menu_item.save()

models.py

class Course(models.Model):
# Individual name of a course (i.e. "Natschis Spezial Fondue")
creator = models.ForeignKey(User, related_name='creator_id_course', on_delete=models.PROTECT)
course_name = models.CharField(max_length=100)
course_description = models.CharField(max_length=1000)
course_price = models.DecimalField(max_digits=7, decimal_places=2, null=True)
course_tags = models.ManyToManyField(CourseTag)
private = models.BooleanField(default=False, verbose_name=_('private item'))
active = models.BooleanField(default=True)
deleted = models.BooleanField(default=False)
objects = CourseManager()
def __str__(self):
return self.course_name

class Menu(models.Model):
class Status(models.TextChoices):
ACTIVE = 'a', _('active')
SUSPENDED = 's', _('suspended')
DELETED = 'd', _('deleted')
# assemble a menu from different courses --> "Movie"
creator = models.ForeignKey(User, related_name='creator_id_menu', on_delete=models.PROTECT)
menu_name = models.CharField(max_length=100, default='', verbose_name=_('Menu Name'), help_text=_('i.e. saturday night fajita night'))
menu_description = models.TextField(max_length=1000, default='', verbose_name='Menu Description', help_text=_('this menu will blow your mind...'))
menu_duration_minutes = models.IntegerField(default=30, blank=True, null=True)
menu_item = models.ManyToManyField(Course, through='MenuItem', blank=True)  # collects all courses related to this menu
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')
updated_at = models.DateTimeField(auto_now=True, verbose_name='Updated on')
private = models.BooleanField(default=False, verbose_name=_('private'))
status = models.CharField(max_length=1, default='a', choices=Status.choices, verbose_name=_('menu status'))
objects = MenuManager()
def __str__(self):
return self.menu_name

class MenuItem(models.Model):
class CourseType(models.TextChoices):
APPETIZER = '10', _('appetizer')
STARTER = '20', _('starter')
MAIN = '30', _('main course')
DESSERT = '40', _('dessert')
SOUP = '21', _('soup')
SALAD = '22', _('salad')
PRIMO = '31', _('primo')
PASTA = '32', _('pasta')
SECONDO = '33', _('secondo')
FISH = '34',  _('fish')
MEAT = '35', _('meat')
PIZZA = '36', _('pizza')
HAMBURGER = '37', _('hamburger')
CHEESE = '41', _('cheese')
FRUITS = '42', _('fruits')
CAKES = '43', _('cakes')
ICE_CREAM = '44', _('ice cream')
DIGESTIVE = '45', _('digestive')
menu = models.ForeignKey(Menu, on_delete=models.CASCADE, verbose_name=_('menu name'))
course = models.ForeignKey(Course, on_delete=models.PROTECT, verbose_name=_('course name'))
course_type = models.CharField(choices=CourseType.choices, max_length=3, verbose_name=_('course type'))
course_position = models.PositiveSmallIntegerField(verbose_name=_('course position'))

forms.py

class MenuItemForm(forms.ModelForm):
course_type = forms.ChoiceField(choices=MenuItem.CourseType.choices)
class Meta:
model = MenuItem
fields = '__all__'

MenuItemFormset = modelformset_factory(MenuItem,
form=MenuItemForm,
extra=1)
class CourseForm(forms.ModelForm):
class Meta:
model = Course
fields = ['course_name', 'course_description']
class Media(object):
# todo: can this be deleted? used for
js = formset_media_js + (
# Other form media here
)

CourseFormset = modelformset_factory(Course,
form=CourseForm,
extra=1)

views.py

def create_menu_with_courses(request):
context = {}
user = request.user
menu_form = MenuCreationForm(user, request.POST or None)
course_formset = CourseFormset(request.POST or None, queryset=Course.objects.none(), prefix='course')
menu_item_formset = MenuItemFormset(request.POST or None, queryset=MenuItem.objects.none(), prefix='menu-item')
print(f"debug: {request.POST}")
if request.method == 'POST':
current_user = request.user
if all([menu_form.is_valid(), course_formset.is_valid()]):
print("menu & course_formset is valid")
menu = menu_form.save(commit=False)
menu.creator = current_user
menu.save()
for (position, course_form), menu_item_form in zip(enumerate(course_formset), menu_item_formset):
course = course_form.save(commit=False)
course.creator = request.user
course.save()
menu.menu_item.add(course_position=position,
course_type=menu_item_form.course_type,
course=course)
menu.save()
messages.success(request, f'Well done! Your menu "{menu}" was successfully created!')
return redirect('menu-list')
else:
print(menu_form.errors, course_formset.errors)
context['menu_form'] = menu_form
context['course_formset'] = course_formset
context['menu_item_formset'] = menu_item_formset
return render(request, 'menus/create_menu.html', context)

<div id="formset" data-formset-prefix="{{ course_formset.prefix }}">
{{ course_formset.media }}
{{ course_formset.management_form }}
{{ menu_item_formset.management_form }}
<div id="formset-body" data-formset-body>
<!-- New forms will be inserted in here -->
<div data-formset-form>
{% for course_form in course_formset %}
{% for menu_item_form in menu_item_formset %}
<div class="form-floating">
{{ menu_item_form.id }}
{% render_field menu_item_form.course_type class+="form-select" aria-label="Floating label select" %}
{{ course_form.id }}
<label for="input{{ course_form.course_name.label }}" class+="form-label">{{ course_form.course_name.label }}</label>
{% render_field course_form.course_name class+="form-control" %}
{% for error in course_form.course_name.errors %}
<p>{{ error }}</p>
{% endfor %}
<label for="input{{ course_form.course_description.label }}" class="form-label">{{ course_form.course_description.label }}</label>
{% render_field course_form.course_description class+="form-control" rows="3" id="input{{ course_form.course_description.label }}" %}
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-move-up-button>Move up</button>
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-move-down-button>Move down</button>
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-delete-button>Delete form</button>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
<!-- The empty form template. By wrapping this in a <script> tag, the
__prefix__ placeholder can easily be replaced in both attributes and
any scripts -->
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div data-formset-form>
<!-- Course Formset-->
<div class="form-floating">
<!---
{% render_field menu_item_formset.empty_form.course_type class+="form-select" aria-label="Floating label select"%}
-->
{% render_field menu_item_formset.empty_form.course_type class+="form-select" aria-label="Floating label select" %}
<label for="input{{ course_formset.empty_form.course_name.label }}" class+="form-label">{{ course_formset.empty_form.course_name.label }}</label>
{% render_field course_formset.empty_form.course_name class+="form-control" %}
{% for error in course_formset.empty_form.course_name.errors %}
<p>{{ error }}</p>
{% endfor %}
<label for="input{{ course_formset.empty_form.course_description.label }}" class="form-label">{{ course_formset.empty_form.course_description.label }}</label>
{% render_field course_formset.empty_form.course_description class+="form-control" rows="3" id="input{{ course_formset.empty_form.course_description.label }}" %}
</div>
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-move-up-button>Move up</button>
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-move-down-button>Move down</button>
<button class="btn btn-outline-primary btn-block my-3 type="button" data-formset-delete-button>Delete form</button>
</div>
{% endescapescript %}
</script>
<!-- This button will add a new form when clicked -->
<input class="btn btn-outline-primary btn-block my-3 type="button" value="Add new" data-formset-add>

<script>jQuery(function($) {
$("#formset").formset({
animateForms: true,
reorderMode: 'dom',
});
});</script>
</div>

基于这个线程(在is_valid()之前设置表单字段值),我解决了我的问题。

我将字段course_type添加到course_formset中。我从请求中检索了数据。文章本身。

menu_item = MenuItem(menu=menu, course=course, course_position=position, course_type=request.POST[f'course-{position}-course_type'])
menu_item.save()

最新更新