【问题标题】:Django Class BasedView - UpdateView with multiple models and multiple formsDjango Class BasedView - 具有多个模型和多个表单的 UpdateView
【发布时间】:2017-04-03 02:24:49
【问题描述】:

我有一个用户列表或用户/角色类型。 我希望能够单击链接以更新他们各自的个人资料用户。

当我在个人资料用户中执行点击时,我应该能够根据处理每个用户的个人资料编辑相关数据到各个用户。每个数据配置文件都是简单的页面或模板,并管理相同的基于 UpdateView 类的视图。

详细我的方案如下:

我有User(继承自AbstractBaseUser)模型,在这里我管理所有用户的帐户数据,这意味着我想在我的应用程序中管理的所有用户角色或类型的所有数据,例如:

  • 用户学生
  • 用户教授
  • 用户主管

此用户/角色类型有自己的模型,我在其中为每个人定义各自的字段。所以,我也有 StudentProfile ProfessorProfileExecutiveProfile 模型,这种方式:

我的User 型号是:

class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(unique=True)

    username = models.CharField(max_length=40, unique=True)

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    is_student = models.BooleanField(
        default=False,
        verbose_name='Student',
        help_text='Student profile'
    )

    is_professor = models.BooleanField(
        default=False,
        verbose_name='Professor',
        help_text='Professor profile'
    )

    is_executive = models.BooleanField(
        default=False,
        verbose_name='Executive',
        help_text='Executive profile',
    )

    other fields ...

    def get_student_profile(self):
        student_profile = None
        if hasattr(self, 'studentprofile'):
            student_profile = self.studentprofile
        return student_profile

    def get_professor_profile(self):
        professor_profile = None
        if hasattr(self, 'professorprofile'):
            professor_profile = self.professorprofile
        return professor_profile

    def get_executive_profile(self):
        executive_profile = None
        if hasattr(self, 'executiveprofile'):
            executive_profile = self.executiveprofile
        return executive_profile

    def save(self, *args, **kwargs):
    user = super(User,self).save(*args,**kwargs)

    # Creating an user with student profile
    if self.is_student and not StudentProfile.objects.filter(user=self).exists():
        student_profile = StudentProfile(user = self)
        student_slug = self.username
        student_profile.slug = student_slug
        student_profile.save()

    # Creating an user with professor profile
    elif self.is_professor and not ProfessorProfile.objects.filter(user=self).exists():
        professor_profile = ProfessorProfile(user=self)
        professor_slug = self.username
        professor_profile.slug = professor_slug
        professor_profile.save()

    # Creating an user with executive profile
    elif self.is_executive and not ExecutiveProfile.objects.filter(user=self).exists():
        executive_profile = ExecutiveProfile(user = self)
        executive_slug = self.username
        executive_profile.slug = executive_slug
        executive_profile.save()

# I have this signal to get the username and assign to slug field 
@receiver(post_save, sender=User)
def post_save_user(sender, instance, **kwargs):
    slug = slugify(instance.username)
    User.objects.filter(pk=instance.pk).update(slug=slug)

这些模式背后的想法是,当我创建并使用 is_student 字段检查用户时,StudentProfile 模型用于完成他们的数据。

当我创建并使用 is_professor 字段检查用户时,ProfessorProfile 模型用于完成他们的数据。

当我创建并使用 is_executive 字段检查用户时,ExecutiveProfile 模型用于完成他们的数据。

每个 Profile(StudentProfessorExecutive)的型号如下:

class StudentProfile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    origin_education_school = models.CharField(
        _("origin education institute"), max_length=128
    )

    current_education_school = models.CharField(
        _("current education institute"), max_length=128
    )

    extra_occupation = models.CharField(
        _("extra occupation"), max_length=128
    )

class ProfessorProfile(models.Model):

    CATHEDRAL_PROFESSOR = 'CATHEDRAL'
    RESEARCH_PROFESSOR = 'RESEARCH'
    INSTITUTIONAL_DIRECTIVE = 'DIRECTIVE'


    OCCUPATION_CHOICES = (
        (CATHEDRAL_PROFESSOR, 'Cathedral Professor'),
        (RESEARCH_PROFESSOR, 'Research Professor'),
        (INSTITUTIONAL_DIRECTIVE, 'Institutional Directive'),
    )

    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    occupation = models.CharField(
        max_length=255,
        blank = False,
    )

