1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Custom model to count user interaction in application

Discussion in 'Programming/Internet' started by khashashin, Aug 2, 2020.

  1. khashashin

    khashashin Guest

    In language dictionary application users can add new words and do other staff with this word such as adding additional information to the word (description, etymology, speech parts, noun class etc.) I'll count all this information and maybe later create some application gamification based on this information. I decided to create custom model and count all this contribution inside this model. There are one base abstract model which defines common fields:

    class BaseModel(models.Model, ModelDiffMixin):
    WORD_MODERATE_STATUS = [
    ('UNDER_CONCIDERATION', 'under consideration'),
    ('PREVIOUSLY_ACCEPTED', 'previously accepted')
    ]
    moderate_status = models.CharField(
    max_length=50, choices=WORD_MODERATE_STATUS, blank=True)
    created_on = models.DateTimeField(auto_now_add=True)
    modified_on = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
    get_user_model(), on_delete=models.CASCADE,
    related_name='%(class)s_createdby')
    modified_by = models.ForeignKey(
    get_user_model(), on_delete=models.CASCADE,
    related_name='%(class)s_modifiedby', null=True, blank=True)
    accepted = models.BooleanField(default=False)
    accepted_by = models.ForeignKey(
    get_user_model(), on_delete=models.CASCADE,
    related_name='%(class)s_acceptedby')

    class Meta:
    abstract = True


    And then I've found solution how to watch field change and based on this I've created post_save signal which does update desired field value.

    class ModelDiffMixin(object):
    """
    A model mixin that tracks model fields values and provide some useful api
    to know what fields have been changed.
    """

    def __init__(self, *args, **kwargs):
    super(ModelDiffMixin, self).__init__(*args, **kwargs)
    self.__initial = self._dict

    @property
    def diff(self):
    d1 = self.__initial
    d2 = self._dict
    diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
    return dict(diffs)

    @property
    def has_changed(self):
    return bool(self.diff)

    @property
    def changed_fields(self):
    return self.diff.keys()

    def get_field_diff(self, field_name):
    """
    Returns a diff for field if it's changed and None otherwise.
    """
    return self.diff.get(field_name, None)

    def save(self, *args, **kwargs):
    """
    Saves model and set initial state.
    """
    super(ModelDiffMixin, self).save(*args, **kwargs)
    self.__initial = self._dict

    @property
    def _dict(self):
    return model_to_dict(
    self, fields=[field.name for field in self._meta.fields])


    Using this mixin I'll be able to check which field values was changed and update the user statistics respectively. Here is the core word model WordCoreModel in which users can do some data manipulations through front-ent(using Django REST Framework):

    class CoreWordCategoryModel(models.Model):
    core_abbreviation = models.CharField(max_length=10, blank=True)
    core_description = models.CharField(max_length=50, blank=True)
    ru_abbreviation = models.CharField(max_length=10, blank=True)
    ru_description = models.CharField(max_length=50, blank=True)
    en_abbreviation = models.CharField(max_length=10, blank=True)
    en_description = models.CharField(max_length=50, blank=True)

    def __str__(self):
    return "{} - {} - {}".format(self.core_description, self.ru_description, self.en_description)


    class WordCoreModel(BaseModel):
    NOUN_CLASSESS = [
    ('V_CLASS', 'В класс'),
    ('Y_CLASS', 'Й класс'),
    ('Y_CLASS_SECOND', 'Й класс II'),
    ('D_CLASS', 'Д класс'),
    ('B_CLASS', 'Б класс'),
    ('B_CLASS_SECOND', 'Д класс II')
    ]
    PART_OF_SPEECH = [
    ('NOUN', 'Noun'),
    ('VERB', 'Verb'),
    ('ADVERB', 'Adverb'),
    ('ADJECTIVE', 'Adjective'),
    ('PRONOUN', 'Pronoun'),
    ('PREPOSITION', 'Preposition'),
    ('CONJUNCTION', 'Conjunction'),
    ('INTERJECTION', 'Interjection')
    ]
    word_core = models.CharField(max_length=255, default="")
    word_russian_typed = models.CharField(max_length=255, default="", blank=True)
    word_english_typed = models.CharField(max_length=255, default="", blank=True)

    homonyms = models.ManyToManyField('self', blank=True)
    synonyms = models.ManyToManyField('self', blank=True)
    antonyms = models.ManyToManyField('self', blank=True)

    word_category = models.ForeignKey(
    CoreWordCategoryModel, on_delete=models.CASCADE,
    related_name='wordcategory', null=True, blank=True)

    description = models.TextField(default="", blank=True)
    latin_version = models.CharField(max_length=255, default="", blank=True)
    transcription = models.CharField(max_length=255, default="", blank=True)
    ipa = models.CharField(max_length=200, default="", blank=True)
    root = models.CharField(max_length=80, default="", blank=True)
    word_class = models.CharField(max_length=20, choices=NOUN_CLASSESS, blank=True)
    part_of_speech = models.CharField(max_length=20, choices=PART_OF_SPEECH, blank=True)
    etymology = models.TextField(default="", blank=True)

    # Declensions Singular
    declension_singular_absolutive = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_genitive = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_dative = models.CharField(max_length=255, default="", blank=True)
    declension_singular_ergative = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_allative = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_instrumental = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_locative_stay = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_locative_direction = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_locative_outcome = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_locative_through = models.CharField(
    max_length=255, default="", blank=True)
    declension_singular_comparative = models.CharField(
    max_length=255, default="", blank=True)

    # Declension Plural
    declension_plural_absolutive = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_genitive = models.CharField(max_length=255, default="", blank=True)
    declension_plural_dative = models.CharField(max_length=255, default="", blank=True)
    declension_plural_ergative = models.CharField(max_length=255, default="", blank=True)
    declension_plural_allative = models.CharField(max_length=255, default="", blank=True)
    declension_plural_instrumental = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_locative_stay = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_locative_direction = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_locative_outcome = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_locative_through = models.CharField(
    max_length=255, default="", blank=True)
    declension_plural_comparative = models.CharField(
    max_length=255, default="", blank=True)

    # Tenses
    imperative_tense_basic_form = models.CharField(max_length=255, default="", blank=True)
    imperative_tense_causative = models.CharField(max_length=255, default="", blank=True)
    imperative_tense_permissive = models.CharField(max_length=255, default="", blank=True)
    imperative_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    imperative_tense_potential = models.CharField(max_length=255, default="", blank=True)
    imperative_tense_inceptive = models.CharField(max_length=255, default="", blank=True)
    ######
    simple_present_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    simple_present_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    simple_present_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    simple_present_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    simple_present_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    simple_present_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    near_preterite_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    near_preterite_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    near_preterite_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    near_preterite_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    near_preterite_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    near_preterite_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    witnessed_past_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    witnessed_past_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    witnessed_past_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    witnessed_past_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    witnessed_past_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    witnessed_past_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    perfect_tense_basic_form = models.CharField(max_length=255, default="", blank=True)
    perfect_tense_causative = models.CharField(max_length=255, default="", blank=True)
    perfect_tense_permissive = models.CharField(max_length=255, default="", blank=True)
    perfect_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    perfect_tense_potential = models.CharField(max_length=255, default="", blank=True)
    perfect_tense_inceptive = models.CharField(max_length=255, default="", blank=True)
    ######
    plusquamperfect_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    plusquamperfect_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    plusquamperfect_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    plusquamperfect_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    plusquamperfect_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    plusquamperfect_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    repeated_past_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    repeated_past_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    repeated_past_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    repeated_past_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    repeated_past_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    repeated_past_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    possible_future_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    possible_future_tense_causative = models.CharField(
    max_length=255, default="", blank=True)
    possible_future_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    possible_future_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    possible_future_tense_potential = models.CharField(
    max_length=255, default="", blank=True)
    possible_future_tense_inceptive = models.CharField(
    max_length=255, default="", blank=True)
    ######
    real_future_tense_basic_form = models.CharField(
    max_length=255, default="", blank=True)
    real_future_tense_causative = models.CharField(max_length=255, default="", blank=True)
    real_future_tense_permissive = models.CharField(
    max_length=255, default="", blank=True)
    real_future_tense_permissive_causative = models.CharField(
    max_length=255, default="", blank=True)
    real_future_tense_potential = models.CharField(max_length=255, default="", blank=True)
    real_future_tense_inceptive = models.CharField(max_length=255, default="", blank=True)

    class Meta:
    indexes = [models.Index(fields=['word_core'])]
    verbose_name = 'Core Word'
    verbose_name_plural = 'Core Words'

    def __str__(self):
    return self.word_core


    Adding new word or modifying word triggers post_save signal and executes following method:

    @receiver(post_save, sender=WordCoreModel)
    def update_user_statistics(sender, instance, **kwargs):
    if instance.has_changed and instance.accepted:
    changed_fields = instance.changed_fields
    for field in changed_fields:
    _field = '{}_added'.format(field)
    if hasattr(UserStats, _field):
    user_stats = UserStats.objects.get_or_create(
    user_id=instance.modified_by.id)[0]
    user_stats.increment(_field)
    user_stats.save()


    The custom model to collect all interaction of users UserStats looks like(it's a huge but the fields are almost the same so if I need to change the logic or something just apply it to one field and I'll correct rest of them):

    class UserStats(models.Model):
    user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
    reputation = models.IntegerField(default=0, blank=True)

    word_core_added = models.IntegerField(default=0, blank=True)
    word_russian_typed_added = models.IntegerField(default=0, blank=True)
    word_english_typed_added = models.IntegerField(default=0, blank=True)

    description_added = models.IntegerField(default=0, blank=True)
    latin_version_added = models.IntegerField(default=0, blank=True)
    transcription_added = models.IntegerField(default=0, blank=True)
    ipa_added = models.IntegerField(default=0, blank=True)

    root_added = models.IntegerField(default=0, blank=True)
    prefix_added = models.IntegerField(default=0, blank=True)
    suffix_added = models.IntegerField(default=0, blank=True)
    ending_added = models.IntegerField(default=0, blank=True)

    syllable_added = models.IntegerField(default=0, blank=True)
    phraseologism_added = models.IntegerField(default=0, blank=True)
    source_added = models.IntegerField(default=0, blank=True)
    word_class_added = models.IntegerField(default=0, blank=True)
    part_of_speech_added = models.IntegerField(default=0, blank=True)
    etymology_added = models.IntegerField(default=0, blank=True)

    declension_singular_absolutive_added = models.IntegerField(default=0, blank=True)
    declension_singular_genitive_added = models.IntegerField(default=0, blank=True)
    declension_singular_dative_added = models.IntegerField(default=0, blank=True)
    declension_singular_ergative_added = models.IntegerField(default=0, blank=True)
    declension_singular_allative_added = models.IntegerField(default=0, blank=True)
    declension_singular_instrumental_added = models.IntegerField(default=0, blank=True)
    declension_singular_locative_stay_added = models.IntegerField(default=0, blank=True)
    declension_singular_locative_direction_added = models.IntegerField(
    default=0, blank=True)
    declension_singular_locative_outcome_added = models.IntegerField(
    default=0, blank=True)
    declension_singular_locative_through_added = models.IntegerField(
    default=0, blank=True)
    declension_singular_comparative_added = models.IntegerField(default=0, blank=True)

    declension_plural_absolutive_added = models.IntegerField(default=0, blank=True)
    declension_plural_genitive_added = models.IntegerField(default=0, blank=True)
    declension_plural_dative_added = models.IntegerField(default=0, blank=True)
    declension_plural_ergative_added = models.IntegerField(default=0, blank=True)
    declension_plural_allative_added = models.IntegerField(default=0, blank=True)
    declension_plural_instrumental_added = models.IntegerField(default=0, blank=True)
    declension_plural_locative_stay_added = models.IntegerField(default=0, blank=True)
    declension_plural_locative_direction_added = models.IntegerField(
    default=0, blank=True)
    declension_plural_locative_outcome_added = models.IntegerField(default=0, blank=True)
    declension_plural_locative_through_added = models.IntegerField(default=0, blank=True)
    declension_plural_comparative_added = models.IntegerField(default=0, blank=True)

    imperative_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    imperative_tense_causative_added = models.IntegerField(default=0, blank=True)
    imperative_tense_permissive_added = models.IntegerField(default=0, blank=True)
    imperative_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    imperative_tense_potential_added = models.IntegerField(default=0, blank=True)
    imperative_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    simple_present_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    simple_present_tense_causative_added = models.IntegerField(default=0, blank=True)
    simple_present_tense_permissive_added = models.IntegerField(default=0, blank=True)
    simple_present_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    simple_present_tense_potential_added = models.IntegerField(default=0, blank=True)
    simple_present_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    near_preterite_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    near_preterite_tense_causative_added = models.IntegerField(default=0, blank=True)
    near_preterite_tense_permissive_added = models.IntegerField(default=0, blank=True)
    near_preterite_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    near_preterite_tense_potential_added = models.IntegerField(default=0, blank=True)
    near_preterite_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    witnessed_past_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    witnessed_past_tense_causative_added = models.IntegerField(default=0, blank=True)
    witnessed_past_tense_permissive_added = models.IntegerField(default=0, blank=True)
    witnessed_past_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    witnessed_past_tense_potential_added = models.IntegerField(default=0, blank=True)
    witnessed_past_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    perfect_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    perfect_tense_causative_added = models.IntegerField(default=0, blank=True)
    perfect_tense_permissive_added = models.IntegerField(default=0, blank=True)
    perfect_tense_permissive_causative_added = models.IntegerField(default=0, blank=True)
    perfect_tense_potential_added = models.IntegerField(default=0, blank=True)
    perfect_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    plusquamperfect_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    plusquamperfect_tense_causative_added = models.IntegerField(default=0, blank=True)
    plusquamperfect_tense_permissive_added = models.IntegerField(default=0, blank=True)
    plusquamperfect_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    plusquamperfect_tense_potential_added = models.IntegerField(default=0, blank=True)
    plusquamperfect_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    repeated_past_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    repeated_past_tense_causative_added = models.IntegerField(default=0, blank=True)
    repeated_past_tense_permissive_added = models.IntegerField(default=0, blank=True)
    repeated_past_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    repeated_past_tense_potential_added = models.IntegerField(default=0, blank=True)
    repeated_past_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    possible_future_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    possible_future_tense_causative_added = models.IntegerField(default=0, blank=True)
    possible_future_tense_permissive_added = models.IntegerField(default=0, blank=True)
    possible_future_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    possible_future_tense_potential_added = models.IntegerField(default=0, blank=True)
    possible_future_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    real_future_tense_basic_form_added = models.IntegerField(default=0, blank=True)
    real_future_tense_causative_added = models.IntegerField(default=0, blank=True)
    real_future_tense_permissive_added = models.IntegerField(default=0, blank=True)
    real_future_tense_permissive_causative_added = models.IntegerField(
    default=0, blank=True)
    real_future_tense_potential_added = models.IntegerField(default=0, blank=True)
    real_future_tense_inceptive_added = models.IntegerField(default=0, blank=True)

    word_category_added = models.IntegerField(default=0, blank=True)

    homonyms_added = models.IntegerField(default=0, blank=True)
    synonyms_added = models.IntegerField(default=0, blank=True)
    antonyms_added = models.IntegerField(default=0, blank=True)

    def increment(self, name):
    """Increments a counter specified by the 'name' argument."""
    self.__dict__[name] += 1


    I'm not sure about this post_save method. I can exclude counting due to minor changes by users like changing 1 letter in word or description etc. through the logic of the field "accepted" in WordCoreModel. Any change will set accepted to false until some of the moderators set it manually to true again and this will prevent counting statistics in minor changes or abuse the statistics. But another thing is that only accepted words will be available in the application and setting it to false each time some one change it not that much good idea.

    Login To add answer/comment
     

Share This Page