3
\$\begingroup\$

I am creating a translation application and the users can add new translations to the "core" language, e.g. a Latin translation of the German word, and other users can vote on this translation. A translation can only be voted up or down once by the same user.

Here are my django models for these purposes, could you please look through this code and give me some feedback.

class BaseModel(models.Model):
 class PublishingStatus(models.TextChoices):
 DRAFT = 'draft', _('Draft')
 ACCEPTED = 'accepted', _('Accepted'),
 REJECTED = 'rejected', _('Rejected')
 publishing_status = models.CharField(
 max_length=9,
 choices=PublishingStatus.choices,
 default=PublishingStatus.DRAFT,
 help_text="Publishing status represents the state of the object. By default it is 'draft'"
 )
 created_at = models.DateTimeField(auto_now_add=True)
 created_by = models.ForeignKey(
 get_user_model(), on_delete=models.CASCADE,
 related_name='%(class)s_created_by', null=True, blank=True)
 modified_at = models.DateTimeField(auto_now=True)
 modified_by = models.ForeignKey(
 get_user_model(), on_delete=models.CASCADE,
 related_name='%(class)s_modified_by', null=True, blank=True)
 accepted_at = models.DateTimeField(null=True)
 accepted_by = models.ForeignKey(
 get_user_model(), on_delete=models.CASCADE,
 related_name='%(class)s_accepted_by', null=True, blank=True)
 rejected_at = models.DateTimeField(null=True)
 rejected_by = models.ForeignKey(
 get_user_model(), on_delete=models.CASCADE,
 related_name='%(class)s_rejected_by', null=True, blank=True)
 rejection_reason = models.TextField(blank=True, default="")
 class Meta:
 abstract = True
class LatinGermanTranslation(BaseModel):
 lt = models.ForeignKey(
 LtWords, on_delete=models.CASCADE, related_name="lt_de_translations", null=True, blank=True)
 de = models.ForeignKey(
 'GermanWord', on_delete=models.CASCADE, related_name="de_lt_translations", null=True, blank=True)
 vote = models.IntegerField(default=0)
 class Meta:
 verbose_name = 'Latin German Translations'
 verbose_name_plural = 'Latin German Translations'
 def up_vote(self, created_by):
 try:
 self.de_transl.create(
 voted_by=created_by, transl=self, vote_type=GermanTranslationVote.Vote.UP)
 self.vote += 1
 self.save()
 except (DatabaseError, IntegrityError, ValueError) as e:
 raise Exception(f'Some error occurred during up vote: {e}')
 return 'ok'
 def down_vote(self, created_by):
 try:
 self.de_transl.create(
 voted_by=created_by, transl=self, vote_type=GermanTranslationVote.Vote.DOWN)
 self.vote -= 1
 self.save()
 except (DatabaseError, IntegrityError, ValueError) as e:
 raise Exception(f'Some error occurred during down vote: {e}')
 return 'ok'
 def __str__(self):
 return f'{self.lt} - {self.de}'
class GermanWord(BaseModel):
 word = models.CharField(max_length=255, default="", blank=True)
 lt_word = models.ManyToManyField(
 LtWords, through=LatinGermanTranslation, through_fields=('de', 'lt'), blank=True)
 class Meta:
 indexes = [models.Index(fields=['word'])]
 verbose_name = 'German Word'
 verbose_name_plural = 'German Words'
 def __str__(self):
 return self.word
class GermanTranslationVote(models.Model):
 class Vote(models.TextChoices):
 UP = 'up', _('Up')
 DOWN = 'down', _('Down')
 translation = models.OneToOneField(
 LatinGermanTranslation, on_delete=models.CASCADE,
 related_name='de_transl')
 voted_by = models.OneToOneField(
 get_user_model(), on_delete=models.CASCADE,
 related_name='vore_de', null=True, blank=True)
 vote_type = models.CharField(
 max_length=4, choices=Vote.choices, default=None)
 class Meta:
 indexes = [models.Index(fields=['translation', 'voted_by'])]
 verbose_name = 'Latin Word Vote'
 verbose_name_plural = 'Latin Words Votes'

Espetially I'd like to hear about the translation and voted_by attributes inside GermanTranslationVote model, since I'm not sure about OneToOne implementation.

asked Apr 7, 2020 at 23:31
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I've honestly never used Django before, so I'm not going to touch on that aspect.

You can reduce a little duplication that exists in your *_vote methods though:

def _vote(self, created_by, vote_change, vote_type):
 try:
 self.de_transl.create(
 voted_by=created_by, transl=self, vote_type=vote_type)
 self.vote += vote_change
 self.save()
 except (DatabaseError, IntegrityError, ValueError) as e:
 direction = "down" if vote_change < 0 else "up" # Will say "up" on 0.
 raise Exception(f'Some error occurred during {direction} vote: {e}')
 return 'ok'
def up_vote(self, created_by):
 return self._vote(created_by, 1, GermanTranslationVote.Vote.UP)
def down_vote(self, created_by):
 return self._vote(created_by, -1, GermanTranslationVote.Vote.DOWN)

Now you only need to have the bulky exception handling and database management once.


You can do something similar for the repeated *_at and *_by variables inside BaseModel:

def atByPair(related_name):
 at_model = models.DateTimeField(auto_now_add=True)
 by_model = models.ForeignKey(
 get_user_model(), on_delete=models.CASCADE,
 related_name=related_name, null=True, blank=True)
 return at_model, by_model 
created_at, created_by = atByPair('%(class)s_created_by')
modified_at, modified_by = atByPair('%(class)s_modified_by')
accepted_at, accepted_by = atByPair('%(class)s_accepted_by')
rejected_at, rejected_by = atByPair('%(class)s_rejected_by')
rejection_reason = models.TextField(blank=True, default="")

Much less duplication and worrying about needing to make consistent changes in multiple places.

answered Apr 8, 2020 at 0:02
\$\endgroup\$
3
  • \$\begingroup\$ Thank you so much! I especially like your implementation of the _vote method. I'm not sure about defining attributes with the atByPair method, because django suggests to set all methods after the Meta class. \$\endgroup\$ Commented Apr 8, 2020 at 13:31
  • \$\begingroup\$ You're welcome. And I was going to comment on how odd the setup is here (classes inside of classes and many class-level attributes), but I figured that it had something to do with Django. If something I suggested doesn't work with Django, then ignore it. I'm not sure how Django operates. \$\endgroup\$ Commented Apr 8, 2020 at 13:34
  • \$\begingroup\$ atByPair should be converted to lowercase (Python code style). \$\endgroup\$ Commented May 31, 2020 at 12:45

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.