【问题标题】:How to avoid this redundancy when using Django model inheritance?使用 Django 模型继承时如何避免这种冗余?
【发布时间】:2023-03-11 22:09:01
【问题描述】:

我的 Django 网站中的所有页面都有一个页脚链接“反馈/问题”。如果新用户访问该站点并单击该链接,则应将他们引导至带有下拉菜单的表单,以指示他们是否有反馈与问题以及他们的电子邮件地址和他们的反馈或问题的字段。该页面将有一个简单的标题,所有未经身份验证的用户都会看到。另一方面,如果站点成员登录并通过身份验证,他们应该会看到相同的表单,但没有电子邮件字段(因为我已经知道他们的电子邮件地址)以及包含站点内部导航栏、按钮等的不同身份验证标题。

我最初的想法是创建一个抽象类FeedbackQuestion:

class FeedbackQuestion(models.Model):
  submission_type = ...  (type, i.e. feedback or question)
  submission_text = ...  (actual feedback or question)
  ...
  class Meta:
    abstract = True

然后我会创建两个单独的具体子类:

class AnonFeedbackQuestion(FeedbackQuestion):
  email = models.EmailField(...)
  class Meta:
    db_table = anon_feedback_question

class AuthFeedbackQuestion(FeedbackQuestion):
  user = models.ForeignKey(User, related_name="user")
  class Meta:
    db_table = auth_feedback_question

这两个类会有自己的模型形式:

class AnonFeedbackQuestionForm(ModelForm):
  class Meta:
    model = AnonFeedbackQuestion
    fields = ['submission_type', 'submission_text', 'email']

class AuthFeedbackQuestionForm(ModelForm):
  class Meta:
    model = AuthFeedbackQuestion
    fields = ['submission_type', 'submission_text']

我预见到的问题是我必须在显示反馈表的视图中执行以下操作:

def get_feedback_questions(request, template):
  if request.method == 'POST':
    ...
    if request.user.is_authenticated():
      form = AuthFeedbackQuestionForm(request.POST)
    else:
      form = AnonFeedbackQuestionForm(request.POST)
    if form.is_valid():
      (process form)
      ...
  else:
    if request.user.is_authenticated():
      form = AuthFeedbackQuestionForm(request.POST)
    else:
      form = AnonFeedbackQuestionForm(request.POST)
    ...
  context = {'form': form}
  return render(request, template, context)

必须重复这些 if/then/else 块来确定使用哪种形式似乎相当不雅。有没有更好、更干净的“Django”方式来做到这一点?

谢谢!

【问题讨论】:

    标签: python django inheritance django-models


    【解决方案1】:

    我不会对您的模型进行子类化 - 如果这是一个匿名问题,您可以只在一个带有 blank=Truenull=True 的模型上包含 user 属性和 email 属性:

    class FeedbackQuestion(models.Model):
        submission_type = ...  (type, i.e. feedback or question)
        submission_text = ...  (actual feedback or question)
        email = models.EmailField(..., blank=True, null=True)
        user = models.ForeignKey(User, related_name="user", blank=True, null=True)
        ...
        class Meta:
            abstract = True
    

    通过这种方式,您可以添加 email 用于匿名用户的反馈/问题,或者添加 user(如果他们已通过身份验证)。

    然后我会将您的表单合并为一个包含电子邮件字段的表单,但删除电子邮件字段取决于用户是否经过身份验证(请参阅this answer):

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super(UserForm, self).__init__(*args, **kwargs)
        if self.user:
            # For logged-in users, email field not necessary
            self.fields.pop('email')
        else:
            # Otherwise, the field needs to be required
            self.fields['email'].required = True
    

    然后,您只需确保在清理表单数据时适当地创建用户(例如,确保电子邮件地址尚未被占用等)

    【讨论】:

    • 谢谢,但是这个解决方案不能很好地工作有两个原因。 #1 如果电子邮件字段可以为空白/空,如果匿名用户提交没有电子邮件的表单,则电子邮件字段不会显示错误。 #2 如果您通过将电子邮件字段设为必填来修复 #1,并且经过身份验证的用户提交的表单在其他字段中存在错误,则电子邮件字段会重新出现并被标记为错误,因为它没有值。我正在继续寻找其他解决方案。
    • @Robert 是否根据需要设置字段也应该在表单级别,而不是模型级别。那应该可以解决您的第一个问题。第二名也应该不是问题。无论如何,您应该只有两种可能性:该字段存在并且是必需的,或者它不存在。看看我更新的答案,这有意义吗?
    • @YPCrumble 您能否详细说明为什么不将模型子类化?
    • @Robert 基本上是因为simple is better than complex。您的模型应该用于一种类型的数据 - 在您的情况下,它是 FeedbackQuestion。 (事实上​​,深入研究我可能会将反馈和问题分成两个不同的模型,但还没有看到您的应用程序的上下文)。它不是FeedbackQuestionUsertypeModel。我的建议是根据用户的存在与否以某种方式制作is_anonymous 参数。实际上,另一种选择是将email 直接移动到user 模型上,而不是将其放在FeedbackQuestion 上...
    • ...并设置占位符密码以便User 保存。您必须有一个is_anonymous 标志或其他东西,具体取决于您是否为只提供电子邮件地址的用户设置默认密码(is_anonymous=True),或者如果用户实际创建了密码(is_anonymous=False)/登录。不过,在核心上,我更专注于建议您不要对模型进行子类化,因为这会分解FeedbackQuestion,并且子类化模型会增加更高级别的复杂性(例如,需要 3 个模型和 2 个表单)比在表单中执行if...else 语句。
    猜你喜欢
    • 2012-11-17
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多