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.
1 Answer 1
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.
-
\$\begingroup\$ Thank you so much! I especially like your implementation of the
_vote
method. I'm not sure about defining attributes with theatByPair
method, because django suggests to set all methods after theMeta
class. \$\endgroup\$khashashin– khashashin2020年04月08日 13:31:24 +00:00Commented 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\$Carcigenicate– Carcigenicate2020年04月08日 13:34:07 +00:00Commented Apr 8, 2020 at 13:34
-
\$\begingroup\$
atByPair
should be converted to lowercase (Python code style). \$\endgroup\$Uri– Uri2020年05月31日 12:45:15 +00:00Commented May 31, 2020 at 12:45