Django: несколько моделей в одном шаблоне с использованием форм
Я создаю приложение для отслеживания заявок и у меня есть несколько моделей, которые я хотел бы создать на одной странице. Билеты принадлежат Заказчику через ForeignKey. Примечания принадлежат Билетам также через ForeignKey. Я хотел бы иметь возможность выбрать клиента (это целый отдельный проект) ИЛИ создать нового клиента, затем создать билет и, наконец, создать примечание, назначенное новому билету.
Поскольку я довольно новичок в Django, я склонен работать итеративно, каждый раз пробуя новые функции. Я играл с ModelForms, но я хочу скрыть некоторые поля и провести некоторую сложную проверку. Кажется, что уровень контроля, который я ищу, требует либо наборов форм, либо выполнения всего вручную, в комплекте с утомительной, вручную закодированной страницей шаблона, которой я стараюсь избегать.
Есть ли какая-то прекрасная особенность, которую я пропускаю? У кого-нибудь есть хорошая ссылка или пример для использования форм-наборов? Я потратил целые выходные на документацию по API для них, и до сих пор ничего не понимаю. Это проблема дизайна, если я сломаюсь и все вручную пишу?
7 ответов
Это действительно не так сложно реализовать с помощью ModelForms. Допустим, у вас есть формы A, B и C. Вы распечатываете каждую из форм и страницу, и теперь вам нужно обработать POST.
if request.POST():
a_valid = formA.is_valid()
b_valid = formB.is_valid()
c_valid = formC.is_valid()
# we do this since 'and' short circuits and we want to check to whole page for form errors
if a_valid and b_valid and c_valid:
a = formA.save()
b = formB.save(commit=False)
c = formC.save(commit=False)
b.foreignkeytoA = a
b.save()
c.foreignkeytoB = b
c.save()
Вот документы для пользовательской проверки.
Я просто был примерно в той же ситуации день назад, и вот мои 2 цента:
1) Я нашел, пожалуй, самую короткую и краткую демонстрацию записи нескольких моделей в одной форме здесь: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/,
В двух словах: создайте форму для каждой модели, отправьте оба шаблона в один <form>
, с помощью prefix
keyarg и иметь проверку дескриптора представления. Если есть зависимость, просто убедитесь, что вы сохранили "родительскую" модель перед зависимой, и использовали идентификатор родителя для внешнего ключа, прежде чем сохранять "дочернюю" модель. По ссылке есть демоверсия.
2) Возможно, формальные наборы можно превзойти в этом, но, насколько я понял, наборы форм в основном предназначены для ввода кратных значений одной и той же модели, которые могут быть привязаны к другой модели / моделям с помощью внешних ключей. Тем не менее, похоже, что по умолчанию нет возможности ввода более чем одной модели данных, и это не то, для чего, похоже, предназначен набор форм.
У меня совсем недавно была какая-то проблема, и я просто понял, как это сделать. Предполагая, что у вас есть три класса, Primary, B, C и что B, C имеют внешний ключ к первичному
class PrimaryForm(ModelForm):
class Meta:
model = Primary
class BForm(ModelForm):
class Meta:
model = B
exclude = ('primary',)
class CForm(ModelForm):
class Meta:
model = C
exclude = ('primary',)
def generateView(request):
if request.method == 'POST': # If the form has been submitted...
primary_form = PrimaryForm(request.POST, prefix = "primary")
b_form = BForm(request.POST, prefix = "b")
c_form = CForm(request.POST, prefix = "c")
if primary_form.is_valid() and b_form.is_valid() and c_form.is_valid(): # All validation rules pass
print "all validation passed"
primary = primary_form.save()
b_form.cleaned_data["primary"] = primary
b = b_form.save()
c_form.cleaned_data["primary"] = primary
c = c_form.save()
return HttpResponseRedirect("/viewer/%s/" % (primary.name))
else:
print "failed"
else:
primary_form = PrimaryForm(prefix = "primary")
b_form = BForm(prefix = "b")
c_form = Form(prefix = "c")
return render_to_response('multi_model.html', {
'primary_form': primary_form,
'b_form': b_form,
'c_form': c_form,
})
Этот метод должен позволить вам выполнить любую необходимую вам проверку, а также создать все три объекта на одной странице. Я также использовал javascript и скрытые поля для создания нескольких объектов B, C на одной странице.
MultiModelForm от django-betterforms
удобная оболочка для выполнения того, что описано в ответе Гнудифа. Оборачивается регулярно ModelForm
s в одном классе, который прозрачно (по крайней мере, для базового использования) используется как единая форма. Я скопировал пример из их документов ниже.
# forms.py
from django import forms
from django.contrib.auth import get_user_model
from betterforms.multiform import MultiModelForm
from .models import UserProfile
User = get_user_model()
class UserEditForm(forms.ModelForm):
class Meta:
fields = ('email',)
class UserProfileForm(forms.ModelForm):
class Meta:
fields = ('favorite_color',)
class UserEditMultiForm(MultiModelForm):
form_classes = {
'user': UserEditForm,
'profile': UserProfileForm,
}
# views.py
from django.views.generic import UpdateView
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
from django.contrib.auth import get_user_model
from .forms import UserEditMultiForm
User = get_user_model()
class UserSignupView(UpdateView):
model = User
form_class = UserEditMultiForm
success_url = reverse_lazy('home')
def get_form_kwargs(self):
kwargs = super(UserSignupView, self).get_form_kwargs()
kwargs.update(instance={
'user': self.object,
'profile': self.object.profile,
})
return kwargs
В настоящее время у меня есть обходной функционал (он проходит мои юнит-тесты). Это хорошее решение, на мой взгляд, когда вы хотите добавить ограниченное количество полей из других моделей.
Я что-то здесь упускаю?
class UserProfileForm(ModelForm):
def __init__(self, instance=None, *args, **kwargs):
# Add these fields from the user object
_fields = ('first_name', 'last_name', 'email',)
# Retrieve initial (current) data from the user object
_initial = model_to_dict(instance.user, _fields) if instance is not None else {}
# Pass the initial data to the base
super(UserProfileForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
# Retrieve the fields from the user model and update the fields with it
self.fields.update(fields_for_model(User, _fields))
class Meta:
model = UserProfile
exclude = ('user',)
def save(self, *args, **kwargs):
u = self.instance.user
u.first_name = self.cleaned_data['first_name']
u.last_name = self.cleaned_data['last_name']
u.email = self.cleaned_data['email']
u.save()
profile = super(UserProfileForm, self).save(*args,**kwargs)
return profile
"Я хочу скрыть некоторые поля и выполнить некоторую сложную проверку".
Я начинаю со встроенного интерфейса администратора.
Создайте ModelForm, чтобы показать нужные поля.
Расширьте форму с помощью правил проверки в форме. Обычно это
clean
метод.Убедитесь, что эта часть работает достаточно хорошо.
Как только это будет сделано, вы можете отойти от встроенного интерфейса администратора.
Тогда вы можете дурачиться с несколькими, частично связанными формами на одной веб-странице. Это набор шаблонов для представления всех форм на одной странице.
Затем вы должны написать функцию просмотра, чтобы читать и проверять различные формы и делать различные сохранения объектов ().
"Это проблема дизайна, если я сломаюсь и все вручную напишу?" Нет, это просто много времени для небольшой выгоды.
Согласно документации Django, встроенные наборы форм предназначены для этой цели: "Встроенные наборы форм - это небольшой уровень абстракции поверх наборов форм модели. Они упрощают случай работы со связанными объектами через внешний ключ".
См. https://docs.djangoproject.com/en/dev/topics/forms/modelforms/