class ExecutiveProfile(models.Model):

    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    occupation = models.CharField(
        max_length=255,
        blank = False,
    )

    enterprise_name = models.CharField(
        max_length=255,
        blank = False,
    )

我的forms.py中有这种方式的每个配置文件更新数据的表格:

class UserUpdateForm(forms.ModelForm):
    class Meta:
        widgets = {
            'gender':forms.RadioSelect,
        }
        fields = ("username", "email", "is_student",
            "is_professor", "is_executive",)
        model = get_user_model() #My model User


class StudentProfileForm(forms.ModelForm):
    class Meta:
        model = StudentProfile
        fields = ('origin_education_school', 'current_education_school',
            'extra_occupation')


class ProfessorProfileForm(forms.ModelForm):
    class Meta:
        model = ProfessorProfile
        fields = ('occupation',)


class ExecutiveProfileForm(forms.ModelForm):
    class Meta:
        model = ExecutiveProfile
        fields = ('occupation', 'enterprise_name', 'culturals_arthistic',
            'ecological')

在基于类的视图 AccountSettingsUpdateView 中,我更新了与模型用户相关的数据,这意味着帐户数据

class AccountSettingsUpdateView(LoginRequiredMixin, UpdateView):
    model = get_user_model()
    form_class = forms.UserUpdateForm
    # success_url = reverse_lazy('dashboard')
    context_object_name = 'preferences'

    def get_context_data(self, **kwargs):
        context = super(AccountSettingsUpdateView, self).get_context_data(**kwargs)

        user = self.request.user
        if user.is_student:
            profile = user.get_student_profile()
            context.update({'userprofile': profile})
        elif user.is_professor:
            profile = user.get_professor_profile()
            context.update({'userprofile': profile})
        elif user.is_executive:
            profile = user.get_executive_profile()
            context.update({'userprofile': profile})
        return context

下面视图的网址是这个

url(r"^preferences/(?P<slug>[\w\-]+)/$",
        views.AccountSettingsUpdateView.as_view(),
        name='preferences'
    ),

这个视图AccountSettingsUpdateView 工作正常。

[02/Apr/2017 23:51:17] "GET /accounts/preferences/luisa/ HTTP/1.1" 200 18092

而且,在我的另一个视图中,并且仅在这个视图中,我正在更新与每个用户的个人资料相关的数据。这意味着上述配置文件:

class AccountProfilesView(LoginRequiredMixin, UpdateView):

    # When I ask for user with Student Profile
    model = StudentProfile
    form_class = forms.StudentProfileForm

    # sending the form to ProfessorProfile 
    second_form_class = forms.ProfessorProfileForm

    # sending the form to ExecutiveProfile 
    third_form_class = forms.ExecutiveProfileForm


    success_url = reverse_lazy('dashboard')
    template_name = 'accounts/student_form.html'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        user = self.request.user

        if 'form' not in context:
            context['form'] = self.form_class(self.request.GET,
                instance=user)
        if 'form2' not in context:
            context['form2'] = self.second_form_class(self.request.GET,
                instance=user)
        '''
        if 'form3' not in context:
            context['form3'] = self.third_form_class(self.request.GET,
                instance=user)
        '''
        if user.is_student:
            profile = user.get_student_profile()
            context.update({'userprofile': profile})
        elif user.is_professor:
            profile = user.get_professor_profile()
            context.update({'userprofile': profile})
        elif user.is_executive:
            profile = user.get_executive_profile()
            context.update({'userprofile': profile})
        return context 

这个AccountProfilesView 视图的网址是这个

url(r"^profile/(?P<slug>[\w\-]+)/$",
        views.AccountProfilesView.as_view(
            model=ProfessorProfile),
            name='profile'
    ),

请注意,在 url 中,我传递了 ProfessorProfile 类似参数的模型,尽管在视图 AccountProfilesView 正文中,我正在定义 StudentProfile 模型,但是在 url 中发生的情况是 model = ProfessorProfile覆盖来自视图的模型 = StundentProfile。

在这一刻,如果我使用具有个人资料 StudentProfileluisa 用户,我会转到网址 http://localhost:8000/accounts/profile/luisa/ 找不到网址:

[03/Apr/2017 01:20:25] "GET /accounts/profile/luisa/ HTTP/1.1" 404 1771
Not Found: /accounts/profile/luisa/ 

