K
K
kicherov_maxim2020-03-02 14:02:21
Django
kicherov_maxim, 2020-03-02 14:02:21

How to make a calculated field in a Django model?

Good afternoon. Need to make a calculated field in Django model. I'm trying to override the save method for this.

from django.db import models
import tlsh
from django.contrib import admin

# Create your models here.


class Font(models.Model):
    name = models.CharField(max_length=500, unique=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["name"]


class Language(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["name"]


class FingerPrint(models.Model):

    def result_string(self):
        return self.user_agent + str(self.time_zone) + str(self.screen_h) + str(self.screen_w) + \
               str(self.hardware_concurrency) + ''.join(lng.name for lng in self.browser_languages.all()) + \
               self.canvas_fingerprint + '1'.join(font.name for font in self.installed_fonts.all()) + \
               '0'.join(font.name for font in self.not_installed_fonts.all())

    def calculate_tlsh(self):
        return tlsh.forcehash(self.result_string().encode())

    user_agent = models.CharField(max_length=1000,blank=True)
    time_zone = models.IntegerField(blank=True)
    screen_w = models.IntegerField(blank=True)
    screen_h = models.IntegerField(blank=True)
    hardware_concurrency = models.IntegerField(blank=True)
    browser_languages = models.ManyToManyField('Language', related_name='browser_languages')
    canvas_fingerprint = models.CharField(max_length=256, blank=True)
    installed_fonts = models.ManyToManyField(Font, related_name='installed_fonts')
    not_installed_fonts = models.ManyToManyField('Font', related_name='not_installed_fonts')
    tlsh = models.CharField(max_length=400, blank=True)

    def save(self, *args, **kwargs):
        #super(FingerPrint, self).save(*args, **kwargs) костыль который чинит ошибку
        self.tlsh = self.calculate_tlsh()
        print(':', self.tlsh)
        super(FingerPrint, self).save(*args, **kwargs)


I get an error
File "/home/maxim/PycharmProjects/fingerprint/core/fingerprinter/models.py", line 52, in save
    self.tlsh = self.calculate_tlsh()
  File "/home/maxim/PycharmProjects/fingerprint/core/fingerprinter/models.py", line 37, in calculate_tlsh
    return tlsh.forcehash(self.result_string().encode())
  File "/home/maxim/PycharmProjects/fingerprint/core/fingerprinter/models.py", line 33, in result_string
    self.canvas_fingerprint + '1'.join(font.name for font in self.installed_fonts.all()) + \
  File "/home/maxim/PycharmProjects/fingerprint/venv/lib/python3.6/site-packages/django/db/models/fields/related_descriptors.py", line 535, in __get__
    return self.related_manager_cls(instance)
  File "/home/maxim/PycharmProjects/fingerprint/venv/lib/python3.6/site-packages/django/db/models/fields/related_descriptors.py", line 848, in __init__
    (instance, self.pk_field_names[self.source_field_name]))
ValueError: "<FingerPrint: FingerPrint object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.


I understand that this is due to the fact that you first need to save the object and then only access ManyToManyFields. and if in the save method you first save, then calculate the field, then save again, then everything works. How to do this task correctly?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
Vladimir, 2020-03-02
@kicherov_maxim

For example, through signals :

@receiver(post_save, sender=FingerPrint)
def update_calculated_fields(sender, instance, **kwargs):
    tlsh = instance.calculate_tlsh()
    sender.objects.filter(pk=instance.pk).update(tlsh=tlsh)

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question