yayu
yayu

Reputation: 8098

How to DRY up my models

I am a making a Q&A site. At present, I have the models as such

class Question(models.Model):
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ...# some additional fields such as tags

class Answer(models.Model):
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ...

class QuestionVote(models.Model):
    voter = models.ForeignKey(User)
    question = models.ForeignKey(Question)

#replicating what I did for QuestionVote
class AnswerVote(models.Model):
        voter = models.ForeignKey(User)
        question = models.ForeignKey(Question)

Question and answer models are the same except a title and tags. To add voting functionality to Answers, I will have to replicate the QuestionVote model as an AnswerVote and repeat everything I did for question voting in the views. I looked a bit into Model Inheritance but if I declare an abstract base class and inherit Question and Answer Models from it then I cannot use foreign keys. So what is the best approach to avoid such repetition?

Upvotes: 1

Views: 122

Answers (3)

ptr
ptr

Reputation: 3384

Have a look at abstract base classes

You can put the common fields/functionality in there (submitter, detail, etc) and then inherit it in your Question and Answer models

Edit:

Based on the other answers and me looking at your problem a bit more I'm changing my answer. You're currently walking the line between "DRY" and "good readability/maintainability" and different developers come down on different sites of the line, so you're likely just going to have to pick the one you like best.

On that note, here's how I'd lay it it out:

##############################
# Question and Answer Models #
##############################

class QuestionAnswerBase(models.Model): # Choose a better name :)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    ... # More common fields here

    class Meta:
        abstract = True


class Question(QuestionAnswerBase):
    title = models.CharField(max_length=150)
    ...# some additional fields such as tags etc


class Answer(QuestionAnswerBase):
    ... # No extra fields needed but you'll prob want a custom __unicode__ method etc


###############
# Vote Models #
###############

class VoteBase(models.Model):
    voter = models.ForeignKey(User)
    ... # Other shared fields etc

    class Meta:
        abstract = True

class AnswerVote(VoteBase):
    answer = models.ForeignKey(Answer)


class QuestionVote(VoteBase):
    question = models.ForeignKey(Question)

(comment blocks are for readability in this instance.)

"But now I have even more models!"

Yes you do, but there are no duplicated fields on any of them, and the abstraction means you have the power to add different behaviour to- for example- a QuestionVote and an AnswerVote (say you want the ability to retract AnswerVotes if a better Answer comes along, but don't want to be able to retract QuestionVotes) without the need to have "Is this X a Y or a Z? If it's a Z do this else do this" clauses in every model method you write.

"It looks ugly!"

It's both readable and explicit, which means that it's beautiful :)

"I want to do it another way!"

That's fine, there are already other good answers that show how to do that. This is just my suggestion as someone that is quite keen on best practices and readable code.

Upvotes: 0

Selcuk
Selcuk

Reputation: 59444

You can use a one-to-one relation the other way around:

class Vote(models.Model):
    voter = models.ForeignKey(User)
    ...# some additional fields

class Question(models.Model):
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    vote = models.OneToOneField(Vote)
    ...# some additional fields such as tags

class Answer(models.Model):
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    vote = models.OneToOneField(Vote)
    ...

Upvotes: 2

DavidM
DavidM

Reputation: 1437

I personally feel that DRY is pretty important, so I would be willing to sacrifice some elegance to achieve it. For example, in this case, I might do this:

class QuestionOrAnswer(models.Model):
    is_question = models.BooleanField()
    title = models.CharField(max_length=150)
    detail = models.TextField()
    submitter = models.ForeignKey(User)
    date_added = models.DateTimeField(auto_now_add = True)
    question_specific_field = ...

    def clean(self):
        # Make sure that question_specific_field is set only if is_question is true.

class Vote(models.Model):
    target = models.ForeignKey(QuestionOrAnswer)
    ...

Upvotes: 0

Related Questions