django模型表单。包含来自相关模型的字段

问题描述:

我有一个名为Student的模型,其中包含一些字段以及与用户的OneToOne关系(django.contrib.auth.User)。django模型表单。包含来自相关模型的字段

class Student(models.Model): 

    phone = models.CharField(max_length = 25) 
    birthdate = models.DateField(null=True) 
    gender = models.CharField(max_length=1,choices = GENDER_CHOICES) 
    city = models.CharField(max_length = 50) 
    personalInfo = models.TextField() 
    user = models.OneToOneField(User,unique=True) 

然后,我有一个的ModelForm该型号

class StudentForm (forms.ModelForm): 
    class Meta: 
     model = Student 

使用领域类元属性,我已经成功地只显示在模板中的一些领域。但是,我可以指出要显示哪些用户字段?

东西为:

fields =('personalInfo','user.username') 

目前未显示任何。尽管只与StudentFields合作,但是/

在此先感谢。

+0

如果学生模型继承了用户模型,你只需要一个的ModelForm。 – 2018-01-26 10:27:19

+0

@KevinL。,如果你在一个答案中详细说明这一点,那将是非常棒的:-) – cel 2018-01-27 07:41:21

+0

@cel AFAIK,在Django核心中没有像这样的发展。这里的'自动'解决方案可能不是微不足道的,并且涉及编写自己的自定义模型表单类或混合类来执行此操作。这比使用建议的方法要复杂得多(可以说更脆弱)。一个可能的解决方案可能包含在[这个回答](https://stackoverflow.com/a/41559015/5747944)中,该解决方案描述了一个ModelForm mixin,它允许定义第二个“子”模型并声称与通用视图兼容。 – sytech 2018-01-31 14:20:33

我不知道这是你想要的,但你可以看看在documentation for Inline Formsets

这两个答案都是正确的:内联表单集使这样做很容易。

但是,请注意,内联只能走一条路:从具有外键的模型开始。在两个主键都没有的情况下(坏的,因为你可能有A→B然后B→A2),你不能在related_to模型中有内联表单。

例如,如果您有一个UserProfile类,并且希望能够在显示时拥有这些类,那么将会显示相关的User对象,如果是inline,那么您的运气就不好。

您可以在ModelForm上拥有自定义字段,并将其用作更灵活的方式,但请注意,它不再像标准ModelForm /内联formset那样是“自动”的。

+0

我碰到这个问题和你的答案中描述的问题。现在已经过去了很多年。你知道现在是否有自动解决方案吗? – cel 2018-01-26 07:06:00

一个常见的做法是使用2表格来实现你的目标。

  • 甲形式为User型号:

    class UserForm(forms.ModelForm): 
        ... Do stuff if necessary ... 
        class Meta: 
         model = User 
         fields = ('the_fields', 'you_want') 
    
  • 甲形式为Student型号:

    class StudentForm (forms.ModelForm): 
        ... Do other stuff if necessary ... 
        class Meta: 
         model = Student 
         fields = ('the_fields', 'you_want') 
    
  • 使用二者在视图那些形式(例如使用情况):

    def register(request): 
        if request.method == 'POST': 
         user_form = UserForm(request.POST) 
         student_form = StudentForm(request.POST) 
         if user_form.is_valid() and student_form.is_valid(): 
          user_form.save() 
          student_form.save() 
    
  • 渲染的形式一起在你的模板:

    <form action="." method="post"> 
        {% csrf_token %} 
        {{ user_form.as_p }} 
        {{ student_form.as_p }} 
        <input type="submit" value="Submit"> 
    </form> 
    

另一种选择是你改变从OneToOneForeignKey的关系(这完全取决于你,我只是提到它,不推荐它),并使用inline_formsets来达到预期的效果。

+1

@cel我不知道“自动”的方式(就像你说的那样),但我在这个答案中有一个手动的解决方案。看一看 :) – 2018-01-26 09:49:24

您可以考虑的另一种方法是通过扩展AbstractUser或AbstractBaseUser模型而不是使用具有Profile模型的one-to-one link(本例中为Student模型)来创建自定义用户模型。这将创建一个可用于创建单个ModelForm的扩展用户模型。

