Jim
Jim

Reputation: 14270

How to avoid this redundancy when using Django model inheritance?

All pages in my Django website have a footer link "Feedback/Questions". If the new person comes to the site and clicks that link, they should be directed to a form with a pulldown to indicate if they have feedback versus a question and fields for their email address and their feedback or question. The page will have a simple header all non-authenticated users will see. On the other hand, if a site member signs in and is authenticated, they should see the same form but without the email field (since I already know their email address) and a different authenticated header containing the site's internal navbar, buttons, etc.

My initial thought was to create an abstract class FeedbackQuestion:

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

Then I'd create two separate concrete child classes:

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

These two classes would have their own model forms:

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']

The problem I forsee is that I will have to do the following in my view that displays the feedback form:

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)

Having to repeat these if/then/else blocks to identify which form to use seems rather inelegant. Is there a better, cleaner "Django" way to do this?

Thanks!

Upvotes: 2

Views: 453

Answers (1)

YPCrumble
YPCrumble

Reputation: 28662

I wouldn't subclass your models - if it's an anonymous question you could just include a user attribute as well as an email attribute on one model with blank=True and null=True:

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

This way you can add either the email for an anonymous user's feedback/question or the user if they're authenticated.

Then I'd combine your forms into one including the email field, but remove the email field depending on if the user is authenticated (see 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

Then you just need to make sure you create the user appropriately as you clean the form's data (e.g., make sure the email address isn't already taken, etc.)

Upvotes: 1

Related Questions