但如果我删除了我在 URL 中传递的类似参数的属性 model=ProfessorProfile,这意味着我的 url 保持不变:

url(r"^profile/(?P<slug>[\w\-]+)/$",      views.AccountProfilesView.as_view(), name='profile')  

网址http://localhost:8000/accounts/profile/luisa/就可以了

[03/Apr/2017 01:28:47] "GET /accounts/profile/luisa/ HTTP/1.1" 200 4469

这是因为在视图中保留了 model=StudentProfile 属性。

在此之前,如果我使用一个名为 david 的 ProfessorProfile 用户,并且我将访问他们的个人资料 URL,则找不到该 URL

Not Found: /accounts/profile/david/
[03/Apr/2017 01:30:19] "GET /accounts/profile/david/ HTTP/1.1" 404 1769

但是我在URL中再次添加了我正在传递的类似参数的属性model=ProfessorProfile,例如上面提到的david profile ProfessorProfile的url就可以了。

[03/Apr/2017 01:33:11] "GET /accounts/profile/david/ HTTP/1.1" 200 4171

ExecutiveProfile 用户类型也有同样的不便。

根据之前的行为,是这样的,我正在定义视图来询问用户类型的角色并呈现它们各自的形式。 但不方便的是,在我看来AccountProfilesView我不能通过或指定多个模型。

我正在尝试以这种方式在我的AccountProfilesView 中指定一秒模型:

class AccountProfilesView(LoginRequiredMixin, UpdateView):
    model = StudentProfile
    form_class = forms.StudentProfileForm
    second_form_class = forms.ProfessorProfileForm
    third_form_class = forms.ExecutiveProfileForm
    #success_url = reverse_lazy('dashboard')
    template_name = 'accounts/student_form.html'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        # Indicate one second model
        context['professor_profile'] = ProfessorProfile

但结果是一样的

总之我的问题是:

在基于 UpdateView 类的视图中... 如何使用多个模型(更准确地说是三个模型 StudentProfileProfessorProfileExecutiveProfile)来按照每个配置文件页面用户的访问顺序呈现它们各自的模型表单?

我希望可以使用我拥有的任意数量的 ProfileUser 来执行此操作。

我不知道我的模式 User 和 ProfileUser 模型是否很好,是否有更好的替代方案来解决这个挑战。

更新

根据@Ma0 Collazos 的回答,他们的解决方案效果很好。

此时的目标是可以组合不同的配置文件,并且可以渲染每个配置文件的形式。所以,如果用户有 is_professor 和 is_executive 个人资料,可以在他们的个人资料视图 (AccountProfilesView) 中显示他们各自的表格,这意味着当我去个人资料用户时,我可以看到表格教授的字段和表格的字段执行官

为了达到这个目的,我添加了用户在我的AccountProfilesView 中拥有个人资料组合的场景,如下所示:

class AccountProfilesView(LoginRequiredMixin, UpdateView):
    # All users can access this view
    model = get_user_model()
    template_name = 'accounts/profile_form.html'
    fields = '__all__'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        user = self.request.user

        if not self.request.POST:
            if user.is_student:
                profile = user.get_student_profile()
                context['userprofile'] = profile
                context['form_student'] = forms.StudentProfileForm()
            elif user.is_professor:
                profile = user.get_professor_profile()
                context['userprofile'] = profile
                context['form_professor'] = forms.ProfessorProfileForm()
            elif user.is_executive:
                profile = user.get_executive_profile()
                context['userprofile'] = profile
                context['form_executive'] = forms.ExecutiveProfileForm()
            elif user.is_student and user.is_professor and user.is_executive:
                student_profile = user.get_student_profile()
                professor_profile = user.get_professor_profile()
                executive_profile = user.get_executive_profile()

                context['student_profile'] = student_profile
                context['professor_profile'] = professor_profile
                context['executive_profile'] = executive_profile

                context['form_student'] = forms.StudentProfileForm()
                context['form_professor'] = forms.ProfessorProfileForm()
                context['form_executive'] = forms.ExecutiveProfileForm()

        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        user = self.request.user
        if user.is_student:
            context['form_student'] = forms.StudentProfileForm(self.request.POST)
        elif user.is_professor:
            context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
        elif user.is_executive:
            context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)
        elif user.is_student and user.is_professor and user.is_executive:
            context['form_student'] = forms.StudentProfileForm(self.request.POST)
            context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
            context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)

        return super(AccountProfilesView, self).post(request, *args, **kwargs)

    def form_valid(self, form):
        context = self.get_context_data(form=form)
        user = self.request.user
        user = form.save()
        if user.is_student:
            student = context['form_student'].save(commit=False)
            student.user = user
            student.save()
        elif user.is_professor:
            professor = context['form_professor'].save(commit=False)
            professor.user = user
            professor.save()
        elif user.is_executive:
            executive = context['form_executive'].save(commit=False)
            executive.user = user
            executive.save()
        elif user.is_student and user.is_professor and user.is_executive:
            student = context['form_student'].save(commit=False)
            student.user = user
            student.save()
            professor = context['form_professor'].save(commit=False)
            professor.user = user
            professor.save()
            executive = context['form_executive'].save(commit=False)
            executive.user = user
            executive.save()

        return super(AccountProfilesView, self).form_valid(form)

在我的个人资料表单模板中,我有以下小逻辑,其中表单以单独的方式呈现给每个个人资料,但是当我询问用户是否有三个个人资料is_studentis_professoris_executive 比如是我模板末尾的代码部分,我要去这个用户的个人资料页面,三个表单没有渲染:

<form method="POST">
        {% csrf_token %}
        {% if userprofile.user.is_student %}
            {% bootstrap_form form_student %}

        {% elif userprofile.user.is_professor %}
            {% bootstrap_form form_professor %}

        {% elif userprofile.user.is_executive %}
            {% bootstrap_form form_executive %}

        {% elif userprofile.user.is_student and 
            userprofile.user.is_professor and 
            userprofile.user.is_executive %}
            {% bootstrap_form form_student %}
            {% bootstrap_form form_professor %}
            {% bootstrap_form form_executive %}

        {% endif %}    
        <input type="submit" value="Save Changes" class="btn btn-default">
    </form>

为什么我的三个表格,不能在一个表格中显示?

【问题讨论】:

标签: django django-forms django-views


【解决方案1】:
  1. 使类AccountProfilesView 具有属性model = get_user_model(),这样所有用户都可以访问此视图。

  2. 在您的 get_context_data 方法中定义要呈现的表单,并确保使用在 POST 方法中输入的数据填写此表单

     # NoQA
     if not self.request.POST:
         if user.is_student:
             context['form_student'] = forms.StudentProfileForm()
         elif user.is_professor:
             context['form_professor'] = forms.ProfessorProfileForm()
     ...
     else:
         if user.is_student:
             context['form_student'] = forms.StudentProfileForm(self.request.POST)
         elif user.is_professor:
             context['form_professor'] =    forms.ProfessorProfileForm(self.request.POST)
     ...
    
  3. 然后重写form_valid方法保存对应的表单

     def form_valid(self, form):
         context = self.get_context_data(form=form) 
         user = form.save() 
         # NoQA
         if user.is_student:
             student = context['form_student'].save(commit=False)
             student.user = user
             student.save()
         elif user.is_professor:
             professor = context['form_professor'].save(commit=False)
             professor.user = user
             professor.save()
     ...
    

根据this post回答

【讨论】:

  • 您的回答很棒。完美运行。我的AccountProfileView 确实是这样的:pastebin.com/t5HqCyCB。有一点我不明白。为什么在我的AccountProfilesView 中放置了fields = '__all__' 属性。我想是因为我正在与postform_valid 中的表单进行交互,从我的角度来看……可能吗?
  • 我通常不建议在字段中使用'__all__',我不明白为什么你必须放置这个属性,也许如果你更具体地解释错误我可以给你一个更好的答案
  • 哦,很简单,ModelFormMixin 是从 FormMixin 扩展而来的,它需要一个 form_class 来渲染一个对象表单以注入到模板中。 ModelFormMixin 允许您构建一个仅指定模型字段的表单,因此为了使 UpdateView 正常工作,您需要指定字段或 form_class。
  • 对那个评论很抱歉,检查这个 pastebin pastebin.com/Wa6W3ieF
  • 确保在get_context_data中正确定义了form_profesor
猜你喜欢
  • 1970-01-01
  • 2019-04-13
  • 2015-03-08
  • 1970-01-01
  • 2015-09-11
  • 2011-06-29
  • 1970-01-01
  • 2017-08-07
相关资源
最近更新 更多