例如做这将是延长AbstractUser model一个办法:

from django.contrib.auth.models import AbstractUser 

class Student(AbstractUser): 

    phone = models.CharField(max_length = 25) 
    birthdate = models.DateField(null=True) 
    gender = models.CharField(max_length=1,choices = GENDER_CHOICES) 
    city = models.CharField(max_length = 50) 
    personalInfo = models.TextField() 
    # user = models.OneToOneField(User,unique=True) <= no longer required 

在settings.py文件,更新AUTH_USER_MODEL

AUTH_USER_MODEL = 'appname.models.Student' 

更新在您的管理模式:

from django.contrib import admin 
from django.contrib.auth.admin import UserAdmin 
from .models import Student 

admin.site.register(Student, UserAdmin) 

然后你可以使用一个单独的ModelForm,它有两个额外的字段哟您需要以及原始用户模型中的字段。在forms.py

from .models import Student  

class StudentForm (forms.ModelForm): 
    class Meta: 
     model = Student 
     fields = ['personalInfo', 'username'] 

一个更复杂的方法是延长AbstractBaseUser,对此进行详细描述in the docs

但是,我不确定是否以这种方式创建自定义用户模型以获得方便的单个ModelForm对您的用例有意义。由于创建自定义用户模型是一项棘手的工作,因此这是您必须进行的设计决策。

如果你不想改变AUTH_USER_MODEL它有许多副作用,您可以使用Multi-table inheritance和子类用户模型代替AbstractUser的。这将创建一个OneToOneField命名user_ptr指向用户学生表。

下面是一个例子

from django.contrib.auth.models import User 
from django.db import models 
from django.utils.translation import gettext_lazy as _ 


class Student(User): 
    phone = models.CharField(max_length=25) 
    birthdate = models.DateField(null=True) 
    city = models.CharField(max_length=50) 
    personalInfo = models.TextField() 

    class Meta: 
     verbose_name = _('student') 
     verbose_name_plural = _('students') 

现在你可以定义你的ModelForm这样

class StudentForm(forms.ModelForm): 
    class Meta: 
     model = Student 
     fields = ('first_name', 'last_name', 'username', 
        'personalInfo', 'phone', 'birthdate', 'city') 

您还可以扩展内置在Django用户表单这样

from django.contrib.auth.forms import UserChangeForm 

class StudentForm(UserChangeForm): 
    class Meta: 
     model = Student 
     fields = ('first_name', 'last_name', 'username', 
       'personalInfo', 'phone', 'birthdate', 'city') 

要在django admin中使用你的表单,添加t他对以下admin.py的:

from django.contrib import admin 
from .views import StudentForm 
from .models import Student 


class StudentAdmin(admin.ModelAdmin): 
    form = StudentForm 

admin.site.register(Student, StudentAdmin) 

按分分级的用户模型,创建学生情况下会自动创建一个新的用户实例,但不是周围的其他方式。因此,一个实例可以存在而不与一个学生实例相关联。如果你想确保学生实例系统中的每一个用户创建,您可以使用以下信号:

from django.contrib.auth.models import User 
from django.db.models.signals import post_save 
from django.dispatch import receiver 

from .models import Student 


@receiver(post_save, sender=User) 
def create_student(sender, instance, created, **kwargs): 
    if created: 
     student = Student(user_ptr_id=instance.pk) 
     student.__dict__.update(instance.__dict__) 
     student.save() 

从我的理解要更新auth.User的用户名字段是OneToOneStudent关系,这是我会做什么?

class StudentForm (forms.ModelForm): 

username = forms.Charfield(label=_('Username')) 
class Meta: 
    model = Student 
    fields = ('personalInfo',) 

def clean_username(self): 
    # using clean method change the value 
    # you can put your logic here to update auth.User 
    username = self.cleaned_data('username') 
    # get AUTH USER MODEL 
    in_db = get_user_model()._default_manager.update_or_create(username=username) 

希望这有助于:)