简述

例如Model A和Model S是一对多的关系,即A是one, S是many, 使用ForeignKey来定义

class A(models.Model):
  name = models.CharFields(max_length=64)

class S(models.Model):
  name = models.CharFields(max_length=64)
  a = models.ForeignKey(A)

在某个页面创建S的时候,需要指定是哪个A。 如果为A和S注册了admin,那么在admin-site里创建S的时候,会看到需要选择哪个A的实例,它会把所有的A的记录都列出来

但这个是针对admin的,不是针对业务的,如果一个用户想为他名下的某个A资源建立一个S的资源,那么把所有A的记录都列出来是不合适的,也不安全;实际上例如POST /A/1/S/create这样的url已经含了A的主键,在创建S的时候只需要为a field赋值即可,而且是自动的,不需要用户选择。

策略

就是在创建S的时候,不能把a的field暴露出来,并且根据url里的A的主键,将a赋值为A.objects.get(pk=1)即可。

方法1

直接使用CreateView,model为S,form暴露的fields需要包括a,在获取form初始值时,将a赋值。form在render的时候,需要隐藏a field,需要自定义各form的field,不能使用form.as_p()等。

class SCreateView(CreateView):
    model = S
    fields = {'name','a'}
    success_url = 'myurl'
    template_name = 's_add.html'

    def get(self, request, *args, **kwargs):
        # 这里根据url参数获取A的对象
        self.a_object = A.objects.get(pk=kwargs['a_id'])

    def get_initial(self):
        # 这里这样默认的form里a field就有值了。
        return {'a':self.a_object.pk}

方法2

使用SignalObjectMixinFormView的组合,SignalObjectMixin是为了获取A的对象,FormView用于处理form的get和post。特别是post,我们的Form不需要有a field,在form_valid()里,通过form.save(commit=False)来创建一个S对象,但是该对象还没有持久化,然后为该对象的a field赋值为self.object.pk(通过SignalObjectMixin)即可,然后调用s.save()即可。

class SForm(form.ModelForm):
    class Meta:
        model = S
        fields = {'name'}

class SCreateView(SingleObjectMixin, FormView):
    model = A
    form_class = SForm

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(SCreateView, self).get(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(SCreateView, self).get_context_data(**kwargs)
        context['form'] = self.form_class()
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super(SCreateView, self).post(request, *args, **kwargs)

    def form_valid(self, form):
        # commit为False,创建一个未持久化对象
        s = form.save(commit=False)
        # 为s的隐藏field赋值
        s.a = self.object
        # 持久化
        s.save()
        # 后续处理
        return super.form_valid(self, form)

方法3

继承CreateView,重写form_valid()

from django.views.generic.edit import CreateView
from myapp.models import Author

class SCreate(CreateView):
    model = S
    fields = ['name']

    def form_valid(self, form):
        # a_object可以从其他地方获取,例如url等获取a的对象,关键是form.instance,因为这个form是ModelForm,为该instance的成员赋值,然后通过super()方法去save即可。
        form.instance.a = self.a_object
        return super(SCreate, self).form_valid